agentsclimarketplace

Differential abundance

Skill FridrichMethod/awesome-skills/skills/differential-abundance

Tests for differentially abundant proteins between conditions with limma/DEqMS empirical-Bayes moderation, proDA/msqrob2/MSstats missingness modeling, and Python Welch+BH alternatives. Frames missing values as left-censored MNAR (model, do not impute), makes variance moderation the load-bearing step at n=3-5, and prefers feature/peptide-level testing. Use when identifying proteins with significant abundance changes between experimental groups. Summarization and normalization mechanics are proteomics/quantification; volcano and MA plots are data-visualization/volcano-and-ma-plots; pathway enrichment of the hit list is pathway-analysis/go-enrichment.From its SKILL.md

Install
npx -y skills add FridrichMethod/awesome-skills --skill differential-abundance

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 11 stars11 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

22.2 KB, ~5.7k tokens by cl100k_base, as published. Nobody here has run it

Version Compatibility

Reference examples tested with: limma 3.58+, DEqMS 1.20+, proDA 1.20+, ashr 2.2+, pandas 2.2+, scipy 1.12+, statsmodels 0.14+

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.

Differential Protein Abundance -- Moderated Testing on a Log-Intensity Matrix with Honest Missingness

"Find differentially abundant proteins between my conditions" -> Moderated statistical testing on a normalized log-intensity matrix, carrying missingness in the likelihood instead of filling it in -- because the missing values are low BECAUSE the protein is low, and imputing them manufactures false positives.

  • R: limma::eBayes(fit, trend=TRUE, robust=TRUE) for empirical-Bayes moderated t-tests (the protein-level workhorse)
  • R: DEqMS::spectraCounteBayes() when PSM/peptide counts are available (preferred over limma-trend when quant depth varies)
  • R: proDA::test_diff() / msqrob2 / MSstats when missing values are extensive (model the dropout, no imputation)
  • Python: scipy.stats.ttest_ind(equal_var=False) + statsmodels BH (large n only; no moderation)

Scope: this skill owns the statistical TEST -- design/contrast construction, variance moderation, missingness handling, multiple-testing correction, minimum-fold-change testing, and fold-change shrinkage. Peptide-to-protein summarization and normalization mechanics -> proteomics/quantification. Volcano/MA plots -> data-visualization/volcano-and-ma-plots. Enrichment of the hit list -> pathway-analysis/go-enrichment. OUT OF SCOPE: how MaxLFQ/TMP/IRS produce the matrix (quantification); how to draw a volcano (data-visualization).

The Single Most Important Modern Insight -- Model the Missingness, Moderate the Variance, Test at the Feature Level

  1. Missing values in label-free MS are left-censored MNAR -- missing BECAUSE the intensity is low -- and imputing them (especially Perseus/MaxQuant downshift) manufactures SYSTEMATIC false positives. Downshift draws each missing value from a narrow Gaussian (mean = mu - 1.8sigma, SD = 0.3sigma). For an on/off protein (seen in all of group A, missing in all of B) the t-statistic numerator is inflated by construction (mean_B fixed ~1.8 sigma below observed, deterministic) and the denominator is artificially deflated (all imputed B values from one 0.3-sigma Gaussian -> collapsed within-group SD) -> enormous t -> tiny p. Because every on/off protein is treated identically, the false positives are systematic: the volcano-plot "anchor/wing" artifact (rigid near-vertical streaks of pinned points far out on both x-axis sides). The honest statement is "undetected in group B", not "20x lower, p=1e-6". The correct approach is to MODEL the dropout in the likelihood -- proDA (probabilistic dropout), msqrob2, MSstats-AFT -- NOT fill it (Lazar 2016; Ahlmann-Eltze & Anders 2019).
  2. At the n=3-5 replicates proteomics actually uses, per-protein variance has only 2-4 residual df and is unusable raw -- variance moderation is the load-bearing element, not optional. limma borrows a prior d0 across all proteins so a 4-replicate design tests on ~10 df instead of 6; trend=TRUE makes the prior a function of mean intensity (effectively mandatory for label-free, where a single global prior mis-calibrates FDR across the abundance range); robust=TRUE Winsorizes outlier variances (Phipson 2016). DEqMS makes the prior a function of PSM/peptide count and generally outperforms limma-trend when quantification depth varies across proteins (Zhu 2020).
  3. Feature/peptide-level modeling beats summarize-then-test. Summarizing first (one number per protein per run) discards the within-protein between-peptide variance and the correct degrees of freedom: 12 consistent peptides deserve a smaller SE than 12 disagreeing ones, but after summarization both look equally certain, and a protein with 30 observations looks as informative as one with 3. msqrob2/MSstats keep every peptide as a degree of freedom; this is why the same data gives different answers (Goeminne 2016; Sticker 2020; Choi 2014).

