Neighborhood enrichment (post 7) tells you which clusters border each other at the spot scale. But tissue interactions don't stop at the immediate neighbor — gradients of signaling molecules, migrating cells, and diffuse niche factors operate over longer distances. Co-occurrence analysis extends the question to a continuous range of distances.
What is co-occurrence?
For a pair of clusters (i, j), sq.gr.co_occurrence computes the probability that a spot from cluster j appears within distance d of a spot from cluster i, normalized against the global frequency of cluster j. Values above 1 mean co-occurrence is enriched at that distance; values below 1 mean avoidance.
This gives you a distance-resolved enrichment curve rather than a single z-score — useful for detecting spatial gradients and halo effects around tissue compartments.
Code
import squidpy as sq
import scanpy as sc
adata = sc.read_h5ad("visium_hne_adata.h5ad")
# 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")
sq.gr.spatial_neighbors(adata)
sq.gr.co_occurrence(adata, cluster_key="cluster")
sq.pl.co_occurrence(
adata,
cluster_key="cluster",
clusters=["0", "1", "2"], # top 3 clusters by spot count
figsize=(10, 4)
)
Note: sq.gr.co_occurrence can be slow on large datasets — it evaluates distances across all spot pairs. On this 2,688-spot dataset it completes in a few minutes. For larger datasets, subsample with adata[adata.obs.sample(frac=0.5).index].
Cluster layout on tissue
Figure 1. Leiden clusters on tissue. Clusters 0, 1, 2 are the three largest (307, 303, 261 spots respectively).
Co-occurrence curves
Figure 2. Co-occurrence probability vs. distance for clusters 0, 1, and 2. Each panel shows how likely each other cluster is to appear within a given distance. Values >1 = enriched, <1 = avoided.
How to read the curves
Each panel in the plot corresponds to one query cluster. The x-axis is distance (in coordinate units), and the y-axis is the co-occurrence score:
- Score = 1: cluster j appears at this distance at its global frequency — no enrichment or avoidance
- Score > 1: cluster j is over-represented at this distance (spatial affinity)
- Score < 1: cluster j is under-represented (spatial segregation)
- Peak distance: the distance at which co-occurrence is maximally enriched — interpretable as the typical interface width between two regions
A cluster showing strong self-enrichment (its own curve rising steeply at short distances) indicates a compact, spatially cohesive region. Clusters that show enrichment for neighbors at intermediate distances (not immediate) often represent transitional or boundary zones.
Neighborhood enrichment vs co-occurrence
| Feature | Neighborhood enrichment | Co-occurrence |
|---|---|---|
| Scale | Immediate neighbors only | All distances up to max range |
| Output | Single z-score per pair | Curve across distances |
| Speed | Fast (permutation on graph) | Slower (all pairwise distances) |
| Best for | Quick overview of adjacency | Spatial gradient / halo effects |
Use both: neighborhood enrichment for an initial overview, co-occurrence when you want to understand the spatial scale at which two populations interact.
Next post: Ligand-receptor interactions (sq.gr.ligrec)
We know which clusters are adjacent. Now we ask: are they actively sending signals to each other?
Notebooks: github.com/Lociven/spatiabio-tutorials
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