Skip to main content

scPreflight

Predict what unsupervised single-cell analysis will recover — before running it.

Almost every single-cell study starts by embedding cells in a low-dimensional space and looking for structure. Nothing in that workflow tells you whether the target you care about — a treatment effect, a donor difference, a developmental state, a ploidy level — will appear in the embedding or be silently missed. scPreflight is the pre-flight check: two numbers, computed from the count matrix and ~100 labelled cells, that say

  1. Is there anything to find? — a cross-validated supervised ceiling, and
  2. Will an unsupervised method find it? — a predicted unsupervised AUC, read off a calibration fitted on 97 contrasts from ten public datasets spanning five species (Arabidopsis, rice, human, mouse, zebrafish), two kingdoms and eight platforms.

Read together, they separate a buried signal (real, but outranked by batch, donor and cell type — a small labelled subset will recover it) from an absent one (redesign the experiment). Across the calibration set, 52% of real targets leave more than 0.10 AUC unclaimed by unsupervised analysis: buried is the common case.

Sixty seconds to a real result

pip install -e ".[test,h5ad]"     # from a repository checkout
scpreflight demo

The demo runs the diagnostic on a bundled real dataset — 1,468 Arabidopsis endosperm nuclei with absolute flow-sorted ploidy labels (GSE157145) — and prints, in seconds:

"supervised_ceiling": 0.9405,          <- the ploidy signal is real and strong
"predicted_unsupervised_auc": 0.8064,  <- but unsupervised analysis won't reach it
"gap": 0.1341,                         <- > 0.10: a buried signal
"auc_total_counts": 0.692,             <- and it rides on library size,
"warnings": [ ... matched evaluation ... ]  <- so score it on a matched subset
"verdict": "buried signal: ... A small labelled subset (~100 cells) recovers most of the gap"

On your own data

From scanpy / AnnData (labels can be strings; NaN / '' / -1 = unlabelled):

from scpreflight import diagnose_adata
out = diagnose_adata(adata, "condition", covariate_keys=["batch"], layer="counts")
print(out["supervised_ceiling"], out["predicted_unsupervised_auc"], out["verdict"])

From bare arrays (cells × genes raw counts, dense or sparse):

from scpreflight import spectral_diagnosis
out = spectral_diagnosis(counts, labels, covariates=meta[["batch", "stage"]])

From the shell (JSON on stdout; .h5ad / .npz / .mtx[.gz] / .csv):

scpreflight diagnose counts.h5ad --labels obs:condition --covariates cov.csv
scpreflight profile counts.npz --labels labels.csv:group   # full recoverability profile
scpreflight depth 950                                      # planning: what can this depth support?

diagnose uses the full matrix — unlabelled cells shape the spectrum, exactly as the calibration defines it. See scpreflight diagnose --help for formats and --genes-axis for .mtx orientation.

How to read the two numbers

Ceiling Gap (ceiling − predicted) Meaning Action
< 0.60 — nothing to find redesign the experiment, not the analysis
≥ 0.60 > 0.10 buried: real but outranked ~100 labels recover most of it; residualise known covariates first
≥ 0.60 ≤ 0.10 recoverable unsupervised analysis will capture most of what exists

If the output warns that the target correlates with total counts, score any recovery on a library-size-matched subset (scpreflight.evaluation.matched_evaluation) — otherwise "recovery" may be sequencing depth, not biology.

Where the prediction comes from. The predicted AUC is 0.5 + 0.5·σ(a + b·alignment), where alignment is the fraction of the target's discriminant direction inside the leading-10 principal subspace. Fitted on the plant contrasts alone, it predicts the 57 animal contrasts at MAE 0.047; held out one dataset at a time, the fit on the remaining nine predicts the tenth at a median MAE 0.046 (range 0.019–0.076). Constants and provenance: scpreflight.diagnostics.CALIBRATION.

Applicability limits. Binary targets; raw counts (not log-normalised — you will be warned); scRNA-seq geometry (the calibration spans Smart-seq2 to 10x to inDrops); 2,000 variable genes is part of the definition, not a tuning knob. Memory scales with cells × 2,000 after sparse HVG selection (~1.6 GB at 100k cells).

Sequencing depth comes first

from scpreflight import depth_advisor
depth_advisor(950)
# {'unsupervised': 0.66, 'with_100_labels': 0.689, 'supervised_ceiling': 0.837,
#  'recommendation': 'too shallow: the information itself is depth-limited...'}

