Install any skill in seconds. Free to start, no credit card required.
Get Started Free →End-to-end multi-omics integration workflow. Orchestrates data harmonization, MOFA/mixOmics integration, factor interpretation, and downstream analysis across transcriptomics, proteomics, metabolomics, and other modalities. Use when integrating multiple omics datasets.
.claude/skills/bio-workflows-multi-omics-pipeline/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 149% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 135% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 79% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 158% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 133% | 0% |
<!--
#
#
-->
RNA-seq Data ─────┐
│
Proteomics Data ──┼──> Data Harmonization ──> Integration ──> Factors/Components
│ │
Metabolomics ─────┘ ▼
┌─────────────────────────────────────────────────────┐
│ multi-omics-pipeline │
├─────────────────────────────────────────────────────┤
│ 1. Data Preprocessing per Modality │
│ 2. Sample Harmonization (matching samples) │
│ 3. Feature Selection/Filtering │
│ 4. Integration (MOFA2 / mixOmics / SNF) │
│ 5. Factor/Component Interpretation │
│ 6. Downstream Analysis │
└─────────────────────────────────────────────────────┘
│
▼
Integrated Factors + Biomarker Signaturesrlibrary(MOFA2) library(MOFAdata) library(ggplot2) library(tidyverse) # === 1. LOAD AND HARMONIZE DATA === # RNA-seq data (samples x genes) rna <- read.csv('rnaseq_normalized.csv', row.names = 1) cat('RNA:', nrow(rna), 'samples,', ncol(rna), 'genes\n') # Proteomics data (samples x proteins) protein <- read.csv('proteomics_normalized.csv', row.names = 1) cat('Protein:', nrow(protein), 'samples,', ncol(protein), 'proteins\n') # Metabolomics data (samples x metabolites) metab <- read.csv('metabolomics_normalized.csv', row.names = 1) cat('Metabolites:', nrow(metab), 'samples,', ncol(metab), 'metabolites\n') # Find common samples common_samples <- Reduce(intersect, list(rownames(rna), rownames(protein), rownames(metab))) cat('Common samples:', length(common_samples), '\n') # Subset to common samples rna <- rna[common_samples, ] protein <- protein[common_samples, ] metab <- metab[common_samples, ] # === 2. FEATURE SELECTION === # Select most variable features per modality select_variable <- function(data, n = 2000) { vars <- apply(data, 2, var, na.rm = TRUE) top_features <- names(sort(vars, decreasing = TRUE))[1:min(n, ncol(data))] data[, top_features] } rna_var <- select_variable(rna, n = 2000) protein_var <- select_variable(protein, n = 1000) metab_var <- select_variable(metab, n = 500) # === 3. CREATE MOFA OBJECT === # Prepare data as list of matrices (features x samples) data_list <- list( RNA = t(as.matrix(rna_var)), Protein = t(as.matrix(protein_var)), Metabolome = t(as.matrix(metab_var)) ) # Create MOFA object mofa <- create_mofa(data_list) # Add sample metadata sample_metadata <- read.csv('sample_metadata.csv') rownames(sample_metadata) <- sample_metadata$sample_id samples_metadata(mofa) <- sample_metadata[common_samples, ] # === 4. CONFIGURE AND TRAIN MODEL === # Data options data_opts <- get_default_data_options(mofa) data_opts$scale_views <- TRUE # Scale each view # Model options model_opts <- get_default_model_options(mofa) model_opts$num_factors <- 15 # Number of factors to learn # Training options train_opts <- get_default_training_options(mofa) train_opts$maxiter <- 1000 train_opts$convergence_mode <- 'slow' train_opts$seed <- 42 # Prepare and train mofa <- prepare_mofa(mofa, data_options = data_opts, model_options = model_opts, training_options = train_opts) cat('Training MOFA model...\n') mofa <- run_mofa(mofa, outfile = 'mofa_model.hdf5', use_basilisk = TRUE) # === 5. ANALYZE FACTORS === # Variance explained per factor per view plot_variance_explained(mofa, max_r2 = 15) ggsave('variance_explained.png', width = 10, height = 6) # Factor values factor_values <- get_factors(mofa)[[1]] # Correlate factors with phenotypes plot_factor_cor(mofa, color_by = 'condition') ggsave('factor_phenotype_correlation.png', width = 8, height = 6) # Factor plots plot_factor(mofa, factors = 1:4, color_by = 'condition', dot_size = 3) ggsave('factor_scatter.png', width = 12, height = 10) # === 6. INTERPRET FACTORS === # Get top weights per factor per view for (f in 1:5) { cat('\nFactor', f, ':\n') weights <- get_weights(mofa, factors = f, as.data.frame = TRUE) for (view in unique(weights$view)) { view_weights <- weights[weights$view == view, ] view_weights <- view_weights[order(abs(view_weights$value), decreasing = TRUE), ] cat(' ', view, ':', paste(head(view_weights$feature, 5), collapse = ', '), '\n') } } # Heatmap of top features per factor plot_top_weights(mofa, view = 'RNA', factors = 1:5, nfeatures = 10) ggsave('top_weights_rna.png', width = 10, height = 8) # === 7. ENRICHMENT ANALYSIS === library(clusterProfiler) library(org.Hs.eg.db) # Get RNA weights for factor 1 rna_weights <- get_weights(mofa, views = 'RNA', factors = 1)[[1]][, 1] top_genes <- names(sort(abs(rna_weights), decreasing = TRUE))[1:200] # GO enrichment ego <- enrichGO(gene = top_genes, OrgDb = org.Hs.eg.db, keyType = 'SYMBOL', ont = 'BP', pvalueCutoff = 0.05) dotplot(ego, showCategory = 15) ggsave('factor1_enrichment.png', width = 8, height = 10) # === 8. DOWNSTREAM: SURVIVAL ANALYSIS === library(survival) library(survminer) # Add factor values to metadata surv_data <- data.frame( sample = rownames(factor_values), factor1 = factor_values[, 1], time = sample_metadata[rownames(factor_values), 'survival_time'], status = sample_metadata[rownames(factor_values), 'survival_status'] ) # Median split surv_data$factor1_group <- ifelse(surv_data$factor1 > median(surv_data$factor1), 'High', 'Low') # Kaplan-Meier fit <- survfit(Surv(time, status) ~ factor1_group, data = surv_data) ggsurvplot(fit, data = surv_data, pval = TRUE, risk.table = TRUE) ggsave('survival_factor1.png', width = 8, height = 8) # === 9. EXPORT RESULTS === # Factor values write.csv(factor_values, 'mofa_factor_values.csv') # Weights all_weights <- get_weights(mofa, as.data.frame = TRUE) write.csv(all_weights, 'mofa_weights.csv', row.names = FALSE) cat('\nMOFA analysis complete!\n')
rlibrary(mixOmics) # === 1. PREPARE DATA === # Same preprocessing as above X <- list( RNA = as.matrix(rna_var), Protein = as.matrix(protein_var), Metabolome = as.matrix(metab_var) ) # Outcome variable Y <- factor(sample_metadata[common_samples, 'condition']) # === 2. DESIGN MATRIX === # Define connections between blocks design <- matrix(0.1, ncol = length(X), nrow = length(X), dimnames = list(names(X), names(X))) diag(design) <- 0 # === 3. TUNE MODEL === # Tune number of components perf.diablo <- perf(block.splsda(X, Y, ncomp = 5, design = design), validation = 'Mfold', folds = 5, nrepeat = 10) ncomp <- perf.diablo$choice.ncomp$WeightedVote['Overall.BER', 'max.dist'] cat('Optimal components:', ncomp, '\n') # Tune number of variables per component test.keepX <- list( RNA = c(10, 25, 50, 100), Protein = c(5, 10, 25, 50), Metabolome = c(5, 10, 25) ) tune.diablo <- tune.block.splsda(X, Y, ncomp = ncomp, test.keepX = test.keepX, design = design, validation = 'Mfold', folds = 5) optimal.keepX <- tune.diablo$choice.keepX # === 4. FINAL MODEL === diablo.model <- block.splsda(X, Y, ncomp = ncomp, keepX = optimal.keepX, design = design) # === 5. VISUALIZATION === # Sample plot plotIndiv(diablo.model, ind.names = FALSE, legend = TRUE, title = 'DIABLO Sample Plot') # Variable plot plotVar(diablo.model, var.names = FALSE, style = 'graphics', legend = TRUE) # Circos plot circosPlot(diablo.model, cutoff = 0.7, line = TRUE, color.blocks = c('darkorchid', 'brown1', 'lightgreen')) # Heatmap cimDiablo(diablo.model, color.blocks = c('darkorchid', 'brown1', 'lightgreen'), margins = c(10, 5)) # === 6. PERFORMANCE === perf.final <- perf(diablo.model, validation = 'Mfold', folds = 5, nrepeat = 10) cat('Classification error rate:', perf.final$WeightedVote.error.rate, '\n') # ROC curves auc.diablo <- auroc(diablo.model, roc.block = 'RNA', roc.comp = 1)
rlibrary(SNFtool) # === 1. CREATE SIMILARITY MATRICES === # Distance matrices per modality dist_rna <- dist2(as.matrix(rna_var), as.matrix(rna_var)) dist_protein <- dist2(as.matrix(protein_var), as.matrix(protein_var)) dist_metab <- dist2(as.matrix(metab_var), as.matrix(metab_var)) # Affinity matrices K <- 20 # Number of neighbors alpha <- 0.5 # Hyperparameter aff_rna <- affinityMatrix(dist_rna, K = K, sigma = alpha) aff_protein <- affinityMatrix(dist_protein, K = K, sigma = alpha) aff_metab <- affinityMatrix(dist_metab, K = K, sigma = alpha) # === 2. FUSE NETWORKS === W <- SNF(list(aff_rna, aff_protein, aff_metab), K = K, t = 20) # === 3. CLUSTER ON FUSED NETWORK === clusters <- spectralClustering(W, K = 3) # K = number of clusters cat('Cluster distribution:', table(clusters), '\n') # === 4. VISUALIZATION === # Plot fused network displayClustersWithHeatmap(W, clusters)
| Stage | Check | Action if Failed | |-------|-------|------------------| | Sample matching | >80% samples shared | Check sample IDs | | Missing values | <20% per modality | Impute or remove | | Feature variance | Features vary | Filter low variance | | Model convergence | ELBO plateau | Increase iterations | | Factor variance | >5% per factor | Keep fewer factors |
r# MOFA2 handles missing views gracefully # Use create_mofa_from_df for unbalanced data data_long <- rbind( data.frame(sample = rownames(rna), view = 'RNA', feature = colnames(rna), value = unlist(rna)), data.frame(sample = rownames(protein), view = 'Protein', feature = colnames(protein), value = unlist(protein)) ) mofa <- create_mofa_from_df(data_long)
r# MOFA+ for single-cell library(MOFA2) mofa <- create_mofa_from_Seurat(seurat_obj, groups = 'cell_type', assays = c('RNA', 'ATAC'))
<!-- 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 | pass→pass | 9,704 | 4,311 | -56% | 1 | 1 | 0% | 1,816 | 4,232 | +133% | 0 | 0 | — |
case-02 | pass→pass | 9,332 | 3,435 | -63% | 1 | 1 | 0% | 1,102 | 4,015 | +264% | 0 | 0 | — |
case-03 | pass→pass | 6,864 | 5,504 | -20% | 1 | 1 | 0% | 1,288 | 4,338 | +237% | 0 | 0 | — |
case-04 | fail→pass | 10,434 | 7,024 | -33% | 1 | 1 | 0% | 1,909 | 4,750 | +149% | 0 | 0 | — |
case-05 | pass→pass | 4,051 | 2,687 | -34% | 1 | 1 | 0% | 691 | 3,971 | +475% | 0 | 0 | — |
case-06 | pass→pass | 13,738 | 6,757 | -51% | 1 | 1 | 0% | 2,501 | 4,745 | +90% | 0 | 0 | — |
case-07 | pass→pass | 9,687 | 6,051 | -38% | 1 | 1 | 0% | 1,726 | 4,499 | +161% | 0 | 0 | — |
case-08 | pass→pass | 6,296 | 3,705 | -41% | 1 | 1 | 0% | 1,096 | 4,122 | +276% | 0 | 0 | — |
case-21 | pass→pass | 5,875 | 3,192 | -46% | 1 | 1 | 0% | 1,092 | 3,978 | +264% | 0 | 0 | — |
case-09 | pass→pass | 13,981 | 15,666 | +12% | 1 | 1 | 0% | 2,533 | 6,473 | +156% | 0 | 0 | — |
case-10 | fail→pass | 10,536 | 5,694 | -46% | 1 | 1 | 0% | 1,982 | 4,650 | +135% | 0 | 0 | — |
case-11 | pass→pass | 5,175 | 3,740 | -28% | 1 | 1 | 0% | 976 | 4,088 | +319% | 0 | 0 | — |
case-12 | pass→pass | 4,066 | 2,528 | -38% | 1 | 1 | 0% | 684 | 3,930 | +475% | 0 | 0 | — |
case-13 | pass→pass | 4,198 | 2,443 | -42% | 1 | 1 | 0% | 707 | 3,909 | +453% | 0 | 0 | — |
case-14 | pass→pass | 15,836 | 8,614 | -46% | 1 | 1 | 0% | 2,602 | 4,867 | +87% | 0 | 0 | — |
case-15 | fail→pass | 14,111 | 5,676 | -60% | 1 | 1 | 0% | 2,438 | 4,361 | +79% | 0 | 0 | — |
case-16 | pass→pass | 6,460 | 4,894 | -24% | 1 | 1 | 0% | 1,263 | 4,396 | +248% | 0 | 0 | — |
case-17 | pass→pass | 9,528 | 5,890 | -38% | 1 | 1 | 0% | 1,802 | 4,578 | +154% | 0 | 0 | — |
case-18 | pass→pass | 4,731 | 4,150 | -12% | 1 | 1 | 0% | 793 | 3,980 | +402% | 0 | 0 | — |
case-19 | pass→pass | 16,177 | 14,295 | -12% | 1 | 1 | 0% | 2,888 | 5,840 | +102% | 0 | 0 | — |
case-20 | pass→pass | 3,571 | 2,417 | -32% | 1 | 1 | 0% | 654 | 3,907 | +497% | 0 | 0 | — |
case-22 | fail→pass | 9,201 | 5,112 | -44% | 1 | 1 | 0% | 1,707 | 4,403 | +158% | 0 | 0 | — |
case-23 | pass→pass | 10,937 | 11,994 | +10% | 1 | 1 | 0% | 2,401 | 5,986 | +149% | 0 | 0 | — |
case-24 | pass→pass | 15,736 | 16,479 | +5% | 1 | 1 | 0% | 3,456 | 7,406 | +114% | 0 | 0 | — |
case-25 | pass→pass | 12,984 | 5,973 | -54% | 1 | 1 | 0% | 1,926 | 4,708 | +144% | 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. 25 cases were attempted. The headline lift of +16 percentage points is the difference between those two pass rates over the 25 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 | +9% |
Other measured skills in the registry, with their headline benchmark lift.