Skills › Knowledge Work Plugins › scvi-tools › scarches_mapping.md
scarches_mapping.md
401 lines · bio-research/skills/scvi-tools/references/scarches_mapping.md at 2574e29
1# Reference Mapping with scArches
3This reference covers using scArches for mapping query data to pre-trained reference models without retraining from scratch.
Reference Mapping with scArches
This reference covers using scArches for mapping query data to pre-trained reference models without retraining from scratch.
Raw lines 1 to 45## Overview
7scArches (single-cell architecture surgery) enables:
8- Mapping new data to existing reference atlases
9- Extending models with new batches/studies
10- Transfer learning without full retraining
11- Preserving reference structure while integrating query
Overview
scArches (single-cell architecture surgery) enables:
- Mapping new data to existing reference atlases
- Extending models with new batches/studies
- Transfer learning without full retraining
- Preserving reference structure while integrating query
13## When to Use scArches
15| Scenario | Approach |
16|----------|----------|
17| Map query to existing atlas | scArches query mapping |
18| Extend atlas with new data | scArches model surgery |
19| No pre-trained model available | Train scANVI from scratch |
20| Query very different from reference | Consider retraining |
When to Use scArches
| Scenario | Approach |
|---|---|
| Map query to existing atlas | scArches query mapping |
| Extend atlas with new data | scArches model surgery |
| No pre-trained model available | Train scANVI from scratch |
| Query very different from reference | Consider retraining |
22## Prerequisites
24```python
25import scvi
26import scanpy as sc
27import numpy as np
29print(f"scvi-tools version: {scvi.__version__}")
30```
Prerequisites
import scvi
import scanpy as sc
import numpy as np
print(f"scvi-tools version: {scvi.__version__}")Raw lines 22 to 31Workflow 1: Map Query to Pre-Trained Reference
Raw lines 32 to 3334### Step 1: Load Pre-Trained Reference Model
36```python
37# Load saved reference model
38# The model must have been trained with scvi-tools
39reference_model = scvi.model.SCVI.load("reference_model/")
41# Or load scANVI for label transfer
42reference_model = scvi.model.SCANVI.load("reference_scanvi_model/")
44# Check model info
45print(f"Model type: {type(reference_model)}")
46print(f"Training data shape: {reference_model.adata.shape}")
47```
Step 1: Load Pre-Trained Reference Model
# Load saved reference model
# The model must have been trained with scvi-tools
reference_model = scvi.model.SCVI.load("reference_model/")
# Or load scANVI for label transfer
reference_model = scvi.model.SCANVI.load("reference_scanvi_model/")
# Check model info
print(f"Model type: {type(reference_model)}")
print(f"Training data shape: {reference_model.adata.shape}")Raw lines 34 to 4849### Step 2: Prepare Query Data
51```python
52# Load query data
53adata_query = sc.read_h5ad("query_data.h5ad")
55# CRITICAL: Match genes to reference
56reference_genes = reference_model.adata.var_names
57query_genes = adata_query.var_names
59# Check overlap
60common_genes = reference_genes.intersection(query_genes)
61print(f"Reference genes: {len(reference_genes)}")
62print(f"Query genes: {len(query_genes)}")
63print(f"Overlap: {len(common_genes)}")
65# Subset query to reference genes
66adata_query = adata_query[:, reference_genes].copy()
68# Handle missing genes (filled with zeros automatically by prepare_query_anndata)
69```
Step 2: Prepare Query Data
# Load query data
adata_query = sc.read_h5ad("query_data.h5ad")
# CRITICAL: Match genes to reference
reference_genes = reference_model.adata.var_names
query_genes = adata_query.var_names
# Check overlap
common_genes = reference_genes.intersection(query_genes)
print(f"Reference genes: {len(reference_genes)}")
print(f"Query genes: {len(query_genes)}")
print(f"Overlap: {len(common_genes)}")
# Subset query to reference genes
adata_query = adata_query[:, reference_genes].copy()
# Handle missing genes (filled with zeros automatically by prepare_query_anndata)Raw lines 49 to 7071### Step 3: Prepare Query AnnData
73```python
74# Store raw counts
75adata_query.layers["counts"] = adata_query.X.copy()
77# Prepare query for mapping
78# This aligns the query data structure to match the reference
79scvi.model.SCVI.prepare_query_anndata(adata_query, reference_model)
80```
Step 3: Prepare Query AnnData
# Store raw counts
adata_query.layers["counts"] = adata_query.X.copy()
# Prepare query for mapping
# This aligns the query data structure to match the reference
scvi.model.SCVI.prepare_query_anndata(adata_query, reference_model)Raw lines 71 to 8182### Step 4: Create Query Model
84```python
85# Create query model from reference
86# This initializes with reference weights
87query_model = scvi.model.SCVI.load_query_data(
88 adata_query,
89 reference_model
90)
92# The query model inherits:
93# - Reference architecture
94# - Reference encoder weights (frozen by default)
95# - Decoder is fine-tuned for query
96```
Step 4: Create Query Model
# Create query model from reference
# This initializes with reference weights
query_model = scvi.model.SCVI.load_query_data(
adata_query,
reference_model
)
# The query model inherits:
# - Reference architecture
# - Reference encoder weights (frozen by default)
# - Decoder is fine-tuned for queryRaw lines 82 to 9798### Step 5: Fine-Tune on Query
100```python
101# Fine-tune the query model
102# This adjusts decoder weights for query-specific effects
103query_model.train(
104 max_epochs=200,
105 plan_kwargs={
106 "weight_decay": 0.0 # Less regularization for fine-tuning
107 }
108)
110# Check training
111query_model.history['elbo_train'].plot()
112```
Step 5: Fine-Tune on Query
# Fine-tune the query model
# This adjusts decoder weights for query-specific effects
query_model.train(
max_epochs=200,
plan_kwargs={
"weight_decay": 0.0 # Less regularization for fine-tuning
}
)
# Check training
query_model.history['elbo_train'].plot()Raw lines 98 to 113114### Step 6: Get Query Representation
116```python
117# Get latent representation
118# Query cells are embedded in same space as reference
119adata_query.obsm["X_scVI"] = query_model.get_latent_representation()
121# Visualize
122sc.pp.neighbors(adata_query, use_rep="X_scVI")
123sc.tl.umap(adata_query)
124sc.pl.umap(adata_query, color=['cell_type', 'batch'])
125```
Step 6: Get Query Representation
# Get latent representation
# Query cells are embedded in same space as reference
adata_query.obsm["X_scVI"] = query_model.get_latent_representation()
# Visualize
sc.pp.neighbors(adata_query, use_rep="X_scVI")
sc.tl.umap(adata_query)
sc.pl.umap(adata_query, color=['cell_type', 'batch'])Raw lines 114 to 126127## Workflow 2: scANVI Query Mapping with Label Transfer
129For transferring cell type labels from reference to query:
Workflow 2: scANVI Query Mapping with Label Transfer
For transferring cell type labels from reference to query:
Raw lines 127 to 130131### Step 1: Load scANVI Reference
133```python
134# Reference must be scANVI model (trained with labels)
135reference_scanvi = scvi.model.SCANVI.load("scanvi_reference/")
137# Check available labels
138print("Reference cell types:")
139print(reference_scanvi.adata.obs['cell_type'].value_counts())
140```
Step 1: Load scANVI Reference
# Reference must be scANVI model (trained with labels)
reference_scanvi = scvi.model.SCANVI.load("scanvi_reference/")
# Check available labels
print("Reference cell types:")
print(reference_scanvi.adata.obs['cell_type'].value_counts())Raw lines 131 to 141142### Step 2: Prepare and Map Query
144```python
145# Prepare query
146adata_query.layers["counts"] = adata_query.X.copy()
147adata_query = adata_query[:, reference_scanvi.adata.var_names].copy()
149scvi.model.SCANVI.prepare_query_anndata(adata_query, reference_scanvi)
151# Create query model
152query_scanvi = scvi.model.SCANVI.load_query_data(
153 adata_query,
154 reference_scanvi
155)
157# Fine-tune
158query_scanvi.train(
159 max_epochs=100,
160 plan_kwargs={"weight_decay": 0.0}
161)
162```
Step 2: Prepare and Map Query
# Prepare query
adata_query.layers["counts"] = adata_query.X.copy()
adata_query = adata_query[:, reference_scanvi.adata.var_names].copy()
scvi.model.SCANVI.prepare_query_anndata(adata_query, reference_scanvi)
# Create query model
query_scanvi = scvi.model.SCANVI.load_query_data(
adata_query,
reference_scanvi
)
# Fine-tune
query_scanvi.train(
max_epochs=100,
plan_kwargs={"weight_decay": 0.0}
)Raw lines 142 to 163164### Step 3: Get Predictions
166```python
167# Predict cell types
168predictions = query_scanvi.predict()
169adata_query.obs["predicted_cell_type"] = predictions
171# Get prediction probabilities
172soft_predictions = query_scanvi.predict(soft=True)
173adata_query.obs["prediction_confidence"] = soft_predictions.max(axis=1)
175# Latent representation
176adata_query.obsm["X_scANVI"] = query_scanvi.get_latent_representation()
178# Visualize predictions
179sc.pp.neighbors(adata_query, use_rep="X_scANVI")
180sc.tl.umap(adata_query)
181sc.pl.umap(adata_query, color=['predicted_cell_type', 'prediction_confidence'])
182```
Step 3: Get Predictions
# Predict cell types
predictions = query_scanvi.predict()
adata_query.obs["predicted_cell_type"] = predictions
# Get prediction probabilities
soft_predictions = query_scanvi.predict(soft=True)
adata_query.obs["prediction_confidence"] = soft_predictions.max(axis=1)
# Latent representation
adata_query.obsm["X_scANVI"] = query_scanvi.get_latent_representation()
# Visualize predictions
sc.pp.neighbors(adata_query, use_rep="X_scANVI")
sc.tl.umap(adata_query)
sc.pl.umap(adata_query, color=['predicted_cell_type', 'prediction_confidence'])Raw lines 164 to 183184### Step 4: Evaluate Predictions
186```python
187# Distribution of predictions
188print(adata_query.obs['predicted_cell_type'].value_counts())
190# Confidence statistics
191print(f"Mean confidence: {adata_query.obs['prediction_confidence'].mean():.3f}")
192print(f"Low confidence (<0.5): {(adata_query.obs['prediction_confidence'] < 0.5).sum()}")
194# Filter low-confidence predictions
195high_conf = adata_query[adata_query.obs['prediction_confidence'] >= 0.7].copy()
196print(f"High confidence cells: {len(high_conf)} ({len(high_conf)/len(adata_query)*100:.1f}%)")
197```
Step 4: Evaluate Predictions
# Distribution of predictions
print(adata_query.obs['predicted_cell_type'].value_counts())
# Confidence statistics
print(f"Mean confidence: {adata_query.obs['prediction_confidence'].mean():.3f}")
print(f"Low confidence (<0.5): {(adata_query.obs['prediction_confidence'] < 0.5).sum()}")
# Filter low-confidence predictions
high_conf = adata_query[adata_query.obs['prediction_confidence'] >= 0.7].copy()
print(f"High confidence cells: {len(high_conf)} ({len(high_conf)/len(adata_query)*100:.1f}%)")Raw lines 184 to 198199## Workflow 3: Model Surgery (Extending Reference)
201Extend an existing reference model with new data:
Workflow 3: Model Surgery (Extending Reference)
Extend an existing reference model with new data:
Raw lines 199 to 202203### Step 1: Freeze Reference Layers
205```python
206# Load reference model
207reference_model = scvi.model.SCVI.load("reference_model/")
209# Get reference representation (before surgery)
210adata_ref = reference_model.adata
211adata_ref.obsm["X_scVI_before"] = reference_model.get_latent_representation()
212```
Step 1: Freeze Reference Layers
# Load reference model
reference_model = scvi.model.SCVI.load("reference_model/")
# Get reference representation (before surgery)
adata_ref = reference_model.adata
adata_ref.obsm["X_scVI_before"] = reference_model.get_latent_representation()Raw lines 203 to 213214### Step 2: Prepare Combined Data
216```python
217# Add batch information
218adata_ref.obs["dataset"] = "reference"
219adata_query.obs["dataset"] = "query"
221# Combine
222adata_combined = sc.concat([adata_ref, adata_query])
223adata_combined.layers["counts"] = adata_combined.X.copy()
224```
Step 2: Prepare Combined Data
# Add batch information
adata_ref.obs["dataset"] = "reference"
adata_query.obs["dataset"] = "query"
# Combine
adata_combined = sc.concat([adata_ref, adata_query])
adata_combined.layers["counts"] = adata_combined.X.copy()Raw lines 214 to 225226### Step 3: Surgery Approach
228```python
229# Option A: Use load_query_data (recommended)
230scvi.model.SCVI.prepare_query_anndata(adata_query, reference_model)
231extended_model = scvi.model.SCVI.load_query_data(adata_query, reference_model)
232extended_model.train(max_epochs=200)
234# Option B: Retrain with combined data (if query is large)
235# This doesn't preserve reference exactly but may give better results
236scvi.model.SCVI.setup_anndata(
237 adata_combined,
238 layer="counts",
239 batch_key="dataset"
240)
241new_model = scvi.model.SCVI(adata_combined, n_latent=30)
242new_model.train(max_epochs=200)
243```
Step 3: Surgery Approach
# Option A: Use load_query_data (recommended)
scvi.model.SCVI.prepare_query_anndata(adata_query, reference_model)
extended_model = scvi.model.SCVI.load_query_data(adata_query, reference_model)
extended_model.train(max_epochs=200)
# Option B: Retrain with combined data (if query is large)
# This doesn't preserve reference exactly but may give better results
scvi.model.SCVI.setup_anndata(
adata_combined,
layer="counts",
batch_key="dataset"
)
new_model = scvi.model.SCVI(adata_combined, n_latent=30)
new_model.train(max_epochs=200)Raw lines 226 to 244245## Joint Visualization
247Visualize reference and query together:
249```python
250# Get latent representations
251adata_ref.obsm["X_scVI"] = reference_model.get_latent_representation()
252adata_query.obsm["X_scVI"] = query_model.get_latent_representation()
254# Combine for visualization
255adata_ref.obs["source"] = "reference"
256adata_query.obs["source"] = "query"
257adata_combined = sc.concat([adata_ref, adata_query])
259# Compute joint UMAP
260sc.pp.neighbors(adata_combined, use_rep="X_scVI")
261sc.tl.umap(adata_combined)
263# Visualize
264import matplotlib.pyplot as plt
265fig, axes = plt.subplots(1, 3, figsize=(15, 4))
267sc.pl.umap(adata_combined, color="source", ax=axes[0], show=False, title="Source")
268sc.pl.umap(adata_combined, color="cell_type", ax=axes[1], show=False, title="Cell Type")
269sc.pl.umap(adata_combined, color="batch", ax=axes[2], show=False, title="Batch")
271plt.tight_layout()
272```
Joint Visualization
Visualize reference and query together:
# Get latent representations
adata_ref.obsm["X_scVI"] = reference_model.get_latent_representation()
adata_query.obsm["X_scVI"] = query_model.get_latent_representation()
# Combine for visualization
adata_ref.obs["source"] = "reference"
adata_query.obs["source"] = "query"
adata_combined = sc.concat([adata_ref, adata_query])
# Compute joint UMAP
sc.pp.neighbors(adata_combined, use_rep="X_scVI")
sc.tl.umap(adata_combined)
# Visualize
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
sc.pl.umap(adata_combined, color="source", ax=axes[0], show=False, title="Source")
sc.pl.umap(adata_combined, color="cell_type", ax=axes[1], show=False, title="Cell Type")
sc.pl.umap(adata_combined, color="batch", ax=axes[2], show=False, title="Batch")
plt.tight_layout()Raw lines 245 to 273Using Public Atlas Models
Raw lines 274 to 275276### From HuggingFace Model Hub
278```python
279from huggingface_hub import hf_hub_download
281# Download model files
282model_dir = hf_hub_download(
283 repo_id="scvi-tools/model-name", # Replace with actual repo
284 filename="model.pt",
285 local_dir="./downloaded_model/"
286)
288# Load model
289atlas_model = scvi.model.SCANVI.load(model_dir)
290```
From HuggingFace Model Hub
from huggingface_hub import hf_hub_download
# Download model files
model_dir = hf_hub_download(
repo_id="scvi-tools/model-name", # Replace with actual repo
filename="model.pt",
local_dir="./downloaded_model/"
)
# Load model
atlas_model = scvi.model.SCANVI.load(model_dir)Raw lines 276 to 291292### From CellxGene
294```python
295# Many CellxGene datasets provide pre-trained models
296# Check dataset documentation for model availability
297# https://cellxgene.cziscience.com/
299# Example workflow:
300# 1. Download reference dataset and model
301# 2. Load model: model = scvi.model.SCANVI.load("cellxgene_model/")
302# 3. Map your query data using steps above
303```
From CellxGene
# Many CellxGene datasets provide pre-trained models
# Check dataset documentation for model availability
# https://cellxgene.cziscience.com/
# Example workflow:
# 1. Download reference dataset and model
# 2. Load model: model = scvi.model.SCANVI.load("cellxgene_model/")
# 3. Map your query data using steps aboveRaw lines 292 to 304305## Complete Pipeline
307```python
308def map_query_to_reference(
309 adata_query,
310 reference_model_path,
311 model_type="scanvi",
312 max_epochs=100,
313 confidence_threshold=0.5
314):
315 """
316 Map query data to pre-trained reference model.
318 Parameters
319 ----------
320 adata_query : AnnData
321 Query data with raw counts
322 reference_model_path : str
323 Path to saved reference model
324 model_type : str
325 "scvi" or "scanvi"
326 max_epochs : int
327 Fine-tuning epochs
328 confidence_threshold : float
329 Minimum prediction confidence (for scANVI)
331 Returns
332 -------
333 Mapped AnnData with predictions (if scANVI)
334 """
335 import scvi
337 # Load reference
338 if model_type == "scanvi":
339 reference_model = scvi.model.SCANVI.load(reference_model_path)
340 ModelClass = scvi.model.SCANVI
341 else:
342 reference_model = scvi.model.SCVI.load(reference_model_path)
343 ModelClass = scvi.model.SCVI
345 # Prepare query
346 adata_query = adata_query.copy()
347 adata_query = adata_query[:, reference_model.adata.var_names].copy()
348 adata_query.layers["counts"] = adata_query.X.copy()
350 # Map query
351 ModelClass.prepare_query_anndata(adata_query, reference_model)
352 query_model = ModelClass.load_query_data(adata_query, reference_model)
354 # Fine-tune
355 query_model.train(
356 max_epochs=max_epochs,
357 plan_kwargs={"weight_decay": 0.0}
358 )
360 # Get results
361 rep_key = "X_scANVI" if model_type == "scanvi" else "X_scVI"
362 adata_query.obsm[rep_key] = query_model.get_latent_representation()
364 if model_type == "scanvi":
365 adata_query.obs["predicted_cell_type"] = query_model.predict()
366 soft = query_model.predict(soft=True)
367 adata_query.obs["prediction_confidence"] = soft.max(axis=1)
368 adata_query.obs["confident"] = adata_query.obs["prediction_confidence"] >= confidence_threshold
370 # Compute UMAP
371 sc.pp.neighbors(adata_query, use_rep=rep_key)
372 sc.tl.umap(adata_query)
374 return adata_query, query_model
377# Usage
378adata_mapped, model = map_query_to_reference(
379 adata_query,
380 "reference_scanvi_model/",
381 model_type="scanvi"
382)
384# Visualize
385sc.pl.umap(adata_mapped, color=['predicted_cell_type', 'prediction_confidence'])
386```
Complete Pipeline
def map_query_to_reference(
adata_query,
reference_model_path,
model_type="scanvi",
max_epochs=100,
confidence_threshold=0.5
):
"""
Map query data to pre-trained reference model.
Parameters
----------
adata_query : AnnData
Query data with raw counts
reference_model_path : str
Path to saved reference model
model_type : str
"scvi" or "scanvi"
max_epochs : int
Fine-tuning epochs
confidence_threshold : float
Minimum prediction confidence (for scANVI)
Returns
-------
Mapped AnnData with predictions (if scANVI)
"""
import scvi
# Load reference
if model_type == "scanvi":
reference_model = scvi.model.SCANVI.load(reference_model_path)
ModelClass = scvi.model.SCANVI
else:
reference_model = scvi.model.SCVI.load(reference_model_path)
ModelClass = scvi.model.SCVI
# Prepare query
adata_query = adata_query.copy()
adata_query = adata_query[:, reference_model.adata.var_names].copy()
adata_query.layers["counts"] = adata_query.X.copy()
# Map query
ModelClass.prepare_query_anndata(adata_query, reference_model)
query_model = ModelClass.load_query_data(adata_query, reference_model)
# Fine-tune
query_model.train(
max_epochs=max_epochs,
plan_kwargs={"weight_decay": 0.0}
)
# Get results
rep_key = "X_scANVI" if model_type == "scanvi" else "X_scVI"
adata_query.obsm[rep_key] = query_model.get_latent_representation()
if model_type == "scanvi":
adata_query.obs["predicted_cell_type"] = query_model.predict()
soft = query_model.predict(soft=True)
adata_query.obs["prediction_confidence"] = soft.max(axis=1)
adata_query.obs["confident"] = adata_query.obs["prediction_confidence"] >= confidence_threshold
# Compute UMAP
sc.pp.neighbors(adata_query, use_rep=rep_key)
sc.tl.umap(adata_query)
return adata_query, query_model
# Usage
adata_mapped, model = map_query_to_reference(
adata_query,
"reference_scanvi_model/",
model_type="scanvi"
)
# Visualize
sc.pl.umap(adata_mapped, color=['predicted_cell_type', 'prediction_confidence'])Raw lines 305 to 387388## Troubleshooting
390| Issue | Cause | Solution |
391|-------|-------|----------|
392| Gene mismatch | Different gene naming | Convert gene IDs (Ensembl ↔ Symbol) |
393| Many low-confidence | Query has novel types | Manually annotate low-confidence cells |
394| Poor mapping | Query too different | Consider retraining with combined data |
395| Memory error | Large query | Process in batches |
396| Version mismatch | Different scvi-tools version | Use same version as reference training |
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Gene mismatch | Different gene naming | Convert gene IDs (Ensembl ↔ Symbol) |
| Many low-confidence | Query has novel types | Manually annotate low-confidence cells |
| Poor mapping | Query too different | Consider retraining with combined data |
| Memory error | Large query | Process in batches |
| Version mismatch | Different scvi-tools version | Use same version as reference training |
398## Key References
400- Lotfollahi et al. (2022) "Mapping single-cell data to reference atlases by transfer learning"
401- Xu et al. (2021) "Probabilistic harmonization and annotation of single-cell transcriptomics data with deep generative models"
Key References
- Lotfollahi et al. (2022) "Mapping single-cell data to reference atlases by transfer learning"
- Xu et al. (2021) "Probabilistic harmonization and annotation of single-cell transcriptomics data with deep generative models"