Single-cell RNA-Seq: from counts to cell types

scRNA-Seq
R
Python
Seurat
Scanpy
Droplet-based scRNA-Seq end to end — alignment and counting, quality control that does not throw away real biology, normalisation, clustering, and annotation.
Modified

August 27, 2026

NoteThis walkthrough is being written

The outline below is the plan. Sections will appear here as they are finished. If there is a step you particularly want covered — or a place where the existing tutorials on single-cell analysis lost you — say so and it will be prioritised: aaron.mitchell.dick@duke.edu.

Bulk RNA-Seq gives you the average expression of a tissue. Single-cell RNA-Seq gives you a distribution, and with it the ability to ask which cells changed rather than which genes changed on average. It also gives you an enormous number of new ways to fool yourself, most of them in quality control.

This walkthrough will follow the same principles as the bulk RNA-Seq series: every command runnable, every parameter explained, and explicit about the decisions where reasonable people disagree.

Planned outline

Part 1 — Raw reads to a count matrix

  • What comes off a 10x Genomics run, and how the files are organised
  • Cell Ranger versus the open alternatives (alevin-fry, STARsolo, kb-python)
  • Building a reference, including the spliced/unspliced question and why it matters later
  • Reading the web summary honestly: what a bad run actually looks like
  • Empty droplets: the knee plot, and why emptyDrops beats a hard cutoff

Part 2 — Quality control without throwing away biology

  • The three standard filters — counts, features, mitochondrial fraction — and why fixed thresholds are a bad habit
  • Why high mitochondrial reads are sometimes a dying cell and sometimes a cardiomyocyte
  • Doublet detection with scDblFinder / DoubletFinder, and what it can and cannot catch
  • Ambient RNA and soup correction: when to bother
  • A worked example of a QC decision that changes the biological conclusion

Part 3 — Normalisation, integration, and clustering

  • Why library-size normalisation from bulk does not transfer cleanly
  • Log-normalisation versus SCTransform versus scran pooling
  • Feature selection, PCA, and choosing the number of components without hand-waving
  • Batch effect versus biological difference, and how to tell them apart
  • Integration with Harmony / CCA / scVI — and the case for not integrating at all
  • Clustering resolution: the parameter everyone tunes until they like the answer, and how to be disciplined about it

Part 4 — Annotation and differential expression

  • Marker genes: FindAllMarkers, and why its p-values are optimistic by construction
  • Manual annotation against reference atlases
  • Automated annotation with SingleR / Azimuth
  • Differential expression between conditions, and the pseudobulk argument — why per-cell tests inflate significance, and when pseudobulk is the honest choice
  • Cell type proportion testing, which is harder than it looks

Tooling

The walkthrough will cover both Seurat (R) and Scanpy (Python), because labs are split between them and translating between the two is a recurring tax. Where the two packages make genuinely different choices — normalisation defaults in particular — that will be called out rather than smoothed over.

Prerequisites

The bulk RNA-Seq series is a good foundation but is not required. You should be comfortable at the command line and able to read basic R or Python. Memory matters more here than in bulk: 32 GB makes life easier, and a dataset of a few hundred thousand cells will want more.