Tuesday, June 30, 2026

Visualizing Spatially Variable Genes on Tissue: sq.pl.spatial_scatter

In the previous post we ran Moran's I on the 10x Genomics mouse brain Visium dataset and ranked 500 genes by spatial autocorrelation. The top hit was Prkcd at I = 0.824. But a number alone doesn't tell you much. This post puts those genes on the tissue.

The setup

Same AnnData object as post 5 — 500 HVGs, spatial neighbors computed. Note what is not in the list: a normalization step. This dataset ships .X already log-normalized, so re-normalizing it would log the data twice. We pass the top 5 SVGs directly to sq.pl.spatial_scatter.

import squidpy as sq
import scanpy as sc

adata = sc.read_h5ad("visium_hne_adata.h5ad")
sc.pp.highly_variable_genes(adata, n_top_genes=500, flavor="cell_ranger")
adata = adata[:, adata.var.highly_variable].copy()

# NOTE: no normalize_total / log1p here. This dataset ships .X already
# log-normalized (max ~8.4, non-integer); raw counts live in adata.raw.X.
sq.gr.spatial_neighbors(adata)

top5 = ["Prkcd", "Pmch", "Mobp", "Agrp", "Tcf7l2"]

sq.pl.spatial_scatter(
    adata,
    color=top5,
    ncols=3,
    use_raw=False,   # plot .X (what Moran's I ran on), not the raw counts
    save="svg_top5_combined.png"
)

sq.pl.spatial_scatter reads spot coordinates from adata.obsm["spatial"] and colors each spot by expression value. The colormap runs from purple (low) to yellow (high), matching scanpy's default viridis scale.

Results

The five most spatially variable genes on mouse brain Visium tissue

The five highest-Moran's-I genes among 500 HVGs, plotted on the coronal mouse brain section (each spot ~55 ยตm). The sixth panel repeats Prkcd on a shared 0–8 scale — same gene, same data, only the colour range differs.

What each pattern tells us

Prkcd (I = 0.824) — thalamus
A dense, compact block in the middle of the section. Prkcd encodes protein kinase C delta, enriched in thalamic relay nuclei. It is the clearest example of a regional gene here: high inside one structure, near-silent outside it.

Pmch (I = 0.818) — hypothalamus
A thin, ventral streak along the bottom of the section. Pmch encodes pro-melanin-concentrating hormone, made by a small hypothalamic neuron population. Few spots are positive, but those that are sit right next to each other — which is precisely what Moran's I rewards.

Mobp (I = 0.811) — white matter tracts
The one non-blob pattern: bright along the fiber tracts sweeping through the section. Mobp is a myelin gene, so it traces oligodendrocyte-rich bundles rather than a nucleus. It is also the most broadly expressed of the five, which is why its map looks busier.

Agrp (I = 0.799) — hypothalamic arcuate nucleus
The most extreme case on the page: a handful of bright spots in the lower-left corner and essentially nothing anywhere else (mean expression across all spots is 0.05). A gene can be almost entirely off and still top the SVG list, provided the few spots where it is on are neighbours.

Tcf7l2 (I = 0.794) — thalamus
Overlaps Prkcd's territory. Tcf7l2 is a transcription factor marking thalamic identity, so seeing two independent genes converge on the same block is a useful sanity check that the pattern is anatomy and not noise.

Pattern categories

Across the five maps, two spatial modes emerge — and notably, neither is laminar:

Regional (Prkcd, Tcf7l2, Pmch, Agrp) — compact clusters matching discrete anatomical structures. The gene is on in one place and off everywhere else. That on/off contrast between neighbouring spots is exactly what Moran's I detects.

Tract (Mobp) — a long, branching bundle rather than a blob. High Moran's I here comes from a different geometry: many spots in a row agreeing with each other along the fibre path.

Both score high, for different reasons. Agrp and Mobp are the instructive pair: Agrp wins on extreme contrast from a tiny footprint, Mobp on sheer contiguity across a large one.

A note on the colormap

The viridis colormap scales independently per gene, so the yellow in Prkcd (max 4.4) is not comparable to the yellow in Pmch (max 7.9) — the panels above show this directly. This is the default behavior and is fine for qualitative comparison of spatial pattern, but misleading if you want to compare expression levels across genes. For cross-gene level comparison, fix the colormap range with vmax:

sq.pl.spatial_scatter(adata, color="Prkcd", use_raw=False, vmin=0, vmax=8)

Pick vmax from the data, not by habit: .X here is log-normalized and tops out near 7.9 across these five genes, so 8 covers them all. A leftover vmax=20 from a raw-count workflow would flatten every panel into the dark end of the colormap.

What's next

The next logical step is neighborhood enrichment analysis — looking at which cell types or clusters tend to be spatially adjacent. That's sq.gr.nhood_enrichment, and it answers a different question: not which genes vary across space, but which populations co-locate.

Full code at github.com/Lociven/spatiabio-tutorials.

Want the publication-ready version

Notebook 13 covers exactly that

Pack 1's last notebook is Nature-style figure templates — DPI, font, and colormap standards, built on top of the plotting shown here.

Get Pack 1 for $19 →

Tangram: Mapping Single-Cell Annotations onto Spatial Coordinates

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