Spatial Transcriptomics Analysis with Squidpy
Introduction
While scRNA-seq reveals cellular heterogeneity, it destroys the physical tissue architecture. Spatial transcriptomics (e.g., 10x Visium) preserves spatial context, allowing us to map where specific cells reside and how they interact physically. In this guide, we use Squidpy, an extension of Scanpy, to analyze spatial molecular data.
Architecture Overview
graph LR
HNE(H&E Image) --> SpatialData(AnnData Object)
Counts(Spatial Counts) --> SpatialData
SpatialData --> Pre(Preprocessing & Clustering)
Pre --> UMAP(UMAP & Spatial Plot)
Pre --> Graph(Spatial Neighborhood Graph)
Graph --> Enr(Neighborhood Enrichment)
Graph --> L_R(Ligand-Receptor Co-occurrence)
Prerequisites
- Python 3.9+
- Squidpy, Scanpy, Matplotlib
- A spatial dataset (e.g., a 10x Visium slide of mouse brain)
Step 1: Install and Load Data
pip install squidpy scanpy
We'll use a built-in dataset from Squidpy for demonstration: a coronal section of a mouse brain.
import scanpy as sc
import squidpy as sq
# Load the spatial dataset (includes counts matrix, spatial coordinates, and H&E image)
adata = sq.datasets.visium_hne_image()
# Preprocess the data (similar to standard scRNA-seq)
sc.pp.normalize_total(adata, inplace=True)
sc.pp.log1p(adata)
sc.pp.pca(adata)
sc.pp.neighbors(adata)
sc.tl.umap(adata)
sc.tl.leiden(adata)
Step 2: Visualizing Spatial Data
Unlike standard UMAPs, Squidpy allows you to overlay cluster assignments directly onto the High-Resolution (H&E) histology tissue image.
# Plot UMAP space
sc.pl.umap(adata, color="leiden")
# Plot Spatial space overlaid on the H&E image
sq.pl.spatial_scatter(adata, color="leiden", img_cmap="gray")
Step 3: Spatial Graph Construction
To analyze spatial relationships (like "does Cell Type A live next to Cell Type B?"), we first construct a spatial neighborhood graph connecting physically adjacent capture spots.
# Build spatial graph
sq.gr.spatial_neighbors(adata)
# Visualize the connectivity graph
sq.pl.spatial_scatter(
adata,
connectivity_key="spatial_connectivities",
edges_color="black",
edges_width=1,
color="leiden",
)
Step 4: Neighborhood Enrichment and Ligand-Receptor Analysis
First, we calculate a neighborhood enrichment score to identify which cell clusters co-locate significantly more often than random chance. Then, we look for functional interactions by testing for spatially co-occurring Ligand-Receptor (L-R) pairs.
# 1. Calculate neighborhood enrichment
sq.gr.nhood_enrichment(adata, cluster_key="leiden")
# Plot the enrichment matrix
sq.pl.nhood_enrichment(adata, cluster_key="leiden", method="ward")
# 2. Ligand-Receptor Interaction Analysis
# Load a database of known L-R pairs (e.g., from CellPhoneDB or Omnipath)
res = sq.gr.ligrec(
adata,
n_perms=1000,
cluster_key="leiden",
interactions_params={"resources": "CellPhoneDB"},
)
# Plot dotplot of significant Ligand-Receptor interactions between clusters
sq.pl.ligrec(res, source_groups="Cluster_1", target_groups="Cluster_2", alpha=0.01)
Conclusion
Squidpy bridges the gap between molecular profiling and tissue morphology. By analyzing neighborhood enrichment and specifically pinpointing ligand-receptor interactions across physical space, researchers can map out the complex spatial microenvironments and cell-cell communication networks of tumors, brain tissue, and developing organs.