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.

Work with AnnData and MuData

Single-cell analyses in Python keep their cells in an AnnData Virshup et al., 2024, and scverse tools keep pseudobulk profiles in one of two layouts: a long AnnData with a row per sample and cell type, as scanpy’s get.aggregate and decoupler’s pp.pseudobulk make it Wolf et al., 2018Mompel et al., 2022, or a MuData with a modality per cell type, as LIANA makes it for its analyses of several views Bredikhin et al., 2022Dimitrov et al., 2024. CytePool pools cells from an AnnData, reads and writes both layouts, and keeps a cube’s fingerprint through the conversion, so that a cube can go to these tools and come back as the same cube. The conversions need CytePool’s anndata or mudata extra (Installation); the cells are those of the simulated PBMC study (Datasets).

Cells in an AnnData

An AnnData of cells has the counts in X, or in a layer, and the sample and cell type of every cell in obs. from_cells pools them as they are; the sample table comes from the columns of obs that describe the samples.

CytePool 0.1.dev57+gb65bc7e32, anndata 0.13.4
AnnData object with n_obs × n_vars = 19151 × 2000
    obs: 'sample', 'donor', 'condition', 'cell_type'
    layers: None (.X) 

Cube with 24 samples x 6 groups x 2,000 features
  layers: counts (uint32), detected (uint32), log_sum (float64), log_sum_sq (float64), linear_sum (float64)
  filled: 144 of 144 sample x group entries have cells (100.0%)
  samples: sample_id, donor, condition
  groups: group_id
  features: feature_id

A cube as a long AnnData

to_anndata writes a row for every sample and cell type with cells, the layout that scanpy and decoupler make: obs holds the sample and cell type of each row, the columns of the sample and group tables and the number of cells, and every layer of the cube is a layer of the same name. X is left empty, so that no layer is stored twice; pseudobulk.X = pseudobulk.layers["counts"].copy() gives tools that read X the counts. uns["cytepool"] records the cube’s fingerprint and provenance, and from_anndata reads the AnnData back as the same cube, here after a round trip through an .h5ad file.

AnnData object with n_obs × n_vars = 144 × 2000
    obs: 'sample_id', 'group_id', 'donor', 'condition', 'n_cells'
    uns: 'cytepool'
    layers: 'counts', 'detected', 'log_sum', 'log_sum_sq', 'linear_sum'

the same cube after the .h5ad file: True
Loading...

A cube as a MuData

to_mudata writes a modality for every cell type, whose rows are the samples with cells in it, as LIANA’s multi.adata_to_views makes them, with the columns of the sample table in the MuData’s obs. from_mudata reads it back.

MuData object with n_obs × n_vars = 24 × 12000
  obs:	'donor', 'condition'
  uns:	'cytepool'
  6 modalities
    B cells:	24 × 2000
      obs:	'n_cells'
      layers:	'counts', 'detected', 'log_sum', 'log_sum_sq', 'linear_sum'
    CD4 T cells:	24 × 2000
      obs:	'n_cells'
      layers:	'counts', 'detected', 'log_sum', 'log_sum_sq', 'linear_sum'
    CD8 T cells:	24 × 2000
      obs:	'n_cells'
      layers:	'counts', 'detected', 'log_sum', 'log_sum_sq', 'linear_sum'
    Dendritic cells:	24 × 2000
      obs:	'n_cells'
      layers:	'counts', 'detected', 'log_sum', 'log_sum_sq', 'linear_sum'
    Monocytes:	24 × 2000
      obs:	'n_cells'
      layers:	'counts', 'detected', 'log_sum', 'log_sum_sq', 'linear_sum'
    NK cells:	24 × 2000
      obs:	'n_cells'
      layers:	'counts', 'detected', 'log_sum', 'log_sum_sq', 'linear_sum'

the same cube after the MuData: True

Profiles that scanpy, decoupler or LIANA pooled

from_anndata recognises the layouts of scanpy.get.aggregate and decoupler.pp.pseudobulk, and from_mudata that of LIANA’s views, and reads from each the counts, the number of cells that detect each gene and the number of cells of every profile. Each gives the counts, detecting cells and cell numbers that from_cells computes from the same cells, down to the fingerprint, as the Validation page shows for scanpy 1.12.4, decoupler 2.2.0 and LIANA 1.10.0:

import decoupler as dc
import liana as li
import scanpy as sc

summed = sc.get.aggregate(
    cells, by=["sample", "cell_type"], func=["sum", "count_nonzero"]
)
cube = from_anndata(summed, sample="sample", group="cell_type")

pooled = dc.pp.pseudobulk(cells, sample_col="sample", groups_col="cell_type")
cube = from_anndata(pooled, sample="sample", group="cell_type")

views = li.multi.adata_to_views(
    cells, groupby="cell_type", sample_key="sample", obs_keys=["condition"]
)
n_cells = pd.crosstab(cells.obs["sample"], cells.obs["cell_type"])
cube = from_mudata(views, n_cells=n_cells)

LIANA’s views carry no cell numbers, so they come from the cells, as the crosstab of their samples and cell types. Means, variances and medians are refused: a cube keeps sums, and every tool that gives means gives sums too, with func="sum" in scanpy and mode="sum", its default, in decoupler.

Choices

  • From cells or from profiles. from_cells on the cells computes every layer of a cube, the sums of normalized values included, and needs no other tool; an import of the profiles that another tool pooled keeps that tool’s choices of cells and samples.

  • Import before filtering. decoupler’s pp.filter_samples, which LIANA’s views apply when given filter_samples_kwargs, drops profiles, and a profile that is missing looks like a sample without cells. Import every profile, and leave out those with few cells in the analysis (min_cells of limma_voom).

  • Other layouts. An AnnData of profiles that no tool above made needs sample=, group= and layers=, such as layers={"counts": "X"}, and n_cells=, the name of a column of obs or a crosstab of the cells.

  • Files. .h5ad, .zarr and .h5mu files keep the fingerprint through a round trip; a cube file (Save, share and trace cubes) also reads one cell type at a time.

References
  1. Virshup, I., Rybakov, S., Theis, F. J., Angerer, P., & Wolf, F. A. (2024). anndata: access and store annotated data matrices. Journal of Open Source Software, 9(101), 4371. 10.21105/joss.04371
  2. Wolf, F. A., Angerer, P., & Theis, F. J. (2018). SCANPY: large-scale single-cell gene expression data analysis. Genome Biology, 19(1), 15. 10.1186/s13059-017-1382-0
  3. Badia-i Mompel, P., Vélez Santiago, J., Braunger, J., Geiss, C., Dimitrov, D., Müller-Dott, S., Taus, P., Dugourd, A., Holland, C. H., Ramirez Flores, R. O., & Saez-Rodriguez, J. (2022). decoupleR: ensemble of computational methods to infer biological activities from omics data. Bioinformatics Advances, 2(1), vbac016. 10.1093/bioadv/vbac016
  4. Bredikhin, D., Kats, I., & Stegle, O. (2022). MUON: multimodal omics analysis framework. Genome Biology, 23(1), 42. 10.1186/s13059-021-02577-8
  5. Dimitrov, D., Schäfer, P. S. L., Farr, E., Rodriguez-Mier, P., Lobentanzer, S., Badia-i Mompel, P., Dugourd, A., Tanevski, J., Ramirez Flores, R. O., & Saez-Rodriguez, J. (2024). LIANA+ provides an all-in-one framework for cell-cell communication inference. Nature Cell Biology, 26(9), 1613–1622. 10.1038/s41556-024-01469-w