Cell Annotation: Reference Mapping
TL;DR: Annotate cells by transferring labels from well-characterized reference atlases. Use SingleR for simple projection, Azimuth for UMAP-guided mapping, or Symphony for fast multi-reference integration. Best for standard tissues where high-quality references exist.
What is Reference Mapping?
Instead of manually identifying cell types, reference mapping transfers annotations from a curated atlas to your query data.
The Concept
Your data (unannotated) + Reference atlas (annotated) → Your data (annotated)

Advantages
| Reference Mapping | Manual Annotation |
|---|---|
| Automated | Manual marker checking |
| Consistent | Subjective |
| Fast | Time-consuming |
| Comprehensive | May miss rare types |
Limitations
- ⚠️ Reference must include your cell types (won't find novel populations)
- ⚠️ Batch effects can cause mis-mapping
- ⚠️ Quality depends on reference quality
When to Use Reference Mapping
✅ Ideal for:
- Standard tissues (PBMC, lung, brain, tumor microenvironment)
- Validation against published atlases
- High-throughput annotation of multiple samples
- Cross-study integration
❌ Avoid when:
- Novel cell types expected
- Reference is from different species/technology
- Small sample sizes (reference may overwhelm signal)
Major Reference Databases
Human Cell Atlas (HCA)
Link: data.humancellatlas.org
Content: ~100M cells across 40+ tissues
Best for: Cross-tissue annotation, developmental biology
CellxGene (Chan Zuckerberg Initiative)
Link: cellxgene.cziscience.com
Content: Curated single-cell datasets with interactive browser
Best for: Finding tissue-specific references, exploratory analysis
Azimuth References (Seurat)
Link: azimuth.hubmapconsortium.org
Content: Optimized references for PBMC, lung, kidney, motor cortex, etc.
Best for: Immediate use with Seurat/Azimuth workflow
Additional Resources
| Database | Focus | URL |
|---|---|---|
| HuBMAP | Human tissue atlases | hubmapconsortium.org |
| Tabula Sapiens | Human organs | tabula-sapiens-portal.ds.czbiohub.org |
| Tabula Muris | Mouse organs | tabula-muris.ds.czbiohub.org |
| PanglaoDB | Aggregated databases | panglaodb.se |
Method 1: SingleR (Simple & Fast)
Best for: Quick annotation, Bioconductor workflows
Installation
BiocManager::install("SingleR")
BiocManager::install("celldex") # Reference datasets
Basic Usage
library(SingleR)
library(celldex)
# Load reference (built-in)
ref <- HumanPrimaryCellAtlasData()
# Annotate your Seurat/SingleCellExperiment object
predictions <- SingleR(
test = as.SingleCellExperiment(pbmc),
ref = ref,
labels = ref$label.main
)
# Add to Seurat object
pbmc$SingleR_labels <- predictions$labels
pbmc$SingleR_scores <- predictions$scores
# Visualize
DimPlot(pbmc, group.by = "SingleR_labels", label = TRUE)
Available References
# List available references
celldex::listReferences()
# Common references
ref_immune <- ImmGenData() # Mouse immune cells
ref_encode <- BlueprintEncodeData() # Human immune/bulk
ref_monaco <- MonacoImmuneData() # Human PBMC
ref_hpca <- HumanPrimaryCellAtlasData() # Pan-tissue human
Fine vs Coarse Labels
# Hierarchical annotation
predictions_fine <- SingleR(
test = sce,
ref = ref,
labels = ref$label.fine # Detailed labels
)
predictions_coarse <- SingleR(
test = sce,
ref = ref,
labels = ref$label.main # Broad categories
)
Method 2: Azimuth (UMAP-based)
Best for: User-friendly web interface, Seurat integration
Web Interface (No Coding)
- Go to azimuth.hubmapconsortium.org
- Select reference (PBMC, lung, kidney, etc.)
- Upload Seurat object (.rds) or counts matrix
- Download annotated results
R Workflow
library(Seurat)
library(Azimuth)
# Install reference
InstallData("pbmcref")
# Run Azimuth
pbmc <- RunAzimuth(pbmc, reference = "pbmcref")
# View transferred labels
DimPlot(pbmc, group.by = "predicted.celltype.l2", label = TRUE)
# Check confidence scores
FeaturePlot(pbmc, features = "predicted.celltype.l2.score")
Available Azimuth References
| Reference | Tissue | Resolution |
|---|---|---|
| pbmcref | PBMC | 3 levels (L1, L2, L3) |
| lungref | Lung | Cell states + subtypes |
| kidneyref | Kidney | Developmental + adult |
| motorcortexref | Brain | Neuronal subtypes |
| pancreas | Pancreas | Endocrine + exocrine |
Method 3: Symphony (Fast Multi-Reference)
Best for: Large datasets, batch integration, multiple references
Installation
devtools::install_github("immunogenomics/symphony")
Build Reference
library(symphony)
# Build from Seurat object
ref_obj <- buildReferenceFromSeurat(
seurat_obj,
verbose = TRUE,
do_normalize = TRUE,
do_umap = TRUE
)
# Save for reuse
saveRDS(ref_obj, "my_reference.rds")
Map Query
# Load reference
ref <- readRDS("my_reference.rds")
# Map query data
query_mapped <- mapQuery(
exp_query = query_seurat,
ref_obj = ref,
vars = c("batch", "donor"), # Correct for batch
verbose = TRUE
)
# Transfer labels
query_seurat$predicted_celltype <- query_mapped$meta_data$cell_type
Method 4: Seurat v5 Reference Mapping
Transfer Labels
library(Seurat)
# Find anchors between query and reference
anchors <- FindTransferAnchors(
reference = reference_seurat,
query = query_seurat,
dims = 1:30,
reference.reduction = "pca"
)
# Transfer cell type labels
predictions <- TransferData(
anchorset = anchors,
refdata = reference_seurat$celltype,
dims = 1:30
)
query_seurat$predicted_celltype <- predictions$predicted.id
query_seurat$prediction_score <- predictions$prediction.score.max
# Visualize
DimPlot(query_seurat, group.by = "predicted_celltype", label = TRUE)
VlnPlot(query_seurat, features = "prediction_score", group.by = "predicted_celltype")
Transfer UMAP Embedding
# Project query onto reference UMAP
query_seurat <- MapQuery(
anchorset = anchors,
reference = reference_seurat,
query = query_seurat,
refdata = list(celltype = "celltype"),
reference.reduction = "pca",
reduction.model = "umap"
)
# Plot on reference UMAP
DimPlot(query_seurat, reduction = "ref.umap", group.by = "predicted.celltype")
Quality Control for Predictions
Check Confidence Scores
# SingleR
hist(predictions$scores, main = "Prediction Scores")
# Azimuth/Seurat
VlnPlot(pbmc, features = "predicted.celltype.score", pt.size = 0)
# Flag low-confidence predictions
pbmc$confident <- pbmc$predicted.celltype.score > 0.5
table(pbmc$confident)
Compare Methods
# Run multiple methods
pbmc$singleR <- singleR_predictions$labels
pbmc$azimuth <- azimuth_predictions
pbmc$seurat <- seurat_predictions
# Confusion matrix
table(SingleR = pbmc$singleR, Azimuth = pbmc$azimuth)
# Consensus annotation (all methods agree)
pbmc$consensus <- ifelse(
pbmc$singleR == pbmc$azimuth & pbmc$azimuth == pbmc$seurat,
pbmc$singleR,
"Ambiguous"
)
Validate with Markers
# Check if predicted T cells express CD3D
VlnPlot(pbmc, features = "CD3D", group.by = "predicted.celltype")
# Canonical markers by predicted type
markers_to_check <- c("CD3D", "CD14", "MS4A1", "NKG7")
DotPlot(pbmc, features = markers_to_check, group.by = "predicted.celltype")
Advanced: Cross-Modality Mapping
ATAC-seq to RNA-seq
library(Signac)
# Find transfer anchors (ATAC → RNA)
anchors <- FindTransferAnchors(
reference = rna_seurat,
query = atac_seurat,
reduction = "cca",
features = VariableFeatures(rna_seurat)
)
# Transfer labels
predictions <- TransferData(
anchorset = anchors,
refdata = rna_seurat$celltype,
weight.reduction = atac_seurat[["lsi"]]
)
atac_seurat$predicted_celltype <- predictions$predicted.id
Multi-omic Integration (CITE-seq)
# Transfer protein expression from reference
protein_predictions <- TransferData(
anchorset = anchors,
refdata = GetAssayData(reference, assay = "ADT"),
weight.reduction = "pca"
)
query[["ADT_predicted"]] <- CreateAssayObject(data = protein_predictions)
Troubleshooting
Low prediction scores
Causes:
- Reference doesn't contain your cell types
- Batch effects between query and reference
- Poor data quality
Fix:
# Try different reference
# Or use more lenient thresholds
predictions_filtered <- predictions[predictions$scores > 0.3, ]
Mis-annotation of rare types
Cause: Reference lacks rare populations
Fix: Combine reference mapping with manual curation:
# Auto-annotate common types, manually check rare clusters
pbmc$final_celltype <- pbmc$predicted.celltype
pbmc$final_celltype[pbmc$seurat_clusters == 12] <- "Manual: Rare Type"
Reference is too different (species, technology)
Fix: Use homology-based tools:
# Convert mouse to human genes
library(orthogene)
converted <- convert_orthologs(gene_df, gene_input = "rownames",
input_species = "mouse",
output_species = "human")
Comparison of Methods
| Method | Speed | Accuracy | Integration | Best Use |
|---|---|---|---|---|
| SingleR | Fast | High | Bioconductor | Quick annotation |
| Azimuth | Medium | Very high | Seurat | User-friendly, high-quality refs |
| Symphony | Very fast | High | Harmony-based | Large cohorts, multiple batches |
| Seurat Transfer | Medium | High | Seurat | Custom references |
Resources
- SingleR vignette: bioconductor.org/packages/SingleR
- Azimuth app: azimuth.hubmapconsortium.org
- Symphony paper: Nature Commun 2021
- Reference mapping review: Cell 2024
See also:
Originally posted on WeChat Official Account: bioinfo_studyroom