Skills › Knowledge Work Plugins › scvi-tools › atac_peakvi.md
atac_peakvi.md
398 lines · bio-research/skills/scvi-tools/references/atac_peakvi.md at 2574e29
1# scATAC-seq Analysis with PeakVI
3This reference covers single-cell ATAC-seq analysis using PeakVI for dimensionality reduction, batch correction, and differential accessibility.
scATAC-seq Analysis with PeakVI
This reference covers single-cell ATAC-seq analysis using PeakVI for dimensionality reduction, batch correction, and differential accessibility.
Raw lines 1 to 45## Overview
7PeakVI is a deep generative model for scATAC-seq data that:
8- Models binary accessibility (peak open/closed)
9- Handles batch effects
10- Provides latent representation for clustering
11- Enables differential accessibility analysis
Overview
PeakVI is a deep generative model for scATAC-seq data that:
- Models binary accessibility (peak open/closed)
- Handles batch effects
- Provides latent representation for clustering
- Enables differential accessibility analysis
13## Prerequisites
15```python
16import scvi
17import scanpy as sc
18import numpy as np
19import anndata as ad
21print(f"scvi-tools version: {scvi.__version__}")
22```
Prerequisites
import scvi
import scanpy as sc
import numpy as np
import anndata as ad
print(f"scvi-tools version: {scvi.__version__}")Raw lines 13 to 23Step 1: Load and Prepare ATAC Data
Raw lines 24 to 2526### From 10x Genomics (Cell Ranger ATAC)
28```python
29# Peak-cell matrix from fragments
30# Usually in filtered_peak_bc_matrix format
32adata = sc.read_10x_h5("filtered_peak_bc_matrix.h5")
34# Or from mtx format
35adata = sc.read_10x_mtx("filtered_peak_bc_matrix/")
37# Check structure
38print(f"Cells: {adata.n_obs}, Peaks: {adata.n_vars}")
39print(f"Sparsity: {1 - adata.X.nnz / (adata.n_obs * adata.n_vars):.2%}")
40```
From 10x Genomics (Cell Ranger ATAC)
# Peak-cell matrix from fragments
# Usually in filtered_peak_bc_matrix format
adata = sc.read_10x_h5("filtered_peak_bc_matrix.h5")
# Or from mtx format
adata = sc.read_10x_mtx("filtered_peak_bc_matrix/")
# Check structure
print(f"Cells: {adata.n_obs}, Peaks: {adata.n_vars}")
print(f"Sparsity: {1 - adata.X.nnz / (adata.n_obs * adata.n_vars):.2%}")Raw lines 26 to 4142### From ArchR/Signac
44```python
45# Export from ArchR (in R)
46# saveArchRProject(proj, outputDirectory="atac_export", load=FALSE)
47# Then read the exported files in Python
49# From Signac:
50# Export peak matrix and metadata
51```
From ArchR/Signac
# Export from ArchR (in R)
# saveArchRProject(proj, outputDirectory="atac_export", load=FALSE)
# Then read the exported files in Python
# From Signac:
# Export peak matrix and metadataRaw lines 42 to 5253## Step 2: Quality Control
55```python
56# Calculate QC metrics
57sc.pp.calculate_qc_metrics(adata, inplace=True)
59# Key metrics for ATAC:
60# - n_genes_by_counts: peaks per cell (should rename)
61# - total_counts: fragments per cell
62adata.obs['n_peaks'] = adata.obs['n_genes_by_counts']
63adata.obs['total_fragments'] = adata.obs['total_counts']
65# Filter cells
66adata = adata[adata.obs['n_peaks'] > 500].copy()
67adata = adata[adata.obs['n_peaks'] < 50000].copy() # Remove potential doublets
69# Filter peaks (accessible in at least n cells)
70sc.pp.filter_genes(adata, min_cells=10)
72print(f"After QC: {adata.shape}")
73```
Step 2: Quality Control
# Calculate QC metrics
sc.pp.calculate_qc_metrics(adata, inplace=True)
# Key metrics for ATAC:
# - n_genes_by_counts: peaks per cell (should rename)
# - total_counts: fragments per cell
adata.obs['n_peaks'] = adata.obs['n_genes_by_counts']
adata.obs['total_fragments'] = adata.obs['total_counts']
# Filter cells
adata = adata[adata.obs['n_peaks'] > 500].copy()
adata = adata[adata.obs['n_peaks'] < 50000].copy() # Remove potential doublets
# Filter peaks (accessible in at least n cells)
sc.pp.filter_genes(adata, min_cells=10)
print(f"After QC: {adata.shape}")Raw lines 53 to 7475### Binarize Data
77```python
78# PeakVI works with binary accessibility
79# Binarize if not already binary
80adata.X = (adata.X > 0).astype(np.float32)
82# Verify
83print(f"Unique values: {np.unique(adata.X.data)}")
84```
Binarize Data
# PeakVI works with binary accessibility
# Binarize if not already binary
adata.X = (adata.X > 0).astype(np.float32)
# Verify
print(f"Unique values: {np.unique(adata.X.data)}")Raw lines 75 to 8586## Step 3: Feature Selection
88Unlike RNA-seq, peak selection for ATAC is less established. Options:
Step 3: Feature Selection
Unlike RNA-seq, peak selection for ATAC is less established. Options:
Raw lines 86 to 8990### Option A: Most Accessible Peaks
92```python
93# Select top peaks by accessibility frequency
94peak_accessibility = np.array(adata.X.sum(axis=0)).flatten()
95top_peaks = np.argsort(peak_accessibility)[-50000:] # Top 50k peaks
97adata = adata[:, top_peaks].copy()
98```
Option A: Most Accessible Peaks
# Select top peaks by accessibility frequency
peak_accessibility = np.array(adata.X.sum(axis=0)).flatten()
top_peaks = np.argsort(peak_accessibility)[-50000:] # Top 50k peaks
adata = adata[:, top_peaks].copy()Raw lines 90 to 99100### Option B: Variable Peaks
102```python
103# Select peaks with high variance
104# (Most informative for clustering)
105from sklearn.feature_selection import VarianceThreshold
107selector = VarianceThreshold(threshold=0.05)
108selector.fit(adata.X)
109adata = adata[:, selector.get_support()].copy()
110```
Option B: Variable Peaks
# Select peaks with high variance
# (Most informative for clustering)
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold(threshold=0.05)
selector.fit(adata.X)
adata = adata[:, selector.get_support()].copy()Raw lines 100 to 111112### Option C: Peaks Near Genes
114```python
115# Keep peaks within promoter regions or gene bodies
116# Requires peak annotation
117# gene_peaks = peaks with gene annotation
118# adata = adata[:, adata.var['near_gene']].copy()
119```
Option C: Peaks Near Genes
# Keep peaks within promoter regions or gene bodies
# Requires peak annotation
# gene_peaks = peaks with gene annotation
# adata = adata[:, adata.var['near_gene']].copy()Raw lines 112 to 120121## Step 4: Add Batch Information
123```python
124# Add batch annotation if multiple samples
125adata.obs['batch'] = adata.obs['sample_id'] # Or appropriate column
127print(adata.obs['batch'].value_counts())
128```
Step 4: Add Batch Information
# Add batch annotation if multiple samples
adata.obs['batch'] = adata.obs['sample_id'] # Or appropriate column
print(adata.obs['batch'].value_counts())Raw lines 121 to 129130## Step 5: Setup and Train PeakVI
132```python
133# Setup AnnData
134scvi.model.PEAKVI.setup_anndata(
135 adata,
136 batch_key="batch" # Optional, omit for single batch
137)
139# Create model
140model = scvi.model.PEAKVI(
141 adata,
142 n_latent=20, # Latent dimensions
143 n_layers_encoder=2,
144 n_layers_decoder=2
145)
147# Train
148model.train(
149 max_epochs=200,
150 early_stopping=True,
151 batch_size=128
152)
154# Check training
155model.history['elbo_train'].plot()
156```
Step 5: Setup and Train PeakVI
# Setup AnnData
scvi.model.PEAKVI.setup_anndata(
adata,
batch_key="batch" # Optional, omit for single batch
)
# Create model
model = scvi.model.PEAKVI(
adata,
n_latent=20, # Latent dimensions
n_layers_encoder=2,
n_layers_decoder=2
)
# Train
model.train(
max_epochs=200,
early_stopping=True,
batch_size=128
)
# Check training
model.history['elbo_train'].plot()Raw lines 130 to 157158## Step 6: Get Latent Representation
160```python
161# Latent space for downstream analysis
162adata.obsm["X_PeakVI"] = model.get_latent_representation()
164# Clustering and visualization
165sc.pp.neighbors(adata, use_rep="X_PeakVI", n_neighbors=15)
166sc.tl.umap(adata)
167sc.tl.leiden(adata, resolution=0.5)
169# Visualize
170sc.pl.umap(adata, color=['leiden', 'batch'], ncols=2)
171```
Step 6: Get Latent Representation
# Latent space for downstream analysis
adata.obsm["X_PeakVI"] = model.get_latent_representation()
# Clustering and visualization
sc.pp.neighbors(adata, use_rep="X_PeakVI", n_neighbors=15)
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=0.5)
# Visualize
sc.pl.umap(adata, color=['leiden', 'batch'], ncols=2)Raw lines 158 to 172173## Step 7: Differential Accessibility
175```python
176# Differential accessibility between clusters
177da_results = model.differential_accessibility(
178 groupby='leiden',
179 group1='0',
180 group2='1'
181)
183# Filter significant peaks
184da_sig = da_results[
185 (da_results['is_da_fdr_0.05']) &
186 (abs(da_results['lfc_mean']) > 1)
187]
189print(f"Significant DA peaks: {len(da_sig)}")
190print(da_sig.head())
191```
Step 7: Differential Accessibility
# Differential accessibility between clusters
da_results = model.differential_accessibility(
groupby='leiden',
group1='0',
group2='1'
)
# Filter significant peaks
da_sig = da_results[
(da_results['is_da_fdr_0.05']) &
(abs(da_results['lfc_mean']) > 1)
]
print(f"Significant DA peaks: {len(da_sig)}")
print(da_sig.head())Raw lines 173 to 192193### DA Between Conditions
195```python
196# Compare conditions within cell type
197adata_subset = adata[adata.obs['cell_type'] == 'CD4 T cells'].copy()
199da_condition = model.differential_accessibility(
200 groupby='condition',
201 group1='treated',
202 group2='control'
203)
204```
DA Between Conditions
# Compare conditions within cell type
adata_subset = adata[adata.obs['cell_type'] == 'CD4 T cells'].copy()
da_condition = model.differential_accessibility(
groupby='condition',
group1='treated',
group2='control'
)Raw lines 193 to 205206## Step 8: Peak Annotation
208```python
209# Annotate peaks with nearest genes
210# Using pybedtools or similar
212# Example peak name format: chr1:1000-2000
213# Parse into bed format for annotation
215import pandas as pd
217def parse_peak_names(peak_names):
218 """Parse peak names into bed format."""
219 records = []
220 for peak in peak_names:
221 chrom, coords = peak.split(':')
222 start, end = coords.split('-')
223 records.append({
224 'chrom': chrom,
225 'start': int(start),
226 'end': int(end),
227 'peak': peak
228 })
229 return pd.DataFrame(records)
231peak_bed = parse_peak_names(adata.var_names)
232```
Step 8: Peak Annotation
# Annotate peaks with nearest genes
# Using pybedtools or similar
# Example peak name format: chr1:1000-2000
# Parse into bed format for annotation
import pandas as pd
def parse_peak_names(peak_names):
"""Parse peak names into bed format."""
records = []
for peak in peak_names:
chrom, coords = peak.split(':')
start, end = coords.split('-')
records.append({
'chrom': chrom,
'start': int(start),
'end': int(end),
'peak': peak
})
return pd.DataFrame(records)
peak_bed = parse_peak_names(adata.var_names)Raw lines 206 to 233234## Step 9: Motif Analysis
236```python
237# Export significant peaks for motif analysis
238# Use HOMER, MEME, or chromVAR
240# Export peak sequences
241sig_peaks = da_sig.index.tolist()
242peak_bed_sig = peak_bed[peak_bed['peak'].isin(sig_peaks)]
243peak_bed_sig.to_csv("significant_peaks.bed", sep='\t', index=False, header=False)
245# Then run HOMER:
246# findMotifsGenome.pl significant_peaks.bed hg38 motif_output/ -size 200
247```
Step 9: Motif Analysis
# Export significant peaks for motif analysis
# Use HOMER, MEME, or chromVAR
# Export peak sequences
sig_peaks = da_sig.index.tolist()
peak_bed_sig = peak_bed[peak_bed['peak'].isin(sig_peaks)]
peak_bed_sig.to_csv("significant_peaks.bed", sep='\t', index=False, header=False)
# Then run HOMER:
# findMotifsGenome.pl significant_peaks.bed hg38 motif_output/ -size 200Raw lines 234 to 248249## Step 10: Gene Activity Scores
251```python
252# Compute gene activity from peak accessibility
253# (Requires peak-gene annotations)
255def compute_gene_activity(adata, peak_gene_map):
256 """
257 Compute gene activity scores from peak accessibility.
259 Parameters
260 ----------
261 adata : AnnData
262 ATAC data with peaks
263 peak_gene_map : dict
264 Mapping of peaks to genes
266 Returns
267 -------
268 AnnData with gene activity scores
269 """
270 from scipy.sparse import csr_matrix
272 genes = list(set(peak_gene_map.values()))
273 gene_matrix = np.zeros((adata.n_obs, len(genes)))
275 for i, gene in enumerate(genes):
276 gene_peaks = [p for p, g in peak_gene_map.items() if g == gene]
277 if gene_peaks:
278 peak_idx = [list(adata.var_names).index(p) for p in gene_peaks if p in adata.var_names]
279 if peak_idx:
280 gene_matrix[:, i] = np.array(adata.X[:, peak_idx].sum(axis=1)).flatten()
282 adata_gene = ad.AnnData(
283 X=csr_matrix(gene_matrix),
284 obs=adata.obs.copy(),
285 var=pd.DataFrame(index=genes)
286 )
288 return adata_gene
289```
Step 10: Gene Activity Scores
# Compute gene activity from peak accessibility
# (Requires peak-gene annotations)
def compute_gene_activity(adata, peak_gene_map):
"""
Compute gene activity scores from peak accessibility.
Parameters
----------
adata : AnnData
ATAC data with peaks
peak_gene_map : dict
Mapping of peaks to genes
Returns
-------
AnnData with gene activity scores
"""
from scipy.sparse import csr_matrix
genes = list(set(peak_gene_map.values()))
gene_matrix = np.zeros((adata.n_obs, len(genes)))
for i, gene in enumerate(genes):
gene_peaks = [p for p, g in peak_gene_map.items() if g == gene]
if gene_peaks:
peak_idx = [list(adata.var_names).index(p) for p in gene_peaks if p in adata.var_names]
if peak_idx:
gene_matrix[:, i] = np.array(adata.X[:, peak_idx].sum(axis=1)).flatten()
adata_gene = ad.AnnData(
X=csr_matrix(gene_matrix),
obs=adata.obs.copy(),
var=pd.DataFrame(index=genes)
)
return adata_geneRaw lines 249 to 290291## Complete Pipeline
293```python
294def analyze_scatac(
295 adata,
296 batch_key=None,
297 n_top_peaks=50000,
298 n_latent=20,
299 resolution=0.5
300):
301 """
302 Complete scATAC-seq analysis with PeakVI.
304 Parameters
305 ----------
306 adata : AnnData
307 Raw peak-cell matrix
308 batch_key : str, optional
309 Batch annotation column
310 n_top_peaks : int
311 Number of top peaks to use
312 n_latent : int
313 Latent dimensions
314 resolution : float
315 Leiden clustering resolution
317 Returns
318 -------
319 Tuple of (processed AnnData, trained model)
320 """
321 import scvi
322 import scanpy as sc
323 import numpy as np
325 adata = adata.copy()
327 # QC
328 sc.pp.calculate_qc_metrics(adata, inplace=True)
329 adata = adata[adata.obs['n_genes_by_counts'] > 500].copy()
330 sc.pp.filter_genes(adata, min_cells=10)
332 # Binarize
333 adata.X = (adata.X > 0).astype(np.float32)
335 # Select top peaks
336 if adata.n_vars > n_top_peaks:
337 peak_accessibility = np.array(adata.X.sum(axis=0)).flatten()
338 top_peaks = np.argsort(peak_accessibility)[-n_top_peaks:]
339 adata = adata[:, top_peaks].copy()
341 # Setup PeakVI
342 scvi.model.PEAKVI.setup_anndata(adata, batch_key=batch_key)
344 # Train
345 model = scvi.model.PEAKVI(adata, n_latent=n_latent)
346 model.train(max_epochs=200, early_stopping=True)
348 # Latent representation
349 adata.obsm["X_PeakVI"] = model.get_latent_representation()
351 # Clustering
352 sc.pp.neighbors(adata, use_rep="X_PeakVI")
353 sc.tl.umap(adata)
354 sc.tl.leiden(adata, resolution=resolution)
356 return adata, model
358# Usage
359adata, model = analyze_scatac(
360 adata,
361 batch_key="sample",
362 n_top_peaks=50000
363)
365# Visualize
366sc.pl.umap(adata, color=['leiden', 'sample'])
368# Differential accessibility
369da_results = model.differential_accessibility(
370 groupby='leiden',
371 group1='0',
372 group2='1'
373)
374```
Complete Pipeline
def analyze_scatac(
adata,
batch_key=None,
n_top_peaks=50000,
n_latent=20,
resolution=0.5
):
"""
Complete scATAC-seq analysis with PeakVI.
Parameters
----------
adata : AnnData
Raw peak-cell matrix
batch_key : str, optional
Batch annotation column
n_top_peaks : int
Number of top peaks to use
n_latent : int
Latent dimensions
resolution : float
Leiden clustering resolution
Returns
-------
Tuple of (processed AnnData, trained model)
"""
import scvi
import scanpy as sc
import numpy as np
adata = adata.copy()
# QC
sc.pp.calculate_qc_metrics(adata, inplace=True)
adata = adata[adata.obs['n_genes_by_counts'] > 500].copy()
sc.pp.filter_genes(adata, min_cells=10)
# Binarize
adata.X = (adata.X > 0).astype(np.float32)
# Select top peaks
if adata.n_vars > n_top_peaks:
peak_accessibility = np.array(adata.X.sum(axis=0)).flatten()
top_peaks = np.argsort(peak_accessibility)[-n_top_peaks:]
adata = adata[:, top_peaks].copy()
# Setup PeakVI
scvi.model.PEAKVI.setup_anndata(adata, batch_key=batch_key)
# Train
model = scvi.model.PEAKVI(adata, n_latent=n_latent)
model.train(max_epochs=200, early_stopping=True)
# Latent representation
adata.obsm["X_PeakVI"] = model.get_latent_representation()
# Clustering
sc.pp.neighbors(adata, use_rep="X_PeakVI")
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=resolution)
return adata, model
# Usage
adata, model = analyze_scatac(
adata,
batch_key="sample",
n_top_peaks=50000
)
# Visualize
sc.pl.umap(adata, color=['leiden', 'sample'])
# Differential accessibility
da_results = model.differential_accessibility(
groupby='leiden',
group1='0',
group2='1'
)Raw lines 291 to 375376## Integration with scRNA-seq
378For multiome data or separate RNA/ATAC from same cells:
380```python
381# See MultiVI for joint RNA+ATAC analysis
382# Or use WNN (weighted nearest neighbors) approach
384# Transfer labels from RNA to ATAC using shared latent space
385```
Integration with scRNA-seq
For multiome data or separate RNA/ATAC from same cells:
# See MultiVI for joint RNA+ATAC analysis
# Or use WNN (weighted nearest neighbors) approach
# Transfer labels from RNA to ATAC using shared latent spaceRaw lines 376 to 386387## Troubleshooting
389| Issue | Cause | Solution |
390|-------|-------|----------|
391| Training slow | Too many peaks | Subset to top 50k peaks |
392| Poor clustering | Too few informative peaks | Use variable peaks |
393| Batch dominates | Strong technical effects | Ensure batch_key is set |
394| Memory error | Large peak matrix | Use sparse format, reduce peaks |
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Training slow | Too many peaks | Subset to top 50k peaks |
| Poor clustering | Too few informative peaks | Use variable peaks |
| Batch dominates | Strong technical effects | Ensure batch_key is set |
| Memory error | Large peak matrix | Use sparse format, reduce peaks |
396## Key References
398- Ashuach et al. (2022) "PeakVI: A deep generative model for single-cell chromatin accessibility analysis"
Key References
- Ashuach et al. (2022) "PeakVI: A deep generative model for single-cell chromatin accessibility analysis"