{r setup, include=FALSE} knitr::opts_chunk_set(echo = TRUE, warning = FALSE, message = FALSE, fig.width = 10, fig.height = 7) library(readxl) library(ggplot2) library(tidyr) if(!require(patchwork)) install.packages(“patchwork”) library(patchwork) if(!require(ggcorrplot)) install.packages(“ggcorrplot”) library(ggcorrplot) if (!requireNamespace(“BiocManager”, quietly = TRUE)) install.packages(“BiocManager”) if (!requireNamespace(“mixOmics”, quietly = TRUE)) BiocManager::install(“mixOmics”) library(mixOmics) if(!require(openxlsx)) install.packages(“openxlsx”) library(openxlsx)
my_data <- read_excel(“PharmaDx_metabolomics.xlsx”)
n_samples_init <- nrow(my_data) n_features_init <- ncol(my_data) - 6 # Subtracting metadata columns (SampleID, Group, Age, BMI, Gender, Batch) total_nas_init <- sum(is.na(my_data))
dim(my_data) head(my_data) str(my_data) summary(my_data)
table(my_data$Group)
table(my_data$Batch)
bad_metabolites <- colnames(my_data)[colMeans(is.na(my_data)) > 0.20] my_data_clean <- my_data[, !(colnames(my_data) %in% bad_metabolites)]
metabolite_data <- my_data_clean[, 7:ncol(my_data_clean)]
for (col in colnames(metabolite_data)) { if (any(is.na(metabolite_data[[col]]))) { min_val <- min(metabolite_data[[col]], na.rm = TRUE) metabolite_data[[col]][is.na(metabolite_data[[col]])] <- min_val / 2 } }
metabolite_data_log2 <- log2(metabolite_data + 1)
metabolite_data_autoscaled <- scale(metabolite_data_log2)
my_data_log2 <- cbind(my_data_clean[, 1:6], metabolite_data_log2) my_data_autoscaled_final <- cbind(my_data_clean[, 1:6], as.data.frame(metabolite_data_autoscaled))
plot_before <- ggplot(my_data_clean, aes(x = Group, y = Glucose, fill = Group)) + geom_boxplot(alpha = 0.7, outlier.shape = NA) + geom_jitter(width = 0.15, alpha = 0.5) + labs(title = “Glucose: Raw Data”, y = “Raw Intensity”, x = ““) + theme_minimal() + theme(legend.position =”none”)
plot_after <- ggplot(my_data_autoscaled_final, aes(x = Group, y = Glucose, fill = Group)) + geom_boxplot(alpha = 0.7, outlier.shape = NA) + geom_jitter(width = 0.15, alpha = 0.5) + labs(title = “Glucose: Autoscaled Data”, y = “Z-Score”, x = ““) + theme_minimal() + theme(legend.position =”none”)
plot_before + plot_after
pca_result <- prcomp(metabolite_data_autoscaled, scale. = FALSE) var_explained <- round(100 * (pca_result\(sdev^2 / sum(pca_result\)sdev^2)), 1)
pca_df <- data.frame( PC1 = pca_result\(x[, 1], PC2 = pca_result\)x[, 2], Batch = as.factor(my_data_clean\(Batch), Group = as.factor(my_data_clean\)Group) )
pca_batch <- ggplot(pca_df, aes(x = PC1, y = PC2, color = Batch)) + geom_point(size = 3.5, alpha = 0.8) + labs(title = “QC Assessment: PCA by Run Batch”, x = paste0(“PC1 (”, var_explained[1], “%)”), y = paste0(“PC2 (”, var_explained[2], “%)”)) + theme_minimal()
pca_group <- ggplot(pca_df, aes(x = PC1, y = PC2, color = Group)) + geom_point(size = 3.5, alpha = 0.8) + scale_color_manual(values = c(“Healthy” = “#2ca02c”, “PreDiabetic” = “#ff7f0e”, “T2D” = “#d62728”)) + labs(title = “Biological Clustering: PCA by Clinical Group”, x = paste0(“PC1 (”, var_explained[1], “%)”), y = paste0(“PC2 (”, var_explained[2], “%)”)) + theme_minimal()
pca_batch / pca_group
metabolite_cols <- colnames(metabolite_data) anova_p_values <- numeric(length(metabolite_cols)) names(anova_p_values) <- metabolite_cols
for (met in metabolite_cols) { model <- lm(metabolite_data_autoscaled[, met] ~ my_data_clean\(Group) anova_res <- summary(aov(model)) anova_p_values[met] <- anova_res[[1]]["my_data_clean\)Group”, “Pr(>F)”] }
bh_adjusted_p <- p.adjust(anova_p_values, method = “BH”) anova_summary <- data.frame(Raw_P_Value = anova_p_values, BH_adjusted_p = bh_adjusted_p)
sorted_anova <- anova_summary[order(anova_summary[, 2]), ] # Sort by the second column top_10_names <- head(rownames(sorted_anova), 10)
top_10_data <- my_data_autoscaled_final[, c(“Group”, top_10_names)] top_10_long <- pivot_longer(top_10_data, cols = -Group, names_to = “Metabolite”, values_to = “Z_Score”) top_10_long\(Metabolite <- factor(top_10_long\)Metabolite, levels = top_10_names)
ggplot(top_10_long, aes(x = Group, y = Z_Score, fill = Group)) +
geom_boxplot(alpha = 0.7, outlier.shape = NA) + geom_jitter(width =
0.15, alpha = 0.3, size = 1) + facet_wrap(~Metabolite, scales =
“free_y”, ncol = 5) +
scale_fill_manual(values = c(“Healthy” = “#2ca02c”, “PreDiabetic” =
“#ff7f0e”, “T2D” = “#d62728”)) + labs(title = “Top 10 Most Significant
Metabolites Across Clinical States”, y = “Z-Score”, x = ““) + theme_bw()
+ theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position =”none”)
means_healthy <- colMeans(my_data_log2[my_data_log2\(Group == "Healthy", metabolite_cols], na.rm = TRUE) means_prediabetic <- colMeans(my_data_log2[my_data_log2\)Group == “PreDiabetic”, metabolite_cols], na.rm = TRUE) means_t2d <- colMeans(my_data_log2[my_data_log2$Group == “T2D”, metabolite_cols], na.rm = TRUE)
log2fc_prediabetic_vs_healthy <- means_prediabetic - means_healthy log2fc_t2d_vs_healthy <- means_t2d - means_healthy
fc_summary <- data.frame( Log2FC_PreDiabetic_vs_Healthy = log2fc_prediabetic_vs_healthy, Log2FC_T2D_vs_Healthy = log2fc_t2d_vs_healthy )
t2d_sub <- my_data_log2[my_data_log2\(Group == "T2D", metabolite_cols] healthy_sub <- my_data_log2[my_data_log2\)Group == “Healthy”, metabolite_cols]
t_test_p <- numeric(length(metabolite_cols)) names(t_test_p) <- metabolite_cols for (met in metabolite_cols) { t_test_p[met] <- t.test(t2d_sub[[met]], healthy_sub[[met]])$p.value }
t2d_bh_p <- p.adjust(t_test_p, method = “BH”)
volcano_df <- data.frame( Log2FC = fc_summary$Log2FC_T2D_vs_Healthy, P_Value = t2d_bh_p, NegLogP = -log10(t2d_bh_p) )
volcano_df\(Significance <- "Not Significant" volcano_df\)Significance[volcano_df\(Log2FC > 0.58 & volcano_df\)P_Value < 0.05] <- “Up-regulated in T2D” volcano_df\(Significance[volcano_df\)Log2FC < -0.58 & volcano_df$P_Value < 0.05] <- “Down-regulated in T2D”
ggplot(volcano_df, aes(x = Log2FC, y = NegLogP, color = Significance)) + geom_point(alpha = 0.8, size = 3) + geom_hline(yintercept = -log10(0.05), linetype = “dashed”, color = “gray40”) + geom_vline(xintercept = c(-0.58, 0.58), linetype = “dashed”, color = “gray40”) + scale_color_manual(values = c(“Not Significant” = “gray70”, “Up-regulated in T2D” = “#d62728”, “Down-regulated in T2D” = “#1f77b4”)) + labs(title = “Volcano Mapping: T2D vs Healthy Cohorts”, x = “Log2 Fold Change”, y = “-log10(BH-Adjusted P-Value)”) + theme_minimal() + theme(legend.position = “bottom”)
X <- metabolite_data_autoscaled Y <- as.factor(my_data_clean$Group)
plsda_model <- plsda(X, Y, ncomp = 2)
plotIndiv(plsda_model, comp = c(1, 2), group = Y, ind.names = FALSE, ellipse = TRUE, legend = TRUE, title = “PLS-DA Separation Grid”, col.per.group = c(“#2ca02c”, “#ff7f0e”, “#d62728”))
vip_matrix <- vip(plsda_model)
vip_summary1 <- data.frame(Metabolite = rownames(vip_matrix), VIP_Comp1 = vip_matrix[, 1])
vip_summary2 <- vip_summary1[order(-vip_summary1$VIP_Comp1), ]
corr_matrix <- cor(metabolite_data_autoscaled, method = “pearson”, use = “complete.obs”)
ggcorrplot( corr_matrix, hc.order = TRUE, # Hierarchical clustering (groups similar metabolites together) type = “full”, # Shows the full square matrix outline.col = “white”, # Clean white borders between blocks colors = c(“#1f77b4”, “white”, “#d62728”), # Blue (negative), White (none), Red (positive) title = “Global Covariation Feature Matrix”, lab = FALSE # Set to TRUE only if you want numbers inside squares ) + theme( plot.title = element_text(face = “bold”, size = 14, hjust = 0.5), axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1, size = 8), # Vertical labels axis.text.y = element_text(size = 8) )
final_master_sheet <- data.frame( Metabolite = rownames(anova_summary), Raw_ANOVA_P = anova_summary\(Raw_P_Value, BH_Adjusted_P = anova_summary\)BH_adjusted_p, Log2FC_PreD_vs_H = fc_summary[rownames(anova_summary), “Log2FC_PreDiabetic_vs_Healthy”], Log2FC_T2D_vs_H = fc_summary[rownames(anova_summary), “Log2FC_T2D_vs_Healthy”], VIP_Score_Comp1 = vip_summary2[rownames(anova_summary), “VIP_Comp1”] )
write.xlsx(final_master_sheet, “PharmaDx_Metabolomics_Final_Statistics.xlsx”, rowNames = FALSE)