smithery/gptomics

bio-single-cell-perturb-seq

Analyze Perturb-seq / CROP-seq single-cell CRISPR screens.

Installation

$ npx skills add smithery/gptomics --skill bio-single-cell-perturb-seq

Summary

  • Analyze Perturb-seq / CROP-seq single-cell CRISPR screens.
  • Use when assigning guides as a mixture problem, removing non-perturbed escaper cells with Mixscape, choosing a calibrated test (SCEPTRE conditional resampling) over naive DE, quantifying effect size with E-distance, separating compositional shifts from within-state expression change, or judging whether a perturbation-prediction foundation model actually beats a baseline.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/gptomics · top by installs.

npx skills add smithery/gptomics

Browse all from smithery/gptomics

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 17,072 B
  • docs SUMMARY.md 205 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Version Compatibility

Reference examples tested with: pertpy 0.9+, scanpy 1.10+, anndata 0.10+, sceptre 0.10+

Before using code patterns, verify installed versions match. If versions differ:

  • Python: pip show <package> then help(module.function) to check signatures
  • R: packageVersion('<pkg>') then ?function_name to verify parameters

If code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.

Perturb-seq Analysis

"Analyze my Perturb-seq CRISPR screen" -> Assign guides, remove cells that received a guide but were not perturbed, test each perturbation with a calibrated method, and separate "moves cells" from "changes cells".

  • Python: pertpy.pp.GuideAssignment, pertpy.tl.Mixscape, pertpy.tl.Distance/DistanceTest, pertpy.tl.Milo/Sccoda
  • R: sceptre (conditional-resampling test), Seurat Mixscape, scMAGeCK

Governing Principle

Guide assignment is a mixture problem, not a threshold. Each cell's per-guide UMI vector mixes true integration with ambient guide contamination (free transcripts, index hopping, doublets), and the ambient pool is structured: it is dominated by whichever guides are most abundant in the library, so a flat UMI cutoff preferentially mis-assigns cells to common guides and calls rare-guide cells negative. Call guides by a per-guide background/foreground mixture posterior, and report the perturbed fraction. MOI changes the meaning: low-MOI (~1 guide/cell) gives clean single-gene attribution but discards 70-90% of cells; high-MOI is for combinatorial designs but measures every single-gene effect in a co-perturbed background.

Assignment is not effective perturbation. A cell can carry a guide yet be transcriptionally wild-type: incomplete CRISPR-KO editing, in-frame indels, escapers, or weak CRISPRi knockdown. The "perturbed" population is a mixture of truly perturbed and effectively-wild-type cells, which attenuates every effect-size estimate toward the null. Mixscape removes the non-perturbed cells via a local non-targeting-neighbor perturbation signature before testing. Deep caveat: an all-NP result is not evidence the gene is non-functional, because it is confounded with low guide efficiency; Mixscape cannot distinguish "no phenotype" from "no editing".

Naive DE is miscalibrated by depth and pseudoreplication. The probability of detecting a guide covaries with sequencing depth, and depth also drives expression detection, so a plain Wilcoxon/NB test between guide-positive and NT cells has inflated type-I error. SCEPTRE fixes this with conditional resampling: it models P(cell receives this guide | technical covariates incl. depth) and resamples the assignment to build a calibrated null, robust to misspecification of the expression model. Separately, treating thousands of cells from one transfection as independent replicates inflates significance (pseudoreplication, Squair 2021); the replication unit is the transfection, so pseudobulk-per-replicate is required for calibrated inference.

E-distance is the modern effect-size. Energy distance in PCA space, E = 2*sigmabetween - sigmawithinX - sigmawithin_Y, measures separation magnitude (not direction or mechanism), with a permutation E-test. It is interpretable only relative to a fixed embedding and is not comparable across studies with different pipelines.

Separate the compositional shift from the within-state change. A perturbation can (a) shift the proportions of pre-existing cell states (differential abundance) without changing any state's program, or (b) change expression within a state (differential expression). A perturbation that only redistributes cells produces a large pseudobulk "DE signature" that is entirely a composition artifact. These need different tools and answer different questions; report both.

