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

rna_velocity_velovi.md

410 lines · bio-research/skills/scvi-tools/references/rna_velocity_velovi.md at 2574e29

Open on GitHub
1# RNA Velocity with veloVI
2
3This reference covers RNA velocity analysis using veloVI, a deep learning approach that improves upon traditional velocity methods.
4

RNA Velocity with veloVI

This reference covers RNA velocity analysis using veloVI, a deep learning approach that improves upon traditional velocity methods.

Raw lines 1 to 4
5## Overview
6
7RNA velocity estimates the future state of cells by modeling:
8- **Unspliced RNA**: Newly transcribed, contains introns
9- **Spliced RNA**: Mature mRNA, introns removed
11The ratio of unspliced to spliced indicates whether a gene is being upregulated or downregulated.

Overview

RNA velocity estimates the future state of cells by modeling:

  • Unspliced RNA: Newly transcribed, contains introns
  • Spliced RNA: Mature mRNA, introns removed

The ratio of unspliced to spliced indicates whether a gene is being upregulated or downregulated.

Raw lines 5 to 12
13## Why veloVI?
15Traditional methods (velocyto, scVelo) have limitations:
16- Assume steady-state or dynamical model
17- Sensitive to noise
18- Don't handle batch effects
20veloVI addresses these with:
21- Probabilistic modeling
22- Better uncertainty quantification
23- Integration with scVI framework

Why veloVI?

Traditional methods (velocyto, scVelo) have limitations:

  • Assume steady-state or dynamical model
  • Sensitive to noise
  • Don't handle batch effects

veloVI addresses these with:

  • Probabilistic modeling
  • Better uncertainty quantification
  • Integration with scVI framework
Raw lines 13 to 24
25## Prerequisites
27```python
28import scvi
29import scvelo as scv
30import scanpy as sc
31import numpy as np
33print(f"scvi-tools version: {scvi.__version__}")
34print(f"scvelo version: {scv.__version__}")
35```

Prerequisites

import scvi
import scvelo as scv
import scanpy as sc
import numpy as np

print(f"scvi-tools version: {scvi.__version__}")
print(f"scvelo version: {scv.__version__}")
Raw lines 25 to 36
37## Step 1: Generate Spliced/Unspliced Counts

Step 1: Generate Spliced/Unspliced Counts

Raw lines 37 to 38
39### From BAM Files (velocyto)
41```bash
42# Run velocyto on Cell Ranger output
43velocyto run10x /path/to/cellranger_output /path/to/genes.gtf
45# Output: velocyto.loom file with spliced/unspliced layers
46```

From BAM Files (velocyto)

# Run velocyto on Cell Ranger output
velocyto run10x /path/to/cellranger_output /path/to/genes.gtf

# Output: velocyto.loom file with spliced/unspliced layers
Raw lines 39 to 47
48### From kb-python (kallisto|bustools)
50```bash
51# Faster alternative using kallisto
52kb count \
53 --workflow lamanno \
54 -i index.idx \
55 -g t2g.txt \
56 -c1 spliced_t2c.txt \
57 -c2 unspliced_t2c.txt \
58 -x 10xv3 \
59 -o output \
60 R1.fastq.gz R2.fastq.gz
61```

From kb-python (kallisto|bustools)

# Faster alternative using kallisto
kb count \
    --workflow lamanno \
    -i index.idx \
    -g t2g.txt \
    -c1 spliced_t2c.txt \
    -c2 unspliced_t2c.txt \
    -x 10xv3 \
    -o output \
    R1.fastq.gz R2.fastq.gz
Raw lines 48 to 62
63## Step 2: Load Velocity Data
65```python
66# Load loom file from velocyto
67adata = scv.read("velocyto_output.loom")
69# Or load from kb-python
70adata = sc.read_h5ad("adata.h5ad")
71# Spliced in adata.layers["spliced"]
72# Unspliced in adata.layers["unspliced"]
74# Check layers
75print("Available layers:", list(adata.layers.keys()))
76print(f"Spliced shape: {adata.layers['spliced'].shape}")
77print(f"Unspliced shape: {adata.layers['unspliced'].shape}")
78```

Step 2: Load Velocity Data

# Load loom file from velocyto
adata = scv.read("velocyto_output.loom")