Measured by binomial thinning of the endosperm gold standard: below ~2,000 counts per cell the ceiling itself collapses — no estimator can recover what is not there.

The origin case: endopolyploidy

The diagnostic came out of a concrete failure: recovering per-nucleus ploidy from plant single-cell data as a latent variable is impossible not for lack of signal (supervised ceiling 0.943) but because the ploidy direction ranks ~18th in the variance spectrum. The package therefore also ships the weakly supervised protocol that does work:

from scpreflight import PloidyPipeline
p = PloidyPipeline(covariates=meta[["batch", "stage"]]).fit(counts, labels=y_partial)
score = p.score_          # continuous ploidy score; p.tiers(K) for discrete tiers

with the measured label ladder (0 labels → 0.751, 100 → 0.853, 400 → 0.910 on the gold standard, library-size-matched) in PloidyPipeline.label_budget_guide(), and the matched evaluation design in scpreflight.evaluation.

Signature figures and the flight briefing

pip install -e ".[plots]"
scpreflight report --demo -o report.html     # one command, the whole story

scpreflight.pl draws the six signature figures — the Preflight Map (your targets on the recovered-vs-ceiling plane, over the released calibration cloud), the Spectrum Needle, the Twin Gauge, the Alignment Curve, the Calibration Placement and the Depth Runway — with fixed layouts and a fixed semantic palette (amber = buried, viridian = recoverable, slate = absent), all vector and publication-ready:

import scpreflight as spf
outs = [spf.spectral_diagnosis(X, adata.obs[k], keep_arrays=True) for k in keys]
spf.pl.preflight_map(outs).savefig("preflight_map.pdf")

scpreflight report wraps everything into a single self-contained HTML flight briefing: verdict stamp, the two numbers as instrument cards, every figure with a plain-language reading, and a provenance footer (no JavaScript, no external assets, prints cleanly).

Install

pip install -e ".[test,h5ad]"   # h5ad extra = anndata, for .h5ad / AnnData input
pytest                          # full test suite, a few seconds

Requires numpy, scipy, pandas, scikit-learn (Python ≥ 3.9). The research modules behind the papers live in scpreflight.research (repository only, not in the wheel).

Reproducing the paper

scripts/ contains every analysis; results/ every output JSON; figs/source/ a source-data table per figure panel. scripts/check_consistency.py verifies every number in the manuscript against the file that produced it, and scripts/reproduce_calibration.py refits the released calibration constants from the exported contrast set in minutes. The test suite pins the shipped CALIBRATION and DEPTH_TABLE constants to the released result files.

Data

All public: GSE157145 (At endosperm gold standard), GSE261441 (At maturing root), GSE166894 (At cotyledon pools), GSE226097 (At rosette atlas), GSE273875 (rice root), GSE96583 (Kang PBMC), GSE132044 (Ding platform-comparison resource), GSE84133 (Baron pancreas), GSE102827 (Hrvatin visual cortex), GSE112294 (Wagner zebrafish embryogenesis). No new experimental data were generated.

Citation

See CITATION.cff. If you use the bundled demo data beyond the demo, also cite Picard et al., Nature Plants 2021 (GSE157145).

Metadata

Release files for scpreflight 1.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for scpreflight 1.5.0
File Size Uploaded
scpreflight-1.5.0.tar.gz 3.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for scpreflight 1.5.0
File Interpreter ABI Platform
scpreflight-1.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 7.0 MB

Release files / scpreflight-1.5.0.tar.gz

Download URL scpreflight-1.5.0.tar.gz
Size 3.5 MB
Tags Source
SHA-256 checksum
How to use checksums
2167fb133d4db56fe719529bf327eaaeb5cbbae870331aa1aa2bcf3613cf66f3
BLAKE2b-256 checksum
How to use checksums
0c7361c69733de82eac19f32dbeeadbd1db0d5bbc4d24e8091a4d49619c423c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Release files / scpreflight-1.5.0-py3-none-any.whl

Download URL scpreflight-1.5.0-py3-none-any.whl
Size 3.5 MB
Tags Python 3
SHA-256 checksum
How to use checksums
c9e9f5d2c2f6bbdbc6b82d02a8016dc93bb53b095997304fe6450f10e5095d40
BLAKE2b-256 checksum
How to use checksums
997095de4b66f4886d69636ec5c36fdfb1d8e6db656772a26b69b3130e0b5193
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Release history Release notifications | RSS feed

This release

1.5.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page