Chapter 3 — Markers & Manual Cell-Type Annotation

Author

Single Cell RNA-seq Workshop

Note

Where this chapter sits. Companion to Lecture 03 and Tutorial 03. Builds on Chapter 2 (you have a clustered object). Prerequisites: familiarity with the canonical lineage panels for your tissue (the workshop uses PBMC; see Glossary for cell-type definitions).

3.1 The conceptual move from clusters to cell types

A cluster is a graph property: the output of community detection on the SNN graph. A cell type is a biological claim about identity that connects to a literature, an ontology (Cell Ontology), and a set of expected functional behaviors. Going from one to the other is the most subjective step in the entire scRNA-seq workflow, and the one reviewers will scrutinize most.

Two paradigms:

  • Manual annotation (this chapter, Lecture 03): inspect canonical marker expression in each cluster and assign a name. Interpretable, but biased by the annotator’s experience.
  • Reference-based annotation (Chapter 4, Lecture 04): map cells against a labeled reference atlas with Azimuth (other paradigms include SingleR and CellTypist). Reproducible, but biased by the reference’s coverage.

Best practice: run both, reconcile disagreements, document choices. Disagreements typically flag rare subtypes, transitional states, or biology your reference doesn’t cover. They are usually informative, not errors. The community protocol for annotating single-cell maps lays out exactly this combined manual-plus-automated workflow and is the reference to follow1.

3.2 Finding markers — the statistical core

FindAllMarkers(seu) runs, for each cluster \(c\), a Wilcoxon rank-sum test comparing the gene’s expression in cluster \(c\) vs all other clusters combined. The output is a per-cluster ranked gene list. Table 1 describes each output column and how to read it. The table captures five key quantities per gene per cluster; knowing how to read each column is the prerequisite for turning the output into a useful annotation. Most columns are self-explanatory in isolation but become informative only in combination: a high avg_log2FC with a low pct.1 is a gene that is highly upregulated on average but barely expressed in any individual cell of the cluster — a mathematical artifact of the ratio, not a biologically useful marker. Similarly, a high pct.1 with a high pct.2 means the gene is broadly expressed across most cell types and provides no discriminatory power. The most useful markers simultaneously have high pct.1, low pct.2, and high avg_log2FC.

Table 1: Columns returned by FindAllMarkers and FindConservedMarkers. A “good” marker has high pct.1, low pct.2, and high avg_log2FC; ranking by avg_log2FC (or the derived score pct.1 - pct.2) gives a practical top-N list per cluster.
Column Meaning
gene gene symbol
cluster cluster being tested
avg_log2FC log2 fold-change in cluster \(c\) vs others
pct.1 fraction of cells in cluster \(c\) expressing the gene (UMI > 0)
pct.2 fraction of cells in other clusters expressing the gene
p_val raw Wilcoxon p-value
p_val_adj Bonferroni-adjusted (Seurat’s choice)

A “good” marker has high pct.1, low pct.2, and high avg_log2FC. The ranking by avg_log2FC (or a score like pct.1 - pct.2) gets you a top-N list per cluster.

Why the Wilcoxon test? Seurat defaults to the non-parametric Wilcoxon rank-sum test, and the large benchmark of single-cell DE methods found that simple tests like Wilcoxon are surprisingly robust and competitive for marker detection — the more elaborate model-based methods do not consistently beat it2. (presto provides a fast Wilcoxon implementation Seurat uses automatically when installed.) A crucial distinction from Chapter 6: marker detection here is a descriptive, within-dataset ranking used to name clusters, not a cross-condition inference about which genes change with treatment — that question requires pseudobulk and is the subject of Chapter 63.

Note🔑 Key concept — marker detection vs. differential expression

FindAllMarkers and the Chapter 6 pseudobulk DESeq2 workflow are often confused but answer entirely different questions. Marker detection asks: “which genes are enriched in cluster \(c\) relative to all other cells in this same dataset?” It is a descriptive ranking within a single object, and its purpose is to name clusters by their characteristic genes. Differential expression across conditions asks: “does gene \(X\) change between treatment and control groups?” It is an inferential test that requires biological replicates and appropriate statistical calibration. Using FindAllMarkers to detect condition effects produces anti-conservative p-values (cells treated as independent replicates), while using DESeq2 across donors to find cluster markers ignores the within-cluster information. Keep the two workflows separate.

Warning

FindAllMarkers finds genes enriched in a cluster, not necessarily canonical markers. A gene can top the list of cluster \(c\) either because it’s a literature lineage marker (good) or because some technical / state effect happens to differentiate the cluster (e.g. ISGs in the unintegrated ifnb data). Cross-check the top hits against canonical panels before naming clusters from them.

Note⚙️ Key parameter — FindAllMarkers min.pct, logfc.threshold, only.pos