Guide Assignment: Mixture vs Threshold

Method Model Use when Fails when
Mixture (posterior) Per-guide 2-component mixture (background Poisson + foreground Gaussian); pt.pp.GuideAssignment.assignmixturemodel Default; ambient varies by guide abundance; low and high MOI Very few cells per guide (mixture unstable); verify against NT contamination floor
Threshold Flat UMI cutoff; assignbythreshold Quick sanity check, uniform high-signal libraries Ambient scales with abundant guides -> mis-assigns to common guides, calls rare-guide cells negative

Cell Ranger and Replogle's guide_calling fit mixtures on log counts; require a minimum dominant-guide UMI fraction, not just an absolute count, and gate doublets (they masquerade as combinatorial cells).

Method Decision Table (Testing and Effect Size)

Method What it answers Use when Fails when
Mixscape (pertpy/Seurat) Which cells were effectively perturbed; per-perturbation DE after removing escapers CRISPR-KO with heterogeneous editing; need escaper removal All-NP confounded with low guide efficiency; KO posteriors not comparable across targets
SCEPTRE Calibrated perturbation-gene association Rigorous testing under the depth confounder; element-level screens Needs the assignment model roughly right; conservative by design
scMAGeCK (LR / RRA) Per-gene effect across many genes; high-MOI deconvolution Multi-guide cells; ridge-regression effect estimates NEGCTRL choice defines the null; runs on scale.data so covariates propagate
E-distance + E-test (pertpy) Effect-size magnitude; perturbation similarity Ranking/clustering perturbations by how far they move cells Embedding-dependent, not cross-study comparable; floored by permutation count
Pseudobulk DE (DESeq2/edgeR) Average within-state program change >=2-3 biological replicates per condition One replicate per guide -> no valid inference; sum raw counts, not means
Milo / scCODA / Augur Differential abundance / composition "Does the perturbation move cells across states?" Conflated with within-state DE if reported alone

Verify the current best-practice default and parameter names against the installed pertpy/sceptre docs before committing; the APIs drift across releases.

Foundation-Model Reality Check

This is settled as of 2026, not hype. scGPT, Geneformer, scFoundation, scBERT, UCE are pretrained with masked-expression objectives and learn the co-expression manifold of observational data; perturbation prediction is a causal/interventional question, and there is no theorem that co-expression transfers to intervention. The empirical result across benchmarks: none reliably beat trivial baselines on unseen perturbations (Ahlmann-Eltze 2025; Kernfeld 2025; Csendes 2025).

  • For unseen single perturbations, predicting the mean perturbed profile across training perturbations is hard to beat; for unseen doubles, an additive model (sum the two single effects) captures most variance because genetic interactions are the exception.
  • All-gene MSE/correlation is dominated by unchanged genes, so a "predict no change"/mean model scores deceptively high. Evaluate on DE genes against mean/additive baselines.
  • Train-test leakage inflates reported success: random cell-level splits put the same perturbation in train and test. True generalization holds out entire perturbations (and ideally cell-type contexts), not random cells.

The defensible reviewer stance: demand whole-perturbation holdout, DE-gene metrics, and an explicit additive/mean baseline. Without these, a positive result is not credible.

Guide Assignment (pertpy)

Goal: Call which guide each cell actually received using a mixture posterior, not a flat threshold.

Approach: Fit a per-guide Poisson-Gaussian mixture to the guide-count modality and assign by posterior, allowing negative and multi-guide calls.

import pertpy as pt
import scanpy as sc

gdo = mdata.mod['gdo']                       # guide-count modality (cells x guides)
gdo.layers['counts'] = gdo.X.copy()

ga = pt.pp.GuideAssignment()
ga.assign_mixture_model(gdo, assigned_guides_key='assigned_guide')   # background Poisson + foreground Gaussian
# Inspect NT/abundant-guide UMI distributions as a contamination floor before trusting calls
ga.plot_heatmap(gdo, layer='counts')

Mixscape: Remove Non-Perturbed Escapers (pertpy)

Goal: Separate effectively-perturbed (KO) from non-perturbed (NP) cells before any DE.

