Skill Tracer
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

Open on GitHub
1# Reference Mapping with scArches
2
3This reference covers using scArches for mapping query data to pre-trained reference models without retraining from scratch.
4

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 4
5## Overview
6
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
Raw lines 5 to 12
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

ScenarioApproach
Map query to existing atlasscArches query mapping
Extend atlas with new datascArches model surgery
No pre-trained model availableTrain scANVI from scratch
Query very different from referenceConsider retraining
Raw lines 13 to 21
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 31
32## Workflow 1: Map Query to Pre-Trained Reference

Workflow 1: Map Query to Pre-Trained Reference

Raw lines 32 to 33
34### 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 48
49### 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 70
71### 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 81
82### 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
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 query
Raw lines 82 to 97
98### 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 }
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 113
114### 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 126
127## 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 130
131### 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 141
142### 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
157# Fine-tune
158query_scanvi.train(
159 max_epochs=100,
160 plan_kwargs={"weight_decay": 0.0}
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 163
164### 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 183
184### 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 198
199## 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 202
203### 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 213
214### 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 225
226### 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"
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 244
245## 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 273
274## Using Public Atlas Models

Using Public Atlas Models

Raw lines 274 to 275
276### 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/"
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 291
292### 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 above
Raw lines 292 to 304
305## 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"
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 387
388## 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

IssueCauseSolution
Gene mismatchDifferent gene namingConvert gene IDs (Ensembl ↔ Symbol)
Many low-confidenceQuery has novel typesManually annotate low-confidence cells
Poor mappingQuery too differentConsider retraining with combined data
Memory errorLarge queryProcess in batches
Version mismatchDifferent scvi-tools versionUse same version as reference training
Raw lines 388 to 397
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"
Raw lines 398 to 401