Squidpy: Spatial Transcriptomics
Squidpy is the scverse toolkit for spatially-resolved omics, built on AnnData and
SpatialData. It answers questions a non-spatial scRNA-seq pipeline cannot: which
cell types sit next to which (neighborhood enrichment), how cell-type pairs
co-occur across distance (co-occurrence), which genes vary across tissue space
(Moran's I / spatially variable genes), and what ligand-receptor signalling is
plausible (ligrec). This skill does the spatial analysis; it hands non-spatial
QC/clustering to alterlab-scanpy and spot deconvolution to alterlab-scvi-tools.
When to Use This Skill
Use when the request involves:
- Spatial transcriptomics / spatially-resolved omics on Visium, Visium HD, Xenium, MERFISH/MERSCOPE, or CosMx data.
- Building a spatial neighbor graph and running neighborhood enrichment, co-occurrence, interaction matrix, Ripley's statistics, or centrality scores.
- Finding spatially variable genes via Moran's I (
spatial_autocorr) or Sepal. - Ligand-receptor analysis in a spatial context (
ligrec). - Reading platform output into AnnData/SpatialData and choosing the right
coord_typefor the platform.
Does NOT Trigger
| Request | Route to |
|---|---|
| Non-spatial scRNA-seq QC, normalization, PCA/UMAP, Leiden clustering, marker genes | alterlab-scanpy |
| Spot deconvolution / mapping cell types to Visium spots (destVI, Tangram), probabilistic batch correction/integration | alterlab-scvi-tools (see its references/models-spatial.md) |
Building/slicing/concatenating .h5ad AnnData objects, layer & obsm wrangling (no spatial analysis) |
alterlab-anndata |
| RNA-velocity / trajectory dynamics | alterlab-scvelo |
| Bulk RNA-seq differential expression from a count matrix | alterlab-pydeseq2 |
| Raw FASTQ → expression matrix (read alignment/quantification) | alterlab-rnaseq-quant |
| Diversity / ecology statistics on a feature table | alterlab-scikit-bio |
If the user wants the whole pipeline ("cluster my Xenium data, then find which
cell types are neighbors"), run the scanpy clustering step under alterlab-scanpy
first, then return here for the spatial graph and enrichment.
The One Decision That Matters: Platform → coord_type
Squidpy's spatial graph depends on the measurement geometry. Getting coord_type
wrong silently produces a meaningless graph. (All parameter behavior below is from
the squidpy 1.8 sq.gr.spatial_neighbors API.)
| Platform | Resolution | Builder | Pair with |
|---|---|---|---|
| Visium | spot (multi-cell, hex grid) | coord_type="grid", n_neighs=6, n_rings=1..2 |
deconvolution → alterlab-scvi-tools |
| Visium HD | 2/8/16 µm bins (square grid) | coord_type="grid" (square lattice) |
binning choice up front |
| Xenium / MERFISH / CosMx | single cell | coord_type="generic", delaunay=True (or n_neighs=k) |
direct cell-type analysis |
coord_type=Noneauto-picks"grid"only whenspatialis inadata.unswithn_neighs=6(the Visium signature); otherwise it falls back to"generic". Setcoord_typeexplicitly rather than relying on auto-detection.delaunay=Trueis only used whencoord_type="generic"; it builds the graph from a Delaunay triangulation instead of k-nearest spots.n_ringsis only used forcoord_type="grid".
Loading Data (pick the reader for the platform)
import squidpy as sq
import scanpy as sc
# Visium (legacy spot data) — squidpy's own reader, returns AnnData
adata = sq.read.visium("path/to/visium_outs/")
# Vizgen MERSCOPE / Nanostring CosMx via squidpy readers
adata = sq.read.vizgen("path/to/merscope/", counts_file="cell_by_gene.csv",
meta_file="cell_metadata.csv")
adata = sq.read.nanostring("path/to/cosmx/", counts_file="exprMat_file.csv",
meta_file="metadata_file.csv", fov_file="fov_positions.csv")
For Xenium and Visium HD, use the spatialdata-io readers (squidpy has no
sq.read.xenium) and operate on a SpatialData object:
from spatialdata_io import xenium, visium_hd, merscope
sdata = xenium("path/to/xenium_outs/") # 10x Xenium
sdata = visium_hd("path/to/visium_hd_outs/") # 10x Visium HD
sdata = merscope("path/to/merscope/") # Vizgen MERSCOPE
spatialdata-io reader names are verified against the spatialdata-io stable API.
Squidpy 1.8 accepts SpatialData objects directly; see
references/spatialdata_io.md for the SpatialData ↔ AnnData (table) flow.
Standard Spatial Workflow
QC, normalization, HVGs, PCA, neighbors, Leiden, and sc.tl.umap are scanpy
steps — run them via alterlab-scanpy. Once you have clusters / cell-type labels,
do the spatial part here.
import squidpy as sq
# 1. Build the spatial neighbor graph (choose coord_type per the table above)
sq.gr.spatial_neighbors(adata, coord_type="generic", delaunay=True) # Xenium/MERFISH
# sq.gr.spatial_neighbors(adata, coord_type="grid", n_neighs=6) # Visium
# 2. Neighborhood enrichment: which cluster pairs are spatially adjacent?
sq.gr.nhood_enrichment(adata, cluster_key="leiden")
sq.pl.nhood_enrichment(adata, cluster_key="leiden")
# 3. Co-occurrence across distance
sq.gr.co_occurrence(adata, cluster_key="leiden")
sq.pl.co_occurrence(adata, cluster_key="leiden", clusters="0")
# 4. Spatially variable genes via Moran's I
sq.gr.spatial_autocorr(adata, mode="moran")
svgs = adata.uns["moranI"].head(20) # ranked by Moran's I
# 5. Ligand-receptor interaction (Omnipath-backed)
sq.gr.ligrec(adata, cluster_key="leiden")
Other graph statistics: sq.gr.interaction_matrix, sq.gr.centrality_scores,
sq.gr.ripley (clustering/dispersion vs. CSR), and sq.gr.sepal (an alternative
spatially-variable-gene test). Visualize tissue with sq.pl.spatial_scatter
(spot/point) or sq.pl.spatial_segment (segmented cells). For image features on
H&E/IF, the sq.im module (process, segment, calculate_image_features)
operates on an ImageContainer.
Helper script — build the graph and run the core statistics in one call:
uv run python skills/bioinformatics/alterlab-squidpy-spatial/scripts/spatial_neighborhood.py \
clustered.h5ad --platform xenium --cluster-key leiden --out spatial_report.json
See scripts/spatial_neighborhood.py --help. It chooses coord_type from
--platform, runs spatial_neighbors, nhood_enrichment, co_occurrence, and
spatial_autocorr, and writes a JSON summary (top spatially variable genes + the
enrichment z-score matrix) plus the updated .h5ad.
Deeper References
references/platform_routing.md— full platform→coord_typedecision table, then_neighs/n_rings/delaunayparameter semantics, and per-platform gotchas.references/analysis_recipes.md— copy-paste recipes for eachsq.gr/sq.plfunction with the parameters that matter and how to read the outputs.references/spatialdata_io.md— reading Xenium / Visium HD / MERSCOPE into SpatialData and getting the AnnDatatablesquidpy operates on.
Self-Check Before Reporting
- Did you set
coord_typeto match the platform (grid for Visium, generic for single-cell)? A wrong graph invalidates every downstream statistic. - Did clustering/QC run under
alterlab-scanpy(this skill assumes labels exist)? - For Visium spot data, did you flag that deconvolution (
alterlab-scvi-tools) is needed before cell-type-level claims — spots are multi-cell? - Are
nhood_enrichmentz-scores reported with the permutation context, not as raw counts?
Part of the AlterLab Academic Skills suite.