Skill Tracer
Skills › Knowledge Work Plugins › scvi-tools › data_preparation.md

data_preparation.md

286 lines · bio-research/skills/scvi-tools/references/data_preparation.md at 2574e29

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Data Preparation for scvi-tools
2
3This reference covers how to properly prepare AnnData objects for use with scvi-tools models.
4

Data Preparation for scvi-tools

This reference covers how to properly prepare AnnData objects for use with scvi-tools models.

Raw lines 1 to 4
5## Overview
6
7Proper data preparation is critical for scvi-tools. Key requirements:
81. **Raw counts** (not normalized)
92. **Highly variable gene selection**
103. **Proper setup_anndata() call**

Overview

Proper data preparation is critical for scvi-tools. Key requirements:

  1. Raw counts (not normalized)
  2. Highly variable gene selection
  3. Proper setup_anndata() call
Raw lines 5 to 11
12## Step 1: Load and Inspect Data
14```python
15import scanpy as sc
16import scvi
17import numpy as np
19# Load data
20adata = sc.read_h5ad("data.h5ad")
22# Check what's in adata.X
23print(f"Shape: {adata.shape}")
24print(f"X dtype: {adata.X.dtype}")
25print(f"X contains integers: {np.allclose(adata.X.data, adata.X.data.astype(int))}")
26print(f"X min: {adata.X.min()}, max: {adata.X.max()}")
27```

Step 1: Load and Inspect Data

import scanpy as sc
import scvi
import numpy as np

# Load data
adata = sc.read_h5ad("data.h5ad")

# Check what's in adata.X
print(f"Shape: {adata.shape}")
print(f"X dtype: {adata.X.dtype}")
print(f"X contains integers: {np.allclose(adata.X.data, adata.X.data.astype(int))}")
print(f"X min: {adata.X.min()}, max: {adata.X.max()}")
Raw lines 12 to 28
29### Verify Raw Counts
31```python
32# scvi-tools needs INTEGER counts
33# If X appears normalized, check for raw counts
35if hasattr(adata, 'raw') and adata.raw is not None:
36 print("Found adata.raw")
37 # Use raw counts
38 adata = adata.raw.to_adata()
40# Or check layers
41if 'counts' in adata.layers:
42 print("Found counts layer")
43 # Will specify layer in setup_anndata
44```

Verify Raw Counts

# scvi-tools needs INTEGER counts
# If X appears normalized, check for raw counts

if hasattr(adata, 'raw') and adata.raw is not None:
    print("Found adata.raw")
    # Use raw counts
    adata = adata.raw.to_adata()
    
# Or check layers
if 'counts' in adata.layers:
    print("Found counts layer")
    # Will specify layer in setup_anndata
Raw lines 29 to 45
46## Step 2: Basic Filtering
48```python
49# Filter cells (standard QC)
50sc.pp.filter_cells(adata, min_genes=200)
51sc.pp.filter_cells(adata, max_genes=5000)
53# Calculate mito percent if not present
54# Handle both human (MT-) and mouse (mt-, Mt-) mitochondrial genes
55adata.var['mt'] = (
56 adata.var_names.str.startswith('MT-') |
57 adata.var_names.str.startswith('mt-') |
58 adata.var_names.str.startswith('Mt-')
60sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], inplace=True)
61adata = adata[adata.obs['pct_counts_mt'] < 20].copy()
63# Filter genes
64sc.pp.filter_genes(adata, min_cells=3)
66print(f"After filtering: {adata.shape}")
67```

Step 2: Basic Filtering

# Filter cells (standard QC)
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_cells(adata, max_genes=5000)

# Calculate mito percent if not present
# Handle both human (MT-) and mouse (mt-, Mt-) mitochondrial genes
adata.var['mt'] = (
    adata.var_names.str.startswith('MT-') |
    adata.var_names.str.startswith('mt-') |
    adata.var_names.str.startswith('Mt-')
)
sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], inplace=True)
adata = adata[adata.obs['pct_counts_mt'] < 20].copy()

# Filter genes
sc.pp.filter_genes(adata, min_cells=3)

print(f"After filtering: {adata.shape}")
Raw lines 46 to 68
69## Step 3: Store Raw Counts
71**Critical**: Always preserve raw counts before any normalization.
73```python
74# Store raw counts in a layer
75adata.layers["counts"] = adata.X.copy()
77# Now you can normalize for other purposes (HVG selection)
78# But scvi will use the counts layer
79```

Step 3: Store Raw Counts

Critical: Always preserve raw counts before any normalization.

# Store raw counts in a layer
adata.layers["counts"] = adata.X.copy()

# Now you can normalize for other purposes (HVG selection)
# But scvi will use the counts layer
Raw lines 69 to 80
81## Step 4: Highly Variable Gene Selection
83scvi-tools works best with 1,500-5,000 HVGs.

Step 4: Highly Variable Gene Selection

scvi-tools works best with 1,500-5,000 HVGs.

