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.

Read metadata and select cells

CyteArc keeps counts, metadata, graphs, and saved analysis results in a Zarr store. It reads count blocks as needed, so working with a datastore does not require loading the complete expression matrix into memory. Cell and feature metadata describe the rows and columns of that matrix.

This page starts with metadata queries and cell selections, then shows where counts and saved results live. The examples require CyteArc’s extra optional dependencies. Low-level layout details for contributors live in Zarr internals.

Open a prepared pancreas dataset

DataStore is the main entry point. Each assay owns feature metadata, normalization, and feature selection. Cell-level columns are shared across assays. The Boolean cell column I marks the active cells used by live-metadata methods. Saved runs keep their own frozen cell selection. Every assay spans all cells of the store. When an assay measured only some of them, as after a merge of sources with different assays, the Boolean column <assay>_I, such as ADT_I, marks the cells it measured; the other cells hold zero counts for that assay. Without that column, the assay measured every cell. Those zero counts are no measurement, so an operation that reads an assay’s values, such as normalization or marker search, refuses its unmeasured cells: select the measured ones first with ds.select_measured_cells("ADT", cell_selection=...). Plots show the values of unmeasured cells as missing.

The default assay supplies method defaults when from_assay is omitted. It does not merge assay-specific feature tables or results.

Started: 2026-10-11T18:54:45.876865Z | Wall time: 0.954 s

This page uses the pre-analyzed Bastidas-Ponce pancreas store also used in Core plotting features and Score cell-cycle activity. It contains a completed pipeline run named docs_default. Opening this run gives us its saved cell selection, computed clusters, and UMAP without running the analysis again.

Started: 2026-10-11T18:54:46.833999Z | Wall time: 3.206 s

Open the downloaded store and its saved analysis.

DataStore has 3696 (3696) cells with 1 assays: RNA Cell metadata: 'I', 'ids', 'names', 'G2M_score', 'RNA_UMAP1', 'RNA_UMAP2', 'RNA_clusters', 'RNA_leiden_cluster', 'RNA_nCounts', 'RNA_nFeatures', 'RNA_percentMito', 'RNA_percentRibo', 'S_score', 'clusters', 'clusters_coarse' RNA assay has 27998 features and following metadata: 'I', 'ids', 'names', 'dropOuts', 'highly_variable_genes', 'nCells'

Started: 2026-10-11T18:54:50.048795Z | Wall time: 5.533 s

cells
cluster
1378
2546
3493
4215
5223
6340
7205
8145
993
10500
11315
12243

Started: 2026-10-11T18:54:55.585343Z | Wall time: 0.149 s

The selected clustering remains in its exact artifact. The catalog’s literal clusters column is an imported cell-type annotation, not a copy of this analytical result. Opening the run does not modify either one.

Read cell and feature metadata

Cell and feature tables are MetaData objects (ds.cells, ds.RNA.feats), not pandas DataFrames. Use head for a quick look, to_pandas_dataframe to export selected columns, and fetch / fetch_all for single columns.

idsIclusters
0AAACCTGAGAGGGATATruePre-endocrine
1AAACCTGAGCCTTGATTrueDuctal
2AAACCTGAGGCAATTATrueAlpha
3AAACCTGCATCATCCCTrueDuctal
4AAACCTGGTAAGTGGCTrueNgn3 high EP

Started: 2026-10-11T18:54:55.737534Z | Wall time: 0.150 s

IidsnamesdropOutshighly_variable_genesnCells
0TrueXkr4Xkr43690False6
1TrueGm37381Gm373813696NaN0
2TrueRp1Rp13696NaN0
3TrueRp1-1Rp1-13696NaN0
4TrueSox17Sox173696NaN0

Started: 2026-10-11T18:54:55.893672Z | Wall time: 0.085 s

RNA_nCountsRNA_nFeaturesclusters
ids
AAACCTGAGAGGGATA4954.02356.0Pre-endocrine
AAACCTGAGCCTTGAT7071.02613.0Ductal
AAACCTGAGGCAATTA4070.01984.0Alpha
AAACCTGCATCATCCC8362.02860.0Ductal
AAACCTGGTAAGTGGC5026.02211.0Ngn3 high EP

Started: 2026-10-11T18:54:55.981735Z | Wall time: 0.068 s

insert writes a new column and aligns values to the active subset unless you override key. When the values cover only the rows that key selects, the other rows are missing: they are flagged in the column’s missing mask, to_pandas_dataframe and plots show them as missing, and sift never selects them. Pass fill_value to store a real value in those rows instead; it must fit the values, such as False for a Boolean column. None, NaN, and pd.NA among the values are missing too. Re-inserting an existing column requires overwrite=True, and a failed replacement keeps the previous column. In this prepared store, every cell is active and the saved run contains the same cells, so its cluster labels align with the values we insert. For another analysis, check the cell selection before copying results into live metadata.

