Install any skill in seconds. Free to start, no credit card required.
Get Started Free →End-to-end proteomics workflow from MaxQuant output to differential protein abundance. Orchestrates data import, normalization, imputation, and statistical testing with MSstats or limma. Use when processing mass spectrometry proteomics.
.claude/skills/bio-workflows-proteomics-pipeline/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 92% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 92% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 59% | 0% |
<!--
#
#
-->
Raw MS Data (mzML) ──> MaxQuant/DIA-NN ──> proteinGroups.txt
│
▼
┌────────────────────────────────────────────┐
│ proteomics-pipeline │
├────────────────────────────────────────────┤
│ 1. Data Import & Filtering │
│ 2. Log2 Transform & Normalization │
│ 3. Missing Value Imputation │
│ 4. QC: PCA, Correlation │
│ 5. Differential Abundance (limma/MSstats) │
│ 6. Visualization & Export │
└────────────────────────────────────────────┘
│
▼
Differential Proteins + Volcano Plotsrlibrary(limma) library(ggplot2) library(pheatmap) # === 1. DATA IMPORT === proteins <- read.delim('proteinGroups.txt', stringsAsFactors = FALSE) cat('Loaded', nrow(proteins), 'protein groups\n') # Filter contaminants, reverse, only-by-site proteins <- proteins[proteins$Potential.contaminant != '+' & proteins$Reverse != '+' & proteins$Only.identified.by.site != '+', ] cat('After filtering:', nrow(proteins), 'proteins\n') # Extract LFQ intensities lfq_cols <- grep('^LFQ\\.intensity\\.', colnames(proteins), value = TRUE) intensities <- proteins[, lfq_cols] rownames(intensities) <- proteins$Majority.protein.IDs colnames(intensities) <- gsub('LFQ\\.intensity\\.', '', colnames(intensities)) # === 2. LOG2 TRANSFORM & NORMALIZE === intensities[intensities == 0] <- NA log2_int <- log2(intensities) # Median centering sample_medians <- apply(log2_int, 2, median, na.rm = TRUE) global_median <- median(sample_medians) normalized <- sweep(log2_int, 2, sample_medians - global_median) # === 3. FILTER & IMPUTE === # Keep proteins with < 50% missing valid_rows <- rowSums(is.na(normalized)) < ncol(normalized) * 0.5 filtered <- normalized[valid_rows, ] cat('Proteins after filtering:', nrow(filtered), '\n') # MinProb imputation (left-censored) impute_minprob <- function(x) { nas <- is.na(x) if (all(nas)) return(x) x[nas] <- rnorm(sum(nas), mean = mean(x, na.rm = TRUE) - 1.8 * sd(x, na.rm = TRUE), sd = 0.3 * sd(x, na.rm = TRUE)) x } imputed <- as.data.frame(t(apply(filtered, 1, impute_minprob))) # === 4. QC === # PCA pca <- prcomp(t(imputed), scale. = TRUE) pca_df <- data.frame(PC1 = pca$x[, 1], PC2 = pca$x[, 2], Sample = rownames(pca$x)) # === 5. DIFFERENTIAL ANALYSIS === # Load sample annotation (columns: sample, condition) sample_info <- read.csv('sample_annotation.csv') sample_info$condition <- factor(sample_info$condition) design <- model.matrix(~ 0 + condition, data = sample_info) colnames(design) <- levels(sample_info$condition) fit <- lmFit(as.matrix(imputed), design) contrast <- makeContrasts(Treatment - Control, levels = design) fit2 <- contrasts.fit(fit, contrast) fit2 <- eBayes(fit2) results <- topTable(fit2, number = Inf, adjust.method = 'BH') results$protein <- rownames(results) results$significant <- abs(results$logFC) > 1 & results$adj.P.Val < 0.05 # === 6. OUTPUT === cat('\nResults:\n') cat(' Significant proteins:', sum(results$significant), '\n') cat(' Up-regulated:', sum(results$significant & results$logFC > 0), '\n') cat(' Down-regulated:', sum(results$significant & results$logFC < 0), '\n') write.csv(results, 'differential_proteins.csv', row.names = FALSE)
rlibrary(MSstats) # From MaxQuant evidence <- read.table('evidence.txt', sep = '\t', header = TRUE) proteinGroups <- read.table('proteinGroups.txt', sep = '\t', header = TRUE) annotation <- read.csv('annotation.csv') # Convert to MSstats format msstats_input <- MaxQtoMSstatsFormat(evidence = evidence, proteinGroups = proteinGroups, annotation = annotation) # Process data processed <- dataProcess(msstats_input, normalization = 'equalizeMedians', summaryMethod = 'TMP', censoredInt = 'NA') # Comparison comparison <- matrix(c(1, -1), nrow = 1) rownames(comparison) <- 'Treatment_vs_Control' colnames(comparison) <- c('Control', 'Treatment') results <- groupComparison(contrast.matrix = comparison, data = processed)
| Stage | Check | Action if Failed | |-------|-------|------------------| | Import | >1000 proteins | Re-run MaxQuant | | Filter | <30% removed | Check sample prep | | Missing | <40% per sample | Check MS performance | | PCA | Replicates cluster | Check for batch effects | | Stats | >1% differential | Adjust thresholds |
rlibrary(MSnbase) # Load TMT data tmt_data <- readMSnSet('tmt_psms.txt') # Normalize with reference channel tmt_norm <- normalize(tmt_data, method = 'center.median') # Summarize to protein level protein_data <- combineFeatures(tmt_norm, groupBy = fData(tmt_norm)$protein, fun = 'median') # Then proceed with limma as above
r# SILAC ratios from MaxQuant silac <- read.delim('proteinGroups.txt') ratio_cols <- grep('Ratio.H.L.normalized', colnames(silac), value = TRUE) # Log2 transform ratios silac_log2 <- log2(silac[, ratio_cols]) # One-sample t-test against 0 (no change) results <- apply(silac_log2, 1, function(x) t.test(x, mu = 0)$p.value)
r# Load DIA-NN report diann <- read.delim('report.tsv') # Pivot to matrix library(tidyr) protein_matrix <- diann %>% select(Protein.Group, Run, PG.MaxLFQ) %>% pivot_wider(names_from = Run, values_from = PG.MaxLFQ) # Then proceed with normalization and limma
<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-09 | fail→pass | 10,308 | 7,019 | -32% | 1 | 1 | 0% | 2,009 | 3,215 | +60% | 0 | 0 | — |
case-10 | pass→pass | 12,874 | 8,063 | -37% | 1 | 1 | 0% | 2,277 | 3,298 | +45% | 0 | 0 | — |
case-01 | fail→fail | 19,719 | 19,159 | -3% | 1 | 1 | 0% | 4,203 | 6,319 | +50% | 0 | 0 | — |
case-02 | fail→fail | 9,197 | 5,370 | -42% | 1 | 1 | 0% | 1,876 | 3,115 | +66% | 0 | 0 | — |
case-03 | fail→fail | 15,880 | 14,431 | -9% | 1 | 1 | 0% | 2,777 | 4,371 | +57% | 0 | 0 | — |
case-04 | pass→pass | 11,667 | 6,569 | -44% | 1 | 1 | 0% | 2,212 | 3,117 | +41% | 0 | 0 | — |
case-05 | fail→pass | 7,195 | 4,352 | -40% | 1 | 1 | 0% | 1,429 | 2,744 | +92% | 0 | 0 | — |
case-06 | pass→pass | 15,868 | 9,440 | -41% | 1 | 1 | 0% | 2,280 | 3,616 | +59% | 0 | 0 | — |
case-07 | pass→pass | 12,380 | 5,156 | -58% | 1 | 1 | 0% | 2,315 | 2,869 | +24% | 0 | 0 | — |
case-08 | pass→pass | 10,050 | 7,729 | -23% | 1 | 1 | 0% | 1,964 | 3,385 | +72% | 0 | 0 | — |
case-11 | fail→pass | 10,334 | 3,666 | -65% | 1 | 1 | 0% | 1,772 | 2,543 | +44% | 0 | 0 | — |
case-12 | pass→pass | 14,538 | 10,108 | -30% | 1 | 1 | 0% | 2,743 | 3,862 | +41% | 0 | 0 | — |
case-13 | pass→pass | 8,696 | 4,079 | -53% | 1 | 1 | 0% | 1,722 | 2,730 | +59% | 0 | 0 | — |
case-14 | pass→pass | 13,137 | 9,287 | -29% | 1 | 1 | 0% | 2,365 | 3,458 | +46% | 0 | 0 | — |
case-15 | pass→pass | 8,264 | 6,302 | -24% | 1 | 1 | 0% | 1,544 | 2,894 | +87% | 0 | 0 | — |
case-16 | pass→pass | 9,563 | 9,592 | +0% | 1 | 1 | 0% | 1,607 | 3,810 | +137% | 0 | 0 | — |
case-17 | fail→pass | 8,036 | 5,702 | -29% | 1 | 1 | 0% | 1,601 | 3,070 | +92% | 0 | 0 | — |
case-18 | fail→pass | 16,392 | 6,686 | -59% | 1 | 1 | 0% | 2,014 | 3,201 | +59% | 0 | 0 | — |
case-19 | pass→pass | 4,805 | 2,990 | -38% | 1 | 1 | 0% | 679 | 2,450 | +261% | 0 | 0 | — |
case-20 | pass→pass | 11,401 | 8,888 | -22% | 1 | 1 | 0% | 2,011 | 3,551 | +77% | 0 | 0 | — |
case-21 | pass→pass | 12,605 | 8,527 | -32% | 1 | 1 | 0% | 2,231 | 3,491 | +56% | 0 | 0 | — |
case-22 | pass→pass | 10,412 | 9,388 | -10% | 1 | 1 | 0% | 1,817 | 3,415 | +88% | 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. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 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.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/24/2026 | +45% |
Other measured skills in the registry, with their headline benchmark lift.