Python API¶
The package is installed from PyPI with pip installm3-sc and imported as import m3.
It has a one-to-one counterpart in the R API — both drive the same
engine and produce identical results.
The public API has two pieces:
- an input facade (
m3.read_h5,m3.read_h5ad,m3.from_anndata,m3.read_matrix,m3.concat, andm3.datasets.*) that turns common single-cell formats into one canonicalDataset; and - the
m3.M3model object, whose methods cover representation learning, donor-level disease prediction, integrated-gradients attribution, and generative augmentation.
A typical run is three lines:
import m3
data = m3.datasets.liu_demo() # 1. load a Dataset
model = m3.M3(data, condition_keys=["cond_group"], # 2. declare column roles
celltype_key="mergedcelltype",
donor_key="sample_id").train() # 3. train
emb = model.embedding(part="bio") # read out
Capability gating¶
m3 is staged, and which read-outs are available depends on how the model was
built and trained. Every trained model unlocks the integration-level
read-outs (embedding, reconstruct,
generate, augment). The donor-level read-outs
(predict_donors, donor_embedding,
attribute) additionally require the donor predictor, which
trains automatically when both donor_key and celltype_key were supplied at
construction (and is skipped when you pass donor_prediction=False). Calling a
read-out before its capability is unlocked raises
m3.M3CapabilityError. Inspect model.capabilities at any time:
Input facade¶
All readers return a Dataset holding raw counts.
m3 normalises internally, so do not pre–log-normalise the input.
m3.read_h5¶
m3.read_h5(*, rna: str | None = None, adt: str | None = None,
atac: str | None = None, metadata: str,
batch: str = "batch0") -> Dataset
Read one batch from paper-format HDF5 count matrices plus a metadata CSV. Pass
the path to each modality you have (at least one of rna / adt / atac) and
the per-cell metadata CSV. Matrix orientation (cells×features vs.
features×cells) is resolved automatically against the feature count.
m3.read_h5ad¶
Read one batch from an AnnData .h5ad. If adata.var has a feature_types
column (Gene Expression / Antibody Capture / Peaks), the matrix is split
into modalities; otherwise the whole matrix is taken as modality.
m3.from_anndata¶
Build one batch from an in-memory AnnData. Splitting by feature_types
behaves as in read_h5ad.
m3.read_matrix¶
Build one batch from in-memory per-modality count arrays.
counts—{modality -> array | sparse}of raw counts, shape[n_cells, n_features]obs— per-cellDataFrame,n_cellsrowsvar—{modality -> list/Index}of feature names, aligned to each matrix's columns
m3.concat¶
Stack single-batch Datasets into one multi-batch Dataset. Features per
modality are harmonised by name intersection (the first batch's order is
kept); a batch missing a whole modality is zero-filled and flagged in
present (mosaic design). Batch labels must be unique across inputs.
m3.datasets.liu_demo¶
Load the built-in Liu et al. COVID-19 demo — a ~30 k-cell (30,534) stratified subsample of the full ~135 k-cell dataset, shipped inside the wheel so every tutorial runs with no downloads.
>>> data = m3.datasets.liu_demo()
>>> print(data)
Dataset(n_cells=30534, batches=['B1', 'B2', 'B3'], modalities=[rna:1000, adt:192])
obs carries the columns batch (B1 / B2 / B3), sample_id, Donor,
cond_group (HC / Severe), Age_interval, and mergedcelltype.
Pass n_cells (2,250 to 21,489) to load a smaller, fixed subsample instead of
all 30,534 cells. Training time is proportional to the number of cells, so this
is the quickest way to try cell-level integration on a CPU:
>>> m3.datasets.liu_demo(n_cells=6000)
Dataset(n_cells=6000, batches=['B1', 'B2', 'B3'], modalities=[rna:1000, adt:192])
The same cells are returned on every call. Each batch contributes equally, and every cell type of every batch keeps at least 50 cells (or all it has), so no cell type is lost. Donor-level prediction and attribution need enough cells per donor, so use the full demo there.
The Dataset container¶
The readers return an immutable, multimodal, multi-batch raw-count container. You normally obtain one from a reader rather than constructing it by hand. Useful read-only members:
| Member | Meaning |
|---|---|
data.modalities |
{name -> scipy.sparse.csr_matrix} of raw counts |
data.obs |
per-cell DataFrame (always contains a batch column) |
data.var |
{name -> pd.Index} of feature names per modality |
data.present |
{name -> bool ndarray} per-cell modality presence (mosaic) |
data.n_cells |
number of cells |
data.modality_names |
list of modality names, e.g. ['rna', 'adt'] |
data.batches |
list of batch labels |
m3.M3¶
m3.M3(
dataset,
*,
condition_keys, # str | list[str] (required)
target_condition: str | None = None,
donor_key: str | None = None,
celltype_key: str | None = None,
batch_key: str = "batch",
held_out: list | None = None,
held_out_samples: list | None = None,
hvg: dict | None = None,
embedding_dim: int = 30,
)
Construct a model and freeze the column-role contract against dataset.obs.
Nothing is trained yet — call train.
| Argument | Role |
|---|---|
condition_keys |
one or more obs columns the model is made aware of; each gets its own latent factor. m3 is condition-aware, so this is required. |
target_condition |
the single condition used for prediction / attribution (defaults to the first of condition_keys). |
donor_key |
obs column identifying a donor/sample. Required for donor-level prediction, attribution, and augment. |
celltype_key |
obs column with cell-type labels. Required for train. |
batch_key |
obs column with the batch label (default "batch", written by every reader). It is the integration / held-out unit, the site the donor adversary removes, and the stratifier for augment(batch=...). After training, cell_metadata["batch"] holds this column's values. A single-batch dataset trains without batch correction and warns. |
held_out |
batch label(s) to hold out as an unlabelled query set — their target_condition labels are masked during training and recovered by predict_donors. |
held_out_samples |
donor/sample ID(s) to hold out as the query set instead of a whole batch — may span batches; requires donor_key and is mutually exclusive with held_out. Their target_condition labels are masked during training and recovered by predict_donors. |
hvg |
{modality -> n_top} highly-variable-feature selection, e.g. {"rna": 1000}. Omit to use all features as given. |
embedding_dim |
size of the shared biological latent (default 30). |
After construction, model.contract records the frozen roles, including
model.contract["reference_vocab"] (the condition values observed in the
reference batches).
M3.train¶
M3.train(
*,
max_epochs: int = 300,
lr: float = 1e-5,
batch_size: int = 256,
early_stop_patience: int = 300,
min_delta: float = 0.0,
val_percentage: float = 0.1,
weight_batch_ae: float = 1.0,
balance_batches: bool = True,
donor_prediction: bool | None = None,
donor_predictor: dict | None = None,
) -> M3
Train the integration VAE and, when applicable, the donor predictor on top.
Returns the model (so you can chain .train()). Uses CUDA automatically when
available; otherwise CPU.
donor_prediction—None(default) auto-enables the donor predictor whendonor_keyandcelltype_keyare set. PassFalsefor pure representation learning,Trueto require it.balance_batches— whenTrue(default), the VAE trains on a batch-balanced subset; with no held-out query, downstream steps are automatically rebuilt on the full reference so sparse cell types are not biased out.donor_predictor— a dict tuning the Stage-2 adversarial corrector. Recognised keys (defaults):enc_lr(1e-5),glr(5e-3),n_epochs(30),patient_w(3.0),adv_max(1.0),adv_warmup(10),n_disc(1),hidden(64),min_cells(5).
Read-outs¶
m3.embedding¶
Cell-level latent, row-aligned to cell_metadata — not
necessarily to the input. Rows are grouped by batch; with held_out /
held_out_samples they are reference cells then query cells. Align on the obs_names column
that from_anndata / read_h5ad carry through, never by
position:
emb = pd.DataFrame(model.embedding("intrinsic"),
index=model.cell_metadata["obs_names"])
adata.obsm["X_m3"] = emb.reindex(adata.obs_names).to_numpy()
part selects which factor(s):
"bio"— the intrinsic biological latent plus every condition factor (the integration embedding for clustering / UMAP)"intrinsic"— the intrinsic biological latent only"batch"— the 2-d batch factor (should carry batch signal, not biology)- any name in
condition_keys— that condition's 2-d factor
m3.reconstruct¶
Posterior-mean decode of every cell, returned as {modality -> ndarray}
([n_cells, n_features]). With remove_batch=True (default) the batch factor is
zeroed before decoding, yielding a batch-corrected matrix suitable for
cross-batch differential expression.
m3.predict_donors¶
Donor-level prediction of target_condition. Requires the donor predictor.
Returns one row per donor with columns donor, is_reference,
predicted_label, and a prob_<label> column per class. By default only the
held-out query donors are returned; pass include_reference=True to include
the reference donors too.
m3.donor_embedding¶
The corrector-corrected per-donor vector used for prediction — a DataFrame
indexed by donor, with an is_reference column followed by embedding dimensions
(m3_0, m3_1, …). Join with predict_donors(include_reference=True) for
labels, or feed straight into UMAP for a patient-level map. Requires the donor
predictor.
m3.generate¶
Posterior-resampled synthetic cells (one per reference cell, 1:1), returned as
{modality -> ndarray}. tau scales the sampling temperature. Handy for
noise-augmenting a training set.
m3.augment¶
M3.augment(*, conditions: list, n_donors: list, tau: float = 0.8,
batch: str | None = None, seed: int = 42) -> dict
Synthesise new donors per condition by posterior-resampling real donor
templates. conditions lists the target_condition values to generate and
n_donors how many synthetic donors for each (same length). Returns
{"expression": {modality -> ndarray}, "obs": DataFrame}. When batch is given
(a value of batch_key), templates are drawn only from that batch — useful for
reproducing batch-stratified figures. Requires donor_key + celltype_key.
m3.cell_metadata¶
Row-aligned metadata for embedding /
reconstruct (reference cells first, then query cells). Data read
with from_anndata / read_h5ad carries the input barcodes in an obs_names
column; for read_matrix / read_h5, put an identifier column in obs yourself.
Attribution¶
m3.attribute¶
M3.attribute(*, reference_labels: list, target_class: int | None = None,
n_steps: int = 50) -> Attribution
End-to-end integrated-gradients attribution of target_condition, returning an
Attribution. Requires the donor predictor.
reference_labels— the healthy/baseline label(s) oftarget_condition(e.g.["HC"]). These set the IG baseline at two levels: the mean expression of reference cells (cell-level IG) and the mean patient vector over reference donors (donor-level IG). Supplying them is what keeps housekeeping / pan–cell-type genes from dominating the ranking.target_class— which non-reference class to attribute toward; defaults to the first class not inreference_labels.n_steps— IG interpolation steps (default50; lower is faster but biased).
The Attribution object¶
Returned by attribute. Holds several views of importance:
| Member | Type | Meaning |
|---|---|---|
attr.genes |
DataFrame(feature, importance) |
raw mean(\|IG\|) over all cells — quick, unfiltered |
attr.celltypes |
DataFrame(celltype, importance) |
per–cell-type importance |
attr.donors |
DataFrame(donor, attribution) or None |
per-donor attribution |
attr.cells |
ndarray |
signed per-cell importance |
attr.attribution |
ndarray [n_cells, n_features] |
full cell × feature IG matrix |
attr.gene_celltype_matrix |
ndarray [n_celltypes, n_features] |
signed per–(cell-type, feature) attribution |
attr.target_label |
str |
the attributed (non-reference) condition |
Attribution.top_genes¶
Attribution.top_genes(n: int = 100, *, min_cells_per_condition: int = 200,
exclude_regex: str | None = <housekeeping>,
modality: str | None = None) -> pd.DataFrame
The publication ranking recipe. Drops cell types where either the reference or
the target condition has fewer than min_cells_per_condition cells, scores each
feature as mean(|gene_celltype_matrix|) across the kept cell types, removes
housekeeping / ribosomal genes by name (exclude_regex, default matches
^MT-, ^RPL, ^RPS, …; pass None to skip), optionally restricts to one
modality ("rna" / "adt" / "atac"), and returns the top-n ranked
DataFrame with columns feature, modality, score, n_celltypes_used.
Attribution.top_celltypes¶
Cell-type importance ranking, filtered to types with at least
min_cells_per_condition cells in both the reference and the target
condition (default 200; set 0 for the raw celltypes table).
Exceptions¶
m3.M3CapabilityError— a read-out was requested before its capability was unlocked (subclassesRuntimeError). The message names what is missing and how to obtain it.
Versioning¶
The installed version is available as m3.__version__.
R ⇄ Python correspondence¶
Every verb has a 1:1 counterpart in the R API. The R package wraps the same engine, so a script translated line-for-line produces identical numbers.
| Python | R |
|---|---|
m3.datasets.liu_demo() |
m3_demo() |
m3.read_h5() / m3.read_h5ad() |
m3_read_h5() / m3_read_h5ad() |
m3.from_anndata() / m3.read_matrix() |
m3_dataset() |
m3.concat() |
m3_concat() |
m3.M3(...).train() |
m3_train(...) |
M3.capabilities |
m3_capabilities() |
M3.embedding() |
m3_embedding() |
M3.cell_metadata |
m3_cell_metadata() |
M3.reconstruct() |
m3_reconstruct() |
M3.predict_donors() |
m3_predict_donors() |
M3.donor_embedding() |
m3_donor_embedding() |
M3.generate() |
m3_generate() |
M3.augment() |
m3_augment() |
M3.attribute() |
m3_attribute() |
Attribution.top_genes() / .top_celltypes() |
m3_top_genes() / m3_top_celltypes() |
| — | m3_shutdown() (R only — releases the engine session) |
See also¶
- R API — the same surface for R (
library(m3)). - Tutorials — Representation learning, Patient prediction, Feature attribution, Data augmentation.