Command reference
Every command takes a .h5ad file or a .zarr directory; the backend is
detected from the path. Results go to stdout and status to stderr, so
output pipes cleanly:
adata export dataframe data.h5ad obs 2>/dev/null | head
adata ls data.h5ad -1 | grep spatial
Run adata <command> --help for the authoritative flag list.
Table of contents
view
AnnData-aware inspection.
adata view data.h5ad # n_obs x n_var and the top-level keys
adata view data.h5ad --tree # a tree with each element's type
adata view data.h5ad --tree --depth 3
adata view data.h5ad obsm/X_pca # detail for one entry
| Flag | Meaning |
|---|---|
--tree, -t |
Show a tree of all entries |
--depth N, -d |
Maximum recursion depth (with --tree) |
info is a deprecated alias, removed in 1.0.0.
ls
Format-agnostic listing. Makes no AnnData assumptions, so it also works on
.loom and plain .h5 files.
adata ls data.h5ad
adata ls data.h5ad --long # type, shape, dtype, encoding
adata ls data.h5ad uns --depth 2 # only below a path
adata ls data.h5ad -1 | grep spatial # bare paths, for piping
subset
Write a filtered copy. Select by name list, by expression, or both axes at once.
adata subset data.h5ad -o out.h5ad --obs barcodes.txt
adata subset data.h5ad -o out.h5ad --obs-query "cluster == Cortex_2"
adata subset data.h5ad -o out.h5ad -q "n_counts > 1000 and cluster in A,B"
adata subset data.h5ad -o out.h5ad --var-query "highly_variable == True"
adata subset data.h5ad --inplace --obs barcodes.txt
| Flag | Meaning |
|---|---|
--output, -o |
Output path. Required unless --inplace |
--inplace |
Replace the source (written to a temporary path first) |
--obs / --var |
File of names to keep, one per line |
--obs-query, -q / --var-query |
Keep rows matching an expression |
--chunk, -C |
Row chunk size for dense matrices |
--zarr-format |
Zarr version to write; defaults to the source’s |
raw/ is carried over and matched against its own var axis, which usually
holds more genes than the main object.
split
One store per distinct value of a column.
adata split data.h5ad --by sample -o per_sample/
adata split data.h5ad --by cell_type -o clusters/ --dry-run
adata split data.h5ad --by cluster -o out/ --min-size 50
| Flag | Meaning |
|---|---|
--by, -b |
Column to split on |
--output-dir, -o |
Where to write the stores |
--axis |
obs (default) or var |
--min-size |
Skip groups smaller than this |
--dry-run |
Print the plan without writing |
--manifest / --no-manifest |
Write a CSV listing the outputs (default on) |
Labels are sanitised for use as filenames; collisions get a numeric suffix.
concat
Concatenate along the obs axis, streaming each input in turn.
adata concat a.h5ad b.h5ad -o merged.h5ad
adata concat *.h5ad -o merged.h5ad --join outer --label sample
adata concat a.h5ad b.h5ad -o m.h5ad --keys a,b --index-unique - --uns-merge same
| Flag | Meaning |
|---|---|
--join, -j |
inner (shared vars only) or outer (their union) |
--label |
Add an obs column recording each cell’s source |
--keys |
Names for the inputs; defaults to their filenames |
--index-unique |
Delimiter for suffixing obs names with their key |
--merge / --uns-merge |
same, unique, first, only; default drops |
--fill-value |
Value for dense cells introduced by an outer join |
obs columns keep their dtypes: categoricals union their category sets, nullable columns keep their masks, and a column missing from one input is padded rather than dropped.
obsp, varp and raw are not carried over — a pairwise value has no
meaning between cells from different inputs. anndata’s own concat_on_disk
refuses these too.
Merge strategies
| Strategy | Keeps a value when |
|---|---|
| (unset) | never — the element is dropped |
same |
every input has it and they all agree |
unique |
exactly one distinct value among the inputs that have it |
first |
always; takes the earliest input that has it |
only |
exactly one input has it at all |
create
Write a new, empty store for import to fill in.
adata create out.h5ad --n-obs 5000 --n-var 2000
adata create out.zarr --obs-names cells.txt --var-names genes.txt
Give each axis either a size (names are generated as cell_0000, gene_0000)
or a file of names. --force overwrites an existing store.
export
| Subcommand | Produces | Accepts |
|---|---|---|
dataframe |
CSV | any dataframe path (obs, var, raw/var, …) |
array |
.npy |
any dense array |
sparse |
.mtx |
CSR/CSC groups |
dict |
JSON | any group or scalar |
image |
PNG/JPEG/TIFF | 2D or 3D arrays |
adata export dataframe data.h5ad obs -o obs.csv
adata export dataframe data.h5ad obs --columns cluster,n_counts --head 100
adata export sparse data.h5ad X -o matrix.mtx
adata export array data.h5ad obsm/X_umap > umap.npy
adata export dict data.h5ad uns -o metadata.json
Omit --output to write to stdout. Writing to a file streams in chunks;
stdout is not seekable, so export array holds the array in memory on that
path.
import
Write data into a store at any path. All subcommands need --output/-o or
--inplace.
adata import dataframe data.h5ad obs cells.csv --inplace -i cell_id
adata import array data.h5ad obsm/X_umap umap.npy --inplace
adata import sparse data.h5ad X counts.mtx --inplace
adata import dict data.h5ad uns/params params.json --inplace
adata import image data.h5ad uns/spatial/hires tissue.png --inplace
Dimensions are validated against the existing obs/var where the path implies an axis.
For dataframe: columns that parse as integers or floats become numeric
arrays; string columns with few distinct values become categoricals. Use
--categorical col1,col2 to force specific columns, or
--no-auto-categorical to keep all strings as string-array.
Giving -o out.zarr for an .h5ad source (or the reverse) converts the store
as it writes.
Query expressions
Used by subset --obs-query and --var-query.
| Operator | Example |
|---|---|
==, != |
cluster == Cortex_2 |
<, <=, >, >= |
n_counts > 1000 |
in, not in |
cluster in A,B,C |
and, or, not |
n_counts > 500 and not cluster == Fiber_tract |
| parentheses | (a == 1 or b == 2) and c > 3 |
Values with spaces need quoting: label == "cell type A". Equality and
membership compare the column’s string form; the ordering operators parse it as
a number, and entries that will not parse simply do not match.
Only the columns a query mentions are read, so filtering on one annotation does not touch the rest of the frame.
This is deliberately not SQL. For anything more involved — joins, aggregates, window functions — export to CSV and use duckdb:
adata export dataframe data.h5ad obs -o cells.csv
duckdb -noheader -list -c \
"SELECT _index FROM 'cells.csv' WHERE cluster='Cortex_2' AND n_counts > 1000" \
> barcodes.txt
adata subset data.h5ad -o cortex.h5ad --obs barcodes.txt
Zarr format selection
New Zarr stores follow the source store’s version, so a v2 input is not
silently upgraded. --zarr-format 2|3 on create, subset, split and
concat overrides that. Writing a Zarr store from an .h5ad source defaults
to v3.
adata create out.zarr --n-obs 100 --n-var 50 --zarr-format 2
adata subset data.zarr -o out.zarr --obs keep.txt --zarr-format 3
adata split data.zarr --by sample -o parts/ --suffix .zarr --zarr-format 3
adata concat a.zarr b.zarr -o merged.zarr --zarr-format 3