pyinfercnv
Pure-Python re-implementation of inferCNV (Broad Institute) — single-cell CNV inference from scRNA-seq, AnnData-native, R-parity-audited.
Status: v0.2.1 — reproduces R inferCNV 1.26.0 (run() with HMM i6/i3, subclusters, denoise and the Bayesian filter), 40–65× faster on one thread. From the same seed (set.seed(s) in R, random_state=s here) pyinfercnv gives R's numbers: the expression matrices to rounding, and the hidden-spike calibration, the subclusters and the HMM states identically whenever the neighbour graphs are identical — always with leiden_method="simple", and with R's default "PCA" whenever Seurat's approximate (Annoy) neighbour search returns the exact neighbours. See Reproducing R below and report/.
Installation
pip install pyinfercnv # core
pip install 'pyinfercnv[viz]' # + matplotlib for heatmap
pip install 'pyinfercnv[compare]' # + hmmlearn for parity cross-check (Phase 2+)
pip install 'pyinfercnv[dev]' # + test/build tooling
Python ≥3.10, <3.13. Wheel is py3-none-any (pure Python; numba JIT at first call). Bundles hg38/hg19/mm10 gene-position parquets from GENCODE v45 basic / v19 / vM25.
Quickstart
30 seconds, no fixture download — runs on synthetic data, prints the populated
InferCNVResult schema, saves a heatmap PNG:
uv run python examples/quickstart_30s.py
# or: python examples/quickstart_30s.py
Real workflow on your own AnnData:
import scanpy as sc
from pyinfercnv import infercnv
from pyinfercnv.io.genome import load_gene_positions
adata = sc.read_h5ad("my_dataset.h5ad")
# Merge gene positions into adata.var (gene_symbol index assumed)
gene_pos = load_gene_positions(genome="hg38")
adata.var = adata.var.merge(gene_pos.set_index("gene_symbol"),
left_index=True, right_index=True, how="left")
infercnv(
adata,
reference_key="cell_type",
reference_cat=["Microglia", "Oligodendrocytes"],
key_added="cnv",
)
# Results:
# adata.obsm["X_cnv"] — log2(FC) CNV matrix
# adata.uns["cnv"]["chr_pos"] — chromosome → bin start index
# adata.uns["cnv"]["profile"] — per-block wallclock / RSS
# adata.uns["cnv_ref_counts_raw"] — raw ref counts (Phase 2 prep)
Phase 2 + 3 — set HMM=True (and optionally BayesMaxPNormal=0.5,
mask_nonDE_genes=True, denoise=True, reassignCNVs=False); see
examples/tutorial_phase{1,2,3}.ipynb for end-to-end notebook walk-throughs
on the bundled R inferCNV oligodendroglioma fixture.
CLI:
pyinfercnv run-h5ad --input dataset.h5ad --output out.h5ad \
--reference-key cell_type --reference-cat Microglia,Oligodendrocytes
Reproducing R
pyinfercnv follows the code R inferCNV 1.26.0 runs, step by step:
- Preprocessing (steps 1–14, 16) — genes on excluded chromosomes are dropped before the depth
normalisation, as in
CreateInfercnvObject; genes are ordered by chromosome, start and end; all arithmetic is in double precision. The matrices equal R's to ~1e-14 (the public float32 outputs to their rounding). - Random numbers — R's Mersenne-Twister and
sample(),rnorm(inversion) andrunifare ported (pyinfercnv._rcompat), and drawn in R's order: the hidden-spike simulation (step 3), the subclustering (step 15) and the hidden-spike trend (step 17) share one generator started byrandom_state, asset.seed(random_state); infercnv::run(...)does. - Hidden spike (i6 HMM) —
smooth.splinewith generalised cross-validation is ported from R'ssbart.c(knots, banded solve, Brent search onspar); the simulated counts, the per-state means and standard deviations and the trend fit equal R's. - Subclusters (step 15) — the genes R leaves out (
z_score_filter) are left out, and the linear fold-change matrix is clustered.leiden_method="PCA"(R's default) runs Seurat's vst, ScaleData and RunPCA (set.seed(42)and irlba's start vector included) and the SNN graph;"simple"the k-NN graph. Leiden runs on python-igraph with a generator that draws R's numbers the way R igraph does, so the same graph gives R's partition. Seurat finds neighbours with Annoy, an approximate search; pyinfercnv uses the exact neighbours, so with"PCA"a few cells can be placed in other subclusters (adjusted Rand index 0.79–0.98 on six 10x tumours). - HMM, Bayesian filter, denoise (steps 17–22) — follow R; CNV regions are built for the observation subclusters only, as R's region report.
On the oligodendroglioma example of inferCNV, infercnv(…, random_state=1) returns R's
subclusters and HMM states exactly. On six breast tumours from 10x Genomics (532–3,225 cells),
the expression and denoised matrices equal R's to float32 rounding and 98.2–99.5% of the HMM
states agree; pyinfercnv ran 41–64 times faster than R on one thread. Scripts and figure:
report/.
Differences from R's defaults to note: BayesMaxPNormal defaults to 0 here (R: 0.5) and
reassignCNVs=True is not ported; the HMM states are numbered from 0 (R from 1).
Testing
# Fast tests (no R)
uv run pytest tests/unit tests/test_smoke.py tests/test_io_contract.py tests/test_viz_smoke.py -v
# R-parity (needs Rscript + infercnv installed; generates TSV fixtures first)
export PYINFERCNV_R_EXTDATA=$(Rscript -e 'cat(system.file("extdata", package = "infercnv"))')
Rscript tests/r_reference.R
uv run pytest tests/test_r_parity.py -v
# Wheel validation
uv run python -m build
uv run pytest tests/test_wheel.py -v
Architecture
pyinfercnv/— package core (noomicverseimport at any level — spec §7)io/— AnnData reader, gene-order BED, bundled genome parquetspreprocess/— filter, normalize, log, subtract_ref, max_thresholdsmooth/— pyramidinal (scipy.ndimage + numba tail)center/— per-cell mediancna/— outlier prunekernels/— numba hot kernels (tail smoothing)_rcompat/— R's random numbers,smooth.spline, base-R arithmetic and Seurat stepsvalidation/— r-parity metrics (max_diff, Spearman ρ, Jaccard; ARI retained as operational helper only)viz/— matplotlib heatmap (no omicverse; ov-aligned palette hardcoded)pipeline.py— end-to-end orchestration with psutil profile hookscli.py— typer app (run-h5ad,version)
tests/— unit + R-parity + smoke + io_contract + viz_smoke + wheelscripts/— codex review tooling, GENCODE GTF → parquet generatordocs/superpowers/— specs, plans, reviews (triple-gate audit trail)
License
BSD-3-Clause, matching upstream R infercnv.
Metadata
Release files for pyinfercnv 0.2.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 |
|---|---|---|---|---|
| pyinfercnv-0.2.1.tar.gz | 6.0 MB | Without provenance | Details for pyinfercnv-0.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyinfercnv-0.2.1-py3-none-any.whl | Python 3 | none | any | Details for pyinfercnv-0.2.1-py3-none-any.whl |
Total release size: 9.4 MB
Release files / pyinfercnv-0.2.1.tar.gz
| Download URL | pyinfercnv-0.2.1.tar.gz |
|---|---|
| Size | 6.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
86ad1b7e4d0b3ef39ecc2fd94d9050cbbd7b41efdbf6fb547579e8f72dfe3449
|
|
BLAKE2b-256 checksum How to use checksums |
177353f7b8f17c36b109d8b09208cfcaafde5f3ac22b096588ae35d0c1bead06
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.22
|
Release files / pyinfercnv-0.2.1-py3-none-any.whl
| Download URL | pyinfercnv-0.2.1-py3-none-any.whl |
|---|---|
| Size | 3.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8058393bc9d0e58f2d7fb6893dc3cf29685d51874660acefab0bf1c8706620f3
|
|
BLAKE2b-256 checksum How to use checksums |
c38f985b982089c20ec7107636de2a6cc506314024907d240a976bce7ef87d1a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.22
|
Security for pyinfercnv 0.2.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.