Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/sickn33-scanpy/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 185% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 119% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 66% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 91% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 118% | 0% |
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.
This skill should be used when:
pythonimport 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/'
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')
The AnnData object is the core data structure in scanpy:
pythonadata.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
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:
bashpython scripts/qc_analysis.py input_file.h5ad --output filtered.h5ad
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)
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)
python# Leiden clustering (recommended) sc.tl.leiden(adata, resolution=0.5) 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, key_added=f'leiden_{res}')
python# Find marker genes for each cluster sc.tl.rank_genes_groups(adata, 'leiden', method='wilcoxon') # 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')
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')
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')
python# Set high-quality defaults sc.settings.set_figure_params(dpi=300, frameon=False, figsize=(5, 5)) sc.settings.file_format_figs = 'pdf' # UMAP with custom styling sc.pl.umap(adata, color='cell_type', palette='Set2', legend_loc='on data', legend_fontsize=12, legend_fontoutline=2, frameon=False, save='_publication.pdf') # Heatmap of marker genes sc.pl.heatmap(adata, var_names=genes, groupby='cell_type', swap_axes=True, show_gene_labels=True, save='_markers.pdf') # Dot plot sc.pl.dotplot(adata, var_names=genes, groupby='cell_type', save='_dotplot.pdf')
Refer to references/plotting_guide.md for comprehensive visualization examples.
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')
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'])
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')
python# ComBat batch correction sc.pp.combat(adata, key='batch') # Alternative: use Harmony or scVI (separate packages)
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%)target_sum: Target counts per cell (default 1e4)n_top_genes: Number of HVGs (typically 2000-3000)min_mean, max_mean, min_disp: HVG selection parametersn_pcs: Number of principal components (check variance ratio plot)n_neighbors: Number of neighbors (typically 10-30)resolution: Clustering granularity (0.4-1.2, higher = more clusters)adata.raw = adata before filtering genesuse_raw=True for gene expression plots: Shows original countsAutomated quality control script that calculates metrics, generates plots, and filters data:
bashpython scripts/qc_analysis.py input.h5ad --output filtered.h5ad \ --mt-threshold 5 --min-genes 200 --min-cells 3
Complete step-by-step workflow with detailed explanations and code examples for:
Read this reference when performing a complete analysis from scratch.
Quick reference guide for scanpy functions organized by module:
sc.read_*, adata.write_*)sc.pp.*)sc.tl.*)sc.pl.*)Use this for quick lookup of function signatures and common parameters.
Comprehensive visualization guide including:
Consult this when creating publication-ready figures.
Complete analysis template providing a full workflow from data loading through cell type annotation. Copy and customize this template for new analyses:
bashcp 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.
assets/analysis_template.py as a starting pointscripts/qc_analysis.py for initial filtering| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 7,832 | 4,103 | -48% | 1 | 1 | 0% | 1,420 | 4,041 | +185% | 0 | 0 | — |
case-02 | pass→pass | 11,642 | 5,881 | -49% | 1 | 1 | 0% | 2,357 | 4,446 | +89% | 0 | 0 | — |
case-17 | pass→pass | 8,818 | 5,770 | -35% | 1 | 1 | 0% | 1,659 | 4,371 | +163% | 0 | 0 | — |
case-03 | pass→pass | 6,901 | 4,288 | -38% | 1 | 1 | 0% | 1,392 | 4,021 | +189% | 0 | 0 | — |
case-04 | pass→pass | 8,091 | 3,406 | -58% | 1 | 1 | 0% | 1,639 | 3,975 | +143% | 0 | 0 | — |
case-05 | pass→pass | 8,370 | 5,258 | -37% | 1 | 1 | 0% | 1,479 | 4,291 | +190% | 0 | 0 | — |
case-06 | pass→pass | 3,349 | 2,628 | -22% | 1 | 1 | 0% | 605 | 3,722 | +515% | 0 | 0 | — |
case-07 | pass→pass | 9,102 | 5,787 | -36% | 1 | 1 | 0% | 1,654 | 4,287 | +159% | 0 | 0 | — |
case-08 | pass→pass | 3,311 | 3,026 | -9% | 1 | 1 | 0% | 555 | 3,805 | +586% | 0 | 0 | — |
case-09 | pass→pass | 11,251 | 8,233 | -27% | 1 | 1 | 0% | 2,234 | 4,946 | +121% | 0 | 0 | — |
case-10 | pass→pass | 9,712 | 5,701 | -41% | 1 | 1 | 0% | 1,678 | 4,159 | +148% | 0 | 0 | — |
case-11 | pass→pass | 11,312 | 8,399 | -26% | 1 | 1 | 0% | 2,243 | 4,853 | +116% | 0 | 0 | — |
case-12 | pass→pass | 2,744 | 3,634 | +32% | 1 | 1 | 0% | 501 | 3,856 | +670% | 0 | 0 | — |
case-13 | pass→pass | 4,227 | 3,294 | -22% | 1 | 1 | 0% | 817 | 3,825 | +368% | 0 | 0 | — |
case-14 | fail→pass | 9,216 | 4,260 | -54% | 1 | 1 | 0% | 1,836 | 4,016 | +119% | 0 | 0 | — |
case-15 | fail→pass | 12,579 | 4,301 | -66% | 1 | 1 | 0% | 2,418 | 4,002 | +66% | 0 | 0 | — |
case-16 | fail→pass | 10,733 | 4,367 | -59% | 1 | 1 | 0% | 2,120 | 4,043 | +91% | 0 | 0 | — |
case-18 | fail→pass | 9,691 | 1,753 | -82% | 1 | 1 | 0% | 1,618 | 3,535 | +118% | 0 | 0 | — |
case-19 | fail→pass | 11,803 | 2,579 | -78% | 1 | 1 | 0% | 2,261 | 3,719 | +64% | 0 | 0 | — |
case-20 | pass→pass | 8,535 | 4,228 | -50% | 1 | 1 | 0% | 1,591 | 4,019 | +153% | 0 | 0 | — |
case-21 | pass→pass | 10,784 | 6,798 | -37% | 1 | 1 | 0% | 2,399 | 4,640 | +93% | 0 | 0 | — |
case-22 | pass→pass | 12,278 | 8,633 | -30% | 1 | 1 | 0% | 2,386 | 4,915 | +106% | 0 | 0 | — |
case-23 | pass→pass | 10,515 | 8,669 | -18% | 1 | 1 | 0% | 2,132 | 4,875 | +129% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +26 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.