alterlab-scanpy
Run the standard single-cell RNA-seq analysis pipeline with Scanpy on AnnData — QC filtering, normalization, dimensionality reduction (PCA, UMAP, t-SNE), Leiden/Louvain clustering, marker/differential expression, PAGA trajectories, and plotting. Use when analyzing scRNA-seq data
Install
npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/bioinformatics/alterlab-scanpy
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Scanpy: Single-Cell Analysis
Overview
Scanpy is a scalable Python toolkit for analyzing single-cell RNA-seq data, built on AnnData. Apply this skill for complete single-cell workflows including quality control, normalization, dimensionality reduction, clustering, marker gene identification, visualization, and trajectory analysis.
When to Use This Skill
This skill should be used when:
- Analyzing single-cell RNA-seq data (.h5ad, 10X, CSV formats)
- Performing quality control on scRNA-seq datasets
- Creating UMAP, t-SNE, or PCA visualizations
- Identifying cell clusters and finding marker genes
- Annotating cell types based on gene expression
- Conducting trajectory inference or pseudotime analysis
- Generating publication-quality single-cell plots
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
Building, reading, or reshaping the .h5ad object itself (layers, obs/var, concatenation, backed/Dask mode) |
alterlab-anndata |
| RNA velocity from spliced/unspliced counts | alterlab-scvelo |
| Probabilistic latent models — scVI/scANVI integration, model-based DE | alterlab-scvi-tools |
| Spatial transcriptomics — Visium/Xenium/MERFISH neighborhood and image analysis | alterlab-squidpy-spatial |
| Bulk RNA-seq differential expression from a count matrix | alterlab-pydeseq2 |
Quick Start
Basic Import and Setup
import scanpy as sc
import pandas as pd
import numpy as np
# Configure settings
sc.settings.verbosity = 3
sc.settings.set_figure_params(dpi=80, facecolor='white')
sc.settings.figdir = './figures/'
Loading Data
# From 10X Genomics
adata = sc.read_10x_mtx('path/to/data/')
adata = sc.read_10x_h5('path/to/data.h5')
# From h5ad (AnnData format)
adata = sc.read_h5ad('path/to/data.h5ad')
# From CSV
adata = sc.read_csv('path/to/data.csv')
Understanding AnnData Structure
The AnnData object is the core data structure in scanpy:
adata.X # Expression matrix (cells × genes)
adata.obs # Cell metadata (DataFrame)
adata.var # Gene metadata (DataFrame)
adata.uns # Unstructured annotations (dict)
adata.obsm # Multi-dimensional cell data (PCA, UMAP)
adata.raw # Raw data backup
# Access cell and gene names
adata.obs_names # Cell barcodes
adata.var_names # Gene names
Standard Analysis Workflow
1. Quality Control
Identify and filter low-quality cells and genes:
# Identify mitochondrial genes
adata.var['mt'] = adata.var_names.str.startswith('MT-')
# Calculate QC metrics
sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], inplace=True)
# Visualize QC metrics
sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt'],
jitter=0.4, multi_panel=True)
# Filter cells and genes
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_genes(adata, min_cells=3)
adata = adata[adata.obs.pct_counts_mt < 5, :] # Remove high MT% cells
Use the QC script for automated analysis:
python scripts/qc_analysis.py input_file.h5ad --output filtered.h5ad
2. Normalization and Preprocessing
# Normalize to 10,000 counts per cell
sc.pp.normalize_total(adata, target_sum=1e4)
# Log-transform
sc.pp.log1p(adata)
# Save raw counts for later
adata.raw = adata
# Identify highly variable genes
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
sc.pl.highly_variable_genes(adata)
# Subset to highly variable genes
adata = adata[:, adata.var.highly_variable]
# Regress out unwanted variation
sc.pp.regress_out(adata, ['total_counts', 'pct_counts_mt'])
# Scale data
sc.pp.scale(adata, max_value=10)
3. Dimensionality Reduction
# PCA
sc.tl.pca(adata, svd_solver='arpack')
sc.pl.pca_variance_ratio(adata, log=True) # Check elbow plot
# Compute neighborhood graph
sc.pp.neighbors(adata, n_neighbors=10, n_pcs=40)
# UMAP for visualization
sc.tl.umap(adata)
sc.pl.umap(adata, color='leiden')
# Alternative: t-SNE
sc.tl.tsne(adata)
4. Clustering
# Leiden clustering (recommended). Pass flavor='igraph' (the future default) +
# n_iterations=2 — the bare call emits a FutureWarning and uses the slow backend.
sc.tl.leiden(adata, resolution=0.5, flavor='igraph', n_iterations=2)
sc.pl.umap(adata, color='leiden', legend_loc='on data')
# Try multiple resolutions to find optimal granularity
for res in [0.3, 0.5, 0.8, 1.0]:
sc.tl.leiden(adata, resolution=res, flavor='igraph', n_iterations=2,
key_added=f'leiden_{res}')
5. Marker Gene Identification
# Find marker genes for each cluster
sc.tl.rank_genes_groups(adata, 'leiden', method='wilcoxon')
# Scanpy >=1.12 warns here on purpose: the clusters were derived from the same
# counts being tested, so the p-values are anti-conservative ("double dipping").
# Treat the ranking as a marker-discovery heuristic, and get real p-values from an
# independent grouping (condition, donor, genotype) or a pseudobulk test
# (`alterlab-pydeseq2`) before reporting significance.
# Visualize results
sc.pl.rank_genes_groups(adata, n_genes=25, sharey=False)
sc.pl.rank_genes_groups_heatmap(adata, n_genes=10)
sc.pl.rank_genes_groups_dotplot(adata, n_genes=5)
# Get results as DataFrame
markers = sc.get.rank_genes_groups_df(adata, group='0')
6. Cell Type Annotation
# Define marker genes for known cell types
marker_genes = ['CD3D', 'CD14', 'MS4A1', 'NKG7', 'FCGR3A']
# Visualize markers
sc.pl.umap(adata, color=marker_genes, use_raw=True)
sc.pl.dotplot(adata, var_names=marker_genes, groupby='leiden')
# Manual annotation
cluster_to_celltype = {
'0': 'CD4 T cells',
'1': 'CD14+ Monocytes',
'2': 'B cells',
'3': 'CD8 T cells',
}
adata.obs['cell_type'] = adata.obs['leiden'].map(cluster_to_celltype)
# Visualize annotated types
sc.pl.umap(adata, color='cell_type', legend_loc='on data')
7. Save Results
# Save processed data
adata.write('results/processed_data.h5ad')
# Export metadata
adata.obs.to_csv('results/cell_metadata.csv')
adata.var.to_csv('results/gene_metadata.csv')
Common Tasks
Creating Publication-Quality Plots
import matplotlib.pyplot as plt
# Set high-quality defaults
sc.settings.set_figure_params(dpi=300, frameon=False, figsize=(5, 5))
# The `save=` parameter of every sc.pl.* function is deprecated as of Scanpy 1.12.
# Pass show=False and save the current figure yourself — this also gives you control
# over bbox/dpi and lets you compose panels.
sc.pl.umap(adata, color='cell_type',
palette='Set2',
legend_loc='on data',
legend_fontsize=12,
legend_fontoutline=2,
frameon=False,
show=False)
plt.savefig('figures/umap_publication.pdf', bbox_inches='tight')
plt.close()
# Heatmap of marker genes
sc.pl.heatmap(adata, var_names=genes, groupby='cell_type',
swap_axes=True, show_gene_labels=True, show=False)
plt.savefig('figures/markers_heatmap.pdf', bbox_inches='tight')
plt.close()
Refer to references/plotting_guide.md for comprehensive visualization examples.
Trajectory Inference
# PAGA (Partition-based graph abstraction)
sc.tl.paga(adata, groups='leiden')
sc.pl.paga(adata, color='leiden')
# Diffusion pseudotime
adata.uns['iroot'] = np.flatnonzero(adata.obs['leiden'] == '0')[0]
sc.tl.dpt(adata)
sc.pl.umap(adata, color='dpt_pseudotime')
Differential Expression Between Conditions
# Compare treated vs control within cell types
adata_subset = adata[adata.obs['cell_type'] == 'T cells']
sc.tl.rank_genes_groups(adata_subset, groupby='condition',
groups=['treated'], reference='control')
sc.pl.rank_genes_groups(adata_subset, groups=['treated'])
Gene Set Scoring
# Score cells for gene set expression
gene_set = ['CD3D', 'CD3E', 'CD3G']
sc.tl.score_genes(adata, gene_set, score_name='T_cell_score')
sc.pl.umap(adata, color='T_cell_score')
Batch Correction
# ComBat batch correction
sc.pp.combat(adata, key='batch')
# Alternative: use Harmony or scVI (separate packages)
Key Parameters to Adjust
Quality Control
min_genes: Minimum genes per cell (typically 200-500)min_cells: Minimum cells per gene (typically 3-10)pct_counts_mt: Mitochondrial threshold (typically 5-20%)
Normalization
target_sum: Target counts per cell (default 1e4)
Feature Selection
n_top_genes: Number of HVGs (typically 2000-3000)min_mean,max_mean,min_disp: HVG selection parameters
Dimensionality Reduction
n_pcs: Number of principal components (check variance ratio plot)n_neighbors: Number of neighbors (typically 10-30)
Clustering
resolution: Clustering granularity (0.4-1.2, higher = more clusters)
Common Pitfalls and Best Practices
- Always save raw counts:
adata.raw = adatabefore filtering genes - Check QC plots carefully: Adjust thresholds based on dataset quality
- Use Leiden with
flavor='igraph': Far faster than the legacyleidenalgbackend, andsc.tl.louvain()is deprecated as of Scanpy 1.12 —uv pip install igraph(not bundled with scanpy) - Try multiple clustering resolutions: Find optimal granularity
- Validate cell type annotations: Use multiple marker genes
- Use
use_raw=Truefor gene expression plots: Shows original counts - Check PCA variance ratio: Determine optimal number of PCs
- Save intermediate results: Long workflows can fail partway through
Bundled Resources
scripts/qc_analysis.py
Automated quality control script that calculates metrics, generates plots, and filters data:
python scripts/qc_analysis.py input.h5ad --output filtered.h5ad \
--mt-threshold 5 --min-genes 200 --min-cells 3
references/standard_workflow.md
Complete step-by-step workflow with detailed explanations and code examples for:
- Data loading and setup
- Quality control with visualization
- Normalization and scaling
- Feature selection
- Dimensionality reduction (PCA, UMAP, t-SNE)
- Clustering (Leiden, Louvain)
- Marker gene identification
- Cell type annotation
- Trajectory inference
- Differential expression
Read this reference when performing a complete analysis from scratch.
references/api_reference.md
Quick reference guide for scanpy functions organized by module:
- Reading/writing data (
sc.read_*,adata.write_*) - Preprocessing (
sc.pp.*) - Tools (
sc.tl.*) - Plotting (
sc.pl.*) - AnnData structure and manipulation
- Settings and utilities
Use this for quick lookup of function signatures and common parameters.
references/plotting_guide.md
Comprehensive visualization guide including:
- Quality control plots
- Dimensionality reduction visualizations
- Clustering visualizations
- Marker gene plots (heatmaps, dot plots, violin plots)
- Trajectory and pseudotime plots
- Publication-quality customization
- Multi-panel figures
- Color palettes and styling
Consult this when creating publication-ready figures.
assets/analysis_template.py
Complete analysis template providing a full workflow from data loading through cell type annotation. Copy and customize this template for new analyses:
cp assets/analysis_template.py my_analysis.py
# Edit parameters and run
python my_analysis.py
The template includes all standard steps with configurable parameters and helpful comments.
Additional Resources
- Official scanpy documentation: https://scanpy.readthedocs.io/
- Scanpy tutorials: https://scanpy-tutorials.readthedocs.io/
- scverse ecosystem: https://scverse.org/ (related tools: squidpy, scvi-tools, cellrank)
- Best practices: Luecken & Theis (2019) "Current best practices in single-cell RNA-seq"
Tips for Effective Analysis
- Start with the template: Use
assets/analysis_template.pyas a starting point - Run QC script first: Use
scripts/qc_analysis.pyfor initial filtering - Consult references as needed: Load workflow and API references into context
- Iterate on clustering: Try multiple resolutions and visualization methods
- Validate biologically: Check marker genes match expected cell types
- Document parameters: Record QC thresholds and analysis settings
- Save checkpoints: Write intermediate results at key steps
Files (alterlab-academic-skills)
-
assets
-
analysis_template.py 9.7 KB
#!/usr/bin/env python3 """ Complete Single-Cell Analysis Template This template provides a complete workflow for single-cell RNA-seq analysis using scanpy, from data loading through clustering and cell type annotation. Customize the parameters and sections as needed for your specific dataset. Figure saving: the `save=` argument used below is deprecated as of Scanpy 1.12 (it still works, with a warning, writing into `sc.settings.figdir` with the plot name prepended). When adapting this template, prefer: sc.pl.umap(adata, color="leiden", show=False) plt.savefig("figures/umap_leiden.pdf", bbox_inches="tight"); plt.close() Requires Scanpy >= 1.12 (Python >= 3.12). """ import scanpy as sc # ============================================================================ # CONFIGURATION # ============================================================================ # File paths INPUT_FILE = 'data/raw_counts.h5ad' # Change to your input file OUTPUT_DIR = 'results/' FIGURES_DIR = 'figures/' # QC parameters MIN_GENES = 200 # Minimum genes per cell MIN_CELLS = 3 # Minimum cells per gene MT_THRESHOLD = 5 # Maximum mitochondrial percentage # Analysis parameters N_TOP_GENES = 2000 # Number of highly variable genes N_PCS = 40 # Number of principal components N_NEIGHBORS = 10 # Number of neighbors for graph LEIDEN_RESOLUTION = 0.5 # Clustering resolution # Scanpy settings sc.settings.verbosity = 3 sc.settings.set_figure_params(dpi=80, facecolor='white') sc.settings.figdir = FIGURES_DIR # ============================================================================ # 1. LOAD DATA # ============================================================================ print("=" * 80) print("LOADING DATA") print("=" * 80) # Load data (adjust based on your file format) adata = sc.read_h5ad(INPUT_FILE) # adata = sc.read_10x_mtx('data/filtered_gene_bc_matrices/') # For 10X data # adata = sc.read_csv('data/counts.csv') # For CSV data print(f"Loaded: {adata.n_obs} cells x {adata.n_vars} genes") # ============================================================================ # 2. QUALITY CONTROL # ============================================================================ print("\n" + "=" * 80) print("QUALITY CONTROL") print("=" * 80) # Identify mitochondrial genes adata.var['mt'] = adata.var_names.str.startswith('MT-') # Calculate QC metrics sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], percent_top=None, log1p=False, inplace=True) # Visualize QC metrics before filtering sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt'], jitter=0.4, multi_panel=True, save='_qc_before_filtering') sc.pl.scatter(adata, x='total_counts', y='pct_counts_mt', save='_qc_mt') sc.pl.scatter(adata, x='total_counts', y='n_genes_by_counts', save='_qc_genes') # Filter cells and genes print(f"\nBefore filtering: {adata.n_obs} cells, {adata.n_vars} genes") sc.pp.filter_cells(adata, min_genes=MIN_GENES) sc.pp.filter_genes(adata, min_cells=MIN_CELLS) adata = adata[adata.obs.pct_counts_mt < MT_THRESHOLD, :] print(f"After filtering: {adata.n_obs} cells, {adata.n_vars} genes") # ============================================================================ # 3. NORMALIZATION # ============================================================================ print("\n" + "=" * 80) print("NORMALIZATION") print("=" * 80) # Normalize to 10,000 counts per cell sc.pp.normalize_total(adata, target_sum=1e4) # Log-transform sc.pp.log1p(adata) # Store normalized data adata.raw = adata # ============================================================================ # 4. FEATURE SELECTION # ============================================================================ print("\n" + "=" * 80) print("FEATURE SELECTION") print("=" * 80) # Identify highly variable genes sc.pp.highly_variable_genes(adata, n_top_genes=N_TOP_GENES) # Visualize sc.pl.highly_variable_genes(adata, save='_hvg') print(f"Selected {sum(adata.var.highly_variable)} highly variable genes") # Subset to highly variable genes adata = adata[:, adata.var.highly_variable] # ============================================================================ # 5. SCALING AND REGRESSION # ============================================================================ print("\n" + "=" * 80) print("SCALING AND REGRESSION") print("=" * 80) # Regress out unwanted sources of variation sc.pp.regress_out(adata, ['total_counts', 'pct_counts_mt']) # Scale data sc.pp.scale(adata, max_value=10) # ============================================================================ # 6. DIMENSIONALITY REDUCTION # ============================================================================ print("\n" + "=" * 80) print("DIMENSIONALITY REDUCTION") print("=" * 80) # PCA sc.tl.pca(adata, svd_solver='arpack') sc.pl.pca_variance_ratio(adata, log=True, save='_pca_variance') # Compute neighborhood graph sc.pp.neighbors(adata, n_neighbors=N_NEIGHBORS, n_pcs=N_PCS) # UMAP sc.tl.umap(adata) # ============================================================================ # 7. CLUSTERING # ============================================================================ print("\n" + "=" * 80) print("CLUSTERING") print("=" * 80) # Leiden clustering. flavor='igraph' is the future default and far faster; # the bare call emits a FutureWarning recommending it. sc.tl.leiden(adata, resolution=LEIDEN_RESOLUTION, flavor='igraph', n_iterations=2) # Visualize sc.pl.umap(adata, color='leiden', legend_loc='on data', save='_leiden') print(f"Identified {len(adata.obs['leiden'].unique())} clusters") # ============================================================================ # 8. MARKER GENE IDENTIFICATION # ============================================================================ print("\n" + "=" * 80) print("MARKER GENE IDENTIFICATION") print("=" * 80) # Find marker genes sc.tl.rank_genes_groups(adata, 'leiden', method='wilcoxon') # Visualize top markers sc.pl.rank_genes_groups(adata, n_genes=25, sharey=False, save='_markers') sc.pl.rank_genes_groups_heatmap(adata, n_genes=10, save='_markers_heatmap') sc.pl.rank_genes_groups_dotplot(adata, n_genes=5, save='_markers_dotplot') # Get top markers for each cluster for cluster in adata.obs['leiden'].unique(): print(f"\nCluster {cluster} top markers:") markers = sc.get.rank_genes_groups_df(adata, group=cluster).head(10) print(markers[['names', 'scores', 'pvals_adj']].to_string(index=False)) # ============================================================================ # 9. CELL TYPE ANNOTATION (CUSTOMIZE THIS SECTION) # ============================================================================ print("\n" + "=" * 80) print("CELL TYPE ANNOTATION") print("=" * 80) # Example marker genes for common cell types (customize for your data) marker_genes = { 'T cells': ['CD3D', 'CD3E', 'CD3G'], 'B cells': ['MS4A1', 'CD79A', 'CD79B'], 'Monocytes': ['CD14', 'LYZ', 'S100A8'], 'NK cells': ['NKG7', 'GNLY', 'KLRD1'], 'Dendritic cells': ['FCER1A', 'CST3'], } # Visualize marker genes for cell_type, genes in marker_genes.items(): available_genes = [g for g in genes if g in adata.raw.var_names] if available_genes: sc.pl.umap(adata, color=available_genes, use_raw=True, save=f'_{cell_type.replace(" ", "_")}') # Manual annotation based on marker expression (customize this mapping) cluster_to_celltype = { '0': 'CD4 T cells', '1': 'CD14+ Monocytes', '2': 'B cells', '3': 'CD8 T cells', '4': 'NK cells', # Add more mappings based on your marker analysis } # Apply annotations adata.obs['cell_type'] = adata.obs['leiden'].map(cluster_to_celltype) adata.obs['cell_type'] = adata.obs['cell_type'].fillna('Unknown') # Visualize annotated cell types sc.pl.umap(adata, color='cell_type', legend_loc='on data', save='_celltypes') # ============================================================================ # 10. ADDITIONAL ANALYSES (OPTIONAL) # ============================================================================ print("\n" + "=" * 80) print("ADDITIONAL ANALYSES") print("=" * 80) # PAGA trajectory analysis (optional) sc.tl.paga(adata, groups='leiden') sc.pl.paga(adata, color='leiden', save='_paga') # Gene set scoring (optional) # example_gene_set = ['CD3D', 'CD3E', 'CD3G'] # sc.tl.score_genes(adata, example_gene_set, score_name='T_cell_score') # sc.pl.umap(adata, color='T_cell_score', save='_gene_set_score') # ============================================================================ # 11. SAVE RESULTS # ============================================================================ print("\n" + "=" * 80) print("SAVING RESULTS") print("=" * 80) import os os.makedirs(OUTPUT_DIR, exist_ok=True) # Save processed AnnData object adata.write(f'{OUTPUT_DIR}/processed_data.h5ad') print(f"Saved processed data to {OUTPUT_DIR}/processed_data.h5ad") # Export metadata adata.obs.to_csv(f'{OUTPUT_DIR}/cell_metadata.csv') adata.var.to_csv(f'{OUTPUT_DIR}/gene_metadata.csv') print(f"Saved metadata to {OUTPUT_DIR}/") # Export marker genes for cluster in adata.obs['leiden'].unique(): markers = sc.get.rank_genes_groups_df(adata, group=cluster) markers.to_csv(f'{OUTPUT_DIR}/markers_cluster_{cluster}.csv', index=False) print(f"Saved marker genes to {OUTPUT_DIR}/") # ============================================================================ # 12. SUMMARY # ============================================================================ print("\n" + "=" * 80) print("ANALYSIS SUMMARY") print("=" * 80) print(f"\nFinal dataset:") print(f" Cells: {adata.n_obs}") print(f" Genes: {adata.n_vars}") print(f" Clusters: {len(adata.obs['leiden'].unique())}") print(f"\nCell type distribution:") print(adata.obs['cell_type'].value_counts()) print("\n" + "=" * 80) print("ANALYSIS COMPLETE") print("=" * 80)
-
-
evals
-
evals.json 4.6 KB
{ "skill": "alterlab-scanpy", "evals": [ { "id": "standard-scrnaseq-pipeline", "prompt": "I just loaded a 10X PBMC dataset into an AnnData. Run the standard scRNA-seq workflow: QC and filtering, normalize and log-transform, pick highly variable genes, PCA, neighbors, UMAP, and Leiden clustering.", "expected_output": "Invokes alterlab-scanpy: runs the established pipeline - calculate_qc_metrics + filter_cells/filter_genes, normalize_total + log1p, highly_variable_genes, pca, neighbors, umap, leiden - in the canonical order using sc.pp/sc.tl/sc.pl functions.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "leiden" }, { "type": "behavior", "value": "Walks the canonical scanpy sequence: QC filtering, normalize_total + log1p, highly_variable_genes, pca, neighbors, umap, leiden." } ] }, { "id": "qc-mito-filtering", "prompt": "My single-cell data has some dying cells with high mitochondrial content. How do I flag the mitochondrial genes, compute QC metrics, visualize them, and filter out the low-quality cells?", "expected_output": "Invokes alterlab-scanpy: marks MT genes via var_names.str.startswith('MT-'), runs sc.pp.calculate_qc_metrics(qc_vars=['mt']), visualizes with sc.pl.violin, then filters on pct_counts_mt and min_genes/min_cells thresholds.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "calculate_qc_metrics" }, { "type": "behavior", "value": "Flags MT- genes, computes QC metrics with qc_vars=['mt'], and filters cells by pct_counts_mt and gene/count thresholds." } ] }, { "id": "marker-genes-per-cluster", "prompt": "I have Leiden clusters on my dataset. Find the marker genes that distinguish each cluster, show me a dotplot and a ranked-genes heatmap, and let me pull the top markers for cluster 0 as a DataFrame.", "expected_output": "Invokes alterlab-scanpy: runs sc.tl.rank_genes_groups(adata, 'leiden', method='wilcoxon'), visualizes with rank_genes_groups_dotplot and rank_genes_groups_heatmap, and extracts cluster 0 with sc.get.rank_genes_groups_df.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "rank_genes_groups" }, { "type": "behavior", "value": "Uses rank_genes_groups with the wilcoxon method and the rank_genes_groups dotplot/heatmap plotting helpers." } ] }, { "id": "de-between-conditions", "prompt": "Within my T-cell population, I want to find genes that are differentially expressed between treated and control cells.", "expected_output": "Invokes alterlab-scanpy: subsets to the T-cell population, then runs sc.tl.rank_genes_groups(groupby='condition', groups=['treated'], reference='control') and plots the result for the condition contrast.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "behavior", "value": "Subsets the cell population then runs rank_genes_groups across condition with an explicit reference group for the treated-vs-control contrast." } ] }, { "id": "near-miss-scvi-tools", "prompt": "I have strong batch effects across ten donors and want a deep generative model to learn a batch-corrected latent representation of my cells - something like a VAE I can also use for label transfer.", "expected_output": "Should NOT trigger alterlab-scanpy. Deep generative / probabilistic models (scVI VAE latent spaces, label transfer via scANVI) are alterlab-scvi-tools territory. scanpy covers the standard pipeline and defers deep generative modeling to scvi-tools (it only offers ComBat/Harmony-style correction).", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "scvi-tools" } ] }, { "id": "near-miss-anndata", "prompt": "I have five separate .h5ad files and I just want to concatenate them into one AnnData along the cells axis with an inner join and a batch key, then write the combined object back to disk. No analysis yet.", "expected_output": "Should NOT trigger alterlab-scanpy. Pure concatenation/I-O of .h5ad files (ad.concat with axis/join/label, write_h5ad) is alterlab-anndata's data-format territory. scanpy is the analysis pipeline and defers object construction/concatenation to anndata.", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "anndata" } ] } ] }
-
-
references
-
api_reference.md 7.7 KB
# Scanpy API Quick Reference Quick reference for commonly used scanpy functions organized by module. ## Import Convention ```python import scanpy as sc ``` ## Reading and Writing Data (sc.read_*) ### Reading Functions ```python sc.read_10x_h5(filename) # Read 10X HDF5 file sc.read_10x_mtx(path) # Read 10X mtx directory sc.read_h5ad(filename) # Read h5ad (AnnData) file sc.read_csv(filename) # Read CSV file sc.read_excel(filename) # Read Excel file sc.read_loom(filename) # Read loom file sc.read_text(filename) # Read text file # sc.read_visium(path) # DEPRECATED (1.11) -> squidpy.read.visium() ``` Other spatial entry points moved to Squidpy in Scanpy 1.11: `sc.datasets.visium_sge()` -> `squidpy.datasets.visium()`, `sc.pl.spatial()` -> `squidpy.pl.spatial_scatter()`. See `alterlab-squidpy-spatial`. ### Writing Functions ```python adata.write_h5ad(filename) # Write to h5ad format adata.write_csvs(dirname) # Write to CSV files adata.write_loom(filename) # Write to loom format adata.write_zarr(filename) # Write to zarr format ``` ## Preprocessing (sc.pp.*) ### Quality Control ```python sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], inplace=True) sc.pp.filter_cells(adata, min_genes=200) sc.pp.filter_genes(adata, min_cells=3) ``` ### Normalization and Transformation ```python sc.pp.normalize_total(adata, target_sum=1e4) # Normalize to target sum sc.pp.log1p(adata) # Log(x + 1) transformation sc.pp.sqrt(adata) # Square root transformation ``` ### Feature Selection ```python sc.pp.highly_variable_genes(adata, min_mean=0.0125, max_mean=3, min_disp=0.5) sc.pp.highly_variable_genes(adata, flavor='seurat_v3', n_top_genes=2000) ``` ### Scaling and Regression ```python sc.pp.scale(adata, max_value=10) # Scale to unit variance sc.pp.regress_out(adata, ['total_counts', 'pct_counts_mt']) # Regress out unwanted variation ``` ### Dimensionality Reduction (Preprocessing) ```python sc.pp.pca(adata, n_comps=50) # Principal component analysis sc.pp.neighbors(adata, n_neighbors=10, n_pcs=40) # Compute neighborhood graph ``` ### Batch Correction ```python sc.pp.combat(adata, key='batch') # ComBat batch correction ``` ## Tools (sc.tl.*) ### Dimensionality Reduction ```python sc.tl.pca(adata, svd_solver='arpack') # PCA sc.tl.umap(adata) # UMAP embedding sc.tl.tsne(adata) # t-SNE embedding sc.tl.diffmap(adata) # Diffusion map sc.tl.draw_graph(adata, layout='fa') # Force-directed graph ``` ### Clustering ```python # flavor='igraph' is the future default and far faster; bare leiden warns sc.tl.leiden(adata, resolution=0.5, flavor='igraph', n_iterations=2) # recommended # sc.tl.louvain(adata, resolution=0.5) # DEPRECATED in Scanpy 1.12 — use leiden ``` ### Marker Genes and Differential Expression ```python sc.tl.rank_genes_groups(adata, groupby='leiden', method='wilcoxon') sc.tl.rank_genes_groups(adata, groupby='leiden', method='t-test') sc.tl.rank_genes_groups(adata, groupby='leiden', method='logreg') # Get results as dataframe sc.get.rank_genes_groups_df(adata, group='0') ``` ### Trajectory Inference ```python sc.tl.paga(adata, groups='leiden') # PAGA trajectory sc.tl.dpt(adata) # Diffusion pseudotime ``` ### Gene Scoring ```python sc.tl.score_genes(adata, gene_list, score_name='score') sc.tl.score_genes_cell_cycle(adata, s_genes, g2m_genes) ``` ### Embeddings and Projections ```python sc.tl.ingest(adata, adata_ref) # Map to reference sc.tl.embedding_density(adata, basis='umap', groupby='leiden') ``` ## Plotting (sc.pl.*) ### Basic Embeddings ```python sc.pl.umap(adata, color='leiden') # UMAP plot sc.pl.tsne(adata, color='gene_name') # t-SNE plot sc.pl.pca(adata, color='leiden') # PCA plot sc.pl.diffmap(adata, color='leiden') # Diffusion map plot ``` ### Heatmaps and Dot Plots ```python sc.pl.heatmap(adata, var_names=genes, groupby='leiden') sc.pl.dotplot(adata, var_names=genes, groupby='leiden') sc.pl.matrixplot(adata, var_names=genes, groupby='leiden') sc.pl.stacked_violin(adata, var_names=genes, groupby='leiden') ``` ### Violin and Scatter Plots ```python sc.pl.violin(adata, keys=['gene1', 'gene2'], groupby='leiden') sc.pl.scatter(adata, x='gene1', y='gene2', color='leiden') ``` ### Marker Gene Visualization ```python sc.pl.rank_genes_groups(adata, n_genes=25, sharey=False) sc.pl.rank_genes_groups_violin(adata, groups='0') sc.pl.rank_genes_groups_heatmap(adata, n_genes=10) sc.pl.rank_genes_groups_dotplot(adata, n_genes=5) ``` ### Trajectory Visualization ```python sc.pl.paga(adata, color='leiden') # PAGA graph sc.pl.dpt_timeseries(adata) # DPT timeseries ``` ### QC Plots ```python sc.pl.highest_expr_genes(adata, n_top=20) sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt']) sc.pl.scatter(adata, x='total_counts', y='n_genes_by_counts') ``` ### Advanced Plots ```python sc.pl.dendrogram(adata, groupby='leiden') sc.pl.correlation_matrix(adata, groupby='leiden') sc.pl.tracksplot(adata, var_names=genes, groupby='leiden') ``` ## Common Parameters ### Color Parameters - `color`: Variable(s) to color by (gene name, obs column) - `use_raw`: Use `.raw` attribute of adata - `palette`: Color palette to use - `vmin`, `vmax`: Color scale limits ### Layout Parameters - `basis`: Embedding basis ('umap', 'tsne', 'pca', etc.) - `legend_loc`: Legend location ('on data', 'right margin', etc.) - `size`: Point size - `alpha`: Point transparency ### Saving Parameters - `save`: Filename to save plot - `show`: Whether to show plot ## AnnData Structure ```python adata.X # Expression matrix (cells × genes) adata.obs # Cell annotations (DataFrame) adata.var # Gene annotations (DataFrame) adata.uns # Unstructured annotations (dict) adata.obsm # Multi-dimensional cell annotations (e.g., PCA, UMAP) adata.varm # Multi-dimensional gene annotations adata.layers # Additional data layers adata.raw # Raw data backup # Access adata.obs_names # Cell barcodes adata.var_names # Gene names adata.shape # (n_cells, n_genes) # Slicing adata[cell_indices, gene_indices] adata[:, adata.var_names.isin(gene_list)] adata[adata.obs['leiden'] == '0', :] ``` ## Settings ```python sc.settings.verbosity = 3 # 0=error, 1=warning, 2=info, 3=hint sc.settings.set_figure_params(dpi=80, facecolor='white') sc.settings.autoshow = False # Don't show plots automatically sc.settings.autosave = True # Autosave figures sc.settings.figdir = './figures/' # Figure directory sc.settings.cachedir = './cache/' # Cache directory sc.settings.n_jobs = 8 # Number of parallel jobs ``` ## Useful Utilities ```python sc.logging.print_header() # Versions of scanpy and key dependencies # (print_versions() is deprecated) sc.logging.print_memory_usage() # Print memory usage adata.copy() # Create a copy of AnnData object # adata.concatenate(...) is deprecated; use anndata.concat (see alterlab-anndata) import anndata as ad ad.concat([adata1, adata2], join='inner', label='batch') # Concatenate AnnData objects ``` -
plotting_guide.md 10.5 KB
# Scanpy Plotting Guide Comprehensive guide for creating publication-quality visualizations with scanpy. ## General Plotting Principles All scanpy plotting functions follow consistent patterns: - Functions in `sc.pl.*` mirror analysis functions in `sc.tl.*` - Most accept `color` parameter for gene names or metadata columns - Multiple plots can be generated in a single call ### Saving figures (Scanpy >= 1.12) The `save=` parameter of `sc.pl.*` is **deprecated as of Scanpy 1.12**. It still works but raises `FutureWarning: Argument 'save' is deprecated and will be removed in a future version.` It writes into `sc.settings.figdir` with the plot's name prepended, so `sc.pl.umap(..., save='_umap.pdf')` lands at `figures/umap_umap.pdf` — the doubled name is part of why it is being retired. The replacement is to suppress the automatic display and save the active Matplotlib figure: ```python import matplotlib.pyplot as plt sc.pl.umap(adata, color='cell_type', show=False) plt.savefig('figures/umap_celltype.pdf', bbox_inches='tight') plt.close() ``` For the functions that return an object (`sc.pl.dotplot`, `matrixplot`, `stacked_violin`, `clustermap`), use `return_fig=True` and the object's own `savefig`: ```python dp = sc.pl.dotplot(adata, var_names=genes, groupby='cell_type', return_fig=True) dp.savefig('figures/markers_dotplot.pdf', bbox_inches='tight') ``` The examples below still use `save=` for brevity; translate them to the pattern above when writing code a user will run. ## Essential Quality Control Plots ### Visualize QC Metrics ```python # Violin plots for QC metrics sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt'], jitter=0.4, multi_panel=True, save='_qc_violin.pdf') # Scatter plots to identify outliers sc.pl.scatter(adata, x='total_counts', y='pct_counts_mt', save='_qc_mt.pdf') sc.pl.scatter(adata, x='total_counts', y='n_genes_by_counts', save='_qc_genes.pdf') # Highest expressing genes sc.pl.highest_expr_genes(adata, n_top=20, save='_highest_expr.pdf') ``` ### Post-filtering QC ```python # Compare before and after filtering sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts'], groupby='sample', save='_post_filter.pdf') ``` ## Dimensionality Reduction Visualizations ### PCA Plots ```python # Basic PCA sc.pl.pca(adata, color='leiden', save='_pca.pdf') # PCA colored by gene expression sc.pl.pca(adata, color=['gene1', 'gene2', 'gene3'], save='_pca_genes.pdf') # Variance ratio plot (elbow plot) sc.pl.pca_variance_ratio(adata, log=True, n_pcs=50, save='_variance.pdf') # PCA loadings sc.pl.pca_loadings(adata, components=[1, 2, 3], save='_loadings.pdf') ``` ### UMAP Plots ```python # Basic UMAP with clusters sc.pl.umap(adata, color='leiden', legend_loc='on data', save='_umap_leiden.pdf') # UMAP colored by multiple variables sc.pl.umap(adata, color=['leiden', 'cell_type', 'batch'], save='_umap_multi.pdf') # UMAP with gene expression sc.pl.umap(adata, color=['CD3D', 'CD14', 'MS4A1'], use_raw=False, save='_umap_genes.pdf') # Customize appearance sc.pl.umap(adata, color='leiden', palette='Set2', size=50, alpha=0.8, frameon=False, title='Cell Types', save='_umap_custom.pdf') ``` ### t-SNE Plots ```python # t-SNE with clusters sc.pl.tsne(adata, color='leiden', legend_loc='right margin', save='_tsne.pdf') # Multiple t-SNE perplexities (if computed) sc.pl.tsne(adata, color='leiden', save='_tsne_default.pdf') ``` ## Clustering Visualizations ### Basic Cluster Plots ```python # UMAP with cluster annotations sc.pl.umap(adata, color='leiden', add_outline=True, legend_loc='on data', legend_fontsize=12, legend_fontoutline=2, frameon=False, save='_clusters.pdf') # Show cluster proportions sc.pl.umap(adata, color='leiden', size=50, edges=True, edges_width=0.1, save='_clusters_edges.pdf') ``` ### Cluster Comparison ```python # Compare clustering results sc.pl.umap(adata, color=['leiden', 'louvain'], save='_cluster_comparison.pdf') # Cluster dendrogram sc.tl.dendrogram(adata, groupby='leiden') sc.pl.dendrogram(adata, groupby='leiden', save='_dendrogram.pdf') ``` ## Marker Gene Visualizations ### Ranked Marker Genes ```python # Overview of top markers per cluster sc.pl.rank_genes_groups(adata, n_genes=25, sharey=False, save='_marker_overview.pdf') # Heatmap of top markers sc.pl.rank_genes_groups_heatmap(adata, n_genes=10, groupby='leiden', show_gene_labels=True, save='_marker_heatmap.pdf') # Dot plot of markers sc.pl.rank_genes_groups_dotplot(adata, n_genes=5, save='_marker_dotplot.pdf') # Stacked violin plots sc.pl.rank_genes_groups_stacked_violin(adata, n_genes=5, save='_marker_violin.pdf') # Matrix plot sc.pl.rank_genes_groups_matrixplot(adata, n_genes=5, save='_marker_matrix.pdf') ``` ### Specific Gene Expression ```python # Violin plots for specific genes marker_genes = ['CD3D', 'CD14', 'MS4A1', 'NKG7', 'FCGR3A'] sc.pl.violin(adata, keys=marker_genes, groupby='leiden', save='_markers_violin.pdf') # Dot plot for curated markers sc.pl.dotplot(adata, var_names=marker_genes, groupby='leiden', save='_markers_dotplot.pdf') # Heatmap for specific genes sc.pl.heatmap(adata, var_names=marker_genes, groupby='leiden', swap_axes=True, save='_markers_heatmap.pdf') # Stacked violin for gene sets sc.pl.stacked_violin(adata, var_names=marker_genes, groupby='leiden', save='_markers_stacked.pdf') ``` ### Gene Expression on Embeddings ```python # Multiple genes on UMAP genes = ['CD3D', 'CD14', 'MS4A1', 'NKG7'] sc.pl.umap(adata, color=genes, cmap='viridis', save='_umap_markers.pdf') # Gene expression with custom colormap sc.pl.umap(adata, color='CD3D', cmap='Reds', vmin=0, vmax=3, save='_umap_cd3d.pdf') ``` ## Trajectory and Pseudotime Visualizations ### PAGA Plots ```python # PAGA graph sc.pl.paga(adata, color='leiden', save='_paga.pdf') # PAGA with gene expression sc.pl.paga(adata, color=['leiden', 'dpt_pseudotime'], save='_paga_pseudotime.pdf') # PAGA overlaid on UMAP sc.pl.umap(adata, color='leiden', save='_umap_with_paga.pdf', edges=True, edges_color='gray') ``` ### Pseudotime Plots ```python # DPT pseudotime on UMAP sc.pl.umap(adata, color='dpt_pseudotime', save='_umap_dpt.pdf') # Gene expression along pseudotime sc.pl.dpt_timeseries(adata, save='_dpt_timeseries.pdf') # Heatmap ordered by pseudotime sc.pl.heatmap(adata, var_names=genes, groupby='leiden', use_raw=False, show_gene_labels=True, save='_pseudotime_heatmap.pdf') ``` ## Advanced Visualizations ### Tracks Plot (Gene Expression Trends) ```python # Show gene expression across cell types sc.pl.tracksplot(adata, var_names=marker_genes, groupby='leiden', save='_tracks.pdf') ``` ### Correlation Matrix ```python # Correlation between clusters sc.pl.correlation_matrix(adata, groupby='leiden', save='_correlation.pdf') ``` ### Embedding Density ```python # Cell density on UMAP sc.tl.embedding_density(adata, basis='umap', groupby='cell_type') sc.pl.embedding_density(adata, basis='umap', key='umap_density_cell_type', save='_density.pdf') ``` ## Multi-Panel Figures ### Creating Panel Figures ```python import matplotlib.pyplot as plt # Create multi-panel figure fig, axes = plt.subplots(2, 2, figsize=(12, 12)) # Plot on specific axes sc.pl.umap(adata, color='leiden', ax=axes[0, 0], show=False) sc.pl.umap(adata, color='CD3D', ax=axes[0, 1], show=False) sc.pl.umap(adata, color='CD14', ax=axes[1, 0], show=False) sc.pl.umap(adata, color='MS4A1', ax=axes[1, 1], show=False) plt.tight_layout() plt.savefig('figures/multi_panel.pdf') plt.show() ``` ## Publication-Quality Customization ### High-Quality Settings ```python # Set publication-quality defaults sc.settings.set_figure_params(dpi=300, frameon=False, figsize=(5, 5), facecolor='white') # Vector graphics output sc.settings.figdir = './figures/' sc.settings.file_format_figs = 'pdf' # or 'svg' ``` ### Custom Color Palettes ```python # Use custom colors custom_colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'] sc.pl.umap(adata, color='leiden', palette=custom_colors, save='_custom_colors.pdf') # Continuous color maps sc.pl.umap(adata, color='CD3D', cmap='viridis', save='_viridis.pdf') sc.pl.umap(adata, color='CD3D', cmap='RdBu_r', save='_rdbu.pdf') ``` ### Remove Axes and Frames ```python # Clean plot without axes sc.pl.umap(adata, color='leiden', frameon=False, save='_clean.pdf') # No legend sc.pl.umap(adata, color='leiden', legend_loc=None, save='_no_legend.pdf') ``` ## Exporting Plots ### Save Individual Plots ```python # Automatic saving with save parameter sc.pl.umap(adata, color='leiden', save='_leiden.pdf') # Saves to: sc.settings.figdir + 'umap_leiden.pdf' # Manual saving import matplotlib.pyplot as plt fig = sc.pl.umap(adata, color='leiden', show=False, return_fig=True) fig.savefig('figures/my_umap.pdf', dpi=300, bbox_inches='tight') ``` ### Batch Export ```python # Save multiple versions for gene in ['CD3D', 'CD14', 'MS4A1']: sc.pl.umap(adata, color=gene, save=f'_{gene}.pdf') ``` ## Common Customization Parameters ### Layout Parameters - `figsize`: Figure size (width, height) - `frameon`: Show frame around plot - `title`: Plot title - `legend_loc`: 'right margin', 'on data', 'best', or None - `legend_fontsize`: Font size for legend - `size`: Point size ### Color Parameters - `color`: Variable(s) to color by - `palette`: Color palette (e.g., 'Set1', 'viridis') - `cmap`: Colormap for continuous variables - `vmin`, `vmax`: Color scale limits - `use_raw`: Use raw counts for gene expression ### Saving Parameters - `save`: Filename suffix for saving - `show`: Whether to display plot - `dpi`: Resolution for raster formats ## Tips for Publication Figures 1. **Use vector formats**: PDF or SVG for scalable graphics 2. **High DPI**: Set dpi=300 or higher for raster images 3. **Consistent styling**: Use the same color palette across figures 4. **Clear labels**: Ensure gene names and cell types are readable 5. **White background**: Use `facecolor='white'` for publications 6. **Remove clutter**: Set `frameon=False` for cleaner appearance 7. **Legend placement**: Use 'on data' for compact figures 8. **Color blind friendly**: Consider palettes like 'colorblind' or 'Set2' -
standard_workflow.md 5.5 KB
# Standard Scanpy Workflow for Single-Cell Analysis This document outlines the standard workflow for analyzing single-cell RNA-seq data using scanpy. ## Complete Analysis Pipeline ### 1. Data Loading and Initial Setup ```python import scanpy as sc import pandas as pd import numpy as np # Configure scanpy settings sc.settings.verbosity = 3 # verbosity: errors (0), warnings (1), info (2), hints (3) sc.settings.set_figure_params(dpi=80, facecolor='white') # Load data (various formats) adata = sc.read_10x_mtx('path/to/data/') # For 10X data # adata = sc.read_h5ad('path/to/data.h5ad') # For h5ad format # adata = sc.read_csv('path/to/data.csv') # For CSV format ``` ### 2. Quality Control (QC) ```python # Calculate QC metrics sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], percent_top=None, log1p=False, inplace=True) # Common filtering thresholds (adjust based on dataset) sc.pp.filter_cells(adata, min_genes=200) sc.pp.filter_genes(adata, min_cells=3) # Remove cells with high mitochondrial content adata = adata[adata.obs.pct_counts_mt < 5, :] # Visualize QC metrics sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt'], jitter=0.4, multi_panel=True) sc.pl.scatter(adata, x='total_counts', y='pct_counts_mt') sc.pl.scatter(adata, x='total_counts', y='n_genes_by_counts') ``` ### 3. Normalization ```python # Normalize to 10,000 counts per cell sc.pp.normalize_total(adata, target_sum=1e4) # Log-transform the data sc.pp.log1p(adata) # Store normalized data in raw for later use adata.raw = adata ``` ### 4. Feature Selection ```python # Identify highly variable genes sc.pp.highly_variable_genes(adata, min_mean=0.0125, max_mean=3, min_disp=0.5) # Visualize highly variable genes sc.pl.highly_variable_genes(adata) # Subset to highly variable genes adata = adata[:, adata.var.highly_variable] ``` ### 5. Scaling and Regression ```python # Regress out effects of total counts per cell and percent mitochondrial genes sc.pp.regress_out(adata, ['total_counts', 'pct_counts_mt']) # Scale data to unit variance and zero mean sc.pp.scale(adata, max_value=10) ``` ### 6. Dimensionality Reduction ```python # Principal Component Analysis (PCA) sc.tl.pca(adata, svd_solver='arpack') # Visualize PCA results sc.pl.pca(adata, color='CST3') sc.pl.pca_variance_ratio(adata, log=True) # Computing neighborhood graph sc.pp.neighbors(adata, n_neighbors=10, n_pcs=40) # UMAP for visualization sc.tl.umap(adata) # t-SNE (alternative to UMAP) # sc.tl.tsne(adata) ``` ### 7. Clustering ```python # Leiden clustering (recommended). flavor='igraph' is the future default and is # orders of magnitude faster; the bare call emits a FutureWarning. sc.tl.leiden(adata, resolution=0.5, flavor='igraph', n_iterations=2) # Alternative: Louvain clustering # sc.tl.louvain(adata, resolution=0.5) # Visualize clustering results sc.pl.umap(adata, color=['leiden'], legend_loc='on data') ``` ### 8. Marker Gene Identification ```python # Find marker genes for each cluster sc.tl.rank_genes_groups(adata, 'leiden', method='wilcoxon') # Visualize top marker genes sc.pl.rank_genes_groups(adata, n_genes=25, sharey=False) # Get marker gene dataframe marker_genes = sc.get.rank_genes_groups_df(adata, group='0') # Visualize specific markers sc.pl.umap(adata, color=['leiden', 'CST3', 'NKG7']) ``` ### 9. Cell Type Annotation ```python # Manual annotation based on marker genes cluster_annotations = { '0': 'CD4 T cells', '1': 'CD14+ Monocytes', '2': 'B cells', '3': 'CD8 T cells', # ... add more annotations } adata.obs['cell_type'] = adata.obs['leiden'].map(cluster_annotations) # Visualize annotated cell types sc.pl.umap(adata, color='cell_type', legend_loc='on data') ``` ### 10. Saving Results ```python # Save the processed AnnData object adata.write('results/processed_data.h5ad') # Export results to CSV adata.obs.to_csv('results/cell_metadata.csv') adata.var.to_csv('results/gene_metadata.csv') ``` ## Additional Analysis Options ### Trajectory Inference ```python # PAGA (Partition-based graph abstraction) sc.tl.paga(adata, groups='leiden') sc.pl.paga(adata, color=['leiden']) # Diffusion pseudotime (DPT) adata.uns['iroot'] = np.flatnonzero(adata.obs['leiden'] == '0')[0] sc.tl.dpt(adata) sc.pl.umap(adata, color=['dpt_pseudotime']) ``` ### Differential Expression Between Conditions ```python # Compare conditions within a cell type sc.tl.rank_genes_groups(adata, groupby='condition', groups=['treated'], reference='control', method='wilcoxon') sc.pl.rank_genes_groups(adata, groups=['treated']) ``` ### Gene Set Scoring ```python # Score cells for gene set expression gene_set = ['CD3D', 'CD3E', 'CD3G'] sc.tl.score_genes(adata, gene_set, score_name='T_cell_score') sc.pl.umap(adata, color='T_cell_score') ``` ## Common Parameters to Adjust - **QC thresholds**: `min_genes`, `min_cells`, `pct_counts_mt` - depends on dataset quality - **Normalization target**: Usually 1e4, but can be adjusted - **HVG parameters**: Affects feature selection stringency - **PCA components**: Check variance ratio plot to determine optimal number - **Clustering resolution**: Higher values give more clusters (typically 0.4-1.2) - **n_neighbors**: Affects granularity of UMAP and clustering (typically 10-30) ## Best Practices 1. Always visualize QC metrics before filtering 2. Save raw counts before normalization (`adata.raw = adata`) 3. Use Leiden instead of Louvain for clustering (more efficient) 4. Try multiple clustering resolutions to find optimal granularity 5. Validate cell type annotations with known marker genes 6. Save intermediate results at key steps
-
-
scripts
-
qc_analysis.py 6.8 KB
#!/usr/bin/env python3 """ Quality Control Analysis Script for Scanpy Performs comprehensive quality control on single-cell RNA-seq data, including calculating metrics, generating QC plots, and filtering cells. Usage: python qc_analysis.py <input_file> [--output <output_file>] """ import argparse import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt # noqa: E402 import scanpy as sc # noqa: E402 def calculate_qc_metrics(adata, mt_threshold=5, min_genes=200, min_cells=3): """ Calculate QC metrics and filter cells/genes. Parameters: ----------- adata : AnnData Annotated data matrix mt_threshold : float Maximum percentage of mitochondrial genes (default: 5) min_genes : int Minimum number of genes per cell (default: 200) min_cells : int Minimum number of cells per gene (default: 3) Returns: -------- AnnData Filtered annotated data matrix """ # Identify mitochondrial genes (assumes gene names follow standard conventions) adata.var['mt'] = adata.var_names.str.startswith(('MT-', 'mt-', 'Mt-')) # Calculate QC metrics sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], percent_top=None, log1p=False, inplace=True) print("\n=== QC Metrics Summary ===") print(f"Total cells: {adata.n_obs}") print(f"Total genes: {adata.n_vars}") print(f"Mean genes per cell: {adata.obs['n_genes_by_counts'].mean():.2f}") print(f"Mean counts per cell: {adata.obs['total_counts'].mean():.2f}") print(f"Mean mitochondrial %: {adata.obs['pct_counts_mt'].mean():.2f}") return adata def generate_qc_plots(adata, output_prefix='qc'): """ Generate comprehensive QC plots. Parameters: ----------- adata : AnnData Annotated data matrix output_prefix : str Prefix for saved figure files """ # Create figure directory if it doesn't exist import os os.makedirs('figures', exist_ok=True) def _save(name): # scanpy's own `save=` argument is deprecated as of 1.12; save the # active matplotlib figure instead. plt.savefig(f'figures/{output_prefix}_{name}.pdf', bbox_inches='tight') plt.close('all') # Violin plots for QC metrics sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt'], jitter=0.4, multi_panel=True, show=False) _save('violin') # Scatter plots sc.pl.scatter(adata, x='total_counts', y='pct_counts_mt', show=False) _save('mt_scatter') sc.pl.scatter(adata, x='total_counts', y='n_genes_by_counts', show=False) _save('genes_scatter') # Highest expressing genes sc.pl.highest_expr_genes(adata, n_top=20, show=False) _save('highest_expr') print(f"\nQC plots saved to figures/ directory with prefix '{output_prefix}'") def filter_data(adata, mt_threshold=5, min_genes=200, max_genes=None, min_counts=None, max_counts=None, min_cells=3): """ Filter cells and genes based on QC thresholds. Parameters: ----------- adata : AnnData Annotated data matrix mt_threshold : float Maximum percentage of mitochondrial genes min_genes : int Minimum number of genes per cell max_genes : int, optional Maximum number of genes per cell min_counts : int, optional Minimum number of counts per cell max_counts : int, optional Maximum number of counts per cell min_cells : int Minimum number of cells per gene Returns: -------- AnnData Filtered annotated data matrix """ n_cells_before = adata.n_obs n_genes_before = adata.n_vars # Filter cells sc.pp.filter_cells(adata, min_genes=min_genes) if max_genes: adata = adata[adata.obs['n_genes_by_counts'] < max_genes, :] if min_counts: adata = adata[adata.obs['total_counts'] >= min_counts, :] if max_counts: adata = adata[adata.obs['total_counts'] < max_counts, :] # Filter by mitochondrial percentage adata = adata[adata.obs['pct_counts_mt'] < mt_threshold, :] # Filter genes sc.pp.filter_genes(adata, min_cells=min_cells) print(f"\n=== Filtering Results ===") print(f"Cells: {n_cells_before} -> {adata.n_obs} ({adata.n_obs/n_cells_before*100:.1f}% retained)") print(f"Genes: {n_genes_before} -> {adata.n_vars} ({adata.n_vars/n_genes_before*100:.1f}% retained)") return adata def main(): parser = argparse.ArgumentParser(description='QC analysis for single-cell data') parser.add_argument('input', help='Input file (h5ad, 10X mtx, csv, etc.)') parser.add_argument('--output', default='qc_filtered.h5ad', help='Output file name (default: qc_filtered.h5ad)') parser.add_argument('--mt-threshold', type=float, default=5, help='Max mitochondrial percentage (default: 5)') parser.add_argument('--min-genes', type=int, default=200, help='Min genes per cell (default: 200)') parser.add_argument('--min-cells', type=int, default=3, help='Min cells per gene (default: 3)') parser.add_argument('--skip-plots', action='store_true', help='Skip generating QC plots') args = parser.parse_args() # Configure scanpy sc.settings.verbosity = 2 sc.settings.set_figure_params(dpi=300, facecolor='white') sc.settings.figdir = './figures/' print(f"Loading data from: {args.input}") # Load data based on file extension if args.input.endswith('.h5ad'): adata = sc.read_h5ad(args.input) elif args.input.endswith('.h5'): adata = sc.read_10x_h5(args.input) elif args.input.endswith('.csv'): adata = sc.read_csv(args.input) else: # Try reading as 10X mtx directory adata = sc.read_10x_mtx(args.input) print(f"Loaded data: {adata.n_obs} cells x {adata.n_vars} genes") # Calculate QC metrics adata = calculate_qc_metrics(adata, mt_threshold=args.mt_threshold, min_genes=args.min_genes, min_cells=args.min_cells) # Generate QC plots (before filtering) if not args.skip_plots: print("\nGenerating QC plots (before filtering)...") generate_qc_plots(adata, output_prefix='qc_before') # Filter data adata = filter_data(adata, mt_threshold=args.mt_threshold, min_genes=args.min_genes, min_cells=args.min_cells) # Generate QC plots (after filtering) if not args.skip_plots: print("\nGenerating QC plots (after filtering)...") generate_qc_plots(adata, output_prefix='qc_after') # Save filtered data print(f"\nSaving filtered data to: {args.output}") adata.write_h5ad(args.output) print("\n=== QC Analysis Complete ===") if __name__ == "__main__": main()
-
-
SKILL.md 13.1 KB
--- name: alterlab-scanpy description: Run the standard single-cell RNA-seq analysis pipeline with Scanpy on AnnData — QC filtering, normalization, dimensionality reduction (PCA, UMAP, t-SNE), Leiden/Louvain clustering, marker/differential expression, PAGA trajectories, and plotting. Use when analyzing scRNA-seq data through clustering, cell-type annotation, DE, or pseudotime workflows; for building or reading the .h5ad data structure itself (layers, obs/var, concatenation, backed mode) prefer alterlab-anndata instead, and for RNA velocity from spliced/unspliced counts prefer alterlab-scvelo instead. Part of the AlterLab Academic Skills suite. license: MIT allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*) compatibility: "Self-contained — runs under `uv run python` with the skill's Python package installed; no API key or account required. Written for Scanpy 1.12 (current 1.12.4 as of 2026-09), which requires Python >= 3.12 and anndata >= 0.10." metadata: skill-author: AlterLab version: "1.1.0" last_updated: "2026-09-23" --- # Scanpy: Single-Cell Analysis ## Overview Scanpy is a scalable Python toolkit for analyzing single-cell RNA-seq data, built on AnnData. Apply this skill for complete single-cell workflows including quality control, normalization, dimensionality reduction, clustering, marker gene identification, visualization, and trajectory analysis. ## When to Use This Skill This skill should be used when: - Analyzing single-cell RNA-seq data (.h5ad, 10X, CSV formats) - Performing quality control on scRNA-seq datasets - Creating UMAP, t-SNE, or PCA visualizations - Identifying cell clusters and finding marker genes - Annotating cell types based on gene expression - Conducting trajectory inference or pseudotime analysis - Generating publication-quality single-cell plots ### Does NOT Trigger | Scenario | Use Instead | |----------|-------------| | Building, reading, or reshaping the `.h5ad` object itself (layers, obs/var, concatenation, backed/Dask mode) | `alterlab-anndata` | | RNA velocity from spliced/unspliced counts | `alterlab-scvelo` | | Probabilistic latent models — scVI/scANVI integration, model-based DE | `alterlab-scvi-tools` | | Spatial transcriptomics — Visium/Xenium/MERFISH neighborhood and image analysis | `alterlab-squidpy-spatial` | | Bulk RNA-seq differential expression from a count matrix | `alterlab-pydeseq2` | ## Quick Start ### Basic Import and Setup ```python import scanpy as sc import pandas as pd import numpy as np # Configure settings sc.settings.verbosity = 3 sc.settings.set_figure_params(dpi=80, facecolor='white') sc.settings.figdir = './figures/' ``` ### Loading Data ```python # From 10X Genomics adata = sc.read_10x_mtx('path/to/data/') adata = sc.read_10x_h5('path/to/data.h5') # From h5ad (AnnData format) adata = sc.read_h5ad('path/to/data.h5ad') # From CSV adata = sc.read_csv('path/to/data.csv') ``` ### Understanding AnnData Structure The AnnData object is the core data structure in scanpy: ```python adata.X # Expression matrix (cells × genes) adata.obs # Cell metadata (DataFrame) adata.var # Gene metadata (DataFrame) adata.uns # Unstructured annotations (dict) adata.obsm # Multi-dimensional cell data (PCA, UMAP) adata.raw # Raw data backup # Access cell and gene names adata.obs_names # Cell barcodes adata.var_names # Gene names ``` ## Standard Analysis Workflow ### 1. Quality Control Identify and filter low-quality cells and genes: ```python # Identify mitochondrial genes adata.var['mt'] = adata.var_names.str.startswith('MT-') # Calculate QC metrics sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], inplace=True) # Visualize QC metrics sc.pl.violin(adata, ['n_genes_by_counts', 'total_counts', 'pct_counts_mt'], jitter=0.4, multi_panel=True) # Filter cells and genes sc.pp.filter_cells(adata, min_genes=200) sc.pp.filter_genes(adata, min_cells=3) adata = adata[adata.obs.pct_counts_mt < 5, :] # Remove high MT% cells ``` **Use the QC script for automated analysis:** ```bash python scripts/qc_analysis.py input_file.h5ad --output filtered.h5ad ``` ### 2. Normalization and Preprocessing ```python # Normalize to 10,000 counts per cell sc.pp.normalize_total(adata, target_sum=1e4) # Log-transform sc.pp.log1p(adata) # Save raw counts for later adata.raw = adata # Identify highly variable genes sc.pp.highly_variable_genes(adata, n_top_genes=2000) sc.pl.highly_variable_genes(adata) # Subset to highly variable genes adata = adata[:, adata.var.highly_variable] # Regress out unwanted variation sc.pp.regress_out(adata, ['total_counts', 'pct_counts_mt']) # Scale data sc.pp.scale(adata, max_value=10) ``` ### 3. Dimensionality Reduction ```python # PCA sc.tl.pca(adata, svd_solver='arpack') sc.pl.pca_variance_ratio(adata, log=True) # Check elbow plot # Compute neighborhood graph sc.pp.neighbors(adata, n_neighbors=10, n_pcs=40) # UMAP for visualization sc.tl.umap(adata) sc.pl.umap(adata, color='leiden') # Alternative: t-SNE sc.tl.tsne(adata) ``` ### 4. Clustering ```python # Leiden clustering (recommended). Pass flavor='igraph' (the future default) + # n_iterations=2 — the bare call emits a FutureWarning and uses the slow backend. sc.tl.leiden(adata, resolution=0.5, flavor='igraph', n_iterations=2) sc.pl.umap(adata, color='leiden', legend_loc='on data') # Try multiple resolutions to find optimal granularity for res in [0.3, 0.5, 0.8, 1.0]: sc.tl.leiden(adata, resolution=res, flavor='igraph', n_iterations=2, key_added=f'leiden_{res}') ``` ### 5. Marker Gene Identification ```python # Find marker genes for each cluster sc.tl.rank_genes_groups(adata, 'leiden', method='wilcoxon') # Scanpy >=1.12 warns here on purpose: the clusters were derived from the same # counts being tested, so the p-values are anti-conservative ("double dipping"). # Treat the ranking as a marker-discovery heuristic, and get real p-values from an # independent grouping (condition, donor, genotype) or a pseudobulk test # (`alterlab-pydeseq2`) before reporting significance. # Visualize results sc.pl.rank_genes_groups(adata, n_genes=25, sharey=False) sc.pl.rank_genes_groups_heatmap(adata, n_genes=10) sc.pl.rank_genes_groups_dotplot(adata, n_genes=5) # Get results as DataFrame markers = sc.get.rank_genes_groups_df(adata, group='0') ``` ### 6. Cell Type Annotation ```python # Define marker genes for known cell types marker_genes = ['CD3D', 'CD14', 'MS4A1', 'NKG7', 'FCGR3A'] # Visualize markers sc.pl.umap(adata, color=marker_genes, use_raw=True) sc.pl.dotplot(adata, var_names=marker_genes, groupby='leiden') # Manual annotation cluster_to_celltype = { '0': 'CD4 T cells', '1': 'CD14+ Monocytes', '2': 'B cells', '3': 'CD8 T cells', } adata.obs['cell_type'] = adata.obs['leiden'].map(cluster_to_celltype) # Visualize annotated types sc.pl.umap(adata, color='cell_type', legend_loc='on data') ``` ### 7. Save Results ```python # Save processed data adata.write('results/processed_data.h5ad') # Export metadata adata.obs.to_csv('results/cell_metadata.csv') adata.var.to_csv('results/gene_metadata.csv') ``` ## Common Tasks ### Creating Publication-Quality Plots ```python import matplotlib.pyplot as plt # Set high-quality defaults sc.settings.set_figure_params(dpi=300, frameon=False, figsize=(5, 5)) # The `save=` parameter of every sc.pl.* function is deprecated as of Scanpy 1.12. # Pass show=False and save the current figure yourself — this also gives you control # over bbox/dpi and lets you compose panels. sc.pl.umap(adata, color='cell_type', palette='Set2', legend_loc='on data', legend_fontsize=12, legend_fontoutline=2, frameon=False, show=False) plt.savefig('figures/umap_publication.pdf', bbox_inches='tight') plt.close() # Heatmap of marker genes sc.pl.heatmap(adata, var_names=genes, groupby='cell_type', swap_axes=True, show_gene_labels=True, show=False) plt.savefig('figures/markers_heatmap.pdf', bbox_inches='tight') plt.close() ``` Refer to `references/plotting_guide.md` for comprehensive visualization examples. ### Trajectory Inference ```python # PAGA (Partition-based graph abstraction) sc.tl.paga(adata, groups='leiden') sc.pl.paga(adata, color='leiden') # Diffusion pseudotime adata.uns['iroot'] = np.flatnonzero(adata.obs['leiden'] == '0')[0] sc.tl.dpt(adata) sc.pl.umap(adata, color='dpt_pseudotime') ``` ### Differential Expression Between Conditions ```python # Compare treated vs control within cell types adata_subset = adata[adata.obs['cell_type'] == 'T cells'] sc.tl.rank_genes_groups(adata_subset, groupby='condition', groups=['treated'], reference='control') sc.pl.rank_genes_groups(adata_subset, groups=['treated']) ``` ### Gene Set Scoring ```python # Score cells for gene set expression gene_set = ['CD3D', 'CD3E', 'CD3G'] sc.tl.score_genes(adata, gene_set, score_name='T_cell_score') sc.pl.umap(adata, color='T_cell_score') ``` ### Batch Correction ```python # ComBat batch correction sc.pp.combat(adata, key='batch') # Alternative: use Harmony or scVI (separate packages) ``` ## Key Parameters to Adjust ### Quality Control - `min_genes`: Minimum genes per cell (typically 200-500) - `min_cells`: Minimum cells per gene (typically 3-10) - `pct_counts_mt`: Mitochondrial threshold (typically 5-20%) ### Normalization - `target_sum`: Target counts per cell (default 1e4) ### Feature Selection - `n_top_genes`: Number of HVGs (typically 2000-3000) - `min_mean`, `max_mean`, `min_disp`: HVG selection parameters ### Dimensionality Reduction - `n_pcs`: Number of principal components (check variance ratio plot) - `n_neighbors`: Number of neighbors (typically 10-30) ### Clustering - `resolution`: Clustering granularity (0.4-1.2, higher = more clusters) ## Common Pitfalls and Best Practices 1. **Always save raw counts**: `adata.raw = adata` before filtering genes 2. **Check QC plots carefully**: Adjust thresholds based on dataset quality 3. **Use Leiden with `flavor='igraph'`**: Far faster than the legacy `leidenalg` backend, and `sc.tl.louvain()` is deprecated as of Scanpy 1.12 — `uv pip install igraph` (not bundled with scanpy) 4. **Try multiple clustering resolutions**: Find optimal granularity 5. **Validate cell type annotations**: Use multiple marker genes 6. **Use `use_raw=True` for gene expression plots**: Shows original counts 7. **Check PCA variance ratio**: Determine optimal number of PCs 8. **Save intermediate results**: Long workflows can fail partway through ## Bundled Resources ### scripts/qc_analysis.py Automated quality control script that calculates metrics, generates plots, and filters data: ```bash python scripts/qc_analysis.py input.h5ad --output filtered.h5ad \ --mt-threshold 5 --min-genes 200 --min-cells 3 ``` ### references/standard_workflow.md Complete step-by-step workflow with detailed explanations and code examples for: - Data loading and setup - Quality control with visualization - Normalization and scaling - Feature selection - Dimensionality reduction (PCA, UMAP, t-SNE) - Clustering (Leiden, Louvain) - Marker gene identification - Cell type annotation - Trajectory inference - Differential expression Read this reference when performing a complete analysis from scratch. ### references/api_reference.md Quick reference guide for scanpy functions organized by module: - Reading/writing data (`sc.read_*`, `adata.write_*`) - Preprocessing (`sc.pp.*`) - Tools (`sc.tl.*`) - Plotting (`sc.pl.*`) - AnnData structure and manipulation - Settings and utilities Use this for quick lookup of function signatures and common parameters. ### references/plotting_guide.md Comprehensive visualization guide including: - Quality control plots - Dimensionality reduction visualizations - Clustering visualizations - Marker gene plots (heatmaps, dot plots, violin plots) - Trajectory and pseudotime plots - Publication-quality customization - Multi-panel figures - Color palettes and styling Consult this when creating publication-ready figures. ### assets/analysis_template.py Complete analysis template providing a full workflow from data loading through cell type annotation. Copy and customize this template for new analyses: ```bash cp assets/analysis_template.py my_analysis.py # Edit parameters and run python my_analysis.py ``` The template includes all standard steps with configurable parameters and helpful comments. ## Additional Resources - **Official scanpy documentation**: https://scanpy.readthedocs.io/ - **Scanpy tutorials**: https://scanpy-tutorials.readthedocs.io/ - **scverse ecosystem**: https://scverse.org/ (related tools: squidpy, scvi-tools, cellrank) - **Best practices**: Luecken & Theis (2019) "Current best practices in single-cell RNA-seq" ## Tips for Effective Analysis 1. **Start with the template**: Use `assets/analysis_template.py` as a starting point 2. **Run QC script first**: Use `scripts/qc_analysis.py` for initial filtering 3. **Consult references as needed**: Load workflow and API references into context 4. **Iterate on clustering**: Try multiple resolutions and visualization methods 5. **Validate biologically**: Check marker genes match expected cell types 6. **Document parameters**: Record QC thresholds and analysis settings 7. **Save checkpoints**: Write intermediate results at key steps
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.