Tangram: Mapping Single-Cell Annotations onto Spatial Coordinates
Transfer cell type labels, gene programs, and custom scores from scRNA-seq directly onto your Visium or Xenium data
Cell2location (covered in Post 12) infers cell type abundances per spot using a Bayesian model. Tangram takes a complementary approach: it learns a mapping from single-cell space to spatial space by aligning gene expression profiles, then transfers any annotation — cell types, transcription factor activity, pseudotime, custom gene scores — onto spatial coordinates. If you have a well-annotated scRNA-seq atlas and want to ask "where do these cells sit in tissue?", Tangram is your tool.
Figure 1. Tangram-mapped dominant cell type per spatial location (left) and deconvolution method comparison by accuracy and compute speed (right).
How Tangram Works
Tangram frames cell-to-space mapping as an optimal transport problem. Given a scRNA-seq dataset with C cells and a spatial dataset with S spots, it learns a mapping matrix M (C × S) that minimizes the difference between:
- The spatially reconstructed gene expression (MT × scRNA matrix)
- The observed spatial gene expression
The key insight: you only need a shared set of marker genes to align the two modalities. Tangram then uses the learned mapping to project everything else — including genes not measured in the spatial assay.
import tangram as tg
import scanpy as sc
import squidpy as sq
# Load data
sc_adata = sc.read_h5ad('sc_annotated.h5ad') # scRNA-seq with cell_type labels
sp_adata = sc.read_h5ad('visium_filtered.h5ad') # Visium spatial data
# Identify shared marker genes (top 100 per cell type works well)
sc.tl.rank_genes_groups(sc_adata, groupby='cell_type', method='wilcoxon')
markers = tg.pp.select_genes(sc_adata, n_genes=100)
tg.pp.filter_gene_list(sc_adata, sp_adata, gene_list=markers)
# Train mapping (GPU recommended for large datasets)
tg.mapping.map_cells_to_space(
sc_adata,
sp_adata,
mode='cells', # or 'clusters' for memory efficiency
target_count=sp_adata.obs.cell_count.sum(),
density_prior='rna_count_based',
num_epochs=500,
device='cpu',
)
Three Mapping Modes
Maps each individual cell to a spot. Best for datasets <50k cells. Gives highest resolution but memory-intensive.
Maps cluster centroids instead of individual cells. Scalable to large atlases. Recommended starting point.
Uses prior cell density estimates (e.g. from DAPI) to constrain the mapping. More accurate when cell counts per spot are known.
Projecting Cell Types and Custom Scores
After mapping, Tangram stores results in sp_adata.obsm['tangram_ct_pred']. You can then project back to the spatial grid:
# Project cell type probabilities onto spatial coords
tg.pl.plot_cell_annotation_sc(sp_adata, annotation='cell_type', perc=0.02)
# Project any custom score from scRNA-seq (e.g. exhaustion score)
sc_adata.obs['exhaustion_score'] = (
sc_adata[:, ['PDCD1','HAVCR2','LAG3','TIGIT','TOX']].X.mean(axis=1)
)
tg.pp.project_genes(adata_map=sp_adata, adata_sc=sc_adata)
# Now sp_adata has exhaustion_score — plot spatially
import squidpy as sq
sq.pl.spatial_scatter(sp_adata, color='exhaustion_score', cmap='Reds')
Tangram vs cell2location: When to Use Which
| Tangram | cell2location | |
|---|---|---|
| Approach | Optimal transport mapping | Bayesian deconvolution |
| Output | Cell-to-spot probability matrix | Cell type abundance per spot |
| Custom score transfer | ✓ Any obs/obsm column | ✗ Cell types only |
| Gene imputation | ✓ Projects unmeasured genes | ✗ No |
| Scalability | Moderate (<200k cells) | Large atlases OK |
| Best for | Transferring rich annotations | Quantifying cell composition |
Common Pitfalls
- Too few marker genes — use at least 50-100 per cell type. With <20, mapping is unreliable.
- Batch effects between sc and spatial — if both datasets are from different labs or platforms, normalize carefully before running Tangram. Harmony or scVI integration first helps.
- Overinterpretation of imputed genes — genes projected via Tangram are predicted, not measured. Don't use imputed values for differential expression.
- Using cells mode on >100k cells — switch to clusters mode. The mapping matrix becomes O(C × S) and will OOM on most machines.
Key Takeaways
- Tangram maps any scRNA-seq annotation onto spatial coordinates via optimal transport
- Use
clustersmode for large atlases,cellsmode for maximum resolution - Combine with Squidpy for spatial statistics on projected scores
- Complements cell2location — use both for a complete picture
Working on neoantigen biology or cancer immunotherapy?
The spatial exhaustion score workflow above connects directly to neoantigen research — see NeoantigenLab for the immunology side.
Explore NeoantigenLab →From the NeoantigenLab sister blog
Working with tumor transcriptomics for neoantigen research?
NeoantigenLab covers neoantigen biology, WES pipelines, pVACseq, and HLA typing for experimental researchers — the biology behind what you're sequencing.
Visit NeoantigenLab →