FindAllMarkers(seu, min.pct = 0.1, logfc.threshold = 0.25, only.pos = FALSE). Defaults: min.pct = 0.1, logfc.threshold = 0.25, only.pos = FALSE. min.pct filters out genes detected in fewer than the specified fraction of cells in either group — raising it to 0.25 (the workshop default) significantly reduces runtime and cuts low-detection noise genes; lowering it can recover rare markers in small clusters. logfc.threshold is a pre-filter on the log2 fold-change that prevents testing genes with trivial effect sizes; the default 0.25 is conservative for marker purposes. only.pos = TRUE restricts output to upregulated markers — almost always what you want when naming a cluster by what it is rather than what it lacks. For small clusters (<50 cells), loosen both thresholds (e.g. min.pct = 0.1, logfc.threshold = 0.1) to avoid returning an empty table.

3.3 Conserved markers — for case-control designs

If your dataset has multiple conditions (e.g. CTRL vs STIM in ifnb), naive FindAllMarkers will surface treatment-induced genes as “cluster markers” — and you’ll mis-label populations by their interferon response rather than their lineage.

FindConservedMarkers runs the marker test inside each condition and reports genes that come up in both. This protects against treatment-driven mis-annotation:

FindConservedMarkers(seu,
                     ident.1      = 3,                  # cluster of interest
                     grouping.var = "stim")             # condition column

The output combines per-condition results, so you can see whether the gene is a marker in CTRL only, STIM only, or both. Both means it’s a stable lineage marker; one only means it’s a state marker.

Use FindConservedMarkers whenever you have a non-trivial perturbation in your design. It is the right default for any multi-condition study.

3.4 The five marker visualizations

Tutorial 03 §3 walks five canonical plot types. Each answers a different diagnostic question; choosing the right one speeds interpretation and produces clearer figures. Table 2 summarizes the five, then the subsections explain when each pays off. A common mistake is to pick one plot type and use it for everything — the DotPlot is elegant for a publication panel but hides bimodal expression that VlnPlot reveals immediately; the FeaturePlot shows spatial context that the DotPlot compresses away. For a rigorous annotation, you should consult at least two plot types per cluster: one to confirm specificity (e.g. DotPlot) and one to confirm distributional properties (e.g. VlnPlot).

Table 2: The five Seurat marker visualization functions, their primary diagnostic use case, and their main limitation. DotPlot is the most widely used in publications; VlnPlot is best at the single-gene investigation stage.
Function Best question to ask Strength Caveat
VlnPlot Is this gene on/off in exactly one cluster? Shows full distribution; reveals bimodality One gene per panel; clutters above 8 genes
FeaturePlot Where on the UMAP does this gene concentrate? Reveals spatial/topological structure Overplotting in dense regions; use order = TRUE
DotPlot Which markers define which clusters, at a glance? Many genes × clusters in one compact figure Hides bimodality; means can mask high-expressing minorities
DoHeatmap Does the global cluster ↔︎ marker block structure look right? Shows whole-dataset diagonal block pattern Dominant clusters monopolize space; noisy at high cell counts
RidgePlot Is this a graded / continuum marker? Density curves capture soft boundaries Less compact than VlnPlot; best for 3–8 clusters

3.4.1 VlnPlot — distribution per cluster, one gene at a time

Best for bimodality, outliers, comparing 4–8 candidate markers side-by-side. A “good” marker has one cluster’s violin bulging while the rest sit at zero. If two clusters bulge similarly, the marker is shared by two cell types — keep looking.

3.4.2 FeaturePlot — single gene, every cell, on the UMAP

Best for spatial/topological structure on the embedding. A good marker shows expression concentrated in one UMAP region; a scattered FeaturePlot suggests the gene is either a poor marker or a technical signature.

3.4.3 DotPlot — mean × percent across clusters × genes

Best for the publication panel figure. Dot color = mean expression; dot size = percent of cells expressing. Compact: 16 markers × 12 clusters fits on one row. The single most-used plot in cell-type annotation papers.

3.4.4 DoHeatmap — top markers across clusters, every cell as a column

Best for showing the global cluster ↔︎ marker block structure. Each cluster’s top markers should “light up” in its own diagonal block. Caveat: it shows cells, not clusters, so a dominant cluster (40%+ of cells) visually monopolizes the plot. For balanced display, prefer DotPlot.

3.4.5 RidgePlot — density per cluster

Best for graded/continuum genes (e.g. naive→memory T-cell markers like S100A4). The density curves overlap, showing where the cell-type boundaries are soft.

3.5 The unintegrated-data special case: more clusters than cell types

When annotating unintegrated multi-condition data (as Tutorial 03 does), the condition effect is the dominant axis of variation, and cell types with a strong transcriptional response to the perturbation often split into two sister clusters — one CTRL cluster and one STIM cluster. The result is that you routinely see more clusters than biologically distinct cell types: for a PBMC dataset with ~12 known types, resolution 0.5 on unintegrated data typically yields 15–20 clusters. This is not a bug; it is the data faithfully representing what it sees before integration.

