Tutorial

Spatial Transcriptomics Analysis with Squidpy

Difficulty: Intermediate Time: 15 min read

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.