Raw lines 81 to 84
85### For Single-Batch Data
87```python
88# Normalize for HVG selection only
89adata_hvg = adata.copy()
90sc.pp.normalize_total(adata_hvg, target_sum=1e4)
91sc.pp.log1p(adata_hvg)
93# Select HVGs
94sc.pp.highly_variable_genes(
95 adata_hvg,
96 n_top_genes=2000,
97 flavor="seurat" # or "cell_ranger"
100# Transfer HVG annotation
101adata.var['highly_variable'] = adata_hvg.var['highly_variable']
102```

For Single-Batch Data

# Normalize for HVG selection only
adata_hvg = adata.copy()
sc.pp.normalize_total(adata_hvg, target_sum=1e4)
sc.pp.log1p(adata_hvg)

# Select HVGs
sc.pp.highly_variable_genes(
    adata_hvg,
    n_top_genes=2000,
    flavor="seurat"  # or "cell_ranger"
)

# Transfer HVG annotation
adata.var['highly_variable'] = adata_hvg.var['highly_variable']
Raw lines 85 to 103
104### For Multi-Batch Data (Recommended)
106```python
107# Use seurat_v3 flavor with batch_key
108# This selects genes variable across batches
109sc.pp.highly_variable_genes(
110 adata,
111 n_top_genes=2000,
112 flavor="seurat_v3",
113 batch_key="batch", # Your batch column
114 layer="counts" # Use raw counts
116```

For Multi-Batch Data (Recommended)

# Use seurat_v3 flavor with batch_key
# This selects genes variable across batches
sc.pp.highly_variable_genes(
    adata,
    n_top_genes=2000,
    flavor="seurat_v3",
    batch_key="batch",  # Your batch column
    layer="counts"      # Use raw counts
)
Raw lines 104 to 117
118### Subset to HVGs
120```python
121# Subset to highly variable genes
122adata = adata[:, adata.var['highly_variable']].copy()
123print(f"After HVG selection: {adata.shape}")
124```

Subset to HVGs

# Subset to highly variable genes
adata = adata[:, adata.var['highly_variable']].copy()
print(f"After HVG selection: {adata.shape}")
Raw lines 118 to 125
126## Step 5: Setup AnnData
128The `setup_anndata()` function registers data for the model.

Step 5: Setup AnnData

The setup_anndata() function registers data for the model.

Raw lines 126 to 129
130### Basic Setup
132```python
133scvi.model.SCVI.setup_anndata(
134 adata,
135 layer="counts" # Specify layer with raw counts
137```

Basic Setup

scvi.model.SCVI.setup_anndata(
    adata,
    layer="counts"  # Specify layer with raw counts
)
Raw lines 130 to 138
139### With Batch Information
141```python
142scvi.model.SCVI.setup_anndata(
143 adata,
144 layer="counts",
145 batch_key="batch" # Column in adata.obs
147```

With Batch Information

scvi.model.SCVI.setup_anndata(
    adata,
    layer="counts",
    batch_key="batch"  # Column in adata.obs
)
Raw lines 139 to 148
149### With Cell Type Labels (for scANVI)
151```python
152scvi.model.SCANVI.setup_anndata(
153 adata,
154 layer="counts",
155 batch_key="batch",
156 labels_key="cell_type" # Column with cell type labels
158```

With Cell Type Labels (for scANVI)

scvi.model.SCANVI.setup_anndata(
    adata,
    layer="counts",
    batch_key="batch",
    labels_key="cell_type"  # Column with cell type labels
)
Raw lines 149 to 159
160### With Continuous Covariates
162```python
163scvi.model.SCVI.setup_anndata(
164 adata,
165 layer="counts",
166 batch_key="batch",
167 continuous_covariate_keys=["percent_mito", "n_genes"]
169```

With Continuous Covariates

scvi.model.SCVI.setup_anndata(
    adata,
    layer="counts",
    batch_key="batch",
    continuous_covariate_keys=["percent_mito", "n_genes"]
)
Raw lines 160 to 170
171### With Categorical Covariates
173```python
174scvi.model.SCVI.setup_anndata(
175 adata,
176 layer="counts",
177 batch_key="batch",
178 categorical_covariate_keys=["donor", "technology"]
180```

With Categorical Covariates

scvi.model.SCVI.setup_anndata(
    adata,
    layer="counts",
    batch_key="batch",
    categorical_covariate_keys=["donor", "technology"]
)
Raw lines 171 to 181
182## Multi-Modal Data Setup

Multi-Modal Data Setup

Raw lines 182 to 183
184### CITE-seq (for totalVI)
186```python
187# Protein data in adata.obsm
188# RNA in adata.X, protein in separate matrix
190# Add protein data
191adata.obsm["protein_expression"] = protein_counts # numpy array
193# Setup for totalVI
194scvi.model.TOTALVI.setup_anndata(
195 adata,
196 layer="counts",
197 batch_key="batch",
198 protein_expression_obsm_key="protein_expression"
200```

CITE-seq (for totalVI)

# Protein data in adata.obsm
# RNA in adata.X, protein in separate matrix

# Add protein data
adata.obsm["protein_expression"] = protein_counts  # numpy array