Practical consequences:

  • You may legitimately assign the same cell-type label to two clusters (e.g. “CD14 Mono” for both a CTRL-dominated and a STIM-dominated monocyte cluster).
  • Clusters whose top markers are interferon-stimulated genes (ISG15, IFI6, IFIT3, MX1) rather than canonical lineage genes are often condition-split copies of an existing cell type. Use FindConservedMarkers (§3.3) to identify the cell-type signal underneath the ISG layer.
  • After integration (Chapter 5), these paired clusters collapse into single clusters, and per-cluster marker lists clean up considerably.

3.6 The decision loop for assigning labels

The practical workflow:

  1. Compute top markers per cluster with FindAllMarkers or FindConservedMarkers
  2. Cross-reference the top 5–10 against canonical panels (CellMarker 2.0, PanglaoDB, Azimuth references)
  3. Confirm visually with a DotPlot of the canonical panel × clusters (does each cluster’s expected marker actually concentrate there?)
  4. Assign labels with RenameIdents, store in seu$celltype_manual
  5. Document why you assigned each label — write a sentence per cluster in your lab notebook (a future reviewer or future-you will thank you)
Note🔑 Key concept — cluster identity is a biological claim, not a data property

A cluster number is an arbitrary label assigned by community-detection software; it has no biological meaning until you interpret it. Assigning “CD14+ Monocyte” to cluster 3 is a hypothesis — you are asserting that the cells in this group share a lineage, a developmental origin, a set of functional capabilities, and a canonical literature context. That hypothesis is supported by (1) high specificity of monocyte markers in the DotPlot, (2) absence of markers for competing identities, and (3) concordance with reference annotation (Chapter 4). The label becomes less defensible when any of those supports is weak. In practice, the annotation step is also the first place where novel biology becomes visible: a cluster with no good match in the canonical panel is either a technical artifact (doublet, ambient contamination — inspect per §3.8) or a cell state worth investigating further.

For the workshop’s ifnb PBMCs, the canonical panel is small and highly stereotyped (see also §3.5 above for how the unintegrated embedding changes how many clusters you will label). Table 3 lists the ten major PBMC populations and their most reliable lineage markers.

Table 3: Canonical lineage markers for the ten major PBMC populations in the workshop ifnb dataset. Markers are chosen for high pct.1 / low pct.2 on healthy donor PBMCs; expression patterns shift with activation state and may require FindConservedMarkers on multi-condition data.
Cell type Canonical markers
CD14+ Monocyte CD14, LYZ, S100A8, S100A9
CD16+ Monocyte FCGR3A, MS4A7, CDKN1C
Dendritic cell FCER1A, CST3, CLEC10A
pDC LILRA4, IL3RA, CLEC4C
CD4 Naive T IL7R, CCR7, LEF1
CD4 Memory T IL7R, S100A4
CD8 T CD8A, CD8B, GZMK
NK GNLY, NKG7, KLRD1
B MS4A1, CD79A, CD79B
Megakaryocyte PPBP, PF4

These panels are not folklore — they are curated in searchable databases such as PanglaoDB4 and CellMarker 2.05, which compile cell-type markers from thousands of single-cell studies. For tumor / non-immune tissues, the panel is much larger and more disputed; consult tissue-specific atlases.

3.7 Validating against a ground truth — reading the confusion table

When a ground-truth annotation is available (as with ifnb’s seurat_annotations), the standard check is to cross-tabulate your manual labels against the reference:

table(manual = Idents(seu), truth = seu$seurat_annotations)

Reading the output: each row is one of your manual labels; each column is a ground-truth label; each cell is a count of cells with that combination. A perfect annotator sees each row concentrate its counts in a single column — one of your labels maps entirely to one ground-truth type. Off-diagonal counts are disagreements; their pattern is informative:

  • Row split across two columns — you lumped two cell types together (e.g. your “NK” cluster contains both NK and CD8 T cells).
  • Two rows that both map to the same column — you over-split one cell type into two clusters (common for condition-split clusters on unintegrated data).
  • Column with no strong row — a ground-truth type that your clustering did not recover as a distinct cluster (often rare populations like pDC or Mk).

After integration (Chapter 5), re-cluster and re-annotate; the same cross-tab should tighten considerably because condition-split doublings collapse.

3.8 Risks and how to avoid them

Four annotation mistakes account for the majority of published errors in single-cell cell-type assignments.

Single-marker labels are the most common. A cluster top-marked by CD3D is “T cell” — but is it CD4 or CD8? Naive or memory? Activated or resting? Always label from combinations: marker presence plus absence of competing markers. The DotPlot format is ideally suited to this because it places the confirmatory and exclusionary markers side-by-side across all clusters simultaneously.