Tool Taxonomy

Tool / methodCitationMechanism / roleWhen
limmaRitchie 2015; Phipson 2016EB moderated t; posterior variance blends a prior d0 with the per-protein estimate; trend ties the prior to mean intensity, robust Winsorizes outliersprotein-level summaries, small n, the default workhorse
DEqMSZhu 2020prior variance = loess of log-variance vs log2(count); precision follows quantification DEPTH not just intensityTMT (count=PSM) and label-free DDA (count=peptide); quant depth varies; preferred over limma-trend
proDAAhlmann-Eltze & Anders 2019 (preprint)probabilistic dropout: missing = left-censored, integrated under a per-sample sigmoid dropout curve; EB on location and variance; no imputationlabel-free DDA with many MNAR missing values, small n, proteins absent in one group
msqrob2Sticker 2020; Goeminne 2016peptide-level robust ridge: Huber M-estimation downweights outlier peptides, ridge shrinks effects from few observations, EB variance moderationlabel-free DDA, outlier-peptide / unbalanced-coverage risk; best FDR in hard spike-in regimes
MSstatsChoi 2014feature-level linear mixed model (group fixed + feature + run/subject random); AFT censored handling for missingSRM/PRM/DIA, technical replicates, nested/repeated-measures, labeled designs
Welch t-test + BH--per-protein two-sample t with equal_var=False + Benjamini-Hochberglarge n (>10/group), Python-only; no moderation, unusable at n=3-5
ashrStephens 2017mixture prior with a point mass at zero; posterior means shrink uncertain effects toward zerorecovering "which proteins truly changed and by how much" (not for GSEA ranking)
volcano / MA plot--(route OUT)visualization -> data-visualization/volcano-and-ma-plots
enrichment of hits--(route OUT)functional interpretation -> pathway-analysis/go-enrichment

Decision Tree by Scenario

ScenarioRecommendedWhy
Small n (3-5/group), protein-level summary matrixlimma eBayes(trend=TRUE, robust=TRUE)EB borrows variance across proteins; the trend calibrates FDR across abundance
PSM/peptide counts available (TMT or label-free DDA)DEqMS spectraCounteBayesprior keyed on quant depth removes single-PSM false positives limma admits
Label-free with many MNAR missing values, on/off proteinsproDA test_diffmodels the censored dropout; never imputes; correct verdict for "undetected in one group"
Outlier-peptide risk, unbalanced peptide coveragemsqrob2 (peptide-level robust ridge)keeps feature df; Huber downweights bad peptides; best FDR in spike-in benchmarks
Technical replicates, nested/repeated-measures, labeled (SRM/PRM/DIA)MSstats (feature-level mixed model)random effects capture run/subject structure summarize-then-test discards
Batch presentbatch as a covariate in the design (~ batch + condition)removeBatchEffect is visualization-only; never feed its output to lmFit
Minimum biologically meaningful fold changetreat() + topTreat() (or SAM s0)tests
Large n (>10/group), Python-onlyWelch t-test + BHvariance estimates reliable; no moderation needed at large n

Default when uncertain: protein-level summary matrix at n=3-5 -> limma eBayes(trend=TRUE, robust=TRUE); if PSM/peptide counts exist, escalate to DEqMS; if missingness is extensive and intensity-dependent, escalate to proDA.

limma Workflow (R)

Goal: Identify differentially abundant proteins using moderated statistics that borrow information across all proteins.

Approach: Build the design (batch as a covariate when present), fit the linear model and contrast, apply EB moderation with the intensity trend and robust fitting, then extract BH-corrected results. Never feed removeBatchEffect output to lmFit.

library(limma)

design <- model.matrix(~0 + condition + batch, data = sample_info)  # batch in the model, not removed first
colnames(design)[1:2] <- levels(factor(sample_info$condition))

fit <- lmFit(protein_matrix, design)
contrast_matrix <- makeContrasts(Treatment - Control, levels = design)
fit2 <- contrasts.fit(fit, contrast_matrix)
fit2 <- eBayes(fit2, trend = TRUE, robust = TRUE)  # trend mandatory for label-free; robust Winsorizes outliers

