pyscdblfinder
A pure-Python port of scDblFinder (Germain et al., F1000Research 2022) for fast, classifier-based doublet detection in single-cell RNA-seq and scATAC-seq data, including scDblFinder's re-implementation of AMULET (Thibodeau et al., Genome Biology 2021).
- AnnData-native — drop-in for the scanpy ecosystem
- No
rpy2, no R install — the full pipeline (artificial doublets → cxds → kNN features → xgboost iterative scoring → thresholding) is implemented in NumPy/SciPy/xgboost - Same function surface as the R
scDblFinder()call - Tests cover each primitive (artificial doublet synthesis, cxds, kNN features, xgboost loop, thresholding) plus an end-to-end smoke test on a synthetic mixture
This is a standalone mirror of the canonical implementation that lives in
omicverse(omicverse.ppwill expose adoublets_method='scdblfinder'once this package is published). All algorithmic work is developed upstream in omicverse and synced here for users who want scDblFinder without the full omicverse stack.
Install
pip install pyscdblfinder
Quick-start (class API)
import anndata as ad
from pyscdblfinder import ScDblFinder
adata = ad.read_h5ad("mydata.h5ad") # cells × genes, raw counts in .X
sdf = ScDblFinder(adata, random_state=0)
sdf.run(dbr=0.07) # 7% expected doublet rate
adata.obs[['scDblFinder_score', 'scDblFinder_class']].head()
scATAC-seq (new in 0.3.0)
Three approaches, as in the scDblFinder ATAC vignette:
import pyscdblfinder as psd
# 1) scDblFinder with feature aggregation (peaks/tiles -> 25 meta-features)
sdf = psd.ScDblFinder(adata_atac, random_state=0) # cells x peaks, raw counts
sdf.run(aggregate_features=True, n_features=25, processing="normFeatures")
# 2) AMULET on the fragment file (loci covered by > 2 fragments)
am = psd.amulet("atac_fragments.tsv.gz") # p.value / q.value per barcode
# 3) combine the two (Fisher's method), recommended by the vignette as the
# most robust overall
bc = adata_atac.obs_names.intersection(am.index)
combined_p = psd.fisher_combine(
1 - adata_atac.obs.loc[bc, "scDblFinder_score"], am.loc[bc, "p.value"])
psd.clamulet(fragments) (classifier on fragment coverages) is ported too;
the R authors report it is slower and outperformed by the combination above.
AMULET recommends excluding repeats as well as mitochondrial and sex
chromosomes: pass a BED file or (chrom, start, end) tuples to
regions_to_exclude. A tabix-indexed fragment file (.tbi next to it) is
processed chromosome by chromosome, as in R.
R behaviour kept on purpose
The port follows R scDblFinder exactly, including a few edge cases worth knowing about:
-
The default
regions_to_excludecovers the sex and mitochondrial chromosomes only over[1, 1e8], so the part of human chrX beyond 100 Mb is not excluded. -
With a tabix-indexed file, R reads each chromosome only over
[1, 1e8]: fragments beyond 100 Mb (human chr1-chr12) are ignored. The port does the same by default (tabix_window=1e8); passtabix_window=Noneorfull_in_memory=Trueto use whole chromosomes. -
processing="atac"fails in R (its embedding has one row per feature); the port embeds the cells (TF-IDF + uncentred SVD), which is the evident intent. The vignette usesprocessing="normFeatures". -
R's
aggregateFeaturesusesmbkmeansonly if that optional package is installed; by default it runskmeans, and so does the port (use_mbk=Trueswitches to mini-batch k-means). -
In R,
amuletFromCountsfilters features with Holm-adjusted p-values (p.adjust's default) before the BH q-values; the port does the same.
Parity against R scDblFinder 1.16.0 is documented in
PARITY_ATAC.md: amulet and amuletFromCounts match R
to ~1e-15, the stochastic paths agree with R as well as R agrees with
itself across seeds, and the port is 9-600x faster (AMULET on a 68 M
fragment file: 5-8 min, where R did not finish in 4.5 h). Parity is with R
scDblFinder's amulet(), not with the original AMULET scripts.
On clusters with old glibc (e.g. CentOS 7), xgboost 3.x wheels may not
install; xgboost 2.1.x works (xgboost>=1.7 is the only constraint).
Low-level functional API (mirrors R one-to-one)
from pyscdblfinder import sc_dbl_finder
# counts must be genes × cells (Seurat orientation)
result = sc_dbl_finder(
counts,
clusters=None, # or a per-cell cluster label array for inter-cluster doublets
artificial_doublets=3000,
dbr=0.07,
dims=20,
k=None, # auto-chosen from n_cells
include_pcs=19,
)
result.table # per-cell DataFrame — features + score + class
result.score_threshold
What's included
| Python | R counterpart | Purpose |
|---|---|---|
ScDblFinder class |
— | AnnData-native lifecycle wrapper (like DoubletFinder, Milo, Monocle) |
sc_dbl_finder |
scDblFinder() |
single-sample pipeline entry point |
get_artificial_doublets |
getArtificialDoublets |
pair-based doublet synthesis with size adjustments |
cxds_score |
cxds2 |
co-expression-based doublet score |
evaluate_knn |
.evaluateKNN |
per-cell kNN features for the classifier |
scDbl_score |
.scDblscore |
iterative xgboost classifier loop |
doublet_thresholding |
doubletThresholding |
score → class thresholding |
sc_dbl_finder(aggregate_features=..., processing=...) |
scDblFinder(aggregateFeatures=, processing=) |
scATAC mode |
aggregate_features |
aggregateFeatures |
aggregate peaks/tiles into meta-features |
tfidf |
TFIDF |
TF-IDF normalisation |
atac_processing |
.atacProcessing |
TF-IDF + uncentred SVD (cells) |
log_norm_counts |
scuttle::logNormCounts |
library-size log-normalisation |
get_fragment_overlaps |
getFragmentOverlaps |
per-barcode loci covered by > 2 fragments |
amulet |
amulet |
AMULET p/q-values from a fragment file |
amulet_from_counts |
amuletFromCounts |
count-matrix approximation of AMULET |
clamulet |
clamulet |
classifier on fragment coverages |
fisher_combine |
vignette's aggregation::fisher |
combine scDblFinder and AMULET |
What's not (yet) ported
Follow-up work from the R package not yet on the Python side:
- Multi-sample dispatch (
samples=,multiSampleMode) — only single-sample supported - Known doublets (
knownDoublets,knownUse) - Cluster-correlation features (
clustCor) - recoverDoublets / findDoubletClusters / computeDoubletDensity
The single-sample RNA path ports the whole classifier loop, kNN feature
extraction, and thresholding — i.e. everything needed for ~95% of real
scDblFinder() calls.
Relationship to R scDblFinder
Since 0.3.1 the RNA path follows R scDblFinder 1.16.0 step by step: feature selection
(rowMeans order), scuttle log-normalisation and PCA, artificial-doublet pairing, the
.defaultKnnKs grid, the training-exclusion rules, .xgbtrain (exact trees, cross-validated
number of rounds) and doubletThresholding (Brent minimisation, as stats::optimize).
Steps without random numbers are checked against stored R output in
tests/test_rna_reference.py (no R needed): selected features, kNN grid, library-size features
and thresholds (relative error < 1e-9). Artificial doublets and xgboost use random numbers in
both languages, so the scores are compared with R's own seed-to-seed agreement.
On the 16 real datasets of the Xi & Li (2021) benchmark (168,239 cells, two seeds per
implementation; scripts in report/scripts):
| R vs R (seed 1 vs 2) | Python vs R | |
|---|---|---|
| score Spearman, median | 0.874 | 0.874 |
| doublet-call Jaccard, median | 0.770 | 0.768 |
| scDblFinder 1.16.0 | pyscdblfinder 0.3.1 | |
|---|---|---|
| mean AUPRC | 0.562 | 0.560 |
| mean AUROC | 0.796 | 0.794 |
On one thread, pyscdblfinder ran 1.1–2.5× faster than R (median 1.4×). ATAC-mode parity is
documented in PARITY_ATAC.md.
Citation
Germain, P.-L., Lun, A.T.L., Garcia Meixide, C., Macnair, W. & Robinson, M.D. Doublet identification in single-cell sequencing data using scDblFinder. F1000Research 10:979 (2022).
License
GPL-3 — matches the upstream R package.
Metadata
Release files for pyscdblfinder 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyscdblfinder-0.3.1.tar.gz | 50.8 kB | Without provenance | Details for pyscdblfinder-0.3.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyscdblfinder-0.3.1-py3-none-any.whl | Python 3 | none | any | Details for pyscdblfinder-0.3.1-py3-none-any.whl |
Total release size: 96.7 kB
Release files / pyscdblfinder-0.3.1.tar.gz
| Download URL | pyscdblfinder-0.3.1.tar.gz |
|---|---|
| Size | 50.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5591becbf0e5276ffcc7decb2197a97adb71f7cb17246af1e2a02b83695a1d8c
|
|
BLAKE2b-256 checksum How to use checksums |
7c77e52440f16a89e58dc49c5e104912d6094eab93cbda1dc8deaa85b9a80d38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.22
|
Release files / pyscdblfinder-0.3.1-py3-none-any.whl
| Download URL | pyscdblfinder-0.3.1-py3-none-any.whl |
|---|---|
| Size | 45.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f0bd9c6a2e9cc5685ecbe0bfa871752c1c34f32d5b124f61fb49b897a5952cae
|
|
BLAKE2b-256 checksum How to use checksums |
68d019ba2b39762b64ba549b32b197639a16876555f9421657311ff45d074496
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.22
|
Security for pyscdblfinder 0.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.