Approach: Build a local perturbation signature by subtracting each cell's NT neighbors, then fit a per-target 2-component mixture to classify cells; drop NP cells.

ms = pt.tl.Mixscape()
ms.perturbation_signature(adata, pert_key='perturbation', control='NT', n_neighbors=20)   # pert_key here = the broad perturbed-vs-control column
ms.mixscape(adata, pert_key='target_gene', control='NT', layer='X_pert')   # pert_key here = the per-target column (intentionally different); renamed from labels; writes adata.obs['mixscape_class_global'] KO/NP/NT
# An all-NP target is confounded with low guide efficiency: report perturbed fraction, do not call the gene non-functional
adata.obs['mixscape_class_global'].value_counts()

E-distance and the E-test (pertpy)

Goal: Quantify how far each perturbation moves cells and test it against a permutation null.

Approach: Compute energy distance in a fixed PCA embedding; pin the embedding and metric, and run the permutation E-test against the control.

sc.pp.pca(adata, n_comps=50)
dist = pt.tl.Distance(metric='edistance', obsm_key='X_pca')   # pin obsm; sqeuclidean vs euclidean default changed across versions
pairwise = dist.pairwise(adata, groupby='target_gene')

etest = pt.tl.DistanceTest('edistance', n_perms=1000)         # smallest p ~ 1/(n_perms+1); crushed by multiple testing
results = etest(adata, groupby='target_gene', contrast='NT')

SCEPTRE: Calibrated Testing (R)

Goal: Test perturbation-gene associations with calibration verified on the data itself.

Approach: Import counts and guide matrices, set parameters, assign guides by mixture, then run the calibration check (negative controls) before the discovery analysis.

library(sceptre)

obj <- import_data(response_matrix = rna_counts, grna_matrix = grna_counts,
                   grna_target_data_frame = grna_targets, moi = 'low')
obj <- set_analysis_parameters(obj, discovery_pairs = pairs)
obj <- assign_grnas(obj, method = 'mixture')        # mixture | thresholding | maximum
obj <- run_qc(obj)
obj <- run_calibration_check(obj)                   # negative-control pairs must be well-calibrated FIRST
obj <- run_discovery_analysis(obj)
results <- get_result(obj, analysis = 'discovery_analysis')

Pseudobulk DE (Within-State Change)

Goal: Test the average program change per perturbation with valid biological replication.

Approach: Sum RAW counts per (target gene, replicate), filter tiny pseudobulk samples, then run DESeq2/edgeR; this respects the replication unit and avoids pseudoreplication.

import pertpy as pt

adata.layers['counts'] = adata.layers.get('counts', adata.X.copy())   # stash RAW counts before any log1p
pb = pt.tl.PseudobulkSpace()
pdata = pb.compute(adata, target_col='target_gene', groups_col='replicate', layer_key='counts', mode='sum')   # sum RAW counts, not .X (log-normalized)
# Drop pseudobulk samples below ~10 cells (verify the per-sample cell-count obs column name with help(pb.compute))
# Hand pdata to pertpy EdgeR / pydeseq2 with design ~ replicate + target_gene; needs >=2-3 replicates per condition

One transfection per guide means no valid biological-replicate inference exists; using guides targeting the same gene as pseudo-replicates partially helps but conflates guide-specific off-targets.

Compositional vs Expression (Separate the Questions)

Goal: Decide whether a perturbation moves cells across states or changes a state's program.

Approach: Run a differential-abundance test (Milo neighborhoods or scCODA) for composition, and report it alongside the within-state pseudobulk DE.

milo = pt.tl.Milo()
mdata_milo = milo.load(adata)
milo.make_nhoods(mdata_milo['rna'])
milo.count_nhoods(mdata_milo, sample_col='replicate')
milo.da_nhoods(mdata_milo, design='~ target_gene')   # differential abundance: does the perturbation shift proportions?

Common Errors