results <- topTable(fit2, coef = 1, number = Inf, adjust.method = 'BH')
# columns: logFC, AveExpr, t, P.Value, adj.P.Val, B  (adj.P.Val is the BH p; there is no $FDR)

Minimum-fold-change testing

Goal: Call proteins whose effect exceeds a biologically meaningful threshold, not merely differ from zero.

Approach: Use treat() against the moderated null and read topTreat(). NEVER topTable(lfc=...) nor a post-hoc volcano double filter (abs(logFC) > 1 & adj.P.Val < 0.05); conditioning on both the FC and the p-value selects for high-variance nulls (a collider effect) and inflates realized FDR above 50% (Ebrahimpoor & Goeman 2021).

LFC_THRESHOLD <- log2(1.2)  # 1.2-fold floor; treat tests against this null, no double-filter FDR inflation
fit2 <- treat(fit2, lfc = LFC_THRESHOLD)
results <- topTreat(fit2, coef = 1, number = Inf)  # topTreat omits the B column

DEqMS Workflow (R)

Goal: Improve on limma by tying each protein's prior variance to its quantification depth -- proteins measured by more PSMs/peptides are more precise.

Approach: Run limma through eBayes, attach the count vector, then apply DEqMS's count-aware EB. Use PSM count for TMT (quant at MS2) and peptide count for label-free DDA; for multi-batch TMT use the MINIMUM count across batches (the bottleneck batch sets precision).

library(DEqMS)

# fit2 is the limma fit through eBayes (above)
fit2$count <- psm_count_per_protein[rownames(fit2$coefficients)]  # PSM for TMT, peptide for LFQ; min across batches
fit3 <- spectraCounteBayes(fit2)

results <- outputResult(fit3, coef_col = 1)
# adds sca.t, sca.P.Value, sca.adj.pval (the count-adjusted statistics; use these, not the limma columns)

proDA Workflow (R)

Goal: Test proteins with extensive MNAR missingness, including on/off proteins, without imputing a single value.

Approach: Fit the probabilistic-dropout model directly on the log-intensity matrix; missing values contribute as left-censored observations under a per-sample dropout curve. Then test the contrast against zero.

library(proDA)

fit <- proDA(protein_matrix, design = ~condition, col_data = sample_info,
             reference_level = 'Control')
result_names(fit)  # list testable coefficients first
results <- test_diff(fit, conditionTreatment - conditionControl)
# columns: name, pval, adj_pval, diff (log2FC), t_statistic, se

Python Workflow

Goal: Run the full pipeline in Python when no R is available and n is large enough that moderation is unnecessary.

Approach: Log2-transform, median-normalize, run per-protein Welch t-tests, apply Benjamini-Hochberg. This has NO variance moderation and should not be used at n=3-5 -- escalate to limma/DEqMS for small n.

import numpy as np
import pandas as pd
from scipy import stats
from statsmodels.stats.multitest import multipletests

def preprocess(intensities):
    log2_data = np.log2(intensities.replace(0, np.nan))  # zeros -> NaN to avoid -inf
    sample_medians = log2_data.median(axis=0)
    return log2_data - sample_medians + sample_medians.median()

def differential_abundance(normalized, case_cols, ctrl_cols):
    rows = []
    for protein in normalized.index:
        case, ctrl = normalized.loc[protein, case_cols].dropna(), normalized.loc[protein, ctrl_cols].dropna()
        if len(case) >= 2 and len(ctrl) >= 2:
            _, pval = stats.ttest_ind(case, ctrl, equal_var=False)  # Welch; scipy defaults to Student's True
            rows.append({'protein': protein, 'log2fc': case.mean() - ctrl.mean(), 'pvalue': pval})
    df = pd.DataFrame(rows)
    df['padj'] = multipletests(df['pvalue'], method='fdr_bh')[1]  # default is Holm-Sidak; pass fdr_bh explicitly
    return df

Fold-Change Reporting

Goal: Hand the right effect estimate to the right consumer.

Approach: Report the RAW fold change (the best unbiased point estimate) for GSEA/pathway ranking and meta-analysis -- those need the full continuous distribution or FC+SE pairs. Apply shrinkage (ashr) only when recovering "which proteins truly changed and by how much"; it fits a mixture prior with a point mass at zero and shrinks uncertain effects smoothly toward zero. This is preferred over hard-thresholding (zeroing FCs at padj 0.05), which creates an arbitrary step function. No mature Python ashr equivalent exists.

