Skill Tracer
Skills › Scientific Agent Skills › anndata › io_operations.md

io_operations.md

497 lines · skills/anndata/references/io_operations.md at 5f2bc03

Open on GitHub
1# Input/Output Operations
2
3Reviewed against AnnData 0.13.4. Native H5AD/Zarr and small text/Excel fixtures were exercised; remote stores and 10X biological inputs below are illustrative. Preserve identifiers, matrix units, transformations, and source checksums through conversion.
4
5Since anndata 0.11, most `read_*` and `write_*` functions live in `anndata.io`. Top-level `read_h5ad` and `read_zarr` remain supported; use `anndata.io` for other readers rather than compatibility aliases.
6
7```python
8import anndata as ad
9from anndata.io import read_csv, read_mtx, read_loom, read_elem, write_elem
10```
12Avoid deprecated I/O aliases such as `ad.read`; use `ad.read_h5ad` or `anndata.io.read_h5ad` explicitly.

Input/Output Operations

Reviewed against AnnData 0.13.4. Native H5AD/Zarr and small text/Excel fixtures were exercised; remote stores and 10X biological inputs below are illustrative. Preserve identifiers, matrix units, transformations, and source checksums through conversion.

Since anndata 0.11, most read_* and write_* functions live in anndata.io. Top-level read_h5ad and read_zarr remain supported; use anndata.io for other readers rather than compatibility aliases.

import anndata as ad
from anndata.io import read_csv, read_mtx, read_loom, read_elem, write_elem

Avoid deprecated I/O aliases such as ad.read; use ad.read_h5ad or anndata.io.read_h5ad explicitly.

Raw lines 1 to 13
14## Native Formats

Native Formats

Raw lines 14 to 15
16### H5AD (HDF5-based)
17The recommended native format for AnnData objects, providing efficient storage and fast access.
19#### Writing H5AD files
20```python
21import anndata as ad
23# Write to file
24adata.write_h5ad('data.h5ad')
26# Write with compression
27adata.write_h5ad('data.h5ad', compression='gzip')
29# Write with specific compression level (0-9, higher = more compression)
30adata.write_h5ad('data.h5ad', compression='gzip', compression_opts=9)
31```
33#### Reading H5AD files
34```python
35# Read entire file into memory
36adata = ad.read_h5ad('data.h5ad')
38# Read in backed mode (lazy loading for large files)
39adata = ad.read_h5ad('data.h5ad', backed='r') # Read-only
40adata.file.close()
41# Alternatively (do not leave a read-only handle open on this file):
42adata = ad.read_h5ad('data.h5ad', backed='r+') # In-place dense X updates only
43adata.file.close()
45# Backed mode keeps X on disk; obs/var and named layers can still load into RAM.
46# 0.13 removed backed sparse item assignment, even with r+.
47# Write a new file for obs/var/uns or sparse-X changes.
48```
50#### Backed mode operations
51```python
52# Open in backed mode
53adata = ad.read_h5ad('large_dataset.h5ad', backed='r')
55# Access metadata without loading X into memory
56print(adata.obs.head())
57print(adata.var.head())
59# Subset operations create views
60subset = adata[:100, :500] # View, no data loaded
62# Load specific data into memory
63X_subset = subset.X[:] # Now loads this subset
65# Convert entire backed object to memory
66adata_memory = adata.to_memory()
67adata.file.close()
68```

H5AD (HDF5-based)

The recommended native format for AnnData objects, providing efficient storage and fast access.

Writing H5AD files

import anndata as ad

# Write to file
adata.write_h5ad('data.h5ad')

# Write with compression
adata.write_h5ad('data.h5ad', compression='gzip')

# Write with specific compression level (0-9, higher = more compression)
adata.write_h5ad('data.h5ad', compression='gzip', compression_opts=9)

Reading H5AD files

# Read entire file into memory
adata = ad.read_h5ad('data.h5ad')

# Read in backed mode (lazy loading for large files)
adata = ad.read_h5ad('data.h5ad', backed='r')  # Read-only
adata.file.close()
# Alternatively (do not leave a read-only handle open on this file):
adata = ad.read_h5ad('data.h5ad', backed='r+')  # In-place dense X updates only
adata.file.close()