# Or load from kb-python
adata = sc.read_h5ad("adata.h5ad")
# Spliced in adata.layers["spliced"]
# Unspliced in adata.layers["unspliced"]

# Check layers
print("Available layers:", list(adata.layers.keys()))
print(f"Spliced shape: {adata.layers['spliced'].shape}")
print(f"Unspliced shape: {adata.layers['unspliced'].shape}")
Raw lines 63 to 79
80### Merge with Existing AnnData
82```python
83# If you have separate loom and h5ad
84ldata = scv.read("velocyto.loom")
85adata = sc.read_h5ad("processed.h5ad")
87# Merge velocity data into processed adata
88adata = scv.utils.merge(adata, ldata)
89```

Merge with Existing AnnData

# If you have separate loom and h5ad
ldata = scv.read("velocyto.loom")
adata = sc.read_h5ad("processed.h5ad")

# Merge velocity data into processed adata
adata = scv.utils.merge(adata, ldata)
Raw lines 80 to 90
91## Step 3: Preprocessing for Velocity
93```python
94# Filter and normalize
95scv.pp.filter_and_normalize(
96 adata,
97 min_shared_counts=20,
98 n_top_genes=2000
101# Compute moments (for scVelo comparison)
102scv.pp.moments(adata, n_pcs=30, n_neighbors=30)
103```

Step 3: Preprocessing for Velocity

# Filter and normalize
scv.pp.filter_and_normalize(
    adata,
    min_shared_counts=20,
    n_top_genes=2000
)

# Compute moments (for scVelo comparison)
scv.pp.moments(adata, n_pcs=30, n_neighbors=30)
Raw lines 91 to 104
105## Step 4: Run veloVI

Step 4: Run veloVI

Raw lines 105 to 106
107### Setup AnnData
109```python
110# Setup for veloVI
111scvi.model.VELOVI.setup_anndata(
112 adata,
113 spliced_layer="spliced",
114 unspliced_layer="unspliced"
116```

Setup AnnData

# Setup for veloVI
scvi.model.VELOVI.setup_anndata(
    adata,
    spliced_layer="spliced",
    unspliced_layer="unspliced"
)
Raw lines 107 to 117
118### Train Model
120```python
121# Create and train veloVI model
122vae = scvi.model.VELOVI(adata)
124vae.train(
125 max_epochs=500,
126 early_stopping=True,
127 batch_size=256
130# Check training
131vae.history["elbo_train"].plot()
132```

Train Model

# Create and train veloVI model
vae = scvi.model.VELOVI(adata)

vae.train(
    max_epochs=500,
    early_stopping=True,
    batch_size=256
)

# Check training
vae.history["elbo_train"].plot()
Raw lines 118 to 133
134### Get Velocity Estimates
136```python
137# Get latent time
138latent_time = vae.get_latent_time(n_samples=25)
139adata.obs["veloVI_latent_time"] = latent_time
141# Get velocity
142velocities = vae.get_velocity(n_samples=25)
143adata.layers["veloVI_velocity"] = velocities
145# Get expression states
146adata.layers["veloVI_expression"] = vae.get_expression_fit(n_samples=25)
147```

Get Velocity Estimates

# Get latent time
latent_time = vae.get_latent_time(n_samples=25)
adata.obs["veloVI_latent_time"] = latent_time

# Get velocity
velocities = vae.get_velocity(n_samples=25)
adata.layers["veloVI_velocity"] = velocities

# Get expression states
adata.layers["veloVI_expression"] = vae.get_expression_fit(n_samples=25)
Raw lines 134 to 148
149## Step 5: Visualize Velocity

Step 5: Visualize Velocity

Raw lines 149 to 150
151### Velocity Streamlines
153```python
154# Compute velocity graph
155scv.tl.velocity_graph(adata, vkey="veloVI_velocity")
157# Plot streamlines on UMAP
158scv.pl.velocity_embedding_stream(
159 adata,
160 basis="umap",
161 vkey="veloVI_velocity",
162 color="cell_type"
164```

Velocity Streamlines

# Compute velocity graph
scv.tl.velocity_graph(adata, vkey="veloVI_velocity")