library(ashr)

se <- sqrt(fit2$s2.post) * fit2$stdev.unscaled[, 1]
shrunk <- ash(fit2$coefficients[, 1], se, mixcompdist = 'normal')
shrunken_fc <- shrunk$result$PosteriorMean  # report alongside raw logFC, not as a replacement for GSEA
lfsr <- shrunk$result$lfsr

Per-Method Failure Modes

Downshift / any imputation feeding a variance-based test

Trigger: Perseus/MaxQuant downshift (or MinDet/MinProb/QRILC) fills NAs, then limma/t-test runs on the filled matrix. Mechanism: Imputed values come from one narrow Gaussian -> fabricated low within-group variance + deterministic mean offset -> inflated t. Symptom: Volcano "anchor/wing" -- rigid near-vertical streaks of pinned on/off proteins at high significance; realized FDR far above nominal. Fix: Model the missingness instead (proDA / msqrob2 / MSstats-AFT); report on/off proteins as "undetected in group X".

kNN imputation on left-censored data

Trigger: kNN/mean imputation applied to label-free data with MNAR dropout. Mechanism: Mean-reverting -- pulls a truly-low (missing because low) value UP toward the mean. Symptom: Real down-regulation is compressed; down hits weakened or lost. Fix: Only valid under MCAR/MAR; for MNAR model the dropout. Under uncertainty Lazar 2016 shows the milder MCAR error beats MNAR-imputers slamming random highs to the floor.

removeBatchEffect before testing

Trigger: removeBatchEffect() output fed to lmFit. Mechanism: Subtracts the fitted batch component with no uncertainty propagation -> understated residual variance, inflated EB df; if batch is confounded with biology it deletes real signal. Symptom: Anticonservative p-values; lost true effects when cases/controls split by batch. Fix: Include batch as a covariate in the SAME model (~ batch + condition); use removeBatchEffect only for PCA/visualization.

eBayes(trend=FALSE) on intensity data

Trigger: Plain eBayes (trend off) on a log-intensity matrix. Mechanism: A single global prior over-shrinks high-abundance and under-shrinks low-abundance proteins. Symptom: Mis-calibrated FDR across the abundance range. Fix: eBayes(trend = TRUE, robust = TRUE); escalate to DEqMS when quant depth varies.

Wrong DEqMS count column

Trigger: Razor+unique counts vs MS2-level PSMs, or total-across-batches vs minimum-across-batches. Mechanism: The variance-vs-count prior is fit on the wrong precision proxy. Symptom: Mis-ranked proteins; the count moderation helps the wrong ones. Fix: PSM count for TMT, peptide count for label-free; minimum count across batches for multi-batch TMT.

proDA on MCAR missingness

Trigger: proDA applied where dropout is random (e.g. a TMT channel lost at random), not detection-limited. Mechanism: The left-censored dropout model is mis-specified. Symptom: Biased estimates; the model fits a dropout curve that does not exist. Fix: proDA needs intensity-dependent missingness; for MCAR use limma/DEqMS on the observed values.

FC + significance double filter

Trigger: abs(logFC) > 1 & adj.P.Val < 0.05 applied after the test. Mechanism: |logFC| is large for a true effect OR a large SE; filtering on both the FC and the p (both depend on SE) selects high-variance nulls (collider effect). Symptom: Realized FDR above 50% at nominal 5% (Ebrahimpoor & Goeman 2021). Fix: treat()+topTreat() or SAM s0, which sit inside the statistic before selection.

Quantitative Thresholds

ThresholdSourceRationale
n=3-5 replicates -> 2-4 residual df--raw per-protein variance unusable; moderation is mandatory, not optional
limma adds prior d0 (~4) dfRitchie 2015a 4-replicate design tests on ~10 df vs 6; the borrowed df is the benefit
downshift mean = mu - 1.8sigma, SD = 0.3sigmaPerseus default1.8 places imputed mass ~3.6th percentile; 0.3 gives only 30% of real spread -> manufactured false positives
trend=TRUE effectively mandatory for label-freeRitchie 2015a single global prior mis-calibrates FDR across abundance
min-FC floor log2(1.2) (1.2-fold) via treat()--example floor; common alternatives 1.5-fold (~0.58) or 2-fold (1.0); set by biology, tested against the moderated null
BH adjusted p < 0.05Benjamini-Hochbergcontrols FDR over the WHOLE rejection set, not subsets carved out afterward
DEqMS multi-batch TMT: minimum count across batchesZhu 2020the bottleneck batch sets the realized precision
realized FDR > 50% from FC+significance double filterEbrahimpoor & Goeman 2021top-100 at n=12 exceeded 50% FDR at nominal 5%

