A cube holds one pseudobulk profile for every sample and group (usually a cell type):
the sums, over the cells of that sample and group, of each feature’s counts and of a few
statistics of its normalized values. from_cells builds it from a cells x features
count matrix and the sample and group label of every cell; from_bulk wraps a
samples x features matrix as a cube with one group.
The layers¶
For sample , group and feature , let be the cells of that sample and group, the count of cell , its library size (its total count, or a given value) and the target sum (10,000 by default). Each cell’s normalized value is , and the layers are
Layers are stored as groups x samples x features, so that one group is a samples x features matrix, the shape a per-group model fits. The counts layer is the one every cube needs; the others are optional. Bulk cubes have counts only and no cell numbers.
Cube.layer returns a layer, for one group or all. From the sums come the
quantities of every entry: Cube.mean (the mean normalized value, on the log or
the linear scale), Cube.variance (the sample variance of the log values,
),
Cube.fraction_detected (detected over ) and Cube.library_sizes (the
counts summed over features).
Exact regrouping¶
Every layer is a sum over cells, so the profile of a union of cells is the sum of the profiles of its parts. Merging two cell types into one, or pooling the samples of a donor, needs no cells:
Cube.regroup merges groups, Cube.collapse_samples pools the samples that
share a value of a sample column, and Cube.select keeps some samples, groups and
features. Integer layers are summed exactly, and a sum that would exceed the largest
64-bit integer raises an error instead of wrapping around. Means and variances, which
are not sums, cannot be merged this way; that is why the cube keeps sums and derives
them.
What a cube carries¶
Tables. Samples, groups and features each have a table (
Table) with a unique text id (sample_id,group_id,feature_id) and any other columns, such as donor and condition. Designs are built from the sample table and matched to the cube bysample_id, never by position.A fingerprint.
Cube.fingerprintis a SHA-256 digest of the tables, cell numbers, normalization and every layer National Institute of Standards and Technology, 2015. Two cubes with the same values have the same fingerprint however they were made, so a result can name the cube it came from. Its byte-level encoding is documented, so another program can compute it (Cube files and provenance records).A provenance record.
Cube.provenancesays where the data came from (a record the caller gives assource), how the cube was built, and every operation since, each with the fingerprint of the cube it was applied to.Cube.historyprints it in plain terms.
Cubes larger than memory¶
write_cube saves a cube as a Zarr v3 store Miles et al., 2025, in chunks of one group,
every sample and up to 4,096 features, and read_cube opens it without reading the
layers: an analysis then reads one group at a time, and only the chunks that hold the
features it needs. The differential-expression functions keep one group in memory at a
time. A layer can also be any array-like with shape, dtype and indexing, such as a
numpy memmap or an HDF5 or Zarr array, read as needed; Cube.to_memory reads every
lazy layer at once.
to_anndata and to_mudata convert a cube to the pseudobulk layouts of
scverse, and from_anndata and from_mudata read them back.
- National Institute of Standards and Technology. (2015). Secure Hash Standard (SHS) (Federal Information Processing Standards Publication No. 180–4). National Institute of Standards and Technology. 10.6028/NIST.FIPS.180-4
- Miles, A., Striebel, J., Rzepka, N., Maitin-Shepard, J., & Moore, J. (Eds.). (2025). Zarr core specification, version 3.1. https://zarr-specs.readthedocs.io/en/latest/v3/core/