# Plot streamlines on UMAP
scv.pl.velocity_embedding_stream(
    adata,
    basis="umap",
    vkey="veloVI_velocity",
    color="cell_type"
)
Raw lines 151 to 165
166### Velocity Arrows
168```python
169# Individual cell arrows
170scv.pl.velocity_embedding(
171 adata,
172 basis="umap",
173 vkey="veloVI_velocity",
174 arrow_length=3,
175 arrow_size=2,
176 color="cell_type"
178```

Velocity Arrows

# Individual cell arrows
scv.pl.velocity_embedding(
    adata,
    basis="umap",
    vkey="veloVI_velocity",
    arrow_length=3,
    arrow_size=2,
    color="cell_type"
)
Raw lines 166 to 179
180### Latent Time
182```python
183# Plot latent time (pseudotime from velocity)
184sc.pl.umap(adata, color="veloVI_latent_time", cmap="viridis")
185```

Latent Time

# Plot latent time (pseudotime from velocity)
sc.pl.umap(adata, color="veloVI_latent_time", cmap="viridis")
Raw lines 180 to 186
187## Step 6: Compare with scVelo
189```python
190# Run standard scVelo for comparison
191scv.tl.velocity(adata, mode="dynamical")
192scv.tl.velocity_graph(adata)
194# Compare velocity fields
195fig, axes = plt.subplots(1, 2, figsize=(12, 5))
197scv.pl.velocity_embedding_stream(
198 adata, basis="umap", ax=axes[0],
199 title="scVelo", show=False
202scv.pl.velocity_embedding_stream(
203 adata, basis="umap", vkey="veloVI_velocity",
204 ax=axes[1], title="veloVI", show=False
207plt.tight_layout()
208```

Step 6: Compare with scVelo

# Run standard scVelo for comparison
scv.tl.velocity(adata, mode="dynamical")
scv.tl.velocity_graph(adata)

# Compare velocity fields
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

scv.pl.velocity_embedding_stream(
    adata, basis="umap", ax=axes[0], 
    title="scVelo", show=False
)

scv.pl.velocity_embedding_stream(
    adata, basis="umap", vkey="veloVI_velocity",
    ax=axes[1], title="veloVI", show=False
)

plt.tight_layout()
Raw lines 187 to 209
210## Step 7: Gene-Level Analysis

Step 7: Gene-Level Analysis

Raw lines 210 to 211
212### Velocity Phase Portraits
214```python
215# Plot phase portrait for specific genes
216genes = ["SOX2", "PAX6", "DCX", "NEUROD1"]
218scv.pl.velocity(
219 adata,
220 var_names=genes,
221 vkey="veloVI_velocity",
222 colorbar=True
224```

Velocity Phase Portraits

# Plot phase portrait for specific genes
genes = ["SOX2", "PAX6", "DCX", "NEUROD1"]

scv.pl.velocity(
    adata,
    var_names=genes,
    vkey="veloVI_velocity",
    colorbar=True
)
Raw lines 212 to 225
226### Gene Dynamics
228```python
229# Plot expression over latent time
230for gene in genes:
231 fig, ax = plt.subplots(figsize=(6, 4))
233 sc.pl.scatter(
234 adata,
235 x="veloVI_latent_time",
236 y=gene,
237 color="cell_type",
238 ax=ax,
239 show=False
240 )
241 ax.set_xlabel("Latent Time")
242 ax.set_ylabel(f"{gene} Expression")
243```

Gene Dynamics

# Plot expression over latent time
for gene in genes:
    fig, ax = plt.subplots(figsize=(6, 4))
    
    sc.pl.scatter(
        adata,
        x="veloVI_latent_time",
        y=gene,
        color="cell_type",
        ax=ax,
        show=False
    )
    ax.set_xlabel("Latent Time")
    ax.set_ylabel(f"{gene} Expression")
Raw lines 226 to 244
245### Driver Genes
247```python
248# Find genes driving velocity
249scv.tl.rank_velocity_genes(
250 adata,
251 vkey="veloVI_velocity",
252 groupby="cell_type"
255# Get top genes per cluster
256df = scv.get_df(adata, "rank_velocity_genes/names")
257print(df.head(10))
258```

Driver Genes

