677 微生物组组成数据分析(CoDA)¶
一句话概述:微生物组数据本质是组成数据(compositional data)——各物种比例总和为1,直接分析会产生"假负相关",需要用对数比变换(ALR/CLR/ILR)将数据从单纯形空间转到欧几里得空间。
核心知识点速查表¶
| 知识点 | 关键内容 |
|---|---|
| 组成数据 | 各成分之和为常数(如100%),数据在单纯形空间中 |
| 假负相关 | 组成约束导致物种间的虚假负相关 |
| ALR | 加性对数比变换,需选参考物种 |
| CLR | 中心对数比变换,最常用 |
| ILR | 等距对数比变换,统计上最正确 |
| ALDEx2 | 基于CLR的差异丰度分析工具 |
一、什么是组成数据?(白话解释)¶
打个比方:假设一个蛋糕被切成几块。你只能知道每块占蛋糕的百分比(如30%、20%、50%),而不知道蛋糕有多大。如果一块变大(30%→50%),其他块看起来就变小了——即使它们的绝对量没变。
微生物组数据为什么是组成数据? - 测序得到的reads总数是固定的(测序仪决定的) - 每个物种的"相对丰度"受其他所有物种影响 - 一个物种的相对丰度"增加"可能只是因为另一个物种减少了
组成约束的后果:
# 示例:3个物种的相对丰度
真实绝对丰度: A=100, B=100, C=100 → 相对丰度: 33%, 33%, 33%
A翻倍后: A=200, B=100, C=100 → 相对丰度: 50%, 25%, 25%
# B和C的绝对量没变,但相对丰度"下降"了!→ 假负相关
二、对数比变换¶
# 组成数据的对数比变换
library(compositions) # 组成数据分析包
library(ALDEx2) # 基于CLR的差异分析
# ============ 准备数据 ============
# 假设otu是物种丰度矩阵(行=物种,列=样本)
# 添加伪计数处理零值
pseudocount <- 0.5 # 常用伪计数
otu_pseudo <- otu + pseudocount # 添加伪计数
# ============ 1. CLR变换(最常用) ============
# CLR(x_i) = ln(x_i / g(x))
# g(x) = 几何均值
clr_transform <- function(x) {
gm <- exp(mean(log(x))) # 几何均值
log(x / gm) # 中心对数比
}
# 对每个样本做CLR变换
otu_clr <- apply(otu_pseudo, 2, clr_transform) # 按列(样本)变换
cat("CLR变换后维度:", dim(otu_clr), "\n")
# 使用compositions包
otu_comp <- acomp(t(otu_pseudo)) # 创建组成数据对象
otu_clr2 <- clr(otu_comp) # CLR变换
# CLR的特点:
# - 维度不变(p个物种→p个CLR值)
# - 不需要选择参考物种
# - CLR值之和=0(每个样本内)
# - 可直接用于PCA、相关分析等
# ============ 2. ALR变换 ============
# ALR(x_i) = ln(x_i / x_ref)
# 需要选择一个参考物种
alr_transform <- function(x, ref_idx) {
log(x[-ref_idx] / x[ref_idx]) # 相对于参考的对数比
}
# 选择最稳定的物种作为参考
# 方法:选择变异系数最小的物种
cv <- apply(otu_pseudo, 1, function(x) sd(x) / mean(x)) # 变异系数
ref_species <- which.min(cv) # CV最小的物种
cat("参考物种:", rownames(otu)[ref_species], "\n")
# ALR变换
otu_alr <- apply(otu_pseudo, 2, alr_transform, ref_idx = ref_species)
# ALR的特点:
# - 维度减少1(p个物种→p-1个ALR值)
# - 需要选择参考物种(结果受选择影响)
# - 适合回归分析
# ============ 3. ILR变换(等距对数比) ============
# ILR使用正交基,统计上最正确
otu_ilr <- ilr(acomp(t(otu_pseudo))) # ILR变换
# ILR的特点:
# - 维度减少1(p→p-1)
# - 不需要选择参考
# - 保持欧几里得距离(Aitchison距离)
# - 解释性差(坐标是正交组合)
# - 适合多元统计检验
# ============ 4. 使用ALDEx2做差异分析 ============
# ALDEx2基于CLR + 蒙特卡洛采样
# 安装: BiocManager::install("ALDEx2")
library(ALDEx2)
# ALDEx2分析
aldex_result <- aldex(
reads = otu, # 原始计数(不加伪计数!)
conditions = meta$Group, # 分组
mc.samples = 128, # MC采样次数
test = "t", # t检验
effect = TRUE, # 计算效应量
include.sample.summary = FALSE, # 不输出样本摘要
denom = "all" # 使用所有物种作为参考
)
# ALDEx2结果解读
head(aldex_result[order(aldex_result$we.eBH), ]) # 按FDR排序
# 关键列:
# - rab.all: 所有样本的CLR中位数
# - diff.btw: 组间CLR差异
# - diff.win: 组内CLR差异
# - effect: 效应量(diff.btw / diff.win的中位数)
# - we.ep: Welch t检验p值
# - we.eBH: BH校正后的p值(FDR)
# 筛选差异物种
sig_species <- aldex_result[aldex_result$we.eBH < 0.05 &
abs(aldex_result$effect) > 1, ]
cat("\n显著差异物种(FDR<0.05, |effect|>1):\n")
print(sig_species[, c("diff.btw", "effect", "we.eBH")])
# ALDEx2效应量图
aldex.plot(aldex_result, type = "MA", # MA图
test = "welch", # Welch检验
cutoff.pval = 0.05, # p值阈值
cutoff.effect = 1) # 效应量阈值
三、Aitchison距离与PCA¶
# 基于CLR的Aitchison PCA
library(compositions) # 组成数据包
library(ggplot2) # 绑图
# 1. Aitchison距离
# = CLR变换后的欧几里得距离
# = 组成数据的"真实距离"
aitchison_dist <- dist(otu_clr2) # CLR后的欧几里得距离
# 等价于:
# library(robCompositions)
# aDist(acomp(t(otu_pseudo)))
# 2. Aitchison PCA(=对CLR数据做PCA)
pca_result <- prcomp(otu_clr2, scale. = FALSE) # PCA
summary(pca_result) # 方差解释比例
# 可视化
pca_df <- data.frame(
PC1 = pca_result$x[, 1], # 第1主成分
PC2 = pca_result$x[, 2], # 第2主成分
Group = meta$Group # 分组
)
var_explained <- summary(pca_result)$importance[2, 1:2] * 100
ggplot(pca_df, aes(x = PC1, y = PC2, color = Group)) +
geom_point(size = 3, alpha = 0.7) + # 散点
stat_ellipse(level = 0.95) + # 95%置信椭圆
labs(x = paste0("PC1 (", round(var_explained[1], 1), "%)"),
y = paste0("PC2 (", round(var_explained[2], 1), "%)"),
title = "Aitchison PCA(CLR变换后)") +
theme_minimal() # 简洁主题
ggsave("aitchison_pca.png", width = 8, height = 6, dpi = 150)
# 3. PERMANOVA with Aitchison距离
library(vegan)
adonis2(aitchison_dist ~ Group + Age, data = meta, permutations = 999)
四、Python实现¶
# Python组成数据分析
import numpy as np # 数值计算
import pandas as pd # 数据处理
from scipy.spatial.distance import pdist, squareform # 距离计算
import matplotlib.pyplot as plt # 绑图
# 1. CLR变换
def clr_transform(counts, pseudocount=0.5):
"""中心对数比变换"""
data = counts + pseudocount # 加伪计数
log_data = np.log(data) # 取对数
gm = log_data.mean(axis=0) # 几何均值(列方向)
clr = log_data - gm # 减去几何均值
return clr
# 2. ALR变换
def alr_transform(counts, ref_idx=-1, pseudocount=0.5):
"""加性对数比变换"""
data = counts + pseudocount # 加伪计数
ref = data.iloc[ref_idx] # 参考物种
alr = np.log(data.drop(data.index[ref_idx]) / ref) # 对数比
return alr
# 3. ILR变换
def ilr_transform(counts, pseudocount=0.5):
"""等距对数比变换"""
data = (counts + pseudocount).values # 加伪计数
n_components = data.shape[0] # 物种数
# 构造Helmert矩阵作为正交基
basis = np.zeros((n_components - 1, n_components))
for i in range(n_components - 1):
basis[i, :i+1] = 1.0 / (i + 1)
basis[i, i+1] = -(i + 1.0) / (i + 1)
basis[i] *= np.sqrt((i + 1.0) / (i + 2.0))
# ILR = Helmert矩阵 × log(数据)
ilr = basis @ np.log(data) # 矩阵乘法
return pd.DataFrame(ilr, columns=counts.columns)
# 4. Aitchison距离
def aitchison_distance(counts, pseudocount=0.5):
"""计算Aitchison距离矩阵"""
clr = clr_transform(counts, pseudocount) # CLR变换
dist_matrix = squareform(pdist(clr.T, metric='euclidean')) # 欧几里得
return pd.DataFrame(dist_matrix,
index=counts.columns,
columns=counts.columns)
# 5. 组成偏差检测
def detect_compositional_bias(counts, method="correlation"):
"""检测组成偏差——用CLR前后的相关性比较"""
# 直接计算相关性(受组成偏差影响)
raw_corr = np.corrcoef(counts.values) # 原始相关矩阵
# CLR后计算相关性(消除组成偏差)
clr = clr_transform(counts)
clr_corr = np.corrcoef(clr.values) # CLR相关矩阵
# 比较
n = len(counts)
raw_neg = (raw_corr[np.triu_indices(n, k=1)] < 0).mean()
clr_neg = (clr_corr[np.triu_indices(n, k=1)] < 0).mean()
print(f"原始数据负相关比例: {raw_neg:.1%}")
print(f"CLR后负相关比例: {clr_neg:.1%}")
if raw_neg > clr_neg + 0.1:
print("→ 检测到组成偏差!原始数据的负相关部分是假的")
else:
print("→ 组成偏差不严重")
return raw_corr, clr_corr
# 6. 可视化对比
def plot_composition_effect(counts, output="composition_bias.png"):
"""可视化组成偏差效应"""
clr = clr_transform(counts)
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 原始相对丰度的相关矩阵
tss = counts / counts.sum(axis=0) # TSS标准化
raw_corr = tss.T.corr() # 相关矩阵
im1 = axes[0].imshow(raw_corr, cmap='RdBu_r', vmin=-1, vmax=1)
axes[0].set_title("TSS相对丰度相关矩阵\n(含组成偏差)")
# CLR变换后的相关矩阵
clr_corr = clr.T.corr() # 相关矩阵
im2 = axes[1].imshow(clr_corr, cmap='RdBu_r', vmin=-1, vmax=1)
axes[1].set_title("CLR变换后相关矩阵\n(消除组成偏差)")
fig.colorbar(im2, ax=axes, label="相关系数")
plt.tight_layout()
plt.savefig(output, dpi=150)
print(f"对比图已保存: {output}")
五、选择指南¶
# CoDA变换选择决策树
你要做什么?
│
├─ 差异丰度分析 → ALDEx2 (CLR + MC) 或 ANCOM-BC
│
├─ PCA/排序分析 → CLR变换 → Aitchison PCA
│
├─ 距离矩阵/PERMANOVA → Aitchison距离(CLR+欧几里得)
│
├─ 相关性/网络分析 → CLR变换 → 标准相关分析
│ (不要对TSS数据直接算Pearson相关!)
│
├─ 回归分析 → ALR变换(减少一个维度)
│
└─ 多元统计检验 → ILR变换(统计上最正确)
常见报错与解决¶
| 报错 | 原因 | 解决方案 |
|---|---|---|
| CLR出现Inf/-Inf | 零值未处理 | 加伪计数(0.5或最小非零值/2) |
| ALDEx2运行很慢 | MC采样次数太多 | 减少mc.samples(64-128足够) |
| ILR结果难以解释 | ILR坐标是物种的正交组合 | 用biplot或转回CLR空间解释 |
| "not all parts are positive" | 输入包含零或负值 | 确保加了伪计数且数据为正 |
| 伪计数选择影响结果 | 不同伪计数给不同结果 | 用ALDEx2的MC采样或CZM方法 |
速查表¶
# 对数比变换对比
CLR: 维度不变(p→p), 不需参考, 最常用
ALR: 维度减1(p→p-1), 需选参考, 适合回归
ILR: 维度减1(p→p-1), 不需参考, 统计最正确
# 伪计数策略
经典: 0.5(最简单)
Bayesian: 每个成分加1/物种数
CZM: Compositional Zero Multiplier
ALDEx2: 蒙特卡洛采样(推荐)
# Aitchison距离 = CLR后的欧几里得距离
# 是组成数据的"正确"距离度量
# 常见错误
× 对TSS数据直接用Pearson相关 → 假负相关
× 对TSS数据做PCA → 不在正确空间
× 忽略零值直接取对数 → Inf值
✓ CLR + PCA = Aitchison PCA
✓ CLR + 欧几里得 = Aitchison距离
✓ ALDEx2处理差异分析
面试高频问题¶
Q1:为什么微生物组相对丰度数据不能直接做相关分析? A:因为相对丰度是组成数据——各物种比例总和=100%。这个"总和约束"会制造假的负相关:一个物种比例增加,数学上其他物种必然减少,即使它们绝对量没变。Pearson/Spearman相关会检测到大量虚假的负相关。解决方法是先做CLR变换,把数据从单纯形空间转到欧几里得空间再分析。
Q2:CLR和ILR有什么区别?怎么选? A:CLR保持原始维度(p个物种→p个CLR值),容易解释(每个CLR值对应一个物种),但CLR值之间线性相关(和=0),不适合需要独立性的检验。ILR用正交基变换,维度减1(p-1),统计性质更好但坐标难解释。实际中:探索性分析/可视化用CLR,严格统计检验用ILR。
Q3:ALDEx2为什么被认为是最严谨的差异分析方法? A:ALDEx2的严谨性来自三方面:(1) 用蒙特卡洛采样从Dirichlet分布生成多个实例,考虑了稀疏数据的不确定性;(2) 用CLR变换处理组成偏差;(3) 用效应量而非仅p值来判断差异。缺点是偏保守——可能漏掉一些真正的差异物种(假阴性率较高)。
Q4:零值怎么处理?加伪计数有什么问题? A:加伪计数是最简单的方法但有争议:(1) 不同伪计数给不同结果;(2) 当零值很多时会扭曲数据结构。更好的方法包括:ALDEx2的蒙特卡洛采样(从后验分布采样多次取中位数)、CZM方法(按比例替换零值)、或Bayesian乘法替换。2024年的共识是避免固定伪计数,优先用概率模型。