# Backed mode keeps X on disk; obs/var and named layers can still load into RAM.
# 0.13 removed backed sparse item assignment, even with r+.
# Write a new file for obs/var/uns or sparse-X changes.

Backed mode operations

# Open in backed mode
adata = ad.read_h5ad('large_dataset.h5ad', backed='r')

# Access metadata without loading X into memory
print(adata.obs.head())
print(adata.var.head())

# Subset operations create views
subset = adata[:100, :500]  # View, no data loaded

# Load specific data into memory
X_subset = subset.X[:]  # Now loads this subset

# Convert entire backed object to memory
adata_memory = adata.to_memory()
adata.file.close()
Raw lines 16 to 69
70### Zarr
72`read_zarr` is eager; use experimental `read_lazy` for lazy arrays and annotations.
73AnnData 0.13 requires the Zarr >=3 Python package and writes sharded Zarr format 3
74by default. Format 2 stores can still be read. Chunk shape and shard size have
75different effects; benchmark representative row/column access instead of assuming
76a universal setting.
78#### Writing Zarr
79```python
80# Write to Zarr store
81adata.write_zarr('data.zarr')
83# Write with specific chunks (important for performance)
84adata.write_zarr('data.zarr', chunks=(100, 100))
85```
87#### Reading Zarr
88```python
89# Read Zarr store
90adata = ad.read_zarr('data.zarr')
91```
93#### Zarr v3 defaults (AnnData 0.13.4)
94```python
95adata.write_zarr('data.zarr', chunks=(1000, 1000), consolidate_metadata=True)
96```
98The deprecated `settings.zarr_write_format` is scheduled for removal in 0.14.
99If a downstream reader requires format 2, verify that compatibility separately;
100do not confuse the Zarr package version with the storage format.
102#### Remote Zarr access
103Use the requested dataset location, with its provider credentials and suitable fsspec adapter (`s3fs` for S3, `gcsfs` for GCS). The following placeholder stores are illustrative and were not contacted. These examples use Zarr 3 `FsspecStore`; credentials should come from provider configuration, not source code.
105```python
106from zarr.storage import FsspecStore
107from anndata.experimental import read_lazy
109store = FsspecStore.from_url('s3://bucket-name/data.zarr', read_only=True)
110adata = read_lazy(store) # Verify provider/store support on a small slice first
111```

Zarr

read_zarr is eager; use experimental read_lazy for lazy arrays and annotations. AnnData 0.13 requires the Zarr >=3 Python package and writes sharded Zarr format 3 by default. Format 2 stores can still be read. Chunk shape and shard size have different effects; benchmark representative row/column access instead of assuming a universal setting.

Writing Zarr

# Write to Zarr store
adata.write_zarr('data.zarr')

# Write with specific chunks (important for performance)
adata.write_zarr('data.zarr', chunks=(100, 100))

Reading Zarr

# Read Zarr store
adata = ad.read_zarr('data.zarr')

Zarr v3 defaults (AnnData 0.13.4)

adata.write_zarr('data.zarr', chunks=(1000, 1000), consolidate_metadata=True)

The deprecated settings.zarr_write_format is scheduled for removal in 0.14. If a downstream reader requires format 2, verify that compatibility separately; do not confuse the Zarr package version with the storage format.

Remote Zarr access

Use the requested dataset location, with its provider credentials and suitable fsspec adapter (s3fs for S3, gcsfs for GCS). The following placeholder stores are illustrative and were not contacted. These examples use Zarr 3 FsspecStore; credentials should come from provider configuration, not source code.

from zarr.storage import FsspecStore
from anndata.experimental import read_lazy

store = FsspecStore.from_url('s3://bucket-name/data.zarr', read_only=True)
adata = read_lazy(store)  # Verify provider/store support on a small slice first
Raw lines 70 to 112
113## Alternative Input Formats

Alternative Input Formats