# Find genes driving velocity
scv.tl.rank_velocity_genes(
    adata,
    vkey="veloVI_velocity",
    groupby="cell_type"
)

# Get top genes per cluster
df = scv.get_df(adata, "rank_velocity_genes/names")
print(df.head(10))
Raw lines 245 to 259
260## Step 8: Uncertainty Quantification
262veloVI provides uncertainty estimates:
264```python
265# Get velocity with uncertainty
266velocity_mean, velocity_std = vae.get_velocity(
267 n_samples=100,
268 return_mean=True,
269 return_numpy=True
272# Store uncertainty
273adata.layers["velocity_uncertainty"] = velocity_std
275# Visualize uncertainty
276adata.obs["mean_velocity_uncertainty"] = velocity_std.mean(axis=1)
277sc.pl.umap(adata, color="mean_velocity_uncertainty")
278```

Step 8: Uncertainty Quantification

veloVI provides uncertainty estimates:

# Get velocity with uncertainty
velocity_mean, velocity_std = vae.get_velocity(
    n_samples=100,
    return_mean=True,
    return_numpy=True
)

# Store uncertainty
adata.layers["velocity_uncertainty"] = velocity_std

# Visualize uncertainty
adata.obs["mean_velocity_uncertainty"] = velocity_std.mean(axis=1)
sc.pl.umap(adata, color="mean_velocity_uncertainty")
Raw lines 260 to 279
280## Complete Pipeline
282```python
283def run_velocity_analysis(
284 adata,
285 spliced_layer="spliced",
286 unspliced_layer="unspliced",
287 n_top_genes=2000,
288 max_epochs=500
289):
290 """
291 Complete RNA velocity analysis with veloVI.
293 Parameters
294 ----------
295 adata : AnnData
296 Data with spliced/unspliced layers
297 spliced_layer : str
298 Layer name for spliced counts
299 unspliced_layer : str
300 Layer name for unspliced counts
301 n_top_genes : int
302 Number of velocity genes
303 max_epochs : int
304 Training epochs
306 Returns
307 -------
308 AnnData with velocity and model
309 """
310 import scvi
311 import scvelo as scv
312 import scanpy as sc
314 adata = adata.copy()
316 # Preprocessing
317 scv.pp.filter_and_normalize(
318 adata,
319 min_shared_counts=20,
320 n_top_genes=n_top_genes
321 )
323 # Compute moments (needed for some visualizations)
324 scv.pp.moments(adata, n_pcs=30, n_neighbors=30)
326 # Setup veloVI
327 scvi.model.VELOVI.setup_anndata(
328 adata,
329 spliced_layer=spliced_layer,
330 unspliced_layer=unspliced_layer
331 )
333 # Train
334 model = scvi.model.VELOVI(adata)
335 model.train(max_epochs=max_epochs, early_stopping=True)
337 # Get results
338 adata.obs["latent_time"] = model.get_latent_time(n_samples=25)
339 adata.layers["velocity"] = model.get_velocity(n_samples=25)
341 # Compute velocity graph for visualization
342 scv.tl.velocity_graph(adata, vkey="velocity")
344 # Compute UMAP if not present
345 if "X_umap" not in adata.obsm:
346 sc.pp.neighbors(adata)
347 sc.tl.umap(adata)
349 return adata, model
351# Usage
352adata_velocity, model = run_velocity_analysis(adata)
354# Visualize
355scv.pl.velocity_embedding_stream(
356 adata_velocity,
357 basis="umap",
358 vkey="velocity",
359 color="cell_type"
362sc.pl.umap(adata_velocity, color="latent_time")
363```

Complete Pipeline

