480 lines
12 KiB
Markdown
480 lines
12 KiB
Markdown
---
|
|
title: "Input/Output Operations"
|
|
task: ""
|
|
lineage_type: import
|
|
upstream_source: https://github.com/K-Dense-AI/scientific-agent-skills/blob/9c9bd2e9/skills/anndata/references/io_operations.md
|
|
upstream_sha: 9c9bd2e9
|
|
imported_at: 2026-06-26
|
|
prompt_class: prompt
|
|
upstream_changes: accepted
|
|
author: upstream
|
|
validated: false
|
|
---
|
|
|
|
# Input/Output Operations
|
|
|
|
AnnData provides comprehensive I/O functionality for reading and writing data in various formats.
|
|
|
|
Since anndata 0.11, most `read_*` and `write_*` functions live in `anndata.io`. Top-level `read_h5ad` and `read_zarr` remain at `anndata` without deprecation warnings; other top-level imports still work but emit `FutureWarning`.
|
|
|
|
```python
|
|
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.
|
|
|
|
## Native Formats
|
|
|
|
### H5AD (HDF5-based)
|
|
The recommended native format for AnnData objects, providing efficient storage and fast access.
|
|
|
|
#### Writing H5AD files
|
|
```python
|
|
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
|
|
```python
|
|
# 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 = ad.read_h5ad('data.h5ad', backed='r+') # Read-write for X
|
|
|
|
# Backed mode enables working with datasets larger than RAM
|
|
# Only accessed data is loaded into memory
|
|
# In backed mode, only X updates are persisted; write a new file for obs/var/uns changes.
|
|
```
|
|
|
|
#### Backed mode operations
|
|
```python
|
|
# 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()
|
|
```
|
|
|
|
### Zarr
|
|
Hierarchical array storage format, optimized for cloud storage and parallel I/O.
|
|
|
|
#### Writing Zarr
|
|
```python
|
|
# 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
|
|
```python
|
|
# Read Zarr store
|
|
adata = ad.read_zarr('data.zarr')
|
|
```
|
|
|
|
#### Zarr v3 (anndata 0.12+)
|
|
```python
|
|
import anndata
|
|
|
|
# Default writes Zarr v2; opt into v3 and optional auto-sharding
|
|
anndata.settings.zarr_write_format = 3
|
|
anndata.settings.auto_shard_zarr_v3 = True # experimental; independent of zarr_write_format
|
|
|
|
adata.write_zarr('data.zarr', chunks=(1000, 1000))
|
|
```
|
|
|
|
Zarr v3 writing is available in anndata 0.12, with structured-array exceptions and evolving performance guidance. Consolidated metadata is recommended for remote Zarr stores.
|
|
|
|
#### Remote Zarr access
|
|
Only open remote stores from trusted, expected locations. Prefer allowlisted HTTPS/S3/GCS paths or signed URLs, and avoid asking an agent to fetch arbitrary user-supplied URLs.
|
|
|
|
```python
|
|
import fsspec
|
|
|
|
# Access Zarr from an expected S3 location
|
|
store = fsspec.get_mapper('s3://bucket-name/data.zarr')
|
|
adata = ad.read_zarr(store)
|
|
|
|
# Access Zarr from a trusted HTTPS location
|
|
store = fsspec.get_mapper('https://example.com/data.zarr')
|
|
adata = ad.read_zarr(store)
|
|
```
|
|
|
|
## Alternative Input Formats
|
|
|
|
### CSV/TSV
|
|
```python
|
|
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)
|
|
```
|
|
|
|
### Excel
|
|
```python
|
|
from anndata.io import read_excel
|
|
|
|
# Read Excel file
|
|
adata = read_excel('data.xlsx')
|
|
|
|
# Read specific sheet
|
|
adata = read_excel('data.xlsx', sheet='Sheet1')
|
|
```
|
|
|
|
### Matrix Market (MTX)
|
|
Common format for sparse matrices in genomics.
|
|
|
|
```python
|
|
from anndata.io import read_mtx
|
|
|
|
# Read MTX with associated files
|
|
# Requires: matrix.mtx, genes.tsv, barcodes.tsv
|
|
adata = read_mtx('matrix.mtx')
|
|
|
|
# Read with custom gene and barcode files
|
|
adata = read_mtx(
|
|
'matrix.mtx',
|
|
var_names='genes.tsv',
|
|
obs_names='barcodes.tsv'
|
|
)
|
|
|
|
# Transpose if needed (MTX often has genes as rows)
|
|
adata = adata.T
|
|
```
|
|
|
|
### 10X Genomics formats
|
|
10X readers are provided by **scanpy**, not anndata. After loading, the result is a standard `AnnData` object.
|
|
|
|
```python
|
|
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')
|
|
```
|
|
|
|
### Loom
|
|
```python
|
|
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'
|
|
)
|
|
```
|
|
|
|
### Text files
|
|
```python
|
|
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'
|
|
)
|
|
```
|
|
|
|
### UMI tools
|
|
```python
|
|
from anndata.io import read_umi_tools
|
|
|
|
# Read UMI tools format
|
|
adata = read_umi_tools('counts.tsv')
|
|
```
|
|
|
|
### HDF5 (generic)
|
|
```python
|
|
from anndata.io import read_hdf
|
|
|
|
# Read from HDF5 file (not h5ad format)
|
|
adata = read_hdf('data.h5', key='dataset')
|
|
```
|
|
|
|
## Alternative Output Formats
|
|
|
|
### CSV
|
|
```python
|
|
# Write to CSV files (creates multiple files)
|
|
adata.write_csvs('output_dir/')
|
|
|
|
# This creates:
|
|
# - output_dir/X.csv (expression matrix)
|
|
# - output_dir/obs.csv (observation annotations)
|
|
# - output_dir/var.csv (variable annotations)
|
|
# - output_dir/uns.csv (unstructured annotations, if possible)
|
|
|
|
# Skip certain components
|
|
adata.write_csvs('output_dir/', skip_data=True) # Skip X matrix
|
|
```
|
|
|
|
### Loom
|
|
```python
|
|
# Write to Loom format
|
|
adata.write_loom('output.loom')
|
|
```
|
|
|
|
## Reading Specific Elements
|
|
|
|
For fine-grained control, read specific elements from an open store:
|
|
|
|
```python
|
|
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'])
|
|
```
|
|
|
|
## Writing Specific Elements
|
|
|
|
```python
|
|
from anndata.io import write_elem
|
|
import h5py
|
|
|
|
# Write element to existing file
|
|
with h5py.File('data.h5ad', 'a') as f:
|
|
write_elem(f, 'new_layer', adata.X.copy())
|
|
```
|
|
|
|
## 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.
|
|
|
|
```python
|
|
from anndata.experimental import read_lazy
|
|
|
|
adata = read_lazy('large_data.zarr')
|
|
print(adata.obs.head()) # Does not require loading X
|
|
```
|
|
|
|
For element-level control, use `read_elem_lazy` on an open store:
|
|
|
|
```python
|
|
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()
|
|
```
|
|
|
|
## Common I/O Patterns
|
|
|
|
### Convert between formats
|
|
```python
|
|
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')
|
|
```
|
|
|
|
### Load metadata without data
|
|
```python
|
|
# 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
|
|
```
|
|
|
|
### Update backed data or write a new file
|
|
```python
|
|
# Open in read-write mode for X updates
|
|
adata = ad.read_h5ad('data.h5ad', backed='r+')
|
|
|
|
# X updates can be persisted in backed mode
|
|
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_memory.write_h5ad('data_with_metadata.h5ad')
|
|
```
|
|
|
|
### Download from a trusted URL
|
|
Validate remote sources before downloading. Prefer local files or vetted object-store paths over arbitrary URLs.
|
|
|
|
```python
|
|
import anndata as ad
|
|
import urllib.request
|
|
from urllib.parse import urlparse
|
|
|
|
url = 'https://example.org/datasets/reference.h5ad'
|
|
parsed = urlparse(url)
|
|
trusted_hosts = {'example.org'}
|
|
|
|
if parsed.scheme != 'https' or parsed.netloc not in trusted_hosts:
|
|
raise ValueError('Refusing to download from an untrusted host')
|
|
|
|
urllib.request.urlretrieve(url, 'reference.h5ad')
|
|
adata = ad.read_h5ad('reference.h5ad')
|
|
```
|
|
|
|
## Performance Tips
|
|
|
|
### Reading
|
|
- Use `backed='r'` for large files you only need to query
|
|
- Use `backed='r+'` only for `X` updates; write a new file for metadata changes
|
|
- H5AD format is generally fastest for random access
|
|
- Zarr is better for cloud storage and parallel access
|
|
- Consider compression for storage, but note it may slow down reading
|
|
|
|
### 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)
|
|
|
|
### Memory management
|
|
```python
|
|
# 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)
|
|
```
|
|
|
|
## Handling Large Datasets
|
|
|
|
### Strategy 1: Backed mode
|
|
```python
|
|
# 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()
|
|
```
|
|
|
|
### Strategy 2: Chunked processing
|
|
```python
|
|
# 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)
|
|
```
|
|
|
|
### Strategy 3: Use AnnCollection
|
|
```python
|
|
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
|
|
```
|
|
|
|
## Common Issues and Solutions
|
|
|
|
### Issue: Out of memory when reading
|
|
**Solution**: Use backed mode or read in chunks
|
|
```python
|
|
adata = ad.read_h5ad('file.h5ad', backed='r')
|
|
```
|
|
|
|
### Issue: Slow reading from cloud storage
|
|
**Solution**: Use Zarr format with appropriate chunking
|
|
```python
|
|
adata.write_zarr('data.zarr', chunks=(1000, 1000))
|
|
```
|
|
|
|
### Issue: Large file sizes
|
|
**Solution**: Use compression and convert to sparse/categorical
|
|
```python
|
|
adata.strings_to_categoricals()
|
|
from scipy.sparse import csr_matrix
|
|
adata.X = csr_matrix(adata.X)
|
|
adata.write_h5ad('compressed.h5ad', compression='gzip')
|
|
```
|
|
|
|
### Issue: Cannot modify backed metadata
|
|
**Solution**: Load to memory and write a new file. Backed mode only persists updates to `X`.
|
|
```python
|
|
adata = adata.to_memory()
|
|
adata.obs['new_column'] = values
|
|
adata.write_h5ad('updated_file.h5ad')
|
|
```
|