Raw lines 113 to 114
115### CSV/TSV
116```python
117from anndata.io import read_csv
119# Read CSV (genes as columns, cells as rows)
120adata = read_csv('data.csv')
122# Read with custom delimiter
123adata = read_csv('data.tsv', delimiter='\t')
125# Specify that first column is row names
126adata = read_csv('data.csv', first_column_names=True)
127```

CSV/TSV

from anndata.io import read_csv

# Read CSV (genes as columns, cells as rows)
adata = read_csv('data.csv')

# Read with custom delimiter
adata = read_csv('data.tsv', delimiter='\t')

# Specify that first column is row names
adata = read_csv('data.csv', first_column_names=True)
Raw lines 115 to 128
129### Excel
131Requires `openpyxl` for `.xlsx`; first column supplies row names and first row supplies feature names. Validate numeric matrix dtype after importing.
132```python
133from anndata.io import read_excel
135# Read Excel file
136adata = read_excel('data.xlsx', sheet=0) # sheet is required
138# Read specific sheet
139adata = read_excel('data.xlsx', sheet='Sheet1')
140```

Excel

Requires openpyxl for .xlsx; first column supplies row names and first row supplies feature names. Validate numeric matrix dtype after importing.

from anndata.io import read_excel

# Read Excel file
adata = read_excel('data.xlsx', sheet=0)  # sheet is required

# Read specific sheet
adata = read_excel('data.xlsx', sheet='Sheet1')
Raw lines 129 to 141
142### Matrix Market (MTX)
143Common format for sparse matrices in genomics.
145```python
146from anndata.io import read_mtx
148import pandas as pd
150# read_mtx reads ONLY the matrix; it has no obs_names/var_names arguments.
151# For this explicit feature-by-cell input, transpose to cells by features.
152adata = read_mtx('matrix.mtx').T
153features = pd.read_csv('features.tsv', sep='\t', header=None, dtype=str)
154barcodes = pd.read_csv('barcodes.tsv', sep='\t', header=None, dtype=str)
155assert adata.shape == (len(barcodes), len(features))
156adata.obs_names = barcodes[0].to_numpy()
157adata.var_names = features[0].to_numpy() # Stable IDs; keep symbols separately
158adata.var['gene_symbol'] = features[1].to_numpy()
159assert adata.obs_names.is_unique and adata.var_names.is_unique
160```

Matrix Market (MTX)

Common format for sparse matrices in genomics.

from anndata.io import read_mtx

import pandas as pd

# read_mtx reads ONLY the matrix; it has no obs_names/var_names arguments.
# For this explicit feature-by-cell input, transpose to cells by features.
adata = read_mtx('matrix.mtx').T
features = pd.read_csv('features.tsv', sep='\t', header=None, dtype=str)
barcodes = pd.read_csv('barcodes.tsv', sep='\t', header=None, dtype=str)
assert adata.shape == (len(barcodes), len(features))
adata.obs_names = barcodes[0].to_numpy()
adata.var_names = features[0].to_numpy()  # Stable IDs; keep symbols separately
adata.var['gene_symbol'] = features[1].to_numpy()
assert adata.obs_names.is_unique and adata.var_names.is_unique
Raw lines 142 to 161
162### 10X Genomics formats
16310X readers are provided by **scanpy**, not anndata. After loading, the result is a standard `AnnData` object.
165```python
166import scanpy as sc
168# Read 10X h5 format
169adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
171# Read 10X MTX directory
172adata = sc.read_10x_mtx('filtered_feature_bc_matrix/')
174# Specify genome if multiple present
175adata = sc.read_10x_h5('data.h5', genome='GRCh38')
176```

10X Genomics formats

10X readers are provided by scanpy, not anndata. After loading, the result is a standard AnnData object.

import scanpy as sc

# Read 10X h5 format
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')

# Read 10X MTX directory
adata = sc.read_10x_mtx('filtered_feature_bc_matrix/')

