Saturday, July 4, 2026

Neighborhood Enrichment Analysis with Squidpy: Which Clusters Are Actually Neighbors?

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)

Wednesday, July 1, 2026

I Gave Claude Science One Prompt. It Ran a Full Spatial Analysis.

By Lociven · SpatiaBio · July 2, 2026

I gave Claude Science AI Workbench — Anthropic's new scientific analysis platform — a single prompt and a dataset. Thirty minutes later, it handed me five publication-quality figures, a fully executable Jupyter notebook, and a reproducibility report.


What is Claude Science AI Workbench?

Released in June 2026, Claude Science is Anthropic's platform for running agentic scientific analysis inside a sandboxed Linux environment. You describe what you want in plain language. The agent installs packages, writes code, executes it, fixes errors, and returns figures and notebooks — all without you touching a terminal.

For spatial transcriptomics researchers, the pitch is simple: skip the environment setup, skip the debugging, get directly to the biology.

The prompt I used

Analyze the Squidpy IMC breast cancer dataset (sq.datasets.imc()):
1. Spatial neighbors graph — Delaunay triangulation
2. Neighborhood enrichment (sq.gr.nhood_enrichment, n_perms=1000)
3. Co-occurrence as a function of distance
4. Interaction matrix and centrality scores
5. Ripley's L for clustering vs. randomness

Use publication-grade conventions. Return a Jupyter notebook with all outputs embedded.

That's it. No code. No conda commands. The agent created a spatial conda environment (squidpy 1.8.2, scanpy 1.11.5), downloaded the Jackson et al. breast cancer IMC dataset (4,668 cells × 34 protein markers, 11 cell types), ran the full pipeline, and produced the figures below.


Figure 1 — Cell types in situ

The IMC dataset captures 11 cell types across a breast cancer tissue section. Apoptotic tumor cells (cyan) dominate numerically and are distributed throughout the tissue.

Breast cancer IMC cell types in situ

Generated with Claude Science AI Workbench (Anthropic, 2026) · squidpy 1.8.2 · Jackson et al., Nature 578, 2020

Figure 2 — Neighborhood enrichment: which cell types co-locate?

The permutation-based z-scores reveal the tissue's immune architecture at a glance. Three patterns stand out:

Immune clustering (z = +29 to +36): T cells, macrophages, and stromal cells form a tightly co-located immune compartment — the canonical TIL niche.
Tumor-immune avoidance (z = −21 to −28): Apoptotic tumor cells strongly avoid macrophages. Immune exclusion at the single-cell spatial level.
Tumor self-clustering (z = +33 to +48): Each tumor subtype clusters with itself, consistent with clonal expansion in distinct spatial niches.
Neighborhood enrichment heatmap

Generated with Claude Science AI Workbench (Anthropic, 2026) · squidpy 1.8.2 · Jackson et al., Nature 578, 2020

Interpretation note: A z-score above +10 is conventionally significant with n_perms=1000. Values of +29 to +48 indicate extremely non-random co-localization.

Figure 3 — Co-occurrence: the spatial scale of immune clustering

Co-occurrence shows at what distance cell types interact. The steep decay curves confirm that immune clustering is a contact-range phenomenon — not a tissue-wide gradient. Macrophages are enriched 7× near other macrophages at minimal distance, then fall sharply.

Co-occurrence distance curves

Generated with Claude Science AI Workbench (Anthropic, 2026) · squidpy 1.8.2 · Jackson et al., Nature 578, 2020

Figure 4 — Graph centrality: who connects the tissue?

Apoptotic tumor cells have degree centrality 0.83 and closeness centrality 0.84 — nearly 5× higher than any other cell type. They are physically positioned at the crossroads of the tissue, spatially interleaved with every other population.

Graph centrality scores

Generated with Claude Science AI Workbench (Anthropic, 2026) · squidpy 1.8.2 · Jackson et al., Nature 578, 2020

Figure 5 — Ripley's L: clustering vs. complete spatial randomness

Apoptotic tumor cells spike massively above the 95% CSR envelope across all distances — extreme, scale-independent clustering. Combined with the centrality result: self-clustering + network centrality is characteristic of a dominant tumor clone that has physically reorganized the tissue architecture.

Ripley's L clustering analysis

Generated with Claude Science AI Workbench (Anthropic, 2026) · squidpy 1.8.2 · Jackson et al., Nature 578, 2020


What Claude Science actually did (under the hood)

The agent's execution log was visible in real time. It:

1. Created a conda environment from scratch (squidpy==1.8.2, scanpy==1.11.5)
2. Downloaded and validated the IMC dataset (4,668 cells × 34 markers)
3. Ran all five Squidpy analyses with appropriate parameters
4. Noticed its own figure title was inaccurate and self-corrected it before saving
5. Authored a 23-cell reproducible notebook with embedded outputs
6. Bundled the dataset, requirements.txt, and README automatically

The self-correction on the Ripley's L title is worth noting. It cross-checked the figure against the data and caught a misleading generalization. That's not what most analysis scripts do.


Bonus: the same pipeline on Visium data

The IMC analysis above uses point-cloud coordinates. I reran the same prompt on a 10x Genomics Visium section (grid-based spots, 55 µm resolution). The agent automatically switched to coord_type="grid" and n_neighs=6 for the hexagonal lattice without being instructed to — it inferred this from the data format.

Figure 6 — Visium: spatial clusters

On the Visium hexagonal grid, cluster boundaries directly reflect anatomical structure. The sharper compartmentalization here vs. the IMC data is expected: Visium spots are physically constrained to a grid, so spatial patterns emerge more cleanly at tissue scale.

Visium spatial clusters

Generated with Claude Science AI Workbench (Anthropic, 2026) · squidpy 1.8.2 · 10x Genomics Visium

Figure 7 — Visium: gene expression overlay

Gene expression mapped onto spatial coordinates reveals domain-specific marker gradients. Unlike IMC (protein-level), Visium captures transcriptomic heterogeneity at spot resolution. Claude Science generated both the continuous expression overlay and the categorical cluster map in a single run.

Visium gene expression overlay

Generated with Claude Science AI Workbench (Anthropic, 2026) · squidpy 1.8.2 · 10x Genomics Visium

Figure 8 — Visium: neighborhood enrichment

The neighborhood enrichment heatmap on Visium shows a clean tissue-layer organization: adjacent anatomical compartments co-enrich (positive z-scores), distant layers avoid each other (negative z-scores). You can trace these directly back to the cluster map in Figure 6.

Visium neighborhood enrichment

Generated with Claude Science AI Workbench (Anthropic, 2026) · squidpy 1.8.2 · 10x Genomics Visium

The full Visium pipeline — grid graph parameters, spatial autocorrelation, and multi-sample batch correction — is in Pack 1 notebooks 03, 06, and 06b.

Honest assessment: is it useful?

✓ Works well for

• Standard Squidpy pipelines
• Exploratory analysis on new datasets
• Generating a reproducible baseline notebook
• Researchers new to spatial omics
• Rapid figure drafts for lab meetings

✗ Limitations

• Requires manual approval for each tool call
• Can't customize beyond what you describe
• No Visium HD / large dataset chunking
• Figure aesthetics are functional, not polished
• Session restarts lose progress

Bottom line: For getting from raw data to interpretable spatial figures in one session without writing code, it genuinely works. For publication-level customization, you still need to go hands-on. Pack 1 covers exactly that layer.


Sister blog

The biology behind the cells you're mapping

NeoantigenLab covers neoantigen biology, HLA typing, pVACseq, and cancer immunotherapy — the immunology context for what spatial analysis reveals.

Visit NeoantigenLab →

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...