Install any skill in seconds. Free to start, no credit card required.
Get Started Free →End-to-end metabolomics workflow from raw MS data to pathway analysis. Orchestrates XCMS preprocessing, annotation, normalization, statistical analysis, and pathway mapping. Use when processing LC-MS metabolomics data.
.claude/skills/bio-workflows-metabolomics-pipeline/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 118% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 222% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 42% | 0% |
<!--
#
#
-->
Raw MS Data (mzML/mzXML) ──> Peak Detection ──> Feature Matrix
│
▼
┌─────────────────────────────────────────────┐
│ metabolomics-pipeline │
├─────────────────────────────────────────────┤
│ 1. Peak Detection (XCMS) │
│ 2. Retention Time Alignment │
│ 3. Feature Grouping & Gap Filling │
│ 4. QC & Normalization │
│ 5. Statistical Analysis │
│ 6. Metabolite Annotation │
│ 7. Pathway Mapping │
└─────────────────────────────────────────────┘
│
▼
Differential Metabolites + Enriched Pathwaysrlibrary(xcms) library(MSnbase) library(MetaboAnalystR) library(ggplot2) # === 1. LOAD DATA === mzml_files <- list.files('data/', pattern = '\\.mzML$', full.names = TRUE) sample_data <- read.csv('sample_metadata.csv') raw_data <- readMSData(mzml_files, mode = 'onDisk') # Add sample metadata pData(raw_data) <- sample_data cat('Loaded', length(mzml_files), 'samples\n') # === 2. PEAK DETECTION === cwp <- CentWaveParam( peakwidth = c(5, 30), ppm = 25, snthresh = 10, prefilter = c(3, 1000), mzdiff = 0.01, noise = 1000 ) xdata <- findChromPeaks(raw_data, param = cwp) cat('Detected', nrow(chromPeaks(xdata)), 'peaks\n') # === 3. RETENTION TIME ALIGNMENT === xdata <- adjustRtime(xdata, param = ObiwarpParam(binSize = 0.6)) cat('Aligned retention times\n') # === 4. FEATURE GROUPING === pdp <- PeakDensityParam( sampleGroups = pData(xdata)$condition, minFraction = 0.5, bw = 5, binSize = 0.025 ) xdata <- groupChromPeaks(xdata, param = pdp) cat('Grouped into', nrow(featureDefinitions(xdata)), 'features\n') # === 5. GAP FILLING === xdata <- fillChromPeaks(xdata, param = ChromPeakAreaParam()) # === 6. EXTRACT FEATURE MATRIX === feature_matrix <- featureValues(xdata, value = 'into', method = 'maxint') feature_info <- featureDefinitions(xdata) # === 7. QC & NORMALIZATION === # Log2 transform feature_matrix[feature_matrix == 0] <- NA log_matrix <- log2(feature_matrix) # Filter features (present in >50% of samples) valid_features <- rowSums(!is.na(log_matrix)) > ncol(log_matrix) * 0.5 filtered_matrix <- log_matrix[valid_features, ] cat('After filtering:', nrow(filtered_matrix), 'features\n') # Median normalization sample_medians <- apply(filtered_matrix, 2, median, na.rm = TRUE) global_median <- median(sample_medians) normalized <- sweep(filtered_matrix, 2, sample_medians - global_median) # === 8. QC PLOTS === # PCA pca <- prcomp(t(normalized), scale. = TRUE) pca_df <- data.frame(PC1 = pca$x[, 1], PC2 = pca$x[, 2], Sample = rownames(pca$x), Condition = pData(xdata)$condition) ggplot(pca_df, aes(PC1, PC2, color = Condition)) + geom_point(size = 3) + theme_bw() + labs(title = 'PCA of Metabolomics Data') ggsave('qc_pca.png', width = 8, height = 6) # === 9. STATISTICAL ANALYSIS === library(limma) design <- model.matrix(~ 0 + condition, data = pData(xdata)) colnames(design) <- levels(factor(pData(xdata)$condition)) # Impute missing values for limma imputed <- normalized imputed[is.na(imputed)] <- min(imputed, na.rm = TRUE) - 1 fit <- lmFit(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$feature_id <- rownames(results) results$significant <- abs(results$logFC) > 1 & results$adj.P.Val < 0.05 cat('\nSignificant features:', sum(results$significant), '\n') # === 10. METABOLITE ANNOTATION === # Add m/z and RT to results results$mz <- feature_info[results$feature_id, 'mzmed'] results$rt <- feature_info[results$feature_id, 'rtmed'] # KEGG annotation (simplified - use CAMERA for adduct annotation) library(KEGGREST) annotate_mz <- function(mz, ppm = 10) { # Query KEGG for matching compounds # This is simplified - real annotation uses databases mz_range <- c(mz * (1 - ppm/1e6), mz * (1 + ppm/1e6)) return(NA) # Placeholder } # === 11. VOLCANO PLOT === ggplot(results, aes(x = logFC, y = -log10(adj.P.Val), color = significant)) + geom_point(alpha = 0.5) + geom_hline(yintercept = -log10(0.05), linetype = 'dashed') + geom_vline(xintercept = c(-1, 1), linetype = 'dashed') + scale_color_manual(values = c('gray', 'red')) + theme_bw() + labs(title = 'Differential Metabolites', x = 'Log2 Fold Change', y = '-Log10(adj. p-value)') ggsave('volcano_metabolites.png', width = 8, height = 6) # === 12. OUTPUT === write.csv(results, 'differential_metabolites.csv', row.names = FALSE) write.csv(normalized, 'normalized_feature_matrix.csv') cat('Results saved!\n')
rlibrary(MetaboAnalystR) # Initialize mSet <- InitDataObjects('conc', 'pathora', FALSE) # Load compound list (HMDB IDs) sig_features <- results[results$significant, ] compound_list <- sig_features$hmdb_id # Requires annotation mSet <- Setup.MapData(mSet, compound_list) mSet <- CrossReferencing(mSet, 'hmdb') mSet <- CreateMappingResultTable(mSet) # Pathway analysis mSet <- SetKEGG.PathLib(mSet, 'hsa') mSet <- SetMetabolomeFilter(mSet, FALSE) mSet <- CalculateOraScore(mSet, 'rbc', 'hyperg') # View results pathway_results <- mSet$analSet$ora.mat head(pathway_results) # Plot mSet <- PlotPathSummary(mSet, 'pathway_overview', 'png', 300, 10, 10)
r# Load MS-DIAL exported data msdial_export <- read.csv('msdial_alignment.csv') # MS-DIAL already provides: # - Peak detection # - Alignment # - Gap filling # - Annotation attempts # Continue with normalization and statistics feature_matrix <- as.matrix(msdial_export[, grep('Area', colnames(msdial_export))]) rownames(feature_matrix) <- msdial_export$`Alignment.ID` # Proceed with normalization and limma as above
| Stage | Check | Action if Failed | |-------|-------|------------------| | Peak detection | >1000 features | Adjust parameters | | Alignment | RT deviation <30s | Check QC samples | | Grouping | >60% features grouped | Adjust bw/minFraction | | Missing values | <30% per sample | Check injection | | QC RSD | <30% for QC features | Check instrument | | PCA | Groups separate | Check batch effects |
r# Adjust peak width for lipids cwp_lipid <- CentWaveParam( peakwidth = c(10, 60), # Broader peaks ppm = 15, snthresh = 5 ) # Use LipidMaps for annotation
r# Define target compounds targets <- data.frame( name = c('Glucose', 'Lactate', 'Citrate'), mz = c(179.0561, 89.0244, 191.0197), rt = c(120, 90, 180) ) # Extract targeted features extractTargets <- function(xdata, targets, mz_ppm = 10, rt_tol = 30) { lapply(1:nrow(targets), function(i) { chromPeaks(xdata, mz = targets$mz[i], ppm = mz_ppm, rt = c(targets$rt[i] - rt_tol, targets$rt[i] + rt_tol)) }) }
<!-- 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-01 | fail→fail | 21,570 | 19,257 | -11% | 1 | 1 | 0% | 4,380 | 6,658 | +52% | 0 | 0 | — |
case-02 | fail→fail | 19,786 | 12,572 | -36% | 1 | 1 | 0% | 3,389 | 5,263 | +55% | 0 | 0 | — |
case-03 | pass→pass | 20,746 | 7,170 | -65% | 1 | 1 | 0% | 2,256 | 3,873 | +72% | 0 | 0 | — |
case-04 | fail→fail | 14,184 | 9,413 | -34% | 1 | 1 | 0% | 2,511 | 4,101 | +63% | 0 | 0 | — |
case-05 | fail→pass | 11,062 | 10,861 | -2% | 1 | 1 | 0% | 2,026 | 4,411 | +118% | 0 | 0 | — |
case-06 | fail→fail | 12,294 | 8,520 | -31% | 1 | 1 | 0% | 2,390 | 4,184 | +75% | 0 | 0 | — |
case-07 | fail→pass | 5,882 | 3,583 | -39% | 1 | 1 | 0% | 997 | 3,213 | +222% | 0 | 0 | — |
case-08 | pass→pass | 6,557 | 6,121 | -7% | 1 | 1 | 0% | 1,229 | 3,602 | +193% | 0 | 0 | — |
case-09 | pass→pass | 11,786 | 3,851 | -67% | 1 | 1 | 0% | 1,978 | 3,299 | +67% | 0 | 0 | — |
case-10 | pass→pass | 12,936 | 29,573 | +129% | 1 | 1 | 0% | 2,555 | 4,109 | +61% | 0 | 0 | — |
case-11 | fail→pass | 13,266 | 4,198 | -68% | 1 | 1 | 0% | 2,155 | 3,327 | +54% | 0 | 0 | — |
case-12 | pass→pass | 13,030 | 7,303 | -44% | 1 | 1 | 0% | 2,419 | 3,897 | +61% | 0 | 0 | — |
case-13 | pass→fail | 11,509 | 5,908 | -49% | 1 | 1 | 0% | 2,191 | 3,698 | +69% | 0 | 0 | — |
case-14 | fail→fail | 16,088 | 10,235 | -36% | 1 | 1 | 0% | 2,967 | 4,512 | +52% | 0 | 0 | — |
case-15 | fail→pass | 9,468 | 4,295 | -55% | 1 | 1 | 0% | 1,958 | 3,357 | +71% | 0 | 0 | — |
case-16 | fail→pass | 27,449 | 2,336 | -91% | 1 | 1 | 0% | 2,077 | 2,954 | +42% | 0 | 0 | — |
case-17 | fail→pass | 11,860 | 2,414 | -80% | 1 | 1 | 0% | 1,953 | 2,988 | +53% | 0 | 0 | — |
case-18 | pass→pass | 7,282 | 2,947 | -60% | 1 | 1 | 0% | 1,163 | 3,054 | +163% | 0 | 0 | — |
case-19 | fail→pass | 14,978 | 2,349 | -84% | 1 | 1 | 0% | 2,489 | 3,073 | +23% | 0 | 0 | — |
case-20 | pass→pass | 17,117 | 15,154 | -11% | 1 | 1 | 0% | 3,296 | 5,826 | +77% | 0 | 0 | — |
case-21 | pass→pass | 30,941 | 19,499 | -37% | 1 | 1 | 0% | 3,821 | 6,458 | +69% | 0 | 0 | — |
case-22 | pass→pass | 19,079 | 16,910 | -11% | 1 | 1 | 0% | 3,379 | 5,773 | +71% | 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 +27 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
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 | +41% |
Other measured skills in the registry, with their headline benchmark lift.