# Specify genome if multiple present
adata = sc.read_10x_h5('data.h5', genome='GRCh38')
Raw lines 162 to 177
178### Loom input
180Requires `loompy`; `read_loom` is deprecated in 0.13 (legacy import still passed
181a synthetic test with loompy 3.0.8). Use the actual source layer and annotation names; defaults
182`CellID`/`Gene` do not match every Loom file. The format is lossy relative to
183AnnData (for example, it is not a full `uns`/`raw` archive).
185```python
186from anndata.io import read_loom
188# Read Loom file
189adata = read_loom('data.loom')
191# Read with specific observation and variable annotations
192adata = read_loom(
193 'data.loom',
194 obs_names='CellID',
195 var_names='Gene'
197```

Loom input

Requires loompy; read_loom is deprecated in 0.13 (legacy import still passed a synthetic test with loompy 3.0.8). Use the actual source layer and annotation names; defaults CellID/Gene do not match every Loom file. The format is lossy relative to AnnData (for example, it is not a full uns/raw archive).

from anndata.io import read_loom

# Read Loom file
adata = read_loom('data.loom')

# Read with specific observation and variable annotations
adata = read_loom(
    'data.loom',
    obs_names='CellID',
    var_names='Gene'
)
Raw lines 178 to 198
199### Text files
200```python
201from anndata.io import read_text
203# Read generic text file
204adata = read_text('data.txt', delimiter='\t')
206# Read with custom parameters
207adata = read_text(
208 'data.txt',
209 delimiter=',',
210 first_column_names=True,
211 dtype='float32'
213```

Text files

from anndata.io import read_text

# Read generic text file
adata = read_text('data.txt', delimiter='\t')

# Read with custom parameters
adata = read_text(
    'data.txt',
    delimiter=',',
    first_column_names=True,
    dtype='float32'
)
Raw lines 199 to 214
215### UMI tools
216```python
217from anndata.io import read_umi_tools
219# Read UMI tools format
220adata = read_umi_tools('counts.tsv.gz') # Long table: gene, cell, count columns
221```

UMI tools

from anndata.io import read_umi_tools

# Read UMI tools format
adata = read_umi_tools('counts.tsv.gz')  # Long table: gene, cell, count columns
Raw lines 215 to 222
223### HDF5 (generic)
224```python
225from anndata.io import read_hdf
227# Read from HDF5 file (not h5ad format)
228adata = read_hdf('data.h5', key='dataset')
229```

HDF5 (generic)

from anndata.io import read_hdf

# Read from HDF5 file (not h5ad format)
adata = read_hdf('data.h5', key='dataset')
Raw lines 223 to 230
231## Alternative Output Formats

Alternative Output Formats

Raw lines 231 to 232
233### CSV
234```python
235# Explicitly include X; skip_data=True is the default. Sparse X is densified.
236adata.write_csvs('output_dir/', skip_data=False)
238# This creates:
239# - output_dir/X.csv (expression matrix)
240# - output_dir/obs.csv (observation annotations)
241# - output_dir/var.csv (variable annotations)
242# - output_dir/obsm.csv and varm.csv
243# - output_dir/uns/<key>.csv (supported unstructured annotations only)
244# This export cannot reconstruct the complete AnnData object.
246# Skip certain components
247adata.write_csvs('output_dir/', skip_data=True) # Skip X matrix
248```

CSV

# Explicitly include X; skip_data=True is the default. Sparse X is densified.
adata.write_csvs('output_dir/', skip_data=False)

# This creates:
# - output_dir/X.csv (expression matrix)
# - output_dir/obs.csv (observation annotations)
# - output_dir/var.csv (variable annotations)
# - output_dir/obsm.csv and varm.csv
# - output_dir/uns/<key>.csv (supported unstructured annotations only)
# This export cannot reconstruct the complete AnnData object.

# Skip certain components
adata.write_csvs('output_dir/', skip_data=True)  # Skip X matrix
Raw lines 233 to 249
250### Loom output
252`write_loom` is deprecated in AnnData 0.13. With AnnData 0.13.4 and loompy 3.0.8,
253a synthetic export also failed because the writer passes the `layers[None]` key
254to loompy (`AttributeError: 'NoneType' object has no attribute 'startswith'`).
255Use `write_h5ad`/`write_zarr` for the complete object; legacy Loom conversion needs
256an independently tested compatible environment and a field-by-field audit.
257Do not delete `layers[None]` to bypass this error: it deletes X.

