| name | bio-applied-spatial-transcriptomics |
| description | Analyze Visium/Xenium/MERFISH spatial transcriptomics with Squidpy/Scanpy: QC, spatial neighbor graphs, Moran's I spatially variable genes, tissue-image plots. Use for spatial autocorrelation or SVG detection. |
| tool_type | python |
| primary_tool | squidpy |
Spatial Transcriptomics
When to Use
- Loading or QC-ing 10x Visium, Xenium, MERFISH, seqFISH+, Slide-seq, or Stereo-seq data with spatial coordinates.
- Detecting spatially variable genes (SVGs) or testing spatial autocorrelation (Moran's I, Geary's C).
- Building a spatial neighbor graph from spot/cell coordinates for downstream spatial statistics.
- Visualizing clusters or gene expression overlaid on a tissue histology image.
- Deconvolving multi-cell Visium spots into cell-type proportions using an scRNA-seq reference.
Version Compatibility
- Python: squidpy >=1.4, scanpy >=1.10, anndata >=0.10, Python >=3.10
- R: Seurat >=5.0 (
Load10X_Spatial, SCTransform, FindSpatiallyVariableFeatures)
Prerequisites
pip install squidpy scanpy leidenalg (or install.packages("Seurat") in R)
- Familiarity with the standard scRNA-seq pipeline (normalize -> HVG -> PCA -> neighbors -> Leiden) — see
bio-single-cell-preprocessing and bio-single-cell-clustering
- Basic AnnData structure (
bio-single-cell-data-io / anndata skill)
Platform Comparison
| Platform | Resolution | Type | Key feature |
|---|
| 10x Visium | 55 um (~10-20 cells/spot) | Capture-based | H&E co-registration |
| 10x Xenium | ~10 um (single-cell) | In situ sequencing | Targeted ~400 genes |
| MERFISH | Sub-cellular | In situ imaging | Error-robust barcoding |
| Slide-seq v2 | ~10 um | Capture-based | High-res bead array |
| Stereo-seq | 500 nm | Capture-based | Ultra-high resolution |
AnnData spatial slots: adata.obsm["spatial"] (n_spots, 2 pixel coords), adata.uns["spatial"] (tissue image + scale factors), adata.obsp["spatial_connectivities"] (spatial neighbor graph, after sq.gr.spatial_neighbors).
Core Workflow
Goal: Load a Visium dataset, QC-filter spots, and cluster on expression (ignoring spatial coordinates).
Approach: reuse the standard scanpy pipeline — spatial coordinates only matter starting at the neighbor-graph step.
import scanpy as sc
import squidpy as sq
def load_and_cluster_visium(min_counts=200, min_cells=5, n_top_genes=2000, resolution=0.5):
"""Load the public Visium mouse-brain dataset, QC-filter, normalize, and Leiden-cluster.
Returns the processed AnnData with `obs["leiden"]` cluster labels.
"""
adata = sq.datasets.visium_hne_adata()
adata.var["mt"] = adata.var_names.str.startswith("mt-")
sc.pp.calculate_qc_metrics(adata, qc_vars=["mt"], inplace=True)
sc.pp.filter_cells(adata, min_counts=min_counts)
sc.pp.filter_genes(adata, min_cells=min_cells)
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=n_top_genes, subset=False)
sc.pp.pca(adata, n_comps=50, use_highly_variable=True)
sc.pp.neighbors(adata, n_pcs=30)
sc.tl.leiden(adata, resolution=resolution, key_added="leiden")
return adata
Goal: Build the spatial neighbor graph and detect spatially variable genes (SVGs).
Approach: sq.gr.spatial_neighbors connects spots by physical proximity, then sq.gr.spatial_autocorr(mode="moran") computes Moran's I with a permutation p-value per gene.
def detect_spatially_variable_genes(adata, n_genes=500, n_perms=100, n_rings=1):
"""Build a spatial neighbor graph and rank genes by Moran's I spatial autocorrelation.
n_perms=100 gives approximate p-values for exploration; use n_perms=1000 for
publication-quality results (permutation testing is required because spatial
data violates the independence assumption of parametric tests).
"""
sq.gr.spatial_neighbors(adata, coord_type="visium", n_rings=n_rings)
sq.gr.spatial_autocorr(
adata, mode="moran", genes=adata.var_names[:n_genes], n_perms=n_perms
)
moran = adata.uns["moranI"].sort_values("I", ascending=False)
return moran
Goal: Visualize Leiden clusters and top SVGs overlaid on the tissue image.
Approach: sq.pl.spatial_scatter renders points on top of the H&E image stored in adata.uns["spatial"].
def plot_clusters_and_svgs(adata, moran, n_top=6):
"""Plot spatial clusters and the top-N spatially variable genes on the tissue image."""
sq.pl.spatial_scatter(adata, color="leiden", size=1.4)
top_svgs = moran.head(n_top).index.tolist()
sq.pl.spatial_scatter(adata, color=top_svgs, ncols=3, size=1.4, img_alpha=0.4)
return top_svgs
R Equivalent (Seurat)
Goal: Load a Visium sample, cluster, and find spatially variable features in R.
Approach: Seurat's spatial workflow mirrors the Scanpy/Squidpy one, with FindSpatiallyVariableFeatures(method = "moransi") as the SVG-detection step.
library(Seurat)
brain <- Load10X_Spatial(data.dir = "visium_sample/")
brain <- SCTransform(brain, assay = "Spatial", verbose = FALSE)
brain <- RunPCA(brain, assay = "SCT", verbose = FALSE)
brain <- FindNeighbors(brain, reduction = "pca", dims = 1:30)
brain <- FindClusters(brain, resolution = 0.5)
brain <- RunUMAP(brain, reduction = "pca", dims = 1:30)
SpatialDimPlot(brain label label.size
brain FindSpatiallyVariableFeatures
brain assay features VariableFeaturesbrain
selection.method
top_svgs headSpatiallyVariableFeaturesbrain selection.method
SpatialFeaturePlotbrain features top_svgs ncol
Cell-type Deconvolution (Visium)
Each Visium spot captures ~10-20 cells; deconvolution estimates per-spot cell-type composition from an scRNA-seq reference.
| Tool | Approach | Reference needed |
|---|
| RCTD (spacexr, R) | Poisson GLM | Yes (scRNA-seq) |
| cell2location | Negative binomial + hierarchical Bayesian | Yes |
| Stereoscope | Probabilistic (scvi-tools based) | Yes |
| NNLS | Non-negative least squares on marker genes | Yes (marker genes) |
Result is typically stored as adata.obsm["cell_type_proportions"] (n_spots x n_types), plottable with sq.pl.spatial_scatter(adata, color=proportions.columns).
Pitfalls
- Mitochondrial prefix: use
"mt-" for mouse, "MT-" for human when computing pct_counts_mt — wrong case silently zeroes out the QC metric.
- Batch effects: check for batch confounding across sections/slides before interpreting spatial patterns as biological.
- Permutation count:
n_perms=100 (Python) gives approximate p-values only; use n_perms=1000+ (or R equivalent) for publication.
- Spot vs. cell resolution: Visium/Slide-seq spots contain multiple cells — cluster labels and SVGs describe local tissue neighborhoods, not single cells, unless deconvolved.
- Deconvolution reference quality: results are highly sensitive to how well the scRNA-seq reference's cell types match the tissue and technology.
coord_type mismatch: sq.gr.spatial_neighbors(coord_type=...) must match the platform ("visium" for hex-grid spots, "generic" with a radius/n_neighs for imaging-based data) or the graph will be topologically wrong.
See Also
bio-single-cell-preprocessing — QC/normalization shared with scRNA-seq
bio-single-cell-clustering — Leiden/Louvain clustering used before spatial analysis
bio-spatial-transcriptomics-spatial-deconvolution — dedicated cell-type deconvolution methods
bio-spatial-transcriptomics-spatial-domains — spatial domain detection beyond expression clustering