Saturday, July 4, 2026

Neighborhood Enrichment Analysis with Squidpy: Which Clusters Co-Locate?

After identifying spatially variable genes (posts 5 and 6), a natural next question is: which cell populations actually live next to each other on the tissue? That's exactly what neighborhood enrichment analysis answers.

In this post we'll use sq.gr.nhood_enrichment from Squidpy to measure which Leiden clusters co-locate more — or less — than you'd expect by chance, on the same 10x Genomics Visium mouse brain section used throughout this series.

What is neighborhood enrichment?

Neighborhood enrichment computes a z-score for every pair of clusters (i, j). It asks: if I stand on a spot in cluster i, how often do I see cluster j as a neighbor, compared to random expectation?

  • Positive z-score → clusters co-localize more than random (spatially adjacent)
  • Negative z-score → clusters are spatially segregated (avoid each other)
  • Significance is assessed by a permutation test (1,000 iterations by default)

The permutation shuffles cluster labels and recomputes adjacency counts each time, building a null distribution. Your observed count is then standardized against that distribution.

Pipeline

The analysis requires spatial coordinates (for the neighbor graph) and cluster labels. We run the standard Scanpy preprocessing first:

import squidpy as sq
import scanpy as sc

adata = sc.read_h5ad("visium_hne_adata.h5ad")

# Preprocessing
# NOTE: visium_hne_adata ships .X ALREADY log-normalized (max ~8.4, non-integer);
# the raw integer counts are in adata.raw.X. Normalizing here would log it twice —
# no error, just quietly wrong numbers everywhere below.
sc.pp.highly_variable_genes(adata, n_top_genes=2000, flavor="cell_ranger")
sc.pp.pca(adata)
sc.pp.neighbors(adata)
sc.tl.leiden(adata, key_added="cluster")   # requires: pip install leidenalg

# Spatial graph + neighborhood enrichment
sq.gr.spatial_neighbors(adata)
sq.gr.nhood_enrichment(adata, cluster_key="cluster")

# Visualize
sq.pl.nhood_enrichment(
    adata,
    cluster_key="cluster",
    title="Neighborhood enrichment (Leiden clusters)"
)
sq.pl.spatial_scatter(adata, color="cluster", title="Leiden clusters on tissue")

Note: sq.gr.spatial_neighbors builds a hexagonal neighbor graph from spot coordinates. sq.gr.nhood_enrichment stores results in adata.uns["cluster_nhood_enrichment"] as both z-scores and raw counts.

Result: Leiden cluster layout on tissue

We get 15 Leiden clusters (0–14) across 2,688 spots. The spatial scatter plot shows how they map onto the tissue section:

Leiden clusters mapped onto mouse brain Visium tissue

Figure 1. Leiden clusters on the Visium mouse brain section. Distinct anatomical regions (cortex, hippocampus, thalamus, white matter) form spatially coherent cluster zones.

The clustering captures visible tissue architecture — cortical layers form a continuous band, hippocampal subfields cluster together, and smaller subcortical nuclei appear as isolated patches. This spatial coherence is a good sign before running neighborhood enrichment.

Result: Neighborhood enrichment heatmap

Neighborhood enrichment z-score heatmap for 15 Leiden clusters

Figure 2. Neighborhood enrichment heatmap. The diagonal (self-enrichment) dominates, but off-diagonal hot spots reveal which distinct clusters spatially border each other.

Reading the heatmap

The diagonal is always strongly positive — every cluster borders itself most. The interesting biology is in the off-diagonal:

Cluster pair z-score Interpretation
12 ↔ 14 +7.59 Strongest co-localization — likely adjacent subcortical zones
5 ↔ 14 +4.62 Cluster 14 (smallest, 31 spots) is a boundary cluster between 5 and 12
10 ↔ 11 +2.49 Moderate co-localization
7 ↔ 13 +2.38 Moderate co-localization
Most other pairs −3 to −15 Spatial segregation — distinct anatomical compartments

Cluster 14 is the smallest cluster (only 31 spots) and shows strong enrichment with both 12 and 5 — this pattern is typical of a narrow interface or transitional zone sitting at the boundary between two larger tissue compartments.

The widespread negative z-scores reflect the highly structured anatomy of the mouse brain: cortical, hippocampal, and subcortical regions are spatially segregated at this resolution.

Accessing the raw z-scores

import numpy as np
import pandas as pd

zscore = adata.uns["cluster_nhood_enrichment"]["zscore"]
n = zscore.shape[0]
rows = []
for i in range(n):
    for j in range(i+1, n):
        rows.append(dict(cluster_i=i, cluster_j=j, zscore=zscore[i, j]))

df = pd.DataFrame(rows).sort_values("zscore", ascending=False)
print(df.head(10))

This extracts every off-diagonal pair ranked by enrichment, which is useful for downstream annotation when you want to know which clusters to examine as boundary zones.

Key takeaways

  • sq.gr.nhood_enrichment requires both spatial neighbors (from sq.gr.spatial_neighbors) and cluster labels — run them in that order.
  • The permutation z-score is robust: values above +2 or below −2 are generally meaningful.
  • Small clusters with high off-diagonal enrichment often mark tissue boundaries or transitional zones.
  • Combining the heatmap with sq.pl.spatial_scatter is essential — the numbers only make sense once you see where clusters sit on the tissue.

In the next post, we'll look at co-occurrence analysis (sq.gr.co_occurrence), which extends this idea to continuous distance scales rather than immediate neighbors.

Next post: Co-occurrence analysis (sq.gr.co_occurrence)

This series covers spatial transcriptomics analysis step by step — from SVGs to cell-cell interactions. New posts go up roughly once a week.

Follow on Blogger to get notified, or bookmark github.com/Lociven/spatiabio-tutorials for the notebooks.

z-score interpretation, in depth

n_perms and other things this post glossed over

Pack 1's neighborhood enrichment notebook goes deeper on permutation count, z-score interpretation, and when the test misleads you.

Get Pack 1 for $19 →

Full notebook: github.com/Lociven/spatiabio-tutorials
Data: squidpy built-in visium_hne_adata (10x Genomics Visium, mouse brain H&E)

No comments:

Post a Comment

Tangram: Mapping Single-Cell Annotations onto Spatial Coordinates

SPATIAL TRANSCRIPTOMICS · TUTORIAL Tangram: Mapping Single-Cell Annotations onto Spatial Coordinates Transfer cell type labels,...