False 3318 True 378 Name: cells, dtype: int64

Started: 2026-10-11T18:54:56.051868Z | Wall time: 0.028 s

idsclustersis_first_cluster
0AAACCTGAGAGGGATAPre-endocrineTrue
1AAACCTGAGCCTTGATDuctalFalse
2AAACCTGAGGCAATTAAlphaFalse
3AAACCTGCATCATCCCDuctalFalse
4AAACCTGGTAAGTGGCNgn3 high EPFalse

Started: 2026-10-11T18:54:56.082460Z | Wall time: 0.054 s

Mouse pancreas UMAP colored by a Boolean cell-metadata column. Orange marks the selected first cluster along the upper-left branch; blue marks other cells.

Started: 2026-10-11T18:54:56.140178Z | Wall time: 1.619 s

The new column marks one cluster on the same active cells used for the insert.

fetch returns values for the active subset (default column I). fetch_all returns every row in the store. With every cell active the lengths match. Pass the new Boolean column as key to select one cluster without changing I:

fetch: (378,) fetch_all: (3696,)

Started: 2026-10-11T18:54:57.762503Z | Wall time: 0.024 s

Select cells from metadata

sift returns a boolean mask over all metadata rows for one numeric range. Its bounds are exclusive by default: the next call selects cells with more than 1,000 and fewer than 15,000 counts. Pass keep_bounds=True to include the endpoints.

multi_sift keeps rows that satisfy every range, with one lower and one upper bound per column. get_index_by returns row indexes for exact categorical matches:

{'cells in count range': 3669}

Started: 2026-10-11T18:54:57.789189Z | Wall time: 0.015 s

Combine count and detected-gene limits.

{'cells in both QC ranges': 3667}

Started: 2026-10-11T18:54:57.806441Z | Wall time: 0.016 s

Find rows by their published annotation.

Ductal cells: 916
Active cells (I) before: 3696
Active cells (I) after: 3696

Started: 2026-10-11T18:54:57.825516Z | Wall time: 0.024 s

Mouse pancreas UMAP colored by whether each cell falls in the selected count range. Most cells are orange, with a small number of blue excluded cells.

Started: 2026-10-11T18:54:57.851838Z | Wall time: 0.461 s

These helpers return masks or indexes aligned with the metadata table. They do not modify I until you explicitly insert or update a cell key. See the MetaData API in Assays and metadata API reference for update and delete helpers.

Optional: inspect counts and saved results

The metadata examples above are enough to read annotations and select cells. Continue here when you need to inspect the underlying arrays or understand where analysis results are stored.

Count matrices and normalization

Raw counts are a Zarr array (often sharded), exposed as rawData, a chunked array with a NumPy-like interface that streams by row. In this store the array is at RNA/counts. RNA assays also store countsT, a gene-major copy used by HVG and marker stages. Routine analysis does not need to touch either array directly.

Normalized values are computed on demand through the lower-level assay normed() view from raw counts. features.normalize(cell_selection, features) is the public persisted path and requires exact stored cell- and feature-selection references. The direct normed() view follows its explicit or literal metadata indexes; in a newly created store the physical feature I column is all true. Inspect the shapes without loading the complete matrix:

Raw shape: (3696, 27998)
Normed shape: (3696, 27998)

Started: 2026-10-11T18:54:58.319524Z | Wall time: 0.034 s

For a worked example that reads count blocks for an external calculation, see Extending CyteArc with custom analyses.

Inspect saved analysis results

Analysis methods return lightweight references to results stored in Zarr. The completed docs_default run retained the HVG and normalization references it created. Asking for the same normalization again reuses that result rather than recomputing:

Reused: True
ArtifactRef(assay='RNA', kind='normalized', artifact_id='9fb6d37118da...')

Started: 2026-10-11T18:54:58.355654Z | Wall time: 0.148 s

Inspect its status and open the underlying group only when a custom method needs direct access:

Complete: True
Operation: run_normalization
Method: norm_lib_size
Arrays: ['data', 'feature_m2', 'feature_sum']
value
setting
size_factor1000.0
log_transformTrue
renormalize_subsetTrue

Started: 2026-10-11T18:54:58.506628Z | Wall time: 0.025 s

Identical inputs and parameters reuse a complete result. Branching, invalidation, and lineage are covered in Reuse results and compare analysis settings.

Look inside the store

CyteArc uses Zarr for chunked on-disk arrays. The store is a directory tree: counts, cell and feature attributes, and immutable artifacts live under named groups. Each array has its own shape, dtype, and chunk layout. CyteArc reads and writes those chunks without loading every array in the store.

