Saturday, July 11, 2026

Co-occurrence Analysis with Squidpy: Spatial Patterns Across Distance

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

Leiden clusters on Visium mouse brain

Figure 1. Leiden clusters on tissue. Clusters 0, 1, 2 are the three largest (307, 303, 261 spots respectively).

Co-occurrence curves

Co-occurrence probability curves for top 3 Leiden clusters

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

Squidpy's Delaunay Radius Pruning Builds a Graph You Can't Save — Here's the Workaround

Squidpy · spatial_neighbors_delaunay · h5ad · IORegistryError · still in latest release Squidpy's Delaunay Radius Pruning Builds a G...