Sanity check · Single-Cell ATAC-seq
How to Find and Remove Doublets in Single-Cell ATAC-seq
A cluster that looks like a transitional cell state is often two nuclei in one droplet, and scATAC-seq doublets need fragment-based tools, not the scRNA-seq playbook.
By Ming "Tommy" Tang, Director of Bioinformatics in Big Pharma · Reviewed September 2026 · 5 min read
You clustered your scATAC-seq data with iterative LSI, and one cluster sits between two otherwise well-separated populations, expressing gene activity scores for markers of both. Or a cluster has fragment counts and TSS enrichment noticeably higher than its neighbors, but no obvious marker signature at all. You already ran a doublet filter because you did this on the scRNA-seq side of the project, so you assume it's handled. It probably isn't, because scATAC-seq doublets behave differently and most of the tools you know don't touch them.
Leave a doublet cluster in and you'll write up a "novel transitional state" that's actually two lineages glued together by a barcode collision, or you'll inflate peak-to-gene links and motif enrichment with fragments that never came from one nucleus. Homotypic doublets are worse: they don't form a visible cluster at all, they just sit inside a real population inflating its fragment counts and quietly biasing any depth-sensitive comparison.
This page gives you an ordered set of checks, from a five-minute sanity look at your loading concentration to running ArchR's synthetic-doublet scoring, scDblFinder's ATAC mode, and AMULET, so you can decide in the next hour whether that suspicious cluster is doublets, biology, or both.
What it looks like when it's happening
- A cluster sits between two well-separated clusters on the UMAP/LSI and its gene activity scores light up for markers of both flanking lineages
- That intermediate cluster has a visibly higher median nFrags or TSS enrichment than either neighbor
- A population you expected to be homogeneous (e.g., a sorted or clonal cell line) has a long right tail of cells with unusually high fragment counts
- addDoubletScores in ArchR reports an R² below 0.9 for the synthetic-doublet projection
- Two samples processed identically show very different flagged-doublet percentages after filterDoublets
- AMULET flags a large fraction of cells as doublets specifically in your highest-depth, most proliferative sample
- scDblFinder and ArchR doublet calls barely overlap on the same dataset
Why it happens
A doublet forms when two nuclei end up in the same droplet or well, which happens at a predictable rate governed by loading concentration: at typical 10x scATAC-seq loading, 5-10% of droplets contain two cells. Unlike scRNA-seq, where a doublet is a mixture of two transcriptomes you can spot by co-expression of two mutually exclusive marker genes, an ATAC doublet is a mixture of two chromatin accessibility profiles layered onto the same barcode. If the two nuclei are different cell types (heterotypic), the combined peak signal looks like an intermediate, transitional profile in LSI space, because the doublet's TF-IDF-weighted fragment counts are literally the union of two cell types' open regions. That's why heterotypic doublets show up as clusters that resemble differentiation intermediates and confuse trajectory calls.
Homotypic doublets (two nuclei of the same type) are the harder case. Their combined accessibility profile looks almost identical to a real cell of that type, just with roughly double the fragments, so they don't separate in embedding space at all. This is where fragment-count logic instead of similarity logic matters: AMULET assumes a diploid genome can be covered by at most two fragments at any locus in a true singlet, so a locus with more than two overlapping fragments implies a second genome copy sharing the barcode. That signal is invisible to synthetic-doublet or nearest-neighbor methods, which is why ArchR's own benchmarking shows fragment-count-based detection catches doublets that similarity-based detection misses, and vice versa.
There's a technical confound layered on top of the biology. In scATAC-seq LSI, the first singular value component almost always tracks sequencing depth rather than biological variation, and doublets have roughly double the DNA to sample from, so they also tend to have higher depth. If you don't exclude depth-correlated LSI dimensions before nearest-neighbor doublet scoring, you end up flagging your highest-depth real cells as doublets just because they resemble synthetic doublets in a depth-dominated space. That's the reason ArchR's addDoubletScores has a corCutOff parameter that drops LSI dimensions correlated with depth before it computes neighbor enrichment.
Finally, the tooling landscape is fragmented by design. Signac, which most people reach for because it mirrors Seurat, ships no native doublet-detection function; its maintainers point users to external, fragment-file-based tools. That means an ATAC doublet check is never "run one function and read one score" the way scRNA-seq doublet detection often is.
The checks
Run them in order. Each one tells you what healthy looks like and what the problem looks like.
0/7 checked · saved in this browser
Look up the number of nuclei loaded and the target recovery for the run (from your wet-lab or Cell Ranger ATAC summary), and compare that to the doublet fraction any tool flags downstream.
- Healthy
- Flagged doublet fraction lands in the same range as expected for your loading, roughly a few percent up to 10% at standard 10x recovery targets, higher if the run was intentionally overloaded.
- Red flag
- Flagged fraction near 0% (detection didn't actually run, or the cutoff is too lax) or well above 15% at standard loading (cutoff too aggressive, or something else, like ambient signal, is being mistaken for doublets).
Using your ArchR or Signac QC metadata, plot a histogram (or density plot) of log10(unique fragments) per cell, and separately of TSS enrichment per cell, for each sample.
- Healthy
- A single, roughly log-normal mode per sample.
- Red flag
- A distinct second mode or long right shoulder at high fragment counts, especially if it aligns with the suspicious cluster's cells; that shoulder is enriched for homotypic doublets even before you run a dedicated tool.
For the suspicious intermediate cluster, compute gene activity scores (ArchR:
getGeneScore_ArchR/addGeneScoreMatrix; Signac:GeneActivity()) for canonical markers of both flanking populations and compare against the flanking clusters themselves.- Healthy
- Each cluster's marker activity is specific to one lineage.
- Red flag
- The intermediate cluster shows meaningful gene activity for markers of both flanking lineages simultaneously, and its median fragment count is higher than either flanking cluster.
Run
addDoubletScoresand inspect the reported R² for the synthetic-doublet projection before trusting the score.rprojHeme1 <- addDoubletScores( input = projHeme1, useMatrix = "TileMatrix", k = 10, nTrials = 5, dimsToUse = 1:30, LSIMethod = 1, scaleDims = FALSE, corCutOff = 0.75, knnMethod = "UMAP" )- Healthy
- R² at or above 0.9, meaning the dataset has enough cellular heterogeneity for synthetic doublets to project meaningfully into distinct space.
- Red flag
- R² below 0.9, which means low heterogeneity is compromising accuracy specifically for homotypic doublet detection; ArchR requires `force=TRUE` to proceed, and you should treat its calls as unreliable for same-type doublets until you cross-check with AMULET.
Build a SingleCellExperiment from your peak/tile matrix and run scDblFinder with feature aggregation enabled, since running it on raw peaks (too sparse, too many zero-inflated features) gives unreliable calls.
rsce <- scDblFinder( sce, artificialDoublets = 1, aggregateFeatures = TRUE, nfeatures = 25, processing = "normFeatures" )- Healthy
- A `doublet`/`singlet` call per cell and a doublet score whose flagged fraction is broadly consistent with your loading-based expectation and with the suspicious cluster you identified visually.
- Red flag
- scDblFinder and ArchR barely agree on which cells are doublets; that's expected for homotypic cases (neither method is built for them) but a large heterotypic-doublet disagreement means one of the two runs needs revisiting (LSI dimensions used, meta-feature count, or depth normalization).
On the fragment file, run AMULET (via scDblFinder's implementation) excluding mitochondrial, sex chromosome, and repetitive regions, and inspect the returned p-values.
rres <- amulet(fragfile, regionsToExclude = toExclude)- Healthy
- Low p-values concentrated in a minority of cells that also show elevated fragment counts; AMULET performs best when median library size is high (10-15k reads/cell).
- Red flag
- A large block of low p-values in a sample that is actively cycling or proliferating (cell lines, tumor tissue, stem cell niches); that pattern is more likely S/G2M cells with replicated DNA than true doublets, see When Not to Fix.
If any samples were species-mixed (barnyard) or genotype-multiplexed, check whether cells flagged as doublets by ArchR/scDblFinder/AMULET actually carry mismatched-species or mismatched-genotype fragments.
- Healthy
- Flagged doublets are enriched for genuine cross-genotype or cross-species fragment mixtures.
- Red flag
- Flagged doublets show no more genotype/species mixing than randomly sampled cells, meaning your computational calls are picking up something other than true multiplets (e.g., depth-correlated LSI dimensions that weren't excluded).
What to do about it
ArchR: score then filter in the same pipeline
When: You're running the whole analysis in ArchR.
Run addDoubletScores as in the checks above, then call filterDoublets(projHeme1) to remove predicted doublets using the default DoubletEnrichment/DoubletScore cutoffs.
Caveat: Default settings remove roughly 3-5% of cells per sample; if your R² diagnostic was below 0.9, this call is unreliable for homotypic doublets specifically, so pair it with AMULET before trusting the filtered object.
scDblFinder for heterotypic doublets outside ArchR
When: You're on Signac, Scanpy, or another pipeline without ArchR's synthetic-doublet scoring.
Build a SingleCellExperiment from the peak or tile matrix and run scDblFinder with aggregateFeatures = TRUE, nfeatures = 25, processing = "normFeatures", then remove cells called doublet.
Caveat: Feature aggregation into meta-features is required on ATAC data (raw peaks are too sparse for SVD); results are sensitive to `nfeatures`, so check the flagged fraction against your loading-based expectation before accepting it.
AMULET for homotypic doublets at high depth
When: You suspect same-type doublets, especially in a uniform or FACS-sorted population, and median library size is in the 10-15k reads/cell range.
Run amulet() on the fragment file with mitochondrial, sex chromosome, and repetitive regions excluded, then filter cells below your chosen p-value cutoff.
Caveat: Underpowered at low sequencing depth, and it will over-flag actively cycling cells (S/G2M) as doublets because their DNA is genuinely replicated; check cell-cycle status before filtering on AMULET alone.
Tune ArchR's filtering stringency to match your loading
When: Your flagged doublet rate is clearly out of line with what your loading concentration predicts.
Rerun filterDoublets(projHeme1, filterRatio = 1.5) (or another ratio) to scale how many cells get removed relative to the default, which targets roughly 3-5% per sample.
Caveat: Raising filterRatio removes more cells outright, including real singlets near the cutoff; tune it against an expectation grounded in loading concentration, not by trial and error until a cluster disappears.
Borrow doublet calls across tools when using Signac
When: You're in Signac, which has no native doublet-detection function.
Run ArchR or scDblFinder on the same fragment file or peak/tile matrix outside Signac, then subset your Signac object to the barcodes that were not called doublets.
Caveat: Requires matching barcodes and peak sets carefully between the two tools; a mismatched peak set between the doublet-calling run and your Signac object will silently misassign scores to the wrong cells.
When not to "fix" it
Don't remove a heterotypic-looking intermediate cluster just because it expresses markers of two lineages, if the population is a genuine differentiation trajectory (hematopoiesis, EMT, any continuous lineage transition). Fit a pseudotime or trajectory model first; if the intermediate accessibility profile forms a smooth continuum with the flanking populations rather than a discrete blob offset in fragment count, it's biology, not a barcode collision. Also don't blindly filter high AMULET scores in actively cycling or proliferative samples (cell lines, tumors, stem cell niches): AMULET's diploid assumption breaks down for cells in S/G2M phase, which have already replicated their DNA and can legitimately show more than two fragments at a locus without containing two nuclei. Check cell-cycle scores before trusting AMULET calls in these samples.
Five things experienced analysts do here
- Run doublet detection before you commit to a clustering resolution, since doublets shift the LSI/UMAP embedding and can pull real clusters apart or merge them.
- Never trust a single doublet tool on scATAC-seq data; cross-check ArchR's synthetic-doublet score against scDblFinder or AMULET, since each catches a blind spot the other misses.
- Record the loading concentration and target cell recovery for every sample in your metadata, so you have a loading-based expectation to sanity-check any downstream doublet fraction against.
- Treat any cluster with gene activity scores split across two unrelated lineages as suspect by default, and confirm with a trajectory model before deciding it's biology rather than doublets.
- Check the ArchR doublet-projection R² before trusting a filterDoublets run; below 0.9, homotypic doublet detection is compromised regardless of what the default cutoffs remove.
Questions people ask
- What is a normal doublet rate for 10x scATAC-seq?
At typical 10x droplet-based loading, 5-10% of droplets contain two cells. The exact rate scales with how many nuclei you load relative to the target recovery, so an overloaded run will sit above that range and an underloaded one below it.
- Does Signac have a built-in doublet detection function?
No. Signac's maintainers state explicitly that there is no native implementation, and recommend adapting external fragment-file-based tools like AMULET or scDblFinder and applying the resulting calls to your Signac object's barcodes.
- What's the difference between ArchR's doublet score and AMULET?
ArchR simulates synthetic doublets and flags cells whose LSI/UMAP embedding neighbors are enriched for those synthetic doublets, which catches heterotypic (different cell type) doublets well. AMULET instead counts fragments per genomic locus and flags cells where a locus is covered more than twice, which targets homotypic (same cell type) doublets that ArchR's similarity-based approach can miss.
- Can I just use Scrublet or DoubletFinder from scRNA-seq on my scATAC-seq data?
Not directly. Those tools expect a gene-by-cell expression matrix and log-normalization; scATAC-seq peak-by-cell matrices are far sparser and near-binary, so ATAC-specific tools aggregate peaks into meta-features (as scDblFinder does with
aggregateFeatures=TRUE) or work from fragment counts instead (AMULET, ArchR).- How do I know if my doublet filtering removed too many real cells?
Check ArchR's R² diagnostic for the synthetic-doublet projection before trusting a filterDoublets run, and compare your flagged doublet percentage per sample against the rate predicted by your loading concentration; a flagged fraction well above the loading-based expectation, especially in a proliferative sample, is a sign the filter is also catching real cycling cells.
Related pages
- Guide · How to Avoid Pseudoreplication in Single-Cell ATAC-seq
- Guide · How to Call Peaks You Can Trust in Single-Cell ATAC-seq
- Guide · How to Choose Cell QC Thresholds in Single-Cell ATAC-seq
- Guide · How to Detect Batch Effects in Single-Cell ATAC-seq
- Guide · How to Sanity-Check Marker Genes and Cell Type Labels in Single-Cell ATAC-seq
- Glossary · Count matrix
- Glossary · Singular value decomposition (SVD)
- Glossary · P-value
Related reading on the blog
Sources
- ArchR is a scalable software package for integrative single-cell chromatin accessibility analysis — ArchR's synthetic-doublet simulation and nearest-neighbor scoring approach
- ArchR: Robust and scaleable analysis of single-cell chromatin accessibility data, Chapter 4.2 Inferring scATAC-seq Doublets — R² diagnostic, homotypic vs heterotypic doublet blind spots, ROC AUC benchmark
- ArchR: Robust and scaleable analysis of single-cell chromatin accessibility data, Chapter 5.6 Filtering Doublets — filterDoublets default cutoffs and filterRatio parameter
- ArchR Reference Documentation: addDoubletScores — addDoubletScores parameters: k, nTrials, dimsToUse, corCutOff, knnMethod
- Bioconductor scDblFinder: Doublet identification in single-cell ATAC-seq — aggregateFeatures workflow and AMULET wrapper, region exclusion for AMULET
- AMULET: a novel read count-based method for effective multiplet detection from single nucleus ATAC-seq data — diploid over-coverage assumption behind AMULET
- Signac Discussion #418: Best practices for filtering and QC with scATAC-seq data — confirmation Signac has no native doublet detection function
Part of the Doublets series.