Skip to main content

cpdrift

Inductive evaluation and representation drift for batch correction in image-based profiling.

Batch-correction methods are usually benchmarked transductively: fitted on every batch, including the ones they are scored on. Production screens are not like that. Plates accumulate over months, and a correction has to be applied to batches that did not exist when it was fitted. cpdrift provides the pieces needed to evaluate a correction the way it will actually be deployed.

pip install cpdrift

numpy and pandas only. MIT licensed.

DOI

Citing

Archived at Zenodo under the concept DOI 10.5281/zenodo.22548754, which always resolves to the latest release. Cite that one rather than a version DOI, so the citation does not go stale.

Why it exists

Two methods sitting side by side in the same benchmark can have categorically different deployment semantics, and the usual evaluation protocol does not distinguish them:

  • Fittable methods expose a fit / transform split. Fit once, apply forward, and profiles you already analysed never move.
  • Refit-only methods offer a single whole-matrix entry point. The only way to add a batch is to refit everything, which silently rewrites earlier results.

harmonypy is refit-only in every released version (0.0.9 through 2.0.0): no transform, predict, apply or fit_transform has ever existed in the published API. pycytominer.Spherize is a BaseEstimator with both. That asymmetry is what this package measures.

Representation drift

The naive measure -- displacement between two corrected embeddings -- is wrong, because several methods are defined only up to a rotation of the feature space. A pure basis change would register as large drift while changing no downstream conclusion. So cpdrift gauge-fixes first:

  1. Fit an orthogonal Procrustes rotation on control wells, whose biology is unchanged by construction.
  2. Measure displacement on treated wells only.
  3. Divide by the assay's own within-batch replicate distance.

D~ = 1 means "already-analysed profiles moved as far as two replicates of the same perturbation differ from each other". Uncorrected input gives exactly zero, and that is a unit test.

import numpy as np
from cpdrift import Sphering, normalised_drift, replicate_distance

# fit on the batches you have, apply forward to the ones that arrive later
op = Sphering().fit(Z_anchor)
before = op.transform(Z_anchor)
...
after = op.transform(Z_anchor)          # no refit, so nothing moved

d = normalised_drift(before, after, is_control,
                     replicate_distance(before, pert_ids, batch=batch))
assert d < 1e-12

Without the gauge fix, a rotation alone scores 1.117 on this scale -- roughly twice the replicate distance. Naive measurement manufactures the result it is looking for.

Evaluating both axes

A method can climb a batch-mixing score by destroying biological signal, so a mixing number quoted alone cannot detect that. Report both:

from cpdrift import batch_mixing_score, mean_average_precision

mix = batch_mixing_score(Z, batch)              # batch homogeneity
mAP, _ = mean_average_precision(Z, pert_ids, batch=batch)   # biological retrieval

batch_mixing_score is a deliberately simple k-NN score. For a published implementation use scib's iLISI; the two rank methods consistently (per-source Spearman +0.77 to +1.00 in our testing) but only the ordering is comparable, since they differ in neighbourhood size and distance.

API

name what it does
FITTABLE, REFIT_ONLY, REGISTRY the deployment-semantics taxonomy
NoCorrection, MeanCenter, ComBat, Sphering fittable corrections, fit / transform
Harmony refit-only; raises if you call transform
NullDMSO the signal-free null model, for auditing whether a metric rewards degenerate solutions
procrustes_rotation, representation_drift, normalised_drift, replicate_distance the drift metric
mean_average_precision, average_precision, batch_mixing_score evaluation metrics

Sphering reproduces pycytominer.Spherize to <=1e-6, including its choice to regularise the singular values rather than the eigenvalues, but avoids its np.linalg.svd(..., full_matrices=True) call -- which materialises an (n x n) matrix it never reads, 53.5 GB at 81,740 wells.

That equivalence is verified against pycytominer 1.7 and later, which is what the sphering extra pins. Older releases give a different result, so the comparison test skips rather than fails on them.

Harmony needs harmonypy, imported lazily and only when called:

pip install "cpdrift[harmony]"

Note on threads

cpdrift deliberately does not set BLAS thread limits. Capping threads is an application decision, and a library that mutates process-wide environment variables at import time is a hazard. Set OMP_NUM_THREADS and friends yourself, before importing numpy.

Licence

MIT.

Metadata

Release files for cpdrift 1.0.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 cpdrift 1.0.0
File Size Uploaded
cpdrift-1.0.0.tar.gz 13.2 kB Details

Built distribution (wheel)

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

Total release size: 28.0 kB

Release files / cpdrift-1.0.0.tar.gz

Download URL cpdrift-1.0.0.tar.gz
Size 13.2 kB
Tags Source
SHA-256 checksum
How to use checksums
889fd1d319f495abbcb62feeed483c7dfcb26753353474e0b1fd84a32ace7b59
BLAKE2b-256 checksum
How to use checksums
5bc7f3ca90628672371669f5f480699f23ddbe96d3900b4f9df8f2186fc7d7cd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release files / cpdrift-1.0.0-py3-none-any.whl

Download URL cpdrift-1.0.0-py3-none-any.whl
Size 14.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e1fae8d6525dee0759763db5412c6a8f146d11f770f0a50bdc016e9b00700ef7
BLAKE2b-256 checksum
How to use checksums
239ecd34df0401850ceda61bc371bb66fd5c208fe319804fdbece07e23c35bf0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release history Release notifications | RSS feed

This release

1.0.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