Cell Annotation: Canonical markers

· 7 min read · Analysis Notes

Cell Annotation: Canonical markers

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

Papers


See also:


Originally posted on WeChat Official Account: bioinfo_studyroom

Cell Annotation