# Setup for totalVI
scvi.model.TOTALVI.setup_anndata(
    adata,
    layer="counts",
    batch_key="batch",
    protein_expression_obsm_key="protein_expression"
)
Raw lines 184 to 201
202### Multiome RNA+ATAC (for MultiVI)
204```python
205# RNA and ATAC in separate AnnData objects or MuData
207import mudata as md
209# If using MuData
210mdata = md.read("multiome.h5mu")
212scvi.model.MULTIVI.setup_mudata(
213 mdata,
214 rna_layer="counts",
215 protein_layer=None,
216 batch_key="batch",
217 modalities={"rna": "rna", "accessibility": "atac"}
219```

Multiome RNA+ATAC (for MultiVI)

# RNA and ATAC in separate AnnData objects or MuData

import mudata as md

# If using MuData
mdata = md.read("multiome.h5mu")

scvi.model.MULTIVI.setup_mudata(
    mdata,
    rna_layer="counts",
    protein_layer=None,
    batch_key="batch",
    modalities={"rna": "rna", "accessibility": "atac"}
)
Raw lines 202 to 220
221## Complete Preparation Pipeline
223For a complete preparation function, use `prepare_adata()` from `scripts/model_utils.py`:
225```python
226from model_utils import prepare_adata
228# Prepare data with QC, HVG selection, and layer setup
229adata = prepare_adata(
230 adata,
231 batch_key="batch",
232 n_top_genes=2000,
233 min_genes=200,
234 max_mito_pct=20
237# Then setup for your model
238import scvi
239scvi.model.SCVI.setup_anndata(adata, layer="counts", batch_key="batch")
240```
242This function handles:
243- Mitochondrial QC filtering
244- Cell and gene filtering
245- Storing counts in layer
246- HVG selection (batch-aware if batch_key provided)
247- Subsetting to HVGs

Complete Preparation Pipeline

For a complete preparation function, use prepare_adata() from scripts/model_utils.py:

from model_utils import prepare_adata

# Prepare data with QC, HVG selection, and layer setup
adata = prepare_adata(
    adata,
    batch_key="batch",
    n_top_genes=2000,
    min_genes=200,
    max_mito_pct=20
)

# Then setup for your model
import scvi
scvi.model.SCVI.setup_anndata(adata, layer="counts", batch_key="batch")

This function handles:

  • Mitochondrial QC filtering
  • Cell and gene filtering
  • Storing counts in layer
  • HVG selection (batch-aware if batch_key provided)
  • Subsetting to HVGs
Raw lines 221 to 248
Runs code scripts/model_utils.py :223
249## Checking Setup
251```python
252# View registered data
253print(adata.uns['_scvi_manager_uuid'])
254print(adata.uns['_scvi_adata_minify_type'])
256# For scVI
257scvi.model.SCVI.view_anndata_setup(adata)
258```

Checking Setup

# View registered data
print(adata.uns['_scvi_manager_uuid'])
print(adata.uns['_scvi_adata_minify_type'])

# For scVI
scvi.model.SCVI.view_anndata_setup(adata)
Raw lines 249 to 259
260## Common Issues and Solutions
262| Issue | Cause | Solution |
263|-------|-------|----------|
264| "X should contain integers" | Normalized data in X | Use layer="counts" |
265| "batch_key not found" | Wrong column name | Check adata.obs.columns |
266| Sparse matrix errors | Incompatible format | Convert: adata.X = adata.X.toarray() |
267| Memory error | Too many genes | Subset to HVGs first |
268| NaN in data | Missing values | Filter or impute |

Common Issues and Solutions

IssueCauseSolution
"X should contain integers"Normalized data in XUse layer="counts"
"batch_key not found"Wrong column nameCheck adata.obs.columns
Sparse matrix errorsIncompatible formatConvert: adata.X = adata.X.toarray()
Memory errorToo many genesSubset to HVGs first
NaN in dataMissing valuesFilter or impute
Raw lines 260 to 269
270## Data Format Reference

Data Format Reference

Raw lines 270 to 271
272### Required
274- `adata.X` or `adata.layers["counts"]`: Raw integer counts (sparse OK)
275- `adata.obs`: Cell metadata DataFrame
276- `adata.var`: Gene metadata DataFrame

Required

  • adata.X or adata.layers["counts"]: Raw integer counts (sparse OK)
  • adata.obs: Cell metadata DataFrame
  • adata.var: Gene metadata DataFrame
Raw lines 272 to 277
278### Recommended
280- `adata.obs["batch"]`: Batch/sample identifiers
281- `adata.var["highly_variable"]`: HVG boolean mask

Recommended

  • adata.obs["batch"]: Batch/sample identifiers
  • adata.var["highly_variable"]: HVG boolean mask
Raw lines 278 to 282
283### For scANVI
285- `adata.obs["labels"]`: Cell type annotations
286- Can include "Unknown" for unlabeled cells

For scANVI

  • adata.obs["labels"]: Cell type annotations
  • Can include "Unknown" for unlabeled cells
Raw lines 283 to 286