19 Single-cell Transcriptomics: Implementations
Everything on this page is generated from tools/singlecell.json. If something here is wrong or out of date, the fix belongs in that file, not in this prose.
For why a zero is ambiguous, why a droplet is not a cell, and why the clustering step is the one that gets over-read, see Section 18.1.
Every implementation chapter carries a review date, and this one means it more than the others. The tools below turn over faster than anything else surveyed here, the unit of choice is a framework rather than a command, and several methods that matter arrive as a function inside an existing package rather than as a new project you could watch. Check the date in the header against today’s before you take a verdict on trust — and prefer the two frameworks’ own changelogs to this page for anything at the level of a single function.
19.1 Start here
Three decisions, and only the first is really about a tool. You need something that turns reads into a count matrix; then you need a framework, which is a choice of language and of data structure more than a choice of methods; and then everything else is a function call inside it.
The cards are the default answer to the first two. The frameworks are listed as a pair rather than ranked, because they are genuinely comparable and the deciding factor is external to both.
The default route from droplet FASTQs to a count matrix, and what distinguishes it is how it treats the reads it cannot assign uniquely: a UMI compatible with several genes is resolved under a stated rule rather than silently discarded or split, and which rule is a flag you set. Its authors report doing this in a small fraction of the memory the reference pipeline needs, which decides whether a dataset is processed on a workstation or on a cluster. (He et al. 2022)
The centre of gravity for single-cell analysis in Python, and the reason to name a framework here rather than a list of methods is that it fixes the data structure. Integration, doublet calling, trajectory inference and the plotting all read and write one object, so adding a method to an analysis is a function call instead of a format conversion — which is the cost that actually dominates this kind of work. (Wolf et al. 2018)
The R equivalent and the older of the two, covering the same ground to a comparable standard; the choice between them is which language the rest of your analysis is written in, not what either can do. Where it has led rather than followed is integration — the anchor-based approach it introduced is still the default in most R workflows, and its more recent versions are built around handling datasets too large to hold in memory at once. (Satija et al. 2015; Hao et al. 2024)
19.2 From reads to a count matrix
The job is the one Section 18.2 describes: demultiplex by cell barcode, collapse by UMI, assign to genes. Every tool here does those three things and they disagree mainly at the edges — which barcodes are real cells, what to do with a UMI compatible with more than one gene.
The alternatives to the card above split along a line worth naming. One of them is chosen for agreement rather than for merit, because reproducing a published matrix is a legitimate requirement. The other is chosen for its memory profile at scale.
| Tool | Upstream | Languages | Why / why not |
|---|---|---|---|
| Cell Ranger | activelast release 2026-08 | rspy |
The pipeline 10x ships for its own assay, and the honest argument for it is that it is the reference implementation rather than the best one: it is what the instrument’s documentation assumes, what most published matrices were produced with, and what a collaborator handing you a filtered_feature_bc_matrix almost certainly ran. Reproducing someone else’s numbers is a real requirement and this is how you meet it. (Zheng et al. 2017) |
| kb-python | activelast release 2026-05 | py |
Reduces the kallisto and bustools workflow to two commands, and the reason to know it is the BUS format underneath: barcode, UMI and equivalence class in one sorted binary file, which makes the memory a run needs independent of how many cells it contains. That is the property to choose it for, and it matters at the scale where the alternatives start swapping. (Melsted et al. 2021) |
STARsolo is a mode of STAR, not a separate program, and this registry gives a tool one record — STAR’s is in the read alignment part, where it is recommended as a spliced aligner. Its authors report that it reproduces Cell Ranger’s quantification closely and much faster (Kaminow et al. 2021), which makes it a real option, particularly where you already run STAR and have the index built. Its cost is STAR’s cost: the genome index wants tens of gigabytes of RAM, which is the constraint the transcriptome-based tools above exist to avoid.
19.3 The framework decides more than the methods
Both cards above are frameworks, and picking one is the most consequential choice on this page — not because either is short of methods, but because it fixes the object every subsequent step reads and writes. A method that does not speak your object is a conversion script away, and conversion between the two ecosystems is lossy in the corners: reductions, graphs and unstructured metadata do not all survive.
The two entries below are the ones that make that sentence concrete. One is the container itself, which is why the Python side interoperates as well as it does. The other is the R stack that is neither of the frameworks, for people who want the rest of Bioconductor around them.
| Tool | Upstream | Languages | Why / why not |
|---|---|---|---|
| AnnData | activelast release 2026-08 | pyR |
Not an analysis tool but the container everything Python-side agrees on, and it is surveyed because choosing a container is the interoperability decision of this part. Its on-disk form is HDF5 or Zarr in a documented layout, which is what lets a matrix written by one tool be opened by another without a conversion script standing between them. (Virshup et al. 2024) |
| scran | active | RC++ |
The methods half of the Bioconductor single-cell stack, for the R user who wants a SingleCellExperiment rather than a Seurat object and the rest of Bioconductor around it. Its normalization is the substantive reason to know it: counts are pooled across similar cells and the per-cell factors deconvolved from the pools, which addresses the case that defeats plain library-size scaling — a cell whose own total is too small to be a reliable divisor. (Lun et al. 2016) |
There is no version of this decision where one framework is correct and the other is a mistake. Choose the one your collaborators, your downstream statistics and your existing code are already in, and spend the time you saved on the parameters instead — which is where the analysis is actually decided.
19.4 Cleaning the matrix
Two artifacts of droplet chemistry rather than of sequencing, and both are described in Section 18.4. A doublet is two cells sharing a barcode and reading as one cell with an implausible transcriptome. Ambient RNA is material released by lysed cells that is in every droplet, including the empty ones, which is what makes it estimable.
Neither is optional to think about, and both corrections can be applied too hard. The cards are the defaults; the table is a lighter alternative and the paper you should read rather than the package you should install.
Ambient RNA is a property of droplet chemistry rather than a symptom of a bad run, and this models it rather than thresholding it away: a generative model of the counts that separates what a cell expressed from what was floating in the emulsion, with the empty droplets supplying the estimate of the background. What comes out is a corrected count matrix, not a flag on a report. (Fleming et al. 2023)
Doublet calling done the only way the problem allows: manufacture artificial doublets by adding observed cells together, then score each real cell by how much of its neighbourhood is artificial. Its paper is also a comparison of the available methods on the same data, which is worth more than the tool — it is the reason to argue with this choice from evidence rather than from habit. (Germain et al. 2022)
| Tool | Upstream | Languages | Why / why not |
|---|---|---|---|
| SoupX | dormantlast release 2022-11 | R |
The same correction with a far smaller commitment: estimate the contamination fraction from genes that ought to be absent in a given group of cells, then subtract it. It runs in seconds inside an R session with no GPU and no training, which makes it the reasonable first measurement of how much ambient signal you have even when you intend to correct with something heavier. (Young and Behjati 2020) |
| Scrublet | dormantlast release 2020-12 | py |
The paper that established the simulate-and-score approach every current doublet caller is a variation on, which is why it is worth reading and not worth installing. Scanpy ships a reimplementation as sc.pp.scrublet, so a Python workflow gets the method as part of a package it already has and with a maintained implementation; take it from there. (Wolock et al. 2019) |
Both of these remove counts you cannot get back, and both are tuned by a parameter with no principled default. Run the analysis with and without the correction at least once. If a conclusion appears only on one side of that comparison, the correction is doing the work, and that belongs in the methods rather than in a preprocessing script nobody reads.
19.5 Integration
Integration removes variation attributable to batch so that the same cell type from two experiments lands in the same place. Section 18.7 is about why this is not a solved preprocessing step: the methods cannot tell a batch difference from a biological one, they only know which label you told them to remove.
The two cards differ in what they correct and in what else they give you. One corrects the embedding, cheaply and deterministically. The other fits a generative model whose latent space is the integrated representation and which does several other jobs from the same fit. The table entry is a third set of assumptions, useful mainly as a disagreement.
Corrects batch in the embedding rather than in the counts, alternating between soft clustering and a per-cluster linear correction until cells from different batches share clusters. Choosing what to correct is the decision here, and correcting the low-dimensional space is the right target for what an embedding is used for. It scores well in the independent atlas-integration benchmark and it is cheap enough to rerun while you argue about the parameters. (Korsunsky et al. 2019; Luecken et al. 2022)
Integration as inference rather than as a correction applied afterwards: a variational autoencoder fits a generative model of the counts with batch as a covariate, and its latent space is the integrated representation. It costs more to fit than a linear correction and it returns something a linear correction cannot — one model that also transfers labels between datasets and tests for differential expression with the batch term already inside it. (Gayoso et al. 2022; Luecken et al. 2022)
| Tool | Upstream | Languages | Why / why not |
|---|---|---|---|
| Scanorama | dormantlast release 2023-10 | py |
Matches mutual nearest neighbours between datasets and stitches them the way a panorama is assembled, which needs no dataset nominated as the reference — useful when there is no obvious one. It is here because it assumes something different from the two above rather than because benchmarks favour it: when they disagree about whether a population is batch or biology, a third method built on different assumptions is informative. (Hie et al. 2019) |
An integrated representation is for visualization, clustering and label transfer. It is not an expression matrix and it is not the input to a differential test. When you get to testing, go back to the raw counts and put batch in the model, for the reason Section 16.6 gives about what these tests need from their input.
19.6 What is not here
DoubletFinder is the conspicuous absence, and the reason is the same one that keeps ANNOVAR out of the variant annotation part rather than any judgment about the method. You will meet it constantly in inherited Seurat pipelines and it is a reasonable implementation of the approach every current doublet caller shares. But it publishes no releases, carries no tags, and is not on CRAN or Bioconductor, so there is no feed from which a version and a date can be read together. A record for it would show “unrefreshed”, which claims nobody looked, when what actually happened is that there is nothing to look at. So the position goes here, where the prose can be explicit about its own footing: the method is sound, an existing pipeline using it is not broken, and for a new one the recommended caller above is maintained through a release channel and its paper compares the alternatives on the same data.
Cell type annotation and reference mapping are not surveyed, although they are unmistakably part of this subject. The reason is that the decision is almost entirely about which reference — which atlas, which set of marker genes, which ontology — and barely at all about which program does the transferring. That is the same shape as the annotation databases in the variant part: the substance is a versioned dataset consumed through tooling, and ranking the tooling would put a review date on the wrong object. Both frameworks above ship label transfer, and one of the integration cards does it from the model it already fitted.
Trajectory inference is absent for a different reason. There are many methods, they disagree with each other substantially, and what they produce is a hypothesis about a process that was never observed — a static snapshot ordered by similarity is not a time course. A survey with a “start here” verdict would misrepresent how settled the area is.
Other modalities. Single-cell ATAC, spatial transcriptomics, CITE-seq and multiome data are outside this page. They share the frameworks and almost nothing else: the count matrix means something different, the QC is different, and the questions asked of the result are different. This book does not cover them, and a page that gestured at them from a transcriptomics survey would be worse than saying so.
The atlases themselves — Human Cell Atlas, Tabula Sapiens and the tissue-specific references — are data rather than software, on their own release cycles, and choosing between them is the methods decision this page cannot make for you.
19.7 Language coverage: two complete stacks
This is the only part of the book where the same job has two full answers in two languages and the choice between them is genuinely a coin toss weighted by your own situation.
Everywhere else the pattern has been consistent: performance-bound work in C++ or Rust at the command line, statistics in R, glue in Python. Here the quantifiers still follow it — Rust and C++ behind a thin command — but everything above the count matrix exists twice, in Python around one framework and in R around the other, and neither is a port of the other. Methods appear on both sides within months, usually first wherever their authors work.
The seam is real, though, and it is worth planning around rather than discovering. The container is where it shows: the Python ecosystem converged on one on-disk object with a documented layout, and the R route to that object is a third-party wrapper that drives Python. Conversion in either direction keeps the counts and loses some of the annotations, embeddings and graphs around them.
Decide the language before the first read_10x call, not after the first method you cannot find. If a project genuinely needs both — an R statistician and a Python method — the cheap version is to convert once, early, at the count matrix, where only the counts and the cell metadata have to survive. Converting a fully analysed object late is where the losses hurt.