Chapter 11 — Cell Ranger: From Raw Reads to a Counts Matrix
Where this chapter sits. Companion to Lecture 11 and Tutorial 11. This is a bonus upstream module: it runs before the core pipeline, not after it. Most workshop participants start from a finished counts matrix (filtered_feature_bc_matrix/) because that is what a sequencing core hands them. This chapter explains where that matrix comes from and what every step did to your data along the way — so that when you eventually run Cell Ranger yourself, you understand the choices rather than copying a command. The hand-off target is Chapter 1, the QC/preprocessing step that loads the matrix.
11.1 Why understand the raw-data step at all
For a long time the boundary of single-cell analysis, for most biologists, was the counts matrix. Someone else ran the sequencer, someone else ran Cell Ranger, and a folder of three small files arrived. You can do excellent science starting there, and the rest of this workshop does exactly that.
But the boundary is movable, and it moves the moment your project becomes non-standard. You will need to run Cell Ranger yourself when you want to re-process old data against a newer reference genome, add a late-arriving sample to a study that was already analyzed, handle a chemistry the core does not routinely run (5’-seq, fixed-cell, multiplexed), or simply because you are the core. When that day comes, it is far better to already understand the pipeline than to learn it under a deadline. This chapter builds that understanding.
There is also a subtler reason. Every decision Cell Ranger makes — which reference, which chemistry, where it drew the line between cells and empty droplets — is baked into the matrix you analyze downstream. If you treat the matrix as a black-box input, those decisions become invisible assumptions. Reading this chapter turns them back into choices you can interrogate.
11.2 The sequencer does not produce reads
A common mental model is that a sequencer “produces FASTQ files.” It does not. An Illumina instrument writes BCL files — base-call files — which record, for every cluster on the flow cell, the fluorescence intensity measured at each sequencing cycle. The data are organized by lane and by cycle, not by sample and not by molecule. There is no notion yet of “read 1 of sample A”; there are only per-cycle intensity images, reduced to per-cluster base calls with quality scores.
Two transformations stand between this raw instrument output and anything analyzable:
- Demultiplexing — sorting clusters back to the sample they came from, using the sample-index reads, so that pooled libraries are separated again.
- Assembly into reads — collecting the per-cycle base calls for each cluster into a contiguous sequence with per-base quality, written as a FASTQ record.
Both are handled by cellranger mkfastq, which wraps Illumina’s bcl2fastq / BCL Convert under the hood. The output is a set of FASTQ files in a strict 10x naming pattern.
cellranger count is not a single algorithm — it is a sequential pipeline of five distinct steps that each transform the data in a specific way. Barcode correction maps observed barcodes against the known 10x whitelist, fixing single-base sequencing errors so that cells are not split across multiple barcode identities. Alignment runs STAR to map each cDNA read to the reference transcriptome, classifying reads as exonic (counted), intronic (counted with --include-introns or in newer versions by default), or unmapped (discarded). UMI deduplication collapses PCR amplification: multiple reads sharing the same cell barcode, gene, and UMI sequence represent one original molecule and are merged into a single count. Cell calling separates barcodes that represent real cells from barcodes that represent empty droplets (§11.6). Counting produces the final integer sparse matrix. Each stage has a characteristic failure mode, and the web_summary.html metrics map directly onto the health of each stage — low mapping rate diagnoses stage 2; low cells called diagnoses stage 4.
11.3 mkfastq versus count
Cell Ranger’s two main subcommands map directly onto the two transformations above plus the biology.
cellranger mkfastq takes a BCL run folder and a sample sheet (which index belongs to which sample) and emits FASTQs named <SAMPLE>_S<num>_L<lane>_<R1|R2|I1>_001.fastq.gz. This naming is not cosmetic: count parses the sample name and lane directly from the filenames. Renaming a FASTQ is one of the most common ways to break the pipeline. If a core hands you renamed files, symlink them back to the canonical pattern rather than editing the names in place.
cellranger count is where the biology happens. It takes FASTQs plus a reference and produces the counts matrix. In practice, your sequencing core usually runs mkfastq for you, so the step you actually own is almost always count. The rest of this chapter focuses there — but you should be able to recognize the whole chain so you can diagnose a problem when it lives upstream of where you started.
11.4 What count actually does to your reads
It is worth walking through the internal stages of count, because each one corresponds to a way the data can go right or wrong.
Barcode correction. Each read carries a cell barcode (which droplet) and a UMI (which original molecule). Sequencing introduces errors. Cell Ranger compares each observed barcode against the known 10x whitelist for that chemistry and corrects single-base errors to the nearest valid barcode. Barcodes too far from any whitelist entry are discarded.
Alignment. The cDNA read (R2 in 3’ chemistry) is aligned to the reference transcriptome with STAR1. Reads are classified by where they land: confidently mapped to an exon, to an intron, to an intergenic region, or unmapped. The fraction “confidently mapped to transcriptome” is a key QC number; a low value usually means the wrong reference or contamination.
UMI deduplication. Because libraries are PCR-amplified before sequencing, the same original molecule appears many times. Cell Ranger collapses reads that share a cell barcode, a gene, and a UMI (allowing for one-base UMI errors) into a single counted molecule — the directional-network logic of UMI collapsing was worked out by UMI-tools2. This is what makes the final numbers molecule counts, not read counts — the whole point of the UMI design3.
Cell calling. This is the most consequential step and the one most worth understanding (Section 11.6).
Counting. Finally, the deduplicated molecules are tallied into a sparse genes-by-barcodes matrix, separately for the “raw” set of all barcodes and the “filtered” set of called cells.
11.5 The reference genome
The reference Cell Ranger uses is not a bare FASTA. It is a pre-built bundle containing the genome sequence (FASTA), a gene annotation (GTF), and a precomputed STAR index. 10x distributes ready-made references — refdata-gex-GRCh38-... for human, refdata-gex-GRCm39-... for mouse — and for non-model organisms you build your own with cellranger mkref from a genome FASTA and a GTF.
Note the gex tag in the name: this is the gene-expression reference, which is distinct from the ATAC reference of Chapter 12. They are not interchangeable.
# Human GRCh38 gene-expression reference (~12 GB compressed, ~30 GB extracted)
cd /projects/<PIRG>/<user>/refs
wget https://cf.10xgenomics.com/supp/cell-exp/refdata-gex-GRCh38-2024-A.tar.gz
tar -xzf refdata-gex-GRCh38-2024-A.tar.gz
# fasta/ genes/ reference.json star/Two practical points follow from the size. First, a reference does not belong in your home directory: most clusters give each user a small home quota, and a 30 GB reference will fill it. It belongs in shared project storage (/projects/<PIRG>/...), where one canonical copy serves the whole group. Second, check whether your cluster already provides Cell Ranger and references as a module before downloading anything; maintained modules save disk and avoid version drift.
11.6 Cell calling: separating cells from empty droplets
A droplet-based experiment generates far more barcodes than cells. Most droplets are empty — they captured no cell but still contain a little ambient RNA floating in the suspension, which gets barcoded and sequenced. The defining problem of the count step is drawing the line between barcodes that represent real cells and barcodes that represent empty droplets.
The classic tool for seeing this is the barcode-rank plot (the “knee” plot). Barcodes are ranked from most to fewest UMIs and plotted on log-log axes. A clean experiment shows a sharp knee: a plateau of high-UMI barcodes (real cells) that drops steeply to a long tail of low-UMI barcodes (empties). The location of the knee is, roughly, the cell count.
Modern Cell Ranger does not just cut at the knee. It uses a two-step approach: a knee-based threshold identifies obvious high-count cells, and then an EmptyDrops-style test4 recovers real cells that happen to have lower counts by asking whether each remaining barcode’s expression profile differs significantly from the ambient profile. A small dying cell with few UMIs but a distinctive transcriptome can thus be rescued, where a naive knee cut would have thrown it away. The consequence for you: the “filtered” matrix is the product of a statistical decision, not a simple threshold, and a soft or ambiguous knee in the plot is a sign to scrutinize that decision.
The barcode-rank (knee) plot is the most informative single diagnostic for a 10x run. It conveys three things at a glance: how many cells were loaded (the left plateau), how clean the separation between cells and empty droplets was (the steepness of the drop through the knee), and whether something unusual happened (the shape of the curve). A sharp, clean knee means cell calling was unambiguous. A gradual slope — a slow transition rather than a sharp drop — means there is a population of barcodes with intermediate UMI counts that may be real cells with low RNA content, damaged cells, or over-loaded droplets; --expect-cells (§11.9) can help force a tighter call. Two distinct plateaus typically indicate two populations of cells with different RNA content (e.g., large macrophages and small lymphocytes in the same suspension). A very short upper plateau followed by a long flat tail suggests over-loading: so many cells were loaded that many droplets captured two cells (doublets), inflating the upper plateau and depleting the lower one. The web_summary.html “Estimated number of cells” number is the numerical summary of this plot; always look at the plot itself when that number diverges from your expected target.
11.7 Reading web_summary.html
Every count run writes a web_summary.html. It is the single most informative QC document in the entire pipeline, and it should be the first thing you open — before any R. It summarizes sequencing quality, mapping, cell calling, and the barcode-rank plot in one page; the same numbers are in metrics_summary.csv for programmatic use. Save the web_summary.html alongside your raw data, because reviewers and collaborators will ask for it. To inspect it on Talapas without downloading, use VS Code’s “Remote - SSH” extension and simply double-click the .html file in the explorer panel, or scp the file to your laptop.
A short list of the numbers that most often reveal a problem is given in Table 1.
web_summary.html (also available as metrics_summary.csv). None of these is a hard pass/fail — they are prompts to investigate. A low “median genes per cell” in a sample of small resting lymphocytes may be biology rather than failure; read the numbers together and against your expectations for the tissue.
| Metric | What it tells you | Worry threshold |
|---|---|---|
| Estimated number of cells | Whether cell calling matched your loading | Far off your target |
| Mean reads per cell | Sequencing depth per cell | < ~20,000 → undersequenced |
| Median genes per cell | Capture quality / cell health | < ~1,000 → poor capture or dying cells |
| Q30 bases in barcode | Barcode sequencing quality | < 90% |
| Q30 bases in RNA read | cDNA sequencing quality | < 70% |
| Reads mapped confidently to transcriptome | Reference correctness | < 60% → wrong reference / contamination |
Reading Table 1 requires context. The “Reads mapped confidently to transcriptome” metric is the most decisive: a value below 60% is almost always a reference or sample problem — either the wrong reference genome version, a contaminating organism (e.g., bacterial or mouse cell contamination in a human sample), or a chemistry mismatch where 5’ reads were processed with a 3’ reference (or vice versa). The “Q30 bases in barcode” metric reflects sequencing quality on the short barcode read (R1); below 90% means that many barcodes will be uncorrectable, reducing the effective cell count. “Mean reads per cell” and “Median genes per cell” should be evaluated in the context of the tissue: a PBMC sample with 15,000 reads per cell is undersequenced, but a tumor infiltrating lymphocyte sample from a cell-sparse tumor section might genuinely have fewer reads per cell due to lower loading. The skill is developing a baseline expectation for your own tissue type and flagging deviations from that baseline, not comparing against generic thresholds.
11.8 The hand-off to the core pipeline
When count finishes, the output lives under the outs/ subdirectory of the run folder. Table 2 describes the files most relevant to downstream analysis.
cellranger count. Only filtered_feature_bc_matrix/ and web_summary.html are needed for a standard scRNA-seq analysis; the BAM is optional and large (10–30 GB per sample). Keep raw_feature_bc_matrix/ if you plan to run SoupX or re-call cells with EmptyDrops.
| File / folder | Contents | When you need it |
|---|---|---|
filtered_feature_bc_matrix/ |
Sparse counts matrix (3 files: barcodes.tsv.gz, features.tsv.gz, matrix.mtx.gz) |
Always — the primary input to the core pipeline |
raw_feature_bc_matrix/ |
Same format but all barcodes including empty droplets | SoupX ambient-RNA correction; DropletUtils re-calling |
web_summary.html |
Interactive QC report — mapping, cell calling, barcode-rank plot | Open first, before any R |
metrics_summary.csv |
Same numbers as web_summary.html in machine-readable form |
Automated QC pipelines; batch reporting |
molecule_info.h5 |
Per-molecule barcode/UMI/gene record | Aggregation (cellranger aggr); advanced QC |
possorted_genome_bam.bam + .bai |
Position-sorted aligned BAM | Re-alignment, splice-junction analysis, allele-specific methods; only written with --create-bam=true |
The directory that matters for everything downstream is one subfolder of outs/:
outs/filtered_feature_bc_matrix/
├── barcodes.tsv.gz # the called cell barcodes
├── features.tsv.gz # the genes
└── matrix.mtx.gz # the sparse countsThese three files are exactly what Read10X() loads at the start of the core pipeline:
library(Seurat)
counts <- Read10X("data/filtered_feature_bc_matrix/")
seu <- CreateSeuratObject(counts, project = "pbmc_1k_v3",
min.cells = 3, min.features = 200)
# … continue at Chapter 1 (QC metrics)There is a companion folder, raw_feature_bc_matrix/, that contains all barcodes including the empties. You normally ignore it — except for ambient-RNA correction tools such as SoupX, which need the empty droplets to estimate the contamination profile. Keeping both folders is cheap and occasionally essential.
11.9 Scaling beyond one sample, and pitfalls
A single count run is a long, single-process, multi-threaded job. On a cluster it scales by cores within one task (--cpus-per-task), not by MPI tasks (--ntasks), because Cell Ranger is one Rust+Python process, not an MPI program. Give its --localcores the cores you allocated, and set its --localmem slightly below the scheduler’s memory ceiling so that I/O buffers and brief spikes do not push the job over the hard limit and trigger a silent kill. A “Killed” with no error message is, nine times out of ten, exactly this. In practice a job requesting --mem=128G should pass --localmem=120 to Cell Ranger.
The --create-bam=true flag writes the aligned BAM file to outs/possorted_genome_bam.bam. This is optional (BAMs are large — 10–30 GB per sample) but required for re-alignment, splice-junction analysis, or any downstream tool that works on aligned reads rather than counts. For a fresh analysis where you only need the counts matrix, omit it to save disk. Once a run finishes, monitor its progress in real time with tail -f logs/cellranger_<jobid>.out; Cell Ranger prints stage names as it advances so you can tell how far along it is without cancelling the job.
For many samples, do not write many scripts. A scheduler array job runs one sub-job per sample from a single script, reading the sample name from a list file indexed by $SLURM_ARRAY_TASK_ID — the same script scaling from one sample to dozens. The tutorial walks through the SLURM specifics.
Design consideration: cellranger aggr vs Seurat merge + integration. After generating multiple count outputs you have two paths to a combined matrix. cellranger aggr performs a count-normalized merge at the Cell Ranger level, producing one combined filtered_feature_bc_matrix/ — fast and consistent, but it applies only a simple depth normalization and has no batch correction. The workshop path (Seurat merge() + Harmony/anchor integration in Module 05) keeps samples separate until the R analysis, where you can apply the full integration pipeline and retain per-sample metadata. Use aggr only for technical replicates of the same library that need combining before any analysis; for multiple biological samples from different conditions or donors, let Seurat handle the integration.
A few recurring pitfalls are worth internalizing; Table 3 collects the most common ones.
cellranger count pitfalls and fixes. The wrong-reference and filename-mismatch errors are the two most frequent causes of a failed or misleading run; check these first when a run looks wrong.
| Pitfall | Symptom | Fix |
|---|---|---|
| FASTQ filename mismatch | count errors “no input FASTQs” or wrong sample grouping |
Keep the exact _S<n>_L<lane>_<R1|R2>_001 pattern; symlink rather than rename |
| Mixed chemistries in one input folder | count refuses to run |
Separate 3’ v2 and v3 (or any mismatched chemistry) reads into distinct input folders |
| Wrong reference for the chemistry | Run “succeeds” but mapping rate < 60%; wrong gene coordinates | Match chemistry to reference: 3’ GEX → refdata-gex-...; 5’ GEX → same GEX ref with --chemistry fiveprime |
--localmem too close to --mem |
Job is killed silently with “Killed” and no error | Set --localmem ~10% below the SLURM --mem ceiling to absorb brief I/O spikes |
OOM kill mid-write produces a truncated .h5 / incomplete outs/ |
Downstream loaders (Read10X_h5, Read10X) fail or read partial data |
Check seff for OUT_OF_MEMORY; increase --mem and re-run from scratch |
cellranger aggr used for multi-sample DE |
Integration artifacts; per-sample metadata lost | Use cellranger aggr only for technical replicates; use Seurat merge() + Harmony for biological replicates |
--transcriptome
No default — required. This is the path to the pre-built reference directory (not the tarball), e.g. --transcriptome=/projects/<PIRG>/<user>/refs/refdata-gex-GRCh38-2024-A. The reference must match the organism of your sample (human vs mouse vs other) and must be the GEX reference (refdata-gex-...), not the ATAC reference. Using the wrong reference produces a low mapping rate (< 60%) that Cell Ranger will not flag as an error — it will complete, write a matrix, and report the low mapping rate in web_summary.html. The reference version also matters for reproducibility: a run against GRCh38-2020-A and a run against GRCh38-2024-A will produce slightly different gene counts due to updated annotations. Record the exact reference name and path in your analysis notes.
--expect-cells
Optional — auto-estimated by default. Since Cell Ranger 7, cell calling is automatic (EmptyDrops-based) and you normally do not set this flag; there is no fixed default cell number. --expect-cells is an optional hint that nudges the knee-based component of cell calling, useful mainly when the knee is soft/ambiguous or when you want to reproduce an older analysis. It is not a hard cutoff — Cell Ranger adjusts its algorithm around the expectation rather than truncating at it. If you do supply it, match your actual loading (e.g. --expect-cells=10000). To override automatic calling entirely and force an exact count, use --force-cells instead — but only when the automatic call clearly disagrees with the barcode-rank plot. The “Estimated number of cells” in web_summary.html is the diagnostic: if it diverges badly from your expected target, inspect the knee plot before reaching for either flag.
--localcores and --localmem
Defaults: 1 core, 6 GB (Cell Ranger’s own conservative defaults). These parameters set Cell Ranger’s internal resource budget, distinct from the SLURM allocation. --localcores should match --cpus-per-task from your SLURM script so Cell Ranger uses all the cores SLURM reserved for it. --localmem should be set slightly below the SLURM --mem value — typically 8–10% lower — to absorb I/O buffers, pre-loaded STAR index, and brief memory spikes without exceeding the SLURM hard ceiling. A SLURM --mem=128G job should set --localmem=120. Omitting these flags is technically valid but forces Cell Ranger to use its conservative defaults (1 core, 6 GB), turning a 16-core, 128 GB job into a single-threaded, memory-starved process that will either run extremely slowly or fail outright on large datasets.
--chemistry
Default: auto (automatic detection). Cell Ranger infers the 10x chemistry version (3’ v2, 3’ v3, 5’, Fixed RNA Profiling, etc.) from the read structure. Auto-detection works correctly for the standard chemistries in the vast majority of cases. Set this flag explicitly when auto-detection fails (Cell Ranger will print a warning), when running a non-standard or custom chemistry, or when processing 5’ libraries that need --chemistry=fiveprime. An incorrect chemistry setting causes very low mapping rates and incorrect UMI assignment — symptoms indistinguishable from a wrong reference, so always check both if mapping rate is unexpectedly low.
--create-bam
Default: true in Cell Ranger ≤ 7; false in Cell Ranger ≥ 8 (must be specified explicitly). The aligned BAM file (possorted_genome_bam.bam) is large — 10–30 GB per sample — and is not needed for a standard scRNA-seq analysis that only uses the counts matrix. It is required for splice-junction analysis, allele-specific methods, re-alignment against a different reference, and certain advanced QC tools. Omitting it (or explicitly setting --create-bam=false in newer versions) saves substantial disk space. Set --create-bam=true explicitly when you have a downstream use for the aligned reads; otherwise the disk savings from omitting the BAM are significant for multi-sample projects.
11.10 Alternatives to Cell Ranger
Cell Ranger is the official 10x pipeline, but it is not the only way to turn FASTQs into a counts matrix, and the open-source alternatives are often faster and more flexible. Table 4 compares the three main options.
Reading Table 4: the choice between tools depends on what you are optimizing for. If official 10x support and the widest chemistry compatibility matter (e.g., you are processing Flex, Feature Barcoding, or CRISPR screening data), Cell Ranger is the only supported option. If you want a drop-in open-source replacement for standard GEX data that produces Cell Ranger-compatible output, STARsolo is the closest match — it uses the same STAR aligner and implements the same UMI-deduplication and cell-calling logic, making its outputs concordant with Cell Ranger on the vast majority of genes. If speed is the priority — processing dozens of samples in a pipeline where wall-clock time matters — alevin-fry’s pseudoalignment is 3–5× faster and uses significantly less memory. The kallisto|bustools ecosystem (kb-python) is similarly fast and has an active Python integration. A sample quantified by two different tools will produce concordant counts on highly expressed, unambiguously mapping genes but will differ on genes with complex multimapping patterns or on intronic counts — never switch tools mid-project without validating concordance on a representative sample.
| Tool | Approach | Speed vs Cell Ranger | Custom reference | Key use cases |
|---|---|---|---|---|
| Cell Ranger | STAR alignment + UMI deduplication + EmptyDrops cell calling | Baseline | cellranger mkref from FASTA + GTF |
Official 10x support; full-featured web summary; required for many 10x-specific chemistries |
| STARsolo5 | Same STAR aligner, Cell Ranger–compatible logic | Similar | Standard STAR genome index | Drop-in replacement for Cell Ranger counts; open-source; easier cluster deployment |
| alevin-fry / salmon6 | Selective alignment (quasimapping) | ~3–5× faster | Decoy-aware salmon index | Very fast; low memory; well-suited for high-throughput runs |
| kallisto | bustools (kb-python)7 | Pseudoalignment | ~5–10× faster | kb ref from genome/GTF |
Rapid exploration; constant memory footprint; active Python ecosystem |
All three accept custom references easily, which matters for non-model organisms where no 10x reference exists.
11.11 See also
- Lecture 11 — Cell Ranger: Raw Reads to Counts
- Tutorial 11 — Raw Data + Cell Ranger
counton Talapas - Chapter 12 — Raw scATAC-seq Processing — the chromatin companion
- Chapter 1 — QC & Preprocessing — where the matrix is picked up
- 10x Genomics Cell Ranger documentation