def run_velocity_analysis(
    adata,
    spliced_layer="spliced",
    unspliced_layer="unspliced",
    n_top_genes=2000,
    max_epochs=500
):
    """
    Complete RNA velocity analysis with veloVI.
    
    Parameters
    ----------
    adata : AnnData
        Data with spliced/unspliced layers
    spliced_layer : str
        Layer name for spliced counts
    unspliced_layer : str
        Layer name for unspliced counts
    n_top_genes : int
        Number of velocity genes
    max_epochs : int
        Training epochs
        
    Returns
    -------
    AnnData with velocity and model
    """
    import scvi
    import scvelo as scv
    import scanpy as sc
    
    adata = adata.copy()
    
    # Preprocessing
    scv.pp.filter_and_normalize(
        adata,
        min_shared_counts=20,
        n_top_genes=n_top_genes
    )
    
    # Compute moments (needed for some visualizations)
    scv.pp.moments(adata, n_pcs=30, n_neighbors=30)
    
    # Setup veloVI
    scvi.model.VELOVI.setup_anndata(
        adata,
        spliced_layer=spliced_layer,
        unspliced_layer=unspliced_layer
    )
    
    # Train
    model = scvi.model.VELOVI(adata)
    model.train(max_epochs=max_epochs, early_stopping=True)
    
    # Get results
    adata.obs["latent_time"] = model.get_latent_time(n_samples=25)
    adata.layers["velocity"] = model.get_velocity(n_samples=25)
    
    # Compute velocity graph for visualization
    scv.tl.velocity_graph(adata, vkey="velocity")
    
    # Compute UMAP if not present
    if "X_umap" not in adata.obsm:
        sc.pp.neighbors(adata)
        sc.tl.umap(adata)
    
    return adata, model

# Usage
adata_velocity, model = run_velocity_analysis(adata)

# Visualize
scv.pl.velocity_embedding_stream(
    adata_velocity,
    basis="umap",
    vkey="velocity",
    color="cell_type"
)

sc.pl.umap(adata_velocity, color="latent_time")
Raw lines 280 to 364
365## Advanced: Batch-Aware Velocity
367```python
368# For multi-batch data, include batch in model
369scvi.model.VELOVI.setup_anndata(
370 adata,
371 spliced_layer="spliced",
372 unspliced_layer="unspliced",
373 batch_key="batch"
376model = scvi.model.VELOVI(adata)
377model.train()
378```

Advanced: Batch-Aware Velocity

# For multi-batch data, include batch in model
scvi.model.VELOVI.setup_anndata(
    adata,
    spliced_layer="spliced",
    unspliced_layer="unspliced",
    batch_key="batch"
)

model = scvi.model.VELOVI(adata)
model.train()
Raw lines 365 to 379
380## Interpreting Results

Interpreting Results

Raw lines 380 to 381
382### Good Velocity Signal
384- Streamlines follow expected differentiation
385- Latent time correlates with known biology
386- Phase portraits show clear dynamics

Good Velocity Signal

  • Streamlines follow expected differentiation
  • Latent time correlates with known biology
  • Phase portraits show clear dynamics
Raw lines 382 to 387
388### Poor Velocity Signal
390- Random/chaotic streamlines
391- No correlation with known markers
392- May indicate:
393 - Insufficient unspliced reads
394 - Cells at steady state
395 - Technical issues

Poor Velocity Signal

  • Random/chaotic streamlines
  • No correlation with known markers
  • May indicate:
  • Insufficient unspliced reads
  • Cells at steady state
  • Technical issues
Raw lines 388 to 396
397## Troubleshooting
399| Issue | Cause | Solution |
400|-------|-------|----------|
401| No velocity signal | Low unspliced counts | Check sequencing depth, use kb-python |
402| Reversed direction | Wrong root assignment | Manually set root cells |
403| Noisy streamlines | Too many genes | Reduce n_top_genes |
404| Memory error | Large dataset | Reduce batch_size |

Troubleshooting

IssueCauseSolution
No velocity signalLow unspliced countsCheck sequencing depth, use kb-python
Reversed directionWrong root assignmentManually set root cells
Noisy streamlinesToo many genesReduce n_top_genes
Memory errorLarge datasetReduce batch_size
Raw lines 397 to 405
406## Key References
408- Gayoso et al. (2023) "Deep generative modeling of transcriptional dynamics for RNA velocity analysis in single cells"
409- La Manno et al. (2018) "RNA velocity of single cells"
410- Bergen et al. (2020) "Generalizing RNA velocity to transient cell states through dynamical modeling"

Key References

  • Gayoso et al. (2023) "Deep generative modeling of transcriptional dynamics for RNA velocity analysis in single cells"
  • La Manno et al. (2018) "RNA velocity of single cells"
  • Bergen et al. (2020) "Generalizing RNA velocity to transient cell states through dynamical modeling"
Raw lines 406 to 410