Common Errors

Error / symptomCauseSolution
results$FDR is NULLlimma topTable/topTreat have no $FDR columnuse adj.P.Val (BH-adjusted p)
topTreat row has no BtopTreat omits B (a topTable column)read logFC, AveExpr, t, P.Value, adj.P.Val
FDR mis-calibrated across abundanceeBayes with trend=FALSE on intensity dataeBayes(fit, trend = TRUE, robust = TRUE)
min-FC test inflates FDRtopTable(lfc=...) or post-hoc volcano double filtertreat(fit, lfc=log2(1.2)) then topTreat()
anticonservative p after batch correctionremoveBatchEffect output fed to lmFitput batch in the design: ~ batch + condition
DEqMS columns missingforgot fit$count or read limma columnsset fit$count, run spectraCounteBayes, read sca.adj.pval from outputResult
Student's t instead of Welchscipy.stats.ttest_ind defaults equal_var=Truepass equal_var=False
p-values look like Holm-Sidakstatsmodels multipletests defaults to 'hs'pass method='fdr_bh'
volcano "anchor/wing" streaksdownshift/imputation feeding the testmodel dropout (proDA/msqrob2/MSstats-AFT); report on/off proteins as undetected

References

  • Ritchie ME, Phipson B, Wu D, Hu Y, Law CW, Shi W, Smyth GK. 2015. limma powers differential expression analyses for RNA-sequencing and microarray studies. Nucleic Acids Res 43(7):e47.
  • Phipson B, Lee S, Majewski IJ, Alexander WS, Smyth GK. 2016. Robust hyperparameter estimation protects against hypervariable genes and improves power to detect differential expression. Ann Appl Stat 10(2):946-963.
  • Zhu Y, Orre LM, Zhou Tran Y, et al. 2020. DEqMS: a method for accurate variance estimation in differential protein expression analysis. Mol Cell Proteomics 19(6):1047-1057.
  • Ahlmann-Eltze C, Anders S. 2019. proDA: probabilistic dropout analysis for identifying differentially abundant proteins in label-free mass spectrometry. bioRxiv 661496 (preprint; cite citation("proDA"), never a journal).
  • Choi M, Chang CY, Clough T, Broudy D, Killeen T, MacLean B, Vitek O. 2014. MSstats: an R package for statistical analysis of quantitative mass spectrometry-based proteomic experiments. Bioinformatics 30(17):2524-2526.
  • Goeminne LJE, Gevaert K, Clement L. 2016. Peptide-level robust ridge regression improves estimation, sensitivity, and specificity in data-dependent quantitative label-free shotgun proteomics. Mol Cell Proteomics 15(2):657-668.
  • Sticker A, Goeminne L, Martens L, Clement L. 2020. Robust summarization and inference in proteome-wide label-free quantification. Mol Cell Proteomics 19(7):1209-1219.
  • Lazar C, Gatto L, Ferro M, Bruley C, Burger T. 2016. Accounting for the multiple natures of missing values in label-free quantitative proteomics data sets to compare imputation strategies. J Proteome Res 15(4):1116-1125.
  • Stephens M. 2017. False discovery rates: a new deal. Biostatistics 18(2):275-294.
  • Ebrahimpoor M, Goeman JJ. 2021. Inflated false discovery rate due to volcano plots: problem and solutions. Brief Bioinform 22(5):bbab053.

Related Skills

  • quantification - peptide-to-protein summarization, normalization, and IRS that produce the matrix this skill tests
  • proteomics-qc - quality control and batch-effect assessment before testing
  • protein-inference - razor/shared-peptide ambiguity that drives which protein group gets the quantity
  • ptm-analysis - site-level differential testing for modified peptides
  • differential-expression/de-results - analogous empirical-Bayes interpretation for RNA-seq DE
  • data-visualization/volcano-and-ma-plots - volcano and MA plots of the result table
  • pathway-analysis/go-enrichment - functional enrichment of the significant protein hit list
  • machine-learning/biomarker-discovery - building predictive panels from differential proteins
  • workflows/proteomics-pipeline - end-to-end pipeline that calls this skill as the testing stage

What ships with it: 3 files

6.3 KB alongside SKILL.md, 1 of them executable

Keep looking

Skills are one crate of 326,970. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.