Q&A 16 How do you test for differential abundance of OTUs across groups?

16.1 Explanation

Differential abundance analysis identifies OTUs that significantly differ between groups (e.g., Control vs Treatment).

While tools like DESeq2 are used for RNA-seq and microbiome count data, simpler methods like: - Wilcoxon tests - t-tests - ANCOM / ALDEx2 (specialized tools)
can also be used with filtered OTU data.

Here we demonstrate how to test one OTU at a time between groups.

16.2 Python Code

import pandas as pd
from scipy.stats import mannwhitneyu

# Load OTU table and metadata
otu_df = pd.read_csv("data/otu_table_filtered.tsv", sep="\t", index_col=0)
meta_df = pd.read_csv("data/sample_metadata.tsv", sep="\t").set_index("sample_id")
otu_df = otu_df[meta_df.index]  # ensure matching

# Perform Wilcoxon test for each OTU
results = []
for otu in otu_df.index:
    control = otu_df.loc[otu, meta_df["group"] == "Control"]
    treatment = otu_df.loc[otu, meta_df["group"] == "Treatment"]
    stat, pval = mannwhitneyu(control, treatment)
    results.append((otu, pval))

# Convert to DataFrame
df_results = pd.DataFrame(results, columns=["OTU", "p_value"])
df_results["adjusted_p"] = df_results["p_value"] * len(df_results)  # Bonferroni
df_results.sort_values("p_value").head()

16.3 R Code

library(tidyverse)

otu_df <- read.delim("data/otu_table_filtered.tsv", row.names = 1)
meta_df <- read.delim("data/sample_metadata.tsv")

# Ensure sample order matches
otu_df <- otu_df[, meta_df$sample_id]

# Run Wilcoxon test for each OTU
results <- apply(otu_df, 1, function(x) {
  group <- meta_df$group
  test <- wilcox.test(x[group == "Control"], x[group == "Treatment"])
  return(test$p.value)
})

df_results <- data.frame(OTU = rownames(otu_df), p_value = results)
df_results$adjusted_p <- p.adjust(df_results$p_value, method = "bonferroni")
head(df_results[order(df_results$p_value), ])
          OTU    p_value adjusted_p
OTU_14 OTU_14 0.01962441  0.9812207
OTU_37 OTU_37 0.05855263  1.0000000
OTU_38 OTU_38 0.11049202  1.0000000
OTU_16 OTU_16 0.13262225  1.0000000
OTU_26 OTU_26 0.16123759  1.0000000
OTU_9   OTU_9 0.19882894  1.0000000