Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

The cube and its additive layers

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 jj, group gg and feature ff, let CjgC_{jg} be the cells of that sample and group, ycfy_{cf} the count of cell cc, LcL_c its library size (its total count, or a given value) and TT the target sum (10,000 by default). Each cell’s normalized value is xcf=ycfT/Lcx_{cf} = y_{cf} T / L_c, and the layers are

countsgjf=∑c∈Cjgycf,detectedgjf=∑c∈Cjg[ycf>0],linear_sumgjf=∑c∈Cjgxcf,log_sumgjf=∑c∈Cjglog⁡(1+xcf),log_sum_sqgjf=∑c∈Cjglog⁡(1+xcf)2,njg=∣Cjg∣.\begin{aligned} \text{counts}_{gjf} &= \sum_{c \in C_{jg}} y_{cf}, & \text{detected}_{gjf} &= \sum_{c \in C_{jg}} [y_{cf} > 0], \\ \text{linear\_sum}_{gjf} &= \sum_{c \in C_{jg}} x_{cf}, & \text{log\_sum}_{gjf} &= \sum_{c \in C_{jg}} \log(1 + x_{cf}), \\ \text{log\_sum\_sq}_{gjf} &= \sum_{c \in C_{jg}} \log(1 + x_{cf})^2, & n_{jg} &= |C_{jg}|. \end{aligned}

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, (log_sum_sq−log_sum2/n)/(n−1)(\text{log\_sum\_sq} - \text{log\_sum}^2 / n) / (n - 1)), Cube.fraction_detected (detected over nn) 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:

layer(Cjg∪Cjh)=layer(Cjg)+layer(Cjh).\text{layer}(C_{jg} \cup C_{jh}) = \text{layer}(C_{jg}) + \text{layer}(C_{jh}).

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

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.

References
  1. 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
  2. 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/