Gene Module Detection with Hotspot

Table of contents

  1. TOC

Hotspot identifies genes that are spatially autocorrelated in the cell embedding — i.e., genes that vary coherently across the transcriptional landscape. GeneCircuitry groups these genes into modules and optionally annotates them with pathway enrichment labels.

Functions live in genecircuitry/hotspot_processing.py. Canonical plots are in genecircuitry/plotting/hotspot_plots.py.

Hotspot is an optional dependency. Install it with pip install -e ".[hotspot]". The pipeline skips module detection gracefully when Hotspot is not installed.


What Hotspot does

  1. Autocorrelation test: for every gene, tests whether its expression is more similar between neighbouring cells (in PCA/UMAP space) than expected by chance.
  2. Module detection: clusters autocorrelated genes into modules using their pairwise local correlations.
  3. Module scoring: computes per-cell scores for each module (analogous to a gene signature score).
  4. Enrichment annotation (optional): annotates each module with the top pathway term from over-representation analysis.

Step 1 — Create Hotspot object (create_hotspot_object)

from genecircuitry.hotspot_processing import create_hotspot_object

hs = create_hotspot_object(
    adata,
    top_genes=None,              # genes to test (config.HOTSPOT_TOP_GENES = 500)
    layer_key="raw_count",       # raw count layer (must exist in adata.layers)
    model="danb",                # count model: "danb" | "bernoulli" | "normal" | "none"
    embedding_key="X_pca",       # embedding for spatial graph construction
    normalization_key="n_counts",# adata.obs column for total counts normalization
)

Choosing the count model:

Model Use when
danb (default) UMI-based scRNA-seq (recommended for most data)
bernoulli Binary (0/1) data
normal Already-normalized data
none No count model (not recommended)

Step 2 — Run Hotspot analysis (run_hotspot_analysis)

Executes the full autocorrelation test, module detection, and local correlation computation.

from genecircuitry.hotspot_processing import run_hotspot_analysis

hs = run_hotspot_analysis(
    hs,
    adata,
    cluster_key="leiden",   # adata.obs column for grouping in plots
)

What it does:

  1. hs.create_knn_graph() — builds k-nearest-neighbour graph in embedding space (config.HOTSPOT_N_NEIGHBORS neighbours)
  2. hs.compute_autocorrelations() — tests all genes; filters by FDR (config.HOTSPOT_FDR_THRESHOLD)
  3. hs.compute_local_correlations() — pairwise local correlations between significant genes
  4. hs.create_modules() — clusters genes into modules (minimum config.HOTSPOT_MIN_GENES_PER_MODULE genes)
  5. hs.calculate_module_scores() — per-cell module activity scores

Key outputs stored in the Hotspot object:

Attribute Description
hs.results Autocorrelation results DataFrame (all genes)
hs.modules Gene → module assignment Series (-1 = unassigned)
hs.local_correlation_z Pairwise local correlation z-scores matrix
hs.module_scores Per-cell module scores DataFrame

Output files

Hotspot results are saved to <output>/hotspot/:

File Description
autocorrelation_results.csv Per-gene autocorrelation statistics
gene_modules.csv Gene–module assignments
significant_genes.csv Genes passing FDR threshold
hotspot_result.pkl Full Hotspot object (pickle)

Visualisations

Generated by genecircuitry/plotting/hotspot_plots.py:

Local correlation heatmap

from genecircuitry.plotting.hotspot_plots import plot_hotspot_local_correlations

plot_hotspot_local_correlations(hs)
# Saved to: config.FIGURES_DIR_HOTSPOT/hotspot_local_correlations.png

Shows the pairwise local correlation matrix across all significant genes, with genes ordered by module assignment.

Module scores violin plot

from genecircuitry.plotting.hotspot_plots import plot_module_scores_violin

plot_module_scores_violin(
    hs,
    adata,
    cluster_key="leiden",
)
# Saved to: config.FIGURES_DIR_HOTSPOT/module_scores_violin.png

Enrichment-annotated heatmap

from genecircuitry.hotspot_processing import plot_hotspot_annotation

plot_hotspot_annotation(
    hs,
    gene_sets=["MSigDB_Hallmark_2020"],   # see enrichment_analysis module
    top_n_annotations=1,
)

Runs pathway over-representation analysis (ORA) on each module’s gene set and annotates the heatmap rows with the top enriched term. Requires pip install -e ".[enrichment]".


Complete workflow example

import scanpy as sc
from genecircuitry import config, set_random_seed
from genecircuitry.hotspot_processing import (
    create_hotspot_object,
    run_hotspot_analysis,
)

set_random_seed(42)

# Load clustered data
adata = sc.read_h5ad("results/clustered_adata.h5ad")

# Create Hotspot object
hs = create_hotspot_object(
    adata,
    top_genes=500,
    layer_key="raw_count",
    embedding_key="X_pca",
)

# Run full analysis
hs = run_hotspot_analysis(hs, adata, cluster_key="leiden")

# Inspect modules
print(f"Modules detected: {hs.modules.max()}")
print(f"Genes per module:\n{hs.modules.value_counts()}")

# Save
import pickle
with open(f"{config.OUTPUT_DIR}/hotspot/hotspot_result.pkl", "wb") as f:
    pickle.dump(hs, f)
hs.results.to_csv(f"{config.OUTPUT_DIR}/hotspot/autocorrelation_results.csv")

Configuration reference

Config parameter Default Description
HOTSPOT_TOP_GENES 500 Top autocorrelated genes to test
HOTSPOT_N_NEIGHBORS 30 Neighbours for spatial KNN graph
HOTSPOT_FDR_THRESHOLD 0.05 FDR cutoff for significant genes
HOTSPOT_MIN_GENES_PER_MODULE 10 Minimum genes required to form a module
HOTSPOT_CORE_ONLY True Use only core genes per module
HOTSPOT_N_JOBS 8 Parallel jobs for autocorrelation test
FIGURES_DIR_HOTSPOT output/figures/hotspot Plot output directory

Troubleshooting

ImportError: hotspot — install with pip install -e ".[hotspot]" or skip with --skip-hotspot.

No modules detected — try increasing config.HOTSPOT_FDR_THRESHOLD (e.g. to 0.1) or decreasing config.HOTSPOT_MIN_GENES_PER_MODULE.

KeyError: 'raw_count' — ensure adata.layers['raw_count'] exists. Run perform_normalization() which stores raw counts automatically, or set layer_key=None to use adata.X.

top_genes too small — if fewer than top_genes genes pass the initial variance filter, Hotspot uses all available genes. Check hs.results to see how many were tested.