show_zarr_tree prints the hierarchy. With depth=0 you see the top-level assays and cellData.

/
├── RNA
├── artifacts
├── cellData
└── pipeline

Started: 2026-10-11T18:54:58.534248Z | Wall time: 0.016 s

Cell statistics computed from an assay are stored under cellData with the assay name as a prefix (RNA_…, ADT_…).

shapedtypechunks
cellData column
I(3696,)bool(100000,)
RNA_nCounts(3696,)float64(100000,)
RNA_nFeatures(3696,)float64(100000,)

Started: 2026-10-11T18:54:58.552642Z | Wall time: 0.020 s

The I column is the default user-owned cell key, tracking which cells are active for live-metadata APIs. Values are boolean. Use snapshot_cell_selection("I") to capture this live column before passing it to an analytical producer. Some metadata, mapping, and export utilities still accept cell_key directly.

I True 3696 Name: count, dtype: int64

Started: 2026-10-11T18:54:58.575712Z | Wall time: 0.026 s

This store keeps every barcode active (True). Analytical filtering returns a separate immutable selection artifact and leaves this column unchanged. If you deliberately author a live selection column, its False rows also remain in the table rather than being deleted.

Each assay group holds featureData and its persisted artifacts. Count matrices are Zarr arrays, often sharded. This store keeps RNA counts at RNA/counts.

/RNA
├── artifacts
│   ├── ann_index
│   ├── cluster_labels
│   ├── cluster_selection
│   ├── connectivity_map
│   ├── embedding
│   ├── embedding_initialization
│   ├── feature_scaling
│   ├── feature_selection
│   ├── feature_summary
│   ├── marker_table
│   ├── metadata_snapshot
│   ├── neighbors
│   ├── normalized
│   └── reduction
├── countSummaries
│   ├── columnPositive (27998,) int64
│   ├── featureSums
│   ├── rowPositive (3696,) int64
│   └── rowSums (3696,) float64
├── counts (3696, 27998) uint16
├── countsT (27998, 3696) uint16
└── featureData
    ├── I (27998,) bool
    ├── __cytearc_missing__highly_variable_genes (27998,) bool
    ├── dropOuts (27998,) int64
    ├── highly_variable_genes (27998,) <U5
    ├── ids (27998,) <U15
    ├── nCells (27998,) int64
    └── names (27998,) <U15

  countsT: shape=(27998, 3696), dtype=uint16, chunks=(27056, 1848), shards=(27056, 3696)
  counts: shape=(3696, 27998), dtype=uint16, chunks=(3696, 13528), shards=(3696, 40584)

Started: 2026-10-11T18:54:58.605415Z | Wall time: 0.037 s

For feature arrays, pass start="RNA/featureData" to the same method.

Each persisted result is an artifact. Assay-scoped results live under {assay}/artifacts/{kind}/{artifact_id}; datastore-scoped selections and integrated results live under artifacts/{kind}/{artifact_id}. The kind names the operation family and the identifier is random; CyteArc recognises an equivalent result by its recorded operation, revision, parameters, and inputs instead of recomputing it. Nothing here encodes parameters in the path, so a second PCA at different dimensionality becomes a sibling entry rather than a new branch of the tree.

To inspect saved result groups, use ds.show_zarr_tree(start="RNA/artifacts", depth=1).

Zarr internals covers the complete on-disk layout.

Zarr versions and storage profiles

Current CyteArc versions write new datasets as Zarr v3. RNA assays also write a gene-major countsT copy next to counts. The catalog dataset used here was rebuilt from its raw source with this codebase, so it already has the current layout. Re-import older RNA stores that use Zarr v2 or lack countsT before analysis.

Count matrices from the writers use sharded arrays (default profile fast_local). Set the profile with CYTEARC_ZARR_PROFILE (fast_local or cloud) or zarrProfile= when opening a DataStore.

Storage profiles and conversion belong to the physical store, while mem_budget and nthreads control execution. See Memory and execution for why RNA stores two orientations, and Explore a remote dataset for object storage and local scratch.

Common mistakes and limitations

  • Treating on-disk objects as in-memory ones: MetaData is not a pandas DataFrame; fetch returns rows selected by I by default, while fetch_all returns every row, and an analysis result is a stored artifact, not a matrix to index directly.

  • Expecting filtering to delete cells or edit saved results: filtering never deletes rows or rewrites live I, and saved artifacts are immutable products of analysis methods, not groups to edit by hand.

  • Opening an older RNA store (Zarr v2, or without the countsT copy) in a current CyteArc version; re-import the store before analysis.

Metadata changes and artifacts are written into the Zarr store. Low-level layout details intended for contributors remain in Zarr internals.