Symptom Cause Fix
Rare-guide cells called negative Flat UMI threshold; ambient biased to abundant guides Mixture-model assignment by posterior; require a dominant-guide UMI fraction
Effect sizes weaker than expected Escapers/incomplete KO dilute the perturbed population Run Mixscape, remove NP cells, report perturbed fraction
"Gene is non-functional" from all-NP All-NP confounds no-phenotype with no-editing Do not claim non-functional; check guide efficiency independently
Hundreds of "significant" hits Naive Wilcoxon/NB miscalibrated by depth + pseudoreplication SCEPTRE conditional resampling; pseudobulk-per-replicate DE
Huge DE signature but no program change Perturbation only redistributes cells across states Run Milo/scCODA; attribute the signal to composition
E-distances disagree with another paper Embedding/metric/PC count differ; default metric changed Pin pertpy version, obsm key, and cellwisemetric; do not cross-compare
Combinatorial cells everywhere Doublets masquerade as multi-guide Gate doublets (Scrublet/scDblFinder) before multi-guide analysis
Foundation model "beats" baselines Cell-level split leakage; all-gene metric hides failure Hold out whole perturbations; score DE genes vs additive/mean baseline

Related Skills

  • single-cell/preprocessing - scRNA-seq QC and normalization upstream of the screen
  • single-cell/doublet-detection - gating doublets before multi-guide analysis
  • single-cell/markers-annotation - interpreting per-perturbation DE genes
  • single-cell/differential-abundance - compositional shift testing (Milo/scCODA) for perturbations that change cell-state proportions
  • single-cell/batch-integration - multi-sample/replicate integration
  • crispr-screens/mageck-analysis - bulk CRISPR screen analysis (MAGeCK RRA/MLE)
  • crispr-screens/perturb-seq-analysis - related single-cell CRISPR screen workflow
  • differential-expression/deseq2-basics - pseudobulk DESeq2 testing on summed counts
  • pathway-analysis/go-enrichment - pathway interpretation of perturbation signatures

References

Dixit A, Parnas O, Li B, et al. Perturb-Seq: dissecting molecular circuits with scalable single-cell RNA profiling of pooled genetic screens. Cell 167(7):1853-1866 (2016). Datlinger P, Rendeiro AF, Schmidl C, et al. Pooled CRISPR screening with single-cell transcriptome readout (CROP-seq). Nat Methods 14(3):297-301 (2017). Replogle JM, Norman TM, Xu A, et al. Combinatorial single-cell CRISPR screens by direct guide RNA capture and targeted sequencing. Nat Biotechnol 38(8):954-961 (2020). Papalexi E, Mimitou EP, Butler AW, et al. Characterizing the molecular regulation of inhibitory immune checkpoints with multimodal single-cell screens (Mixscape). Nat Genet 53(3):322-331 (2021). Yang L, Zhu Y, Yu H, et al. scMAGeCK links genotypes with multiple phenotypes in single-cell CRISPR screens. Genome Biol 21:19 (2020). Barry T, Wang X, Morris JA, Roeder K, Katsevich E. SCEPTRE improves calibration and sensitivity in single-cell CRISPR screen analysis. Genome Biol 22:344 (2021). Squair JW, Gautier M, Kathe C, et al. Confronting false discoveries in single-cell differential expression. Nat Commun 12:5692 (2021). Peidli S, Green TD, Shen C, et al. scPerturb: harmonized single-cell perturbation data (E-distance). Nat Methods 21(3):531-540 (2024). Heumos L, Ji Y, May L, et al. Pertpy: an end-to-end framework for perturbation analysis. Nat Methods 23(2):350-359 (2026). Dann E, Henderson NC, Teichmann SA, Morgan MD, Marioni JC. Differential abundance testing on single-cell data using k-nearest neighbor graphs (Milo). Nat Biotechnol 40(2):245-253 (2022). Ahlmann-Eltze C, Huber W, Anders S. Deep-learning-based gene perturbation effect prediction does not yet outperform simple linear baselines. Nat Methods 22(8):1657-1661 (2025). Kernfeld E, Yang Y, Weinstock JS, et al. A comparison of computational methods for expression forecasting. Genome Biol 26:388 (2025). Csendes G, et al. Benchmarking foundation cell models for post-perturbation RNA-seq prediction. BMC Genomics 26:393 (2025).