Saturday, August 22, 2026

SpatialDE2: Finding Spatially Variable Genes Without Clustering

By Lociven · SpatiaBio · August 2026

Finding genes that vary spatially — not just between clusters, but continuously across tissue — is one of the core tasks in spatial transcriptomics. SpatialDE2 is the current standard for this. It's faster than its predecessor, handles Visium HD and single-cell resolution data, and gives you interpretable spatial patterns. This post covers how it works and how to run it.


What is a spatially variable gene?

A spatially variable gene (SVG) is a gene whose expression level is not randomly distributed across tissue — it shows spatial structure. That structure might be a sharp boundary (tumor vs stroma), a gradient (hypoxia from center to edge), or a periodic pattern (cortical layers in brain).

Standard differential expression tests (comparing cluster A vs cluster B) can find SVGs indirectly, but they depend on your clustering choices. SpatialDE2 tests for spatial variation directly, without needing predefined clusters, using a Gaussian process model.

SpatialDE2 vs Moran's I: Moran's I (available in Squidpy via sq.gr.spatial_autocorr) is faster and good for a quick screen. SpatialDE2 is slower but models the spatial covariance structure more accurately and provides pattern classification (linear gradient, periodic, etc.).


Installation

pip install SpatialDE
# SpatialDE2 requires Python 3.9+ and JAX
pip install jax jaxlib
pip install git+https://github.com/PMBio/SpatialDE.git

Running SpatialDE2

import squidpy as sq
import scanpy as sc
import SpatialDE
import numpy as np
import pandas as pd

# Load and prepare Visium data
adata = sq.read.visium("path/to/visium/")
sc.pp.filter_genes(adata, min_cells=3)
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)

# Use highly variable genes to limit runtime
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
adata_hvg = adata[:, adata.var["highly_variable"]].copy()

# Get spatial coordinates
coords = pd.DataFrame(
    adata_hvg.obsm["spatial"],
    columns=["x", "y"],
    index=adata_hvg.obs_names
)

# Expression matrix (spots x genes)
expr = pd.DataFrame(
    adata_hvg.X.toarray() if hasattr(adata_hvg.X, "toarray") else adata_hvg.X,
    index=adata_hvg.obs_names,
    columns=adata_hvg.var_names
)

# Run SpatialDE2
results = SpatialDE.run(coords, expr)
results = results.sort_values("qvalue")

print(results.head(10)[["g", "l", "qvalue", "FSV"]])

Key output columns:

  • g — gene name
  • l — estimated spatial length scale (how broad the pattern is)
  • qvalue — FDR-adjusted p-value for spatial variation
  • FSV — fraction of variance explained by spatial structure (0–1; higher = more spatially structured)

The difference in one view. Left: Moran's I returns a ranking — these are the real top genes from the mouse-brain dataset (top 500 HVGs, n_perms=200). Right: a single score cannot distinguish a compact hotspot from a smooth gradient; all three shapes can score high. Classifying the pattern is what SpatialDE2's Gaussian-process test adds, and what the extra runtime pays for.

Pattern classification with SPARK-X

SpatialDE2 classifies SVGs into spatial patterns: linear gradients, radial patterns, and periodic patterns. For most tissue sections, linear and radial patterns dominate. Periodic patterns are most relevant in brain (cortical layers) and intestine (crypt-villus axis).

# Get top SVGs
sig_results = results[results["qvalue"] < 0.05].sort_values("FSV", ascending=False)
top_svgs = sig_results["g"].head(20).tolist()

print(f"Significant SVGs: {len(sig_results)}")
print(f"Top 5 by FSV:\n{sig_results[['g','FSV','l']].head()}")

# Visualize top SVGs on tissue
sq.pl.spatial_scatter(
    adata,
    color=top_svgs[:4],
    ncols=2,
    cmap="magma",
    size=1.5
)

Quick alternative: Moran's I in Squidpy

If you need a fast screen before committing to SpatialDE2, Squidpy's sq.gr.spatial_autocorr with Moran's I runs in minutes on a full Visium dataset:

sq.gr.spatial_neighbors(adata, coord_type="grid", n_rings=2)
sq.gr.spatial_autocorr(adata, mode="moran", genes=adata.var_names[adata.var.highly_variable])

# Top spatially autocorrelated genes
top_morans = (
    adata.uns["moranI"]
    .sort_values("I", ascending=False)
    .head(20)
)
print(top_morans[["I", "pval_norm_fdr_bh"]])

Use Moran's I to screen, then run SpatialDE2 on the top candidates for pattern classification and accurate FSV estimates.


Get the complete pack

SpatiaBio Pack 2 — Deconvolution & Spatial Pattern Detection

The full SpatialDE2 spatial pattern classification notebook — Moran's I screening, hotspot/gradient pattern detection, and spatial co-expression modules. Plus a cell2location deconvolution notebook.

Get it for $19 →

From the NeoantigenLab sister blog

Using spatial data for cancer immunology research?

NeoantigenLab covers neoantigen biology, pVACseq, HLA typing, and checkpoint inhibitors — the biology behind your spatial findings.

Visit NeoantigenLab →

Tags: SpatialDE2, spatially variable genes, SVG, Moran's I, spatial autocorrelation, Visium, Squidpy, Gaussian process, spatial transcriptomics

No comments:

Post a Comment

Tangram: Mapping Single-Cell Annotations onto Spatial Coordinates

SPATIAL TRANSCRIPTOMICS · TUTORIAL Tangram: Mapping Single-Cell Annotations onto Spatial Coordinates Transfer cell type labels,...