Cell Annotation: Canonical markers

Cell Annotation: Canonical Markers
TL;DR: Annotate single-cell clusters using well-established marker genes. This guide provides curated marker lists for major cell types (immune, epithelial, stromal) with hierarchical annotation levels and R code for validation.
What are Canonical Markers?
Canonical markers are genes with well-documented, cell type-specific expression patterns. They're the gold standard for manual annotation because:
- ✅ Experimentally validated across multiple studies
- ✅ High specificity (minimal cross-reactivity)
- ✅ Easy to interpret (binary presence/absence or gradient)
- ✅ Work across technologies (scRNA-seq, spatial, flow cytometry)
When to Use Canonical Markers
| Scenario | Canonical Markers | Other Methods |
|---|---|---|
| Standard tissues (PBMC, tumor, brain) | ✅ First choice | Reference mapping |
| Novel cell types | ⚠️ Limited | AI-based annotation |
| QC/validation | ✅ Always | - |
| Cross-study comparison | ✅ Consistent | - |
Hierarchical Annotation Strategy
Annotate in 3 levels of increasing resolution:
Level 1: Major Lineages
Separate broad categories first:
| Lineage | Key Marker(s) | Alternative Markers |
|---|---|---|
| Epithelial | EPCAM | KRT8, KRT18, CDH1 |
| Immune | PTPRC (CD45) | - |
| Endothelial | VWF, PECAM1 (CD31) | CDH5, KDR |
| Fibroblast | DCN, COL1A1 | VIM, FN1 |
| Smooth muscle | ACTA2, MYH11 | TAGLN, CNN1 |
Level 2: Cell Type Families
Refine each lineage:
Epithelial Subtypes
| Cell Type | Markers |
|---|---|
| Luminal | KRT8, KRT18, KLK3 (PSA), ACPP |
| Basal | KRT5, KRT14, TP63 |
| Neuroendocrine | SYP, CHGA, ENO2, ASCL1 |
Stromal Subtypes
| Cell Type | Markers |
|---|---|
| Endothelial | VWF, SELE, PECAM1, CLDN5, PLVAP |
| Mural (pericytes) | RGS5, ACTA2, PDGFRB |
| Mesothelial | KRT19, MSLN, CALB2 |
| Proliferating | STMN1, TOP2A, MKI67 |
| Fibroblast | COL1A1, DCN, LUM |
| Glial | GPM6B, S100B, CDH19 |
Immune Subtypes
| Cell Type | Markers |
|---|---|
| T cell | CD3D, CD3E, CD4, CD8A |
| NK cell | ITGAM, IL2RB, KLRB1, KLRK1, NCR1 |
| B cell | CD19, MS4A1 (CD20) |
| Dendritic cell | CD209A, XCR1, ITGAX, HLA-DRA |
| Myeloid | CD14, FCGR1, ADGRE1, CSF1R, LY6C2, S100A8 |
| Mast cell | TPSAB1, FCER1A |
Level 3: Functional Subtypes
Fine-grained classification:
T Cell Subtypes
| Subtype | Markers |
|---|---|
| CD8+ T | CD8A, CCR7, CD28, IFNG, GZMB |
| CD4+ T | CD4, CXCR3, CCR4, CCR3, CCR6, CCR10, CXCR5, ICOS |
| T reg | IL2RA, FOXP3, CTLA4, IL7R |
| γδ T | TRGV2, IL23R, IL17A, RORC |
| NKT | NKG7, KLRB1C, IL4 |
B Cell Subtypes
| Subtype | Markers |
|---|---|
| Naive B | IGHM, IGHD, CCR7, SELL |
| Plasma | JCHAIN, IGHG, IGKC |
Myeloid Subtypes
| Subtype | Markers |
|---|---|
| Neutrophil | CSF3R, S100A8, S100A9, SIGLECF |
| Monocyte | CD14, CSF1R, VCAN |
| Macrophage | C1QB, C1QA, APOE |
| cDC1 | CLEC9A, XCR1, BATF3 |
| cDC2 | ITGAM, SIRPA, CLEC10A, IRF4, CCR7 |
| pDC | LY6D, IRF8, TCF4, DNTT, IL7R |
Practical Annotation in R
Step 1: Visualize Markers with DotPlot
library(Seurat)
# Define marker sets
immune_markers <- list(
T_cells = c("CD3D", "CD3E", "CD3G"),
B_cells = c("CD19", "MS4A1", "CD79A"),
Myeloid = c("CD14", "FCGR1A", "CSF1R"),
NK_cells = c("NKG7", "KLRB1", "NCAM1")
)
# Dot plot
DotPlot(pbmc, features = unlist(immune_markers), group.by = "seurat_clusters") +
RotatedAxis()
Step 2: Assign Cell Types
# View UMAP with marker expression
FeaturePlot(pbmc, features = c("CD3D", "CD14", "MS4A1", "NKG7"))
# Manual annotation based on marker expression
new.cluster.ids <- c(
"0" = "CD14 Mono",
"1" = "CD4 T",
"2" = "CD8 T",
"3" = "B cell",
"4" = "NK",
"5" = "CD16 Mono",
"6" = "DC"
)
pbmc$celltype <- new.cluster.ids[as.character(pbmc$seurat_clusters)]
Step 3: Validate Annotations
# Check marker specificity
VlnPlot(pbmc, features = c("CD3D", "CD14", "MS4A1"),
group.by = "celltype", pt.size = 0)
# Feature plot for spatial validation
FeaturePlot(pbmc, features = "CD3D", split.by = "celltype")
# Proportion check (should match expected biology)
table(pbmc$celltype)
Comprehensive Marker Reference
Human Immune Cells
# Curated lists for human data
myeloid_subtypes <- list(
general_myeloid = c("PTPRC", "ITGAM", "CD33", "HLA-DRA", "CSF1R", "SPI1"),
monocytes = c("CD14", "FCGR3A", "CCR2", "CX3CR1", "VCAN"),
classical_DC = c("CLEC9A", "XCR1", "BATF3", "IRF8", "THBD", "CADM1"),
plasmacytoid_DC = c("IL3RA", "CLEC4C", "TCF4", "IRF7", "LILRA4", "GZMB"),
MDSCs = c("CD33", "ITGAM", "S100A8", "S100A9", "ARG1", "STAT3"),
neutrophils = c("ELANE", "MPO", "FCGR3B", "CXCR2", "S100A8"),
M1_macrophages = c("CD80", "CD86", "STAT1", "IRF5", "IL1B", "TNF"),
M2_macrophages = c("CD163", "MRC1", "MS4A4A", "TGFB1", "IL10", "VEGFA"),
TAMs = c("CD68", "CD163", "MRC1", "VEGFA", "TREM2", "SPP1", "MAF")
)
lymphoid_subtypes <- list(
T_cells_general = c("PTPRC", "CD3D", "CD3E", "CD3G", "TRAC", "IL7R"),
CD4_T_cells = c("CD4", "IL7R", "CCR7"),
CD8_T_cells = c("CD8A", "CD8B", "GZMA", "GZMB", "PRF1"),
Tregs = c("FOXP3", "IL2RA", "CTLA4", "TNFRSF18"),
Th1_cells = c("TBX21", "IFNG", "IL2", "CXCR3"),
Th2_cells = c("GATA3", "IL4", "IL5", "IL13", "CCR4"),
Th17_cells = c("RORC", "IL17A", "IL17F", "IL23R"),
NK_cells = c("NCAM1", "FCGR3A", "KLRB1", "KLRD1", "GZMB", "PRF1"),
B_cells = c("CD19", "MS4A1", "CD79A", "CD79B", "IGHM"),
plasma_cells = c("SDC1", "IGHG1", "MZB1", "XBP1", "PRDM1")
)
stromal_subtypes <- list(
fibroblasts = c("COL1A1", "COL1A2", "VIM", "FN1", "DCN"),
myofibroblasts = c("ACTA2", "TAGLN", "MYL9", "MYLK", "TPM1"),
CAFs = c("FAP", "ACTA2", "PDGFRA", "PDGFRB", "POSTN", "TGFB1"),
endothelial_cells = c("PECAM1", "CDH5", "VWF", "KDR", "FLT1"),
pericytes = c("CSPG4", "PDGFRB", "ACTA2", "RGS5", "MCAM"),
MSCs = c("THY1", "ENG", "NT5E", "VCAM1", "ITGAV", "ALCAM")
)
epithelial_subtypes <- list(
general_epithelial = c("EPCAM", "KRT8", "KRT18", "CDH1"),
luminal_epithelial = c("KRT8", "KRT18", "KRT19", "ESR1", "PGR"),
basal_epithelial = c("KRT5", "KRT14", "TP63", "EGFR"),
progenitor_cells = c("ALDH1A1", "CD44", "ITGA6", "ITGB1"),
neuroendocrine = c("CHGA", "SYP", "NCAM1", "ENO2"),
EMT = c("CDH2", "VIM", "SNAI1", "SNAI2", "TWIST1", "ZEB1")
)
T Cell State Markers
t_cell_state_markers <- c(
# Naive/Memory
"CCR7", "SELL", # Naive
"CD44", # Memory
# Activation
"CD69", "CD25", # Early activation
"IFNG", "GZMB", "PRF1", # Effector
# Exhaustion
"PDCD1", # PD-1
"CTLA4", "LAG3", "HAVCR2", # Exhaustion markers
# Tissue residence
"ITGAE", "ITGA1", # CD103, CD49a
# Stemness
"TCF7", "LEF1"
)
# Visualize T cell states
DotPlot(pbmc, features = t_cell_state_markers, group.by = "seurat_clusters") +
RotatedAxis()
Systematic Annotation Workflow
1. Create Marker Gene Panel
# Combine all markers
all_markers <- c(
unlist(myeloid_subtypes),
unlist(lymphoid_subtypes),
unlist(stromal_subtypes),
unlist(epithelial_subtypes)
) %>% unique()
# Filter for expressed genes
expressed <- rownames(pbmc)[rowSums(GetAssayData(pbmc) > 0) > ncol(pbmc) * 0.01]
markers_to_plot <- intersect(all_markers, expressed)
2. Generate Diagnostic Plots
# Dot plot by lineage
DotPlot(pbmc, features = unique(unlist(myeloid_subtypes))) +
ggtitle("Myeloid Markers") +
RotatedAxis()
DotPlot(pbmc, features = unique(unlist(lymphoid_subtypes))) +
ggtitle("Lymphoid Markers") +
RotatedAxis()
# Heatmap of top markers
DoHeatmap(subset(pbmc, downsample = 100),
features = markers_to_plot,
group.by = "seurat_clusters",
size = 3)
3. Iterative Refinement
# Subset problematic clusters for re-clustering
cluster_5 <- subset(pbmc, subset = seurat_clusters == 5)
cluster_5 <- FindNeighbors(cluster_5)
cluster_5 <- FindClusters(cluster_5, resolution = 0.5)
# Check with finer markers
DotPlot(cluster_5, features = c("CD4", "CD8A", "FOXP3", "IL2RA"))
Validation Strategies
1. Marker Specificity Check
# Should be high in target type, low elsewhere
VlnPlot(pbmc, features = "CD3D", group.by = "celltype", pt.size = 0)
2. Proportion Sanity Check
# Compare to expected biology
table(pbmc$celltype) / ncol(pbmc)
# PBMC expected proportions:
# T cells: 40-60%
# B cells: 5-15%
# Monocytes: 10-30%
# NK cells: 5-15%
3. Cross-Reference with FindAllMarkers
# Find cluster-specific markers
markers <- FindAllMarkers(pbmc, only.pos = TRUE, min.pct = 0.25)
# Do top markers match your annotations?
top_markers <- markers %>%
group_by(cluster) %>%
slice_max(n = 5, order_by = avg_log2FC)
Troubleshooting
No clear marker expression
Cause: Poor quality cells or doublets
Fix:
# Stricter QC
pbmc <- subset(pbmc, subset = nFeature_RNA > 500 & percent.mt < 10)
# Doublet removal
library(scDblFinder)
pbmc <- scDblFinder(pbmc)
pbmc <- subset(pbmc, subset = scDblFinder.class == "singlet")
Ambiguous clusters (multiple lineages)
Cause: Low resolution or doublets
Fix:
# Increase resolution
pbmc <- FindClusters(pbmc, resolution = 0.8)
# Or split ambiguous cluster
VlnPlot(pbmc, features = c("EPCAM", "PTPRC"), split.by = "seurat_clusters")
Marker not detected
Cause: Gene dropout or wrong species/nomenclature
Fix:
# Check if gene exists
"CD3D" %in% rownames(pbmc) # Human
"Cd3d" %in% rownames(pbmc) # Mouse
# Use alternative markers
# CD3D → CD3E, CD3G
# MS4A1 → CD79A
Resources
Marker Databases
- CellMarker 2.0: http://bio-bigdata.hrbmu.edu.cn/CellMarker
- PanglaoDB: https://panglaodb.se
- Weizmann 3CA: https://www.weizmann.ac.il/sites/3CA/marker-genes
- Human Protein Atlas: https://www.proteinatlas.org
Papers
See also:
Originally posted on WeChat Official Account: bioinfo_studyroom