| name | anndata |
| description | Data structure for annotated matrices in single-cell analysis; use when reading/writing .h5ad (or zarr) and exchanging data with the scverse ecosystem. |
| license | MIT |
| author | AIPOCH |
Source: https://github.com/aipoch/medical-research-skills
When to Use
Use AnnData when you need to:
- Load, inspect, or export annotated single-cell datasets stored as
.h5ad (or zarr) for downstream tools.
- Keep a matrix (cells × features) tightly coupled with observation/feature metadata (e.g., cell types, batches, gene annotations).
- Work efficiently with large, sparse count matrices (e.g., scRNA-seq) and avoid loading everything into memory (backed mode).
- Combine multiple experiments/batches/modalities into a unified object while tracking provenance.
- Subset/filter/transform data while preserving alignment between the matrix and metadata.
Key Features
- Unified container:
X (data matrix) plus aligned annotations: obs, var, uns, and multi-dimensional slots (obsm, varm, obsp, varp), plus layers and optional raw.
- Interoperable I/O: Native
.h5ad and zarr, plus common genomics formats (e.g., 10x, loom, mtx, csv).
- Scalable workflows: Sparse matrices and backed mode (
backed="r") for large datasets.
- Safe subsetting: Slicing preserves alignment across matrix and annotations; supports views vs copies.
- Concatenation utilities:
ad.concat(...) with join/merge strategies and batch labeling; experimental lazy collections.
Reference notes: the original material mentions additional guides under references/ (e.g., references/data_structure.md, references/io_operations.md, references/concatenation.md, references/manipulation.md, references/best_practices.md) for deeper explanations of each topic.
Dependencies
anndata (latest compatible with your environment; install via pip/uv)
numpy
pandas
scipy (recommended for sparse matrices)
- Optional ecosystem tools (only if needed):
scanpy
muon
torch (for deep learning) and anndata experimental loader utilities
Example Usage
A complete runnable example that creates an AnnData object, writes/reads .h5ad, subsets, concatenates batches, and demonstrates backed mode.
import numpy as np
import pandas as pd
import anndata as ad
from scipy.sparse import csr_matrix
rng = np.random.default_rng(0)
n_cells, n_genes = 100, 500
X = rng.poisson(1.0, size=(n_cells, n_genes)).astype(np.float32)
obs = pd.DataFrame(
{
"cell_type": (["T cell", "B cell"] * (n_cells // 2)),
"sample": (["A", "B"] * (n_cells // 2)),
"quality_score": rng.random(n_cells),
},
index=[f"cell_{i}" for i in range(n_cells)],
)
var = pd.DataFrame(
{"gene_name": [f"Gene_{j}" for j in range(n_genes)]},
index=[f"ENSG{j:05d}" for j in range(n_genes)],
)
adata = ad.AnnData(X=X, obs=obs, var=var)
adata.X = csr_matrix(adata.X)
adata.strings_to_categoricals()
print(f"Created: {adata.n_obs} obs × {adata.n_vars} vars")
adata.write_h5ad(, compression=)
adata2 = ad.read_h5ad()
()
t_cells = adata2[adata2.obs[] == , :]
high_quality = adata2[adata2.obs[] > , :]
()
()
adata_a = adata2[adata2.obs[] == , :].copy()
adata_b = adata2[adata2.obs[] == , :].copy()
combined = ad.concat(
[adata_a, adata_b],
axis=,
join=,
label=,
keys=[, ],
)
(combined.obs[].value_counts().to_dict())
adata_backed = ad.read_h5ad(, backed=)
subset_mem = adata_backed[:, :].to_memory()
()
Implementation Details
Data model (core slots)
X: primary data matrix (dense numpy.ndarray or sparse scipy.sparse), shape (n_obs, n_vars).
obs: per-observation metadata (pandas.DataFrame), indexed by obs_names (e.g., cell IDs).
var: per-variable metadata (pandas.DataFrame), indexed by var_names (e.g., gene IDs).
layers: named alternative matrices aligned to X (e.g., "counts", "log1p").
obsm / varm: multi-dimensional embeddings aligned to obs/var (e.g., PCA, UMAP coordinates).
obsp / varp: pairwise graphs/matrices (e.g., kNN graph in obsp["connectivities"]).
uns: unstructured metadata (dict-like), often used for parameters and plotting configs.
raw (optional): snapshot of unfiltered/untransformed data for reproducibility.
Views vs copies
- Slicing like
adata_subset = adata[mask, :] typically returns a view (lightweight reference).
- Use
.copy() when you need an independent object (e.g., before in-place modifications).
Backed mode (large datasets)
ad.read_h5ad(path, backed="r") keeps the matrix on disk and loads data lazily.
- Convert a slice to memory with
.to_memory() when you need in-memory computation.
- Backed mode is best for filtering by metadata, chunked processing, and avoiding OOM.
Concatenation behavior
ad.concat([...], axis=0) stacks observations; axis=1 stacks variables.
join="inner" keeps intersection of variables; join="outer" unions variables (may introduce missing values).
label + keys records dataset/batch provenance in .obs[label].
- Merge strategies control how conflicting
.uns and annotation columns are handled (choose based on your data governance needs).
Practical performance parameters
- Prefer sparse matrices (
csr_matrix) for count-like data.
- Convert repeated strings to categoricals (
adata.strings_to_categoricals()).
- Use compression when writing
.h5ad (e.g., compression="gzip") to reduce storage; consider zarr for chunked/cloud-friendly access.