Loom output

write_loom is deprecated in AnnData 0.13. With AnnData 0.13.4 and loompy 3.0.8, a synthetic export also failed because the writer passes the layers[None] key to loompy (AttributeError: 'NoneType' object has no attribute 'startswith'). Use write_h5ad/write_zarr for the complete object; legacy Loom conversion needs an independently tested compatible environment and a field-by-field audit. Do not delete layers[None] to bypass this error: it deletes X.

Raw lines 250 to 258
259## Reading Specific Elements
261For fine-grained control, read specific elements from an open store:
263```python
264import h5py
265from anndata.io import read_elem
267# Read just observation annotations from an h5ad file
268with h5py.File('data.h5ad', 'r') as f:
269 obs = read_elem(f['obs'])
270 layer = read_elem(f['layers/normalized'])
271 params = read_elem(f['uns/pca'])
272```

Reading Specific Elements

For fine-grained control, read specific elements from an open store:

import h5py
from anndata.io import read_elem

# Read just observation annotations from an h5ad file
with h5py.File('data.h5ad', 'r') as f:
    obs = read_elem(f['obs'])
    layer = read_elem(f['layers/normalized'])
    params = read_elem(f['uns/pca'])
Raw lines 259 to 273
274## Writing Specific Elements
276```python
277from anndata.io import write_elem
278import h5py
280# Work on a copy of the native file. A layer belongs under /layers, not the root.
281# Match both axes and use an unused string key without '/'.
282with h5py.File('data.h5ad', 'r+') as f:
283 layers = f['layers']
284 assert 'new_layer' not in layers
285 write_elem(layers, 'new_layer', adata.X.copy())
286reopened = ad.read_h5ad('data.h5ad')
287assert reopened.layers['new_layer'].shape == reopened.shape
288```

Writing Specific Elements

from anndata.io import write_elem
import h5py

# Work on a copy of the native file. A layer belongs under /layers, not the root.
# Match both axes and use an unused string key without '/'.
with h5py.File('data.h5ad', 'r+') as f:
    layers = f['layers']
    assert 'new_layer' not in layers
    write_elem(layers, 'new_layer', adata.X.copy())
reopened = ad.read_h5ad('data.h5ad')
assert reopened.layers['new_layer'].shape == reopened.shape
Raw lines 274 to 289
290## Lazy Operations
292For very large datasets, use lazy reading to avoid loading entire datasets. `read_lazy` is experimental and is designed for on-disk or in-cloud AnnData stores, including lazy `obs` and `var` access.
294```python
295from anndata.experimental import read_lazy
297adata = read_lazy('large_data.zarr')
298print(adata.obs.iloc[:5].to_memory()) # Dataset2D is not a pandas DataFrame
299```
301For element-level control, use `read_elem_lazy` on an open store:
303```python
304import h5py
305from anndata.experimental import read_elem_lazy
307# Lazy read from an open store (returns dask-backed array)
308with h5py.File('large_data.h5ad', 'r') as f:
309 X_lazy = read_elem_lazy(f['X'])
310 subset = X_lazy[:100, :100].compute()
311```

Lazy Operations

For very large datasets, use lazy reading to avoid loading entire datasets. read_lazy is experimental and is designed for on-disk or in-cloud AnnData stores, including lazy obs and var access.

from anndata.experimental import read_lazy

adata = read_lazy('large_data.zarr')
print(adata.obs.iloc[:5].to_memory())  # Dataset2D is not a pandas DataFrame

For element-level control, use read_elem_lazy on an open store:

import h5py
from anndata.experimental import read_elem_lazy

# Lazy read from an open store (returns dask-backed array)
with h5py.File('large_data.h5ad', 'r') as f:
    X_lazy = read_elem_lazy(f['X'])
    subset = X_lazy[:100, :100].compute()
Raw lines 290 to 312
313## Common I/O Patterns

Common I/O Patterns

