Metadata-Version: 2.4
Name: pydoubletfinder
Version: 0.2.0
Summary: Pure-Python DoubletFinder — computational doublet detection in scRNA-seq via artificial-doublet pANN scoring, AnnData-native.
Author-email: Zehua Zeng <starlitnightly@163.com>
License: Creative Commons Legal Code
        
        CC0 1.0 Universal
        
        This Python port is released under the same CC0 license as the original
        R DoubletFinder package (https://github.com/chris-mcginnis-ucsf/DoubletFinder).
        
        CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
        LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
        ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
        INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
        REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
        PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
        THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
        HEREUNDER.
        
        Statement of Purpose
        
        The laws of most jurisdictions throughout the world automatically
        confer exclusive Copyright and Related Rights (defined below) upon the
        creator and subsequent owner(s) (each and all, an "owner") of an
        original work of authorship and/or a database (each, a "Work").
        
        Certain owners wish to permanently relinquish those rights to a Work
        for the purpose of contributing to a commons of creative, cultural and
        scientific works ("Commons") that the public can reliably and without
        fear of later claims of infringement build upon, modify, incorporate
        in other works, reuse and redistribute as freely as possible in any
        form whatsoever and for any purposes, including without limitation
        commercial purposes. These owners may contribute to the Commons to
        promote the ideal of a free culture and the further production of
        creative, cultural and scientific works, or to gain reputation or
        greater distribution for their Work in part through the use and
        efforts of others.
        
        For these and/or other purposes and motivations, and without any
        expectation of additional consideration or compensation, the person
        associating CC0 with a Work (the "Affirmer"), to the extent that he or
        she is an owner of Copyright and Related Rights in the Work,
        voluntarily elects to apply CC0 to the Work and publicly distribute
        the Work under its terms, with knowledge of his or her Copyright and
        Related Rights in the Work and the meaning and intended legal effect
        of CC0 on those rights.
        
        See https://creativecommons.org/publicdomain/zero/1.0/legalcode for
        the full CC0 legal text.
        
Project-URL: Homepage, https://github.com/omicverse/py-DoubletFinder
Project-URL: Repository, https://github.com/omicverse/py-DoubletFinder
Project-URL: Issues, https://github.com/omicverse/py-DoubletFinder/issues
Project-URL: Upstream R package, https://github.com/chris-mcginnis-ucsf/DoubletFinder
Project-URL: Upstream (omicverse), https://github.com/Starlitnightly/omicverse
Keywords: single-cell,scRNA-seq,doublet,doubletfinder,pANN,scanpy,anndata
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.23
Requires-Dist: scipy>=1.10
Requires-Dist: pandas>=1.5
Requires-Dist: anndata>=0.9
Requires-Dist: scikit-learn>=1.2
Requires-Dist: matplotlib>=3.6
Requires-Dist: seaborn>=0.12
Requires-Dist: tqdm
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Provides-Extra: scanpy
Requires-Dist: scanpy>=1.9; extra == "scanpy"
Dynamic: license-file

# pydoubletfinder

A **pure-Python re-implementation of [DoubletFinder](https://github.com/chris-mcginnis-ucsf/DoubletFinder)** (McGinnis et al., *Cell Systems* 2019) for computational doublet detection in single-cell RNA-seq data.

- AnnData-native — drop-in for the scanpy ecosystem
- **No `rpy2`**, no R install — the full pN/pK sweep, bimodality coefficient, BCmvn, and pANN scoring are all implemented directly in NumPy/SciPy
- Same function surface as the R workflow (`paramSweep` → `summarizeSweep` → `find.pK` → `doubletFinder`)
- Bit-for-bit reproducibility against the R reference when fed matching PCA embeddings + artificial-doublet cell pairs (see `tests/test_exact_match.py`)

> This is a **standalone mirror** of the canonical implementation that lives in [`omicverse`](https://github.com/Starlitnightly/omicverse) (`omicverse.single.DoubletFinder`). All algorithmic work is developed upstream in omicverse and synced here for users who want DoubletFinder without the full omicverse stack.

## Install

```bash
pip install pydoubletfinder
```

## Quick-start (class API)

```python
import anndata as ad
from pydoubletfinder import DoubletFinder

adata = ad.read_h5ad("mydata.h5ad")          # cells × genes, raw counts in .X

df = DoubletFinder(adata)

# 1) pN/pK parameter sweep
df.param_sweep(PCs=10)

# 2) Bimodality coefficient summary
df.summarize_sweep()

# 3) Optimal pK via BCmvn
bcmvn = df.find_pK()

# 4) Final scoring + classification
df.run(pN=0.25, nExp=round(0.075 * adata.n_obs))

adata.obs[[c for c in adata.obs.columns if c.startswith("DF.")]]
```

## Low-level functional API (mirrors R one-to-one)

```python
from pydoubletfinder import (
    param_sweep, summarize_sweep, find_pK,
    doublet_finder, model_homotypic,
    bimodality_coefficient,
)

# Per-real-cell pANN (needs a PCA embedding of [real + artificial] cells)
result = doublet_finder(
    pca_coord=my_pca,              # (n_real + n_doublets, n_PCs)
    n_real_cells=n_real,
    pN=0.25, pK=0.09, nExp=250,
)
result.pANN                          # np.ndarray
result.classifications               # {"Singlet", "Doublet"} per real cell
result.column_name_DF                # "DF.classifications_0.25_0.09_250"

# Homotypic-doublet proportion (match R modelHomotypic)
homotypic = model_homotypic(adata.obs["cluster"])
```

## What's included

| Python | R counterpart | Purpose |
|---|---|---|
| `DoubletFinder` class | — | AnnData-native lifecycle wrapper (like `Milo`, `Monocle`) |
| `param_sweep` | `paramSweep` | pN/pK sweep, one `SweepEntry` per (pN, pK) |
| `summarize_sweep` | `summarizeSweep` | bimodality coefficient per sweep entry, optional AUC |
| `find_pK` | `find.pK` | BCmvn + optimal-pK table |
| `doublet_finder` | `doubletFinder` | pANN + `Doublet`/`Singlet` classification |
| `model_homotypic` | `modelHomotypic` | homotypic-doublet proportion from cluster freqs |
| `bimodality_coefficient`, `skewness`, `kurtosis` | same | exported for direct use/testing |
| `bkde`, `approxfun` | `KernSmooth::bkde`, `stats::approxfun` | KernSmooth-compatible KDE + R approxfun |
| `sample_artificial_doublets` | internal | expose doublet-pair sampling for reproducibility |

## Reproducing R results exactly

The pipeline's randomness has two sources: which cell pairs become artificial doublets, and the PCA embedding of the merged matrix. To get identical outputs to an R run, provide both directly:

```python
from pydoubletfinder import doublet_finder

result = doublet_finder(
    pca_coord=r_pca_embedding,      # from Seurat's reductions$pca@cell.embeddings
    n_real_cells=len(real_cells),
    pN=0.25, pK=0.09, nExp=250,
)
```

`tests/test_exact_match.py` runs the R reference (`DoubletFinder::paramSweep` + `doubletFinder`) inside the `CMAP` conda env, saves PCA coords and cell-pair indices, and checks that the Python port reproduces the pANN, BCreal, and classification vectors bit-for-bit.

## Relationship to omicverse

Developed **upstream** in [`omicverse`](https://github.com/Starlitnightly/omicverse):

- Canonical implementation: `omicverse.single.DoubletFinder`
- Standalone mirror (this repo): same code, same API, minus the omicverse packaging

## Citation

If you use this package, please cite the original DoubletFinder paper:

> McGinnis, C.S., Murrow, L.M. & Gartner, Z.J. **DoubletFinder: Doublet Detection in Single-Cell RNA Sequencing Data Using Artificial Nearest Neighbors.** *Cell Systems* 8, 329–337 (2019).

and acknowledge omicverse / this repo for the Python port.

## License

CC0 — matches the upstream R package.