Marker leakage from ambient RNA can mislead annotation when the cell suspension was complex or had high cell death. If you skipped SoupX (Chapter 1), low-level marker expression in the wrong cluster may reflect ambient bleed rather than genuine expression. The classic symptom is every cluster showing low non-zero HBB — hemoglobin is a dominant ambient contaminant in whole-blood or bone-marrow samples. The fix is to re-run from Chapter 1 with SoupX applied.

Treatment state mistaken for cell type is a recurrent trap in perturbation studies. Interferon-stimulated genes (ISG15, IFI6, IFIT1, MX1) lighting up half a dataset usually reflects a stimulation condition, not a new cell type. Use FindConservedMarkers (§3.3) to see whether the lineage markers underneath the ISG signal are consistent with known types.

Doublet-driven “intermediate” types appear when two highly distinct cell populations (e.g. T and myeloid cells) end up doublet-enriched in the same cluster. A “T-myeloid intermediate” expressing both CD3D and CD14 is almost always a doublet artifact. Verify by inspecting the scDblFinder.class distribution per cluster — a cluster where >15–20% of cells were called doublets by scDblFinder should be discarded, not annotated.

3.9 The relationship to the Cell Ontology

The Cell Ontology (CL) is a community-maintained hierarchy of ~2,400 cell-type terms, each with a stable identifier (e.g. CL:0000236 = B cell). Ontologies are the backbone that lets atlas projects like the Human Cell Atlas combine annotations across thousands of datasets6. When you annotate, you should be assigning to an ontology term, not just typing a free-text label. Reasons:

  • Interoperability. Cell-type labels in different studies can only be combined if they reference the same ontology
  • Submission requirement. CELLxGENE Discover (the de-facto atlas browser) requires Cell Ontology IDs, not free-text labels
  • Discoverability. Other groups can find your dataset by querying the ontology

The mapping from Seurat label to CL ID is a manual step. The Ontology Lookup Service is the searchable interface; type “B cell” and you get CL:0000236. Store the IDs in seu@meta.data$cell_type_ontology_term_id. Tutorial 17 walks the full mapping for the ifnb cell types.

3.10 Common errors

Table 4 lists the most common mistakes at the marker-detection and annotation stage, their root causes, and the fixes.

Table 4: Common errors encountered during marker detection and manual annotation. The most consequential is using the "integrated" assay for FindAllMarkers — always reset DefaultAssay(seu) <- "RNA" before running marker tests.
You see What’s wrong What to do
FindAllMarkers is very slow Wilcoxon implementation is the bottleneck install.packages("presto") — Seurat detects it automatically
Top markers per cluster are mostly ribosomal Your QC/normalization didn’t suppress ribosomal variance Consider regressing out percent.rb; consider stricter HVG selection
Marker dot plot has gaps where canonical markers should be Your dataset lacks that cell type, or you’re using the wrong assay Check DefaultAssay(seu) is "RNA" for marker tests, not "integrated"
RenameIdents errors Argument order or factor levels mismatch Build the named vector explicitly: c("0" = "B", "1" = "CD14 Mono", ...)
Manual labels on the unintegrated object don’t match labels on the integrated object Cluster numbers differ between integrations Re-annotate after integration; Cluster numbers are not stable across re-clusterings, only per-cell labels are

3.11 Where this material is also discussed

3.12 Going further

The community protocol for cell-type annotation1 is the single best next read; pair it with the DE-method benchmark that justifies the Wilcoxon default2 and the best-practices tutorial’s clustering-and-annotation chapter7. For marker panels, the curated databases — CellMarker 2.05 and PanglaoDB4 — are searchable by tissue and cell type. The Azimuth web tool automates annotation against curated atlases (the subject of Chapter 4), and scNotebooks Module 04 is a parallel walkthrough of manual plus automated annotation. The full curated list is on Key Papers, Reviews & Benchmarks.

References

1.
2.
Soneson, C. & Robinson, M. D. Bias, robustness and scalability in single-cell differential expression analysis. Nature Methods 15, 255–261 (2018).
3.
Squair, J. W. et al. Confronting false discoveries in single-cell differential expression. Nature Communications 12, 5692 (2021).
4.
Franzén, O., Gan, L.-M. & Björkegren, J. L. M. PanglaoDB: A web server for exploration of mouse and human single-cell RNA sequencing data. Database 2019, baz046 (2019).
5.
6.
Osumi-Sutherland, D. et al. Cell type ontologies of the human cell atlas. Nature Cell Biology 23, 1129–1135 (2021).
7.
Luecken, M. D. & Theis, F. J. Current best practices in single-cell RNA-seq analysis: A tutorial. Molecular Systems Biology 15, e8746 (2019).
Back to top