Raw lines 313 to 314
315### Convert between formats
316```python
317from anndata.io import read_mtx, read_csv
319# MTX to H5AD
320adata = read_mtx('matrix.mtx').T
321adata.write_h5ad('data.h5ad')
323# CSV to H5AD
324adata = read_csv('data.csv')
325adata.write_h5ad('data.h5ad')
327# H5AD to Zarr
328adata = ad.read_h5ad('data.h5ad')
329adata.write_zarr('data.zarr')
330```

Convert between formats

from anndata.io import read_mtx, read_csv

# MTX to H5AD
adata = read_mtx('matrix.mtx').T
adata.write_h5ad('data.h5ad')

# CSV to H5AD
adata = read_csv('data.csv')
adata.write_h5ad('data.h5ad')

# H5AD to Zarr
adata = ad.read_h5ad('data.h5ad')
adata.write_zarr('data.zarr')
Raw lines 315 to 331
332### Load metadata without data
333```python
334# Backed mode allows inspecting metadata without loading X
335adata = ad.read_h5ad('large_file.h5ad', backed='r')
336print(f"Dataset contains {adata.n_obs} observations and {adata.n_vars} variables")
337print(adata.obs.columns)
338print(adata.var.columns)
339# X is not loaded into memory
340```

Load metadata without data

# Backed mode allows inspecting metadata without loading X
adata = ad.read_h5ad('large_file.h5ad', backed='r')
print(f"Dataset contains {adata.n_obs} observations and {adata.n_vars} variables")
print(adata.obs.columns)
print(adata.var.columns)
# X is not loaded into memory
Raw lines 332 to 341
342### Update backed data or write a new file
343```python
344# Open in read-write mode for dense X updates
345import h5py
346adata = ad.read_h5ad('data.h5ad', backed='r+')
348# Only dense HDF5 X supports this in 0.13; backed sparse assignment raises TypeError.
349if isinstance(adata.X, h5py.Dataset):
350 adata.X[0, 0] = 0
352# Metadata changes are not persisted from backed mode; write a new file instead
353adata_memory = adata.to_memory()
354adata_memory.obs['new_column'] = values
355adata.file.close()
356adata_memory.write_h5ad('data_with_metadata.h5ad')
357```

Update backed data or write a new file

# Open in read-write mode for dense X updates
import h5py
adata = ad.read_h5ad('data.h5ad', backed='r+')

# Only dense HDF5 X supports this in 0.13; backed sparse assignment raises TypeError.
if isinstance(adata.X, h5py.Dataset):
    adata.X[0, 0] = 0

# Metadata changes are not persisted from backed mode; write a new file instead
adata_memory = adata.to_memory()
adata_memory.obs['new_column'] = values
adata.file.close()
adata_memory.write_h5ad('data_with_metadata.h5ad')
Raw lines 342 to 358
359### Remote provenance
361Download the dataset from its documented publisher location or object store;
362record its accession/release, exact URI, checksum, and measurement conventions.
363Validate the completed local file before opening it. A URL and valid HDF5 header
364alone do not establish biological identity or count semantics. No particular
365remote dataset, credentials, or provider was exercised for this refresh.

Remote provenance

Download the dataset from its documented publisher location or object store; record its accession/release, exact URI, checksum, and measurement conventions. Validate the completed local file before opening it. A URL and valid HDF5 header alone do not establish biological identity or count semantics. No particular remote dataset, credentials, or provider was exercised for this refresh.

Raw lines 359 to 366
367## Performance Tips

Performance Tips

Raw lines 367 to 368
369### Reading
370- Use `backed='r'` for large files you only need to query
371- Use `backed='r+'` only for dense `X` updates; write a new file for sparse/metadata changes
372- Benchmark H5AD and Zarr for actual density, layout, compression, and access pattern
373- Zarr is better for cloud storage and parallel access
374- Consider compression for storage, but note it may slow down reading

Reading

  • Use backed='r' for large files you only need to query
  • Use backed='r+' only for dense X updates; write a new file for sparse/metadata changes
  • Benchmark H5AD and Zarr for actual density, layout, compression, and access pattern
  • Zarr is better for cloud storage and parallel access
  • Consider compression for storage, but note it may slow down reading
Raw lines 369 to 375
376### Writing
377- Use compression for long-term storage: `compression='gzip'` or `compression='lzf'`
378- LZF compression is faster but compresses less than GZIP
379- For Zarr, tune chunk sizes based on access patterns:
380 - Larger chunks for sequential reads
381 - Smaller chunks for random access
382- Convert string columns to categorical before writing (smaller files)

Writing

  • Use compression for long-term storage: compression='gzip' or compression='lzf'
  • LZF compression is faster but compresses less than GZIP
  • For Zarr, tune chunk sizes based on access patterns:
  • Larger chunks for sequential reads
  • Smaller chunks for random access
  • Convert string columns to categorical before writing (smaller files)
Raw lines 376 to 383
384### Memory management
385```python
386# Convert strings to categoricals (reduces file size and memory)
387adata.strings_to_categoricals()
388adata.write_h5ad('data.h5ad')
390# Use sparse matrices for sparse data
391from scipy.sparse import csr_matrix
392if isinstance(adata.X, np.ndarray):
393 density = np.count_nonzero(adata.X) / adata.X.size
394 if density < 0.5: # If more than 50% zeros
395 adata.X = csr_matrix(adata.X)
396```

Memory management

# Convert strings to categoricals (reduces file size and memory)
adata.strings_to_categoricals()
adata.write_h5ad('data.h5ad')

# Use sparse matrices for sparse data
from scipy.sparse import csr_matrix
if isinstance(adata.X, np.ndarray):
    density = np.count_nonzero(adata.X) / adata.X.size
    if density < 0.5:  # If more than 50% zeros
        adata.X = csr_matrix(adata.X)
Raw lines 384 to 397
398## Handling Large Datasets

Handling Large Datasets

Raw lines 398 to 399
400### Strategy 1: Backed mode
401```python
402# Work with dataset larger than RAM
403adata = ad.read_h5ad('100GB_file.h5ad', backed='r')
405# Filter based on metadata (fast, no data loading)
406filtered = adata[adata.obs['quality_score'] > 0.8]
408# Load filtered subset into memory
409adata_memory = filtered.to_memory()
410```

Strategy 1: Backed mode

# Work with dataset larger than RAM
adata = ad.read_h5ad('100GB_file.h5ad', backed='r')

# Filter based on metadata (fast, no data loading)
filtered = adata[adata.obs['quality_score'] > 0.8]

# Load filtered subset into memory
adata_memory = filtered.to_memory()
Raw lines 400 to 411
412### Strategy 2: Chunked processing
413```python
414# Process data in chunks
415adata = ad.read_h5ad('large_file.h5ad', backed='r')
417chunk_size = 1000
418results = []
420for i in range(0, adata.n_obs, chunk_size):
421 chunk = adata[i:i+chunk_size, :].to_memory()
422 # Process chunk
423 result = process(chunk)
424 results.append(result)
425```

Strategy 2: Chunked processing

# Process data in chunks
adata = ad.read_h5ad('large_file.h5ad', backed='r')

chunk_size = 1000
results = []

for i in range(0, adata.n_obs, chunk_size):
    chunk = adata[i:i+chunk_size, :].to_memory()
    # Process chunk
    result = process(chunk)
    results.append(result)
Raw lines 412 to 426
427### Strategy 3: Use AnnCollection
428```python
429import anndata as ad
430from anndata.experimental import AnnCollection
432# Create backed objects, then lazily concatenate along observations
433adatas = [
434 ad.read_h5ad(f'dataset_{i}.h5ad', backed='r')
435 for i in range(10)
437collection = AnnCollection(
438 adatas,
439 join_obs='inner',
440 join_vars='inner'
443# Process collection lazily
444# Data is loaded only when accessed
445```

Strategy 3: Use AnnCollection

import anndata as ad
from anndata.experimental import AnnCollection

# Create backed objects, then lazily concatenate along observations
adatas = [
    ad.read_h5ad(f'dataset_{i}.h5ad', backed='r')
    for i in range(10)
]
collection = AnnCollection(
    adatas,
    join_obs='inner',
    join_vars='inner'
)

# Process collection lazily
# Data is loaded only when accessed
Raw lines 427 to 446
447## Common Issues and Solutions

Common Issues and Solutions

Raw lines 447 to 448
449### Issue: Out of memory when reading
450**Solution**: Use backed mode or read in chunks
451```python
452adata = ad.read_h5ad('file.h5ad', backed='r')
453```

Issue: Out of memory when reading

Solution: Use backed mode or read in chunks

adata = ad.read_h5ad('file.h5ad', backed='r')
Raw lines 449 to 454
455### Issue: Slow reading from cloud storage
456**Solution**: Use Zarr format with appropriate chunking
457```python
458adata.write_zarr('data.zarr', chunks=(1000, 1000))
459```

Issue: Slow reading from cloud storage

Solution: Use Zarr format with appropriate chunking

adata.write_zarr('data.zarr', chunks=(1000, 1000))
Raw lines 455 to 460
461### Issue: Large file sizes
462**Solution**: Use compression and convert to sparse/categorical
463```python
464adata.strings_to_categoricals()
465from scipy.sparse import csr_matrix
466adata.X = csr_matrix(adata.X)
467adata.write_h5ad('compressed.h5ad', compression='gzip')
468```

Issue: Large file sizes

Solution: Use compression and convert to sparse/categorical

adata.strings_to_categoricals()
from scipy.sparse import csr_matrix
adata.X = csr_matrix(adata.X)
adata.write_h5ad('compressed.h5ad', compression='gzip')
Raw lines 461 to 469
470### Issue: Cannot modify backed metadata
471**Solution**: Materialize a manageable subset and write a new file. Backed mode does not automatically save metadata; sparse X item updates are unsupported in 0.13.
472```python
473source = adata
474try:
475 adata = source.to_memory()
476finally:
477 source.file.close()
478adata.obs['new_column'] = values
479adata.write_h5ad('updated_file.h5ad')
480```

Issue: Cannot modify backed metadata

Solution: Materialize a manageable subset and write a new file. Backed mode does not automatically save metadata; sparse X item updates are unsupported in 0.13.

source = adata
try:
    adata = source.to_memory()
finally:
    source.file.close()
adata.obs['new_column'] = values
adata.write_h5ad('updated_file.h5ad')
Raw lines 470 to 481
482## Store lifetime and verification
484Keep a backed HDF5 object's file open until all views/chunks have been materialized;
485close it in `finally`. `read_lazy` over HDF5 has the same lifetime requirement.
486For every output, reopen it and compare shape, index order, selected values,
487categoricals, named layers, raw feature names, and relevant `uns` provenance.
488Native formats use versioned AnnData encodings; a root-level arbitrary dataset
489is not a named layer.
491Sources: [I/O API](https://anndata.readthedocs.io/en/stable/api.html),
492[read_h5ad](https://anndata.readthedocs.io/en/stable/generated/anndata.io.read_h5ad.html),
493[read_mtx](https://anndata.readthedocs.io/en/stable/generated/anndata.io.read_mtx.html),
494[read_lazy](https://anndata.readthedocs.io/en/stable/generated/anndata.experimental.read_lazy.html),
495[write_csvs](https://anndata.readthedocs.io/en/stable/generated/anndata.AnnData.write_csvs.html),
496[on-disk specification](https://anndata.readthedocs.io/en/stable/fileformat-prose.html),
497and installed AnnData 0.13.4 I/O source.

Store lifetime and verification

Keep a backed HDF5 object's file open until all views/chunks have been materialized; close it in finally. read_lazy over HDF5 has the same lifetime requirement. For every output, reopen it and compare shape, index order, selected values, categoricals, named layers, raw feature names, and relevant uns provenance. Native formats use versioned AnnData encodings; a root-level arbitrary dataset is not a named layer.

Sources: I/O API, read_h5ad, read_mtx, read_lazy, write_csvs, on-disk specification, and installed AnnData 0.13.4 I/O source.

Raw lines 482 to 497