Chapter 6: Graph-Based Clustering (Louvain & Leiden) and Marker Annotation
Chapter 6: Graph-Based Clustering (Louvain & Leiden) and Marker Annotation
Once single-cell data is projected into reduced dimensional space, unsupervised clustering groups cells with similar transcriptomic profiles into discrete cell types and states. Graph-based clustering is the gold standard for single-cell data, outperforming older k-means or hierarchical clustering algorithms.
- k-Nearest Neighbor (k-NN) Graph: Connect each cell to its \(k\) closest neighbors in Principal Component space.
- Shared Nearest Neighbor (SNN) Graph: Weight edges between cells by the Jaccard similarity of their shared neighbor sets.
- Community Detection: Partition the graph into densely connected subgraphs (communities) using modularity optimization algorithms.
1. Community Detection: Louvain vs. Leiden Algorithms
While the Louvain algorithm has been widely used, Traag et al. demonstrated that Louvain has a mathematical flaw: it can produce disconnected or poorly connected sub-communities. The modern Leiden algorithm resolves this by introducing a refinement step that guarantees connected communities and converges faster.
# Graph construction and Leiden clustering in Seurat v5
# Step 1: Compute SNN graph on top 30 PCs
pbmc <- FindNeighbors(pbmc, dims = 1:30, k.param = 25)
# Step 2: Cluster using Leiden algorithm (algorithm = 4)
# resolution parameter: higher values (>1.0) produce more granular clusters; lower (<0.5) produce broad lineages
pbmc <- FindClusters(pbmc, resolution = 0.8, algorithm = 4)
# Visualize clusters on UMAP
DimPlot(pbmc, reduction = "umap", label = TRUE, pt.size = 0.5)2. Canonical Marker Genes for PBMC Cell Types
# Canonical Human Immune Cell Marker Catalog:
# -------------------------------------------------------------------------------------
# Cell Type Primary Positive Markers Negative / Excluded
# -------------------------------------------------------------------------------------
# CD4+ T Cells CD3D, CD3E, CD4, IL7R CD8A, MS4A1
# CD8+ Cytotoxic T Cells CD3D, CD8A, CD8B, GZMB, PRF1 CD4
# B Cells MS4A1 (CD20), CD19, CD79A CD3D
# Natural Killer (NK) Cells GNLY, NKG7, NCAM1 (CD56), PRF1 CD3D
# CD14+ Monocytes CD14, LYZ, S100A9, VCAN FCGR3A (low)
# FCGR3A+ Monocytes FCGR3A (CD16), MS4A7 CD14 (low)
# Dendritic Cells (mDC/pDC) HLA-DRA, CST3, CLEC4C, LILRA4 CD3D, CD19
# Platelets / Megakaryocytes PPBP, PF4 PTPRC (CD45)
# -------------------------------------------------------------------------------------3. Automated Cell Type Annotation (SingleR & Azimuth)
Manual marker annotation can be subjective. Automated annotation compares single-cell profiles against curated reference atlases (e.g. Human Primary Cell Atlas or Blueprint/ENCODE).
library(SingleR)
library(celldex)
# Load reference expression atlas
hpca_ref <- celldex::HumanPrimaryCellAtlasData()
# Run SingleR annotation
predictions <- SingleR(
test = as.SingleCellExperiment(pbmc),
ref = hpca_ref,
labels = hpca_ref$label.main
)
# Assign annotations back to Seurat object
pbmc$SingleR_label <- predictions$labels
DimPlot(pbmc, group.by = "SingleR_label", label = TRUE)# CellTypist automated annotation in Python (Scanpy)
import celltypist
from celltypist import models
# Download Immune_All_Low model
models.download_models(force_update=False, model=['Immune_All_Low.pkl'])
model = models.Model.load(model='Immune_All_Low.pkl')
# Predict cell types
predictions = celltypist.annotate(adata, model='Immune_All_Low.pkl', majority_voting=True)
adata = predictions.to_adata()
sc.pl.umap(adata, color='majority_voting')