跳转至

673 微生物组数据标准化

一句话概述:微生物组数据的标准化/归一化是消除测序深度差异、使样本间可比较的关键步骤——没有万能方法,需根据下游分析选择TSS、CSS、TMM或rarefaction。

核心知识点速查表

知识点关键内容
TSS总和缩放(相对丰度),最简单但有组成偏差
CSS累积和缩放,DESeq2/metagenomeSeq默认
TMM加权截尾均值,edgeR默认
Rarefaction抽平到最小深度,经典但浪费数据
CLR中心对数比变换,组成数据分析推荐
TaxaNorm2024年新方法,考虑批次效应的归一化

一、为什么要标准化?(白话解释)

打个比方:假设你比较两个班的考试成绩,但A班考了100题,B班只考了50题。直接比总分没意义——A班总分天然更高。标准化就是把它们都换算成"百分制",让比较公平。

微生物组数据也一样:不同样本测序深度不同(有的测了100万条reads,有的只有10万条),直接比较计数值会产生偏差。

为什么这么复杂?: - 微生物组数据是组成数据(compositional)——一个物种"多了",其他物种看起来就"少了" - 数据有大量零值——不是真的没有,可能只是没测到 - 不同标准化方法适合不同的下游分析

二、常用标准化方法对比

方法原理优点缺点适用场景
TSS除以总reads数简单直观组成偏差初步探索、多样性分析
Rarefaction抽样到最小深度消除深度差异浪费数据α多样性、β多样性
CSS累积和到百分位适应偏斜分布参数选择差异分析(metagenomeSeq)
TMM加权截尾均值因子统计功效高假设大部分不变差异分析(edgeR)
DESeq2几何均值比中位数自动处理零值假设对称变化差异分析(DESeq2)
CLR中心对数比处理组成偏差零值需处理组成数据分析
GMPR几何均值成对比处理稀疏数据计算稍慢零值多的16S数据

三、R语言实现各种标准化

# 微生物组数据标准化方法合集
library(phyloseq)       # 微生物组数据管理
library(metagenomeSeq)  # CSS标准化
library(edgeR)          # TMM标准化
library(DESeq2)         # DESeq2标准化
library(vegan)          # rarefaction

# ============ 准备数据 ============
# 假设有一个phyloseq对象ps
# ps <- readRDS("phyloseq_object.rds")

# 查看原始数据
cat("样本数:", nsamples(ps), "\n")       # 样本数
cat("物种数:", ntaxa(ps), "\n")          # 物种数
cat("测序深度范围:", range(sample_sums(ps)), "\n")  # 深度范围

# ============ 1. TSS (Total Sum Scaling) ============
# 最简单的方法——除以总reads数得到相对丰度
tss_normalize <- function(ps) {
  otu <- otu_table(ps)                   # 提取OTU表
  if (!taxa_are_rows(ps)) otu <- t(otu)  # 确保行是物种
  tss <- apply(otu, 2, function(x) x / sum(x))  # 每列除以列总和
  otu_table(ps) <- otu_table(tss, taxa_are_rows = TRUE)  # 替换OTU表
  return(ps)                             # 返回标准化后的对象
}
ps_tss <- tss_normalize(ps)              # TSS标准化

# ============ 2. Rarefaction (抽平) ============
# 经典方法——抽样到最小测序深度
min_depth <- min(sample_sums(ps))        # 最小测序深度
cat("抽平深度:", min_depth, "\n")         # 打印深度
ps_rare <- rarefy_even_depth(            # 抽平函数
  ps,
  sample.size = min_depth,               # 抽平到最小深度
  rngseed = 42,                          # 随机种子(可重复)
  replace = FALSE,                       # 不放回抽样
  trimOTUs = TRUE                        # 移除零丰度OTU
)
cat("抽平后样本数:", nsamples(ps_rare), "\n")  # 可能移除了低深度样本
cat("抽平后物种数:", ntaxa(ps_rare), "\n")     # 抽平后物种数

# ============ 3. CSS (Cumulative Sum Scaling) ============
# metagenomeSeq包的方法——基于累积分布的缩放
css_normalize <- function(ps) {
  otu <- as(otu_table(ps), "matrix")     # 转为矩阵
  if (!taxa_are_rows(ps)) otu <- t(otu)  # 确保行是物种

  # 创建MRexperiment对象
  mr <- newMRexperiment(otu)             # metagenomeSeq对象
  p <- cumNormStatFast(mr)               # 计算CSS百分位参数
  mr <- cumNorm(mr, p = p)              # CSS标准化
  css_counts <- MRcounts(mr, norm = TRUE, log = TRUE)  # 提取标准化计数

  otu_table(ps) <- otu_table(css_counts, taxa_are_rows = TRUE)
  return(ps)
}
ps_css <- css_normalize(ps)              # CSS标准化

# ============ 4. TMM (Trimmed Mean of M-values) ============
# edgeR的方法——计算样本间的缩放因子
tmm_normalize <- function(ps) {
  otu <- as(otu_table(ps), "matrix")     # 转为矩阵
  if (!taxa_are_rows(ps)) otu <- t(otu)  # 确保行是物种

  dge <- DGEList(counts = otu)           # 创建DGEList对象
  dge <- calcNormFactors(dge, method = "TMM")  # 计算TMM因子
  tmm_counts <- cpm(dge, log = TRUE)    # 转为log-CPM

  otu_table(ps) <- otu_table(tmm_counts, taxa_are_rows = TRUE)
  return(ps)
}
ps_tmm <- tmm_normalize(ps)             # TMM标准化

# ============ 5. DESeq2归一化 ============
# 基于几何均值比的中位数因子
deseq2_normalize <- function(ps) {
  # 需要非零行
  ps_nz <- prune_taxa(taxa_sums(ps) > 0, ps)  # 移除零丰度物种
  dds <- phyloseq_to_deseq2(ps_nz, ~ 1)       # 转为DESeq2对象
  dds <- estimateSizeFactors(dds, type = "poscounts")  # 估计大小因子
  normalized_counts <- counts(dds, normalized = TRUE)  # 标准化计数

  otu_table(ps_nz) <- otu_table(normalized_counts, taxa_are_rows = TRUE)
  return(ps_nz)
}
ps_deseq <- deseq2_normalize(ps)         # DESeq2标准化

# ============ 6. CLR (Centered Log-Ratio) ============
# 组成数据分析推荐方法
clr_normalize <- function(ps, pseudocount = 0.5) {
  otu <- as(otu_table(ps), "matrix")     # 转为矩阵
  if (!taxa_are_rows(ps)) otu <- t(otu)  # 确保行是物种

  # 添加伪计数处理零值
  otu_pseudo <- otu + pseudocount        # 加伪计数避免log(0)

  # 计算CLR
  gm <- apply(otu_pseudo, 2, function(x) exp(mean(log(x))))  # 几何均值
  clr_values <- log(otu_pseudo / gm)     # CLR变换

  otu_table(ps) <- otu_table(clr_values, taxa_are_rows = TRUE)
  return(ps)
}
ps_clr <- clr_normalize(ps)             # CLR标准化

四、标准化方法选择指南

# Python实现标准化方法选择和比较
import numpy as np   # 数值计算
import pandas as pd  # 数据处理
from scipy import stats  # 统计检验
import matplotlib.pyplot as plt  # 绑图

# 1. TSS标准化
def tss_normalize(count_matrix):
    """TSS标准化:除以列总和得到相对丰度"""
    col_sums = count_matrix.sum(axis=0)  # 每个样本的总reads
    return count_matrix / col_sums       # 除以总reads

# 2. Rarefaction(随机抽样)
def rarefy(count_matrix, depth=None, seed=42):
    """Rarefaction:抽样到指定深度"""
    np.random.seed(seed)                 # 设随机种子
    if depth is None:
        depth = int(count_matrix.sum(axis=0).min())  # 最小深度
    rarefied = pd.DataFrame(            # 创建空矩阵
        0, index=count_matrix.index, columns=count_matrix.columns
    )
    for sample in count_matrix.columns:  # 遍历每个样本
        counts = count_matrix[sample].values  # 该样本的计数
        total = int(counts.sum())        # 总reads数
        if total < depth:                # 跳过深度不够的样本
            continue
        # 构造reads池
        pool = np.repeat(np.arange(len(counts)), counts.astype(int))
        chosen = np.random.choice(pool, size=depth, replace=False)  # 随机抽样
        unique, cnt = np.unique(chosen, return_counts=True)  # 统计
        rarefied.iloc[unique, rarefied.columns.get_loc(sample)] = cnt
    return rarefied

# 3. CSS标准化(Python简化版)
def css_normalize(count_matrix, quantile=0.5):
    """CSS标准化:基于累积和缩放"""
    normalized = count_matrix.copy()     # 复制数据
    for sample in count_matrix.columns:  # 遍历样本
        counts = count_matrix[sample].sort_values()  # 排序计数
        nonzero = counts[counts > 0]     # 非零值
        cumsum = nonzero.cumsum()         # 累积和
        threshold = nonzero.quantile(quantile)  # 分位数阈值
        scaling_factor = counts[counts <= threshold].sum()  # 缩放因子
        if scaling_factor > 0:
            normalized[sample] = count_matrix[sample] / scaling_factor * 1e6
    return normalized

# 4. CLR变换
def clr_transform(count_matrix, pseudocount=0.5):
    """CLR变换:中心对数比"""
    data = count_matrix + pseudocount    # 加伪计数
    log_data = np.log(data)              # 取对数
    gm = log_data.mean(axis=0)           # 几何均值(对数空间的算术均值)
    return log_data - gm                 # 减去几何均值

# 5. 比较不同标准化方法的效果
def compare_normalizations(count_matrix, metadata, group_col="Group"):
    """比较不同标准化方法对PCoA结果的影响"""
    from sklearn.decomposition import PCA  # PCA降维
    from scipy.spatial.distance import pdist, squareform  # 距离计算

    methods = {
        "Raw": count_matrix,                    # 原始数据
        "TSS": tss_normalize(count_matrix),     # TSS
        "CLR": clr_transform(count_matrix),     # CLR
        "CSS": css_normalize(count_matrix),     # CSS
    }

    fig, axes = plt.subplots(2, 2, figsize=(14, 12))
    for idx, (name, data) in enumerate(methods.items()):
        ax = axes[idx // 2, idx % 2]     # 子图位置
        # PCA降维
        pca = PCA(n_components=2)        # 2维PCA
        coords = pca.fit_transform(data.T)  # 转置后降维(样本在行)

        # 按组着色
        groups = metadata[group_col]     # 分组信息
        for group in groups.unique():    # 遍历每个组
            mask = groups == group       # 该组的样本
            ax.scatter(coords[mask, 0], coords[mask, 1],
                      label=group, alpha=0.7, s=60)  # 散点图
        ax.set_xlabel(f"PC1 ({pca.explained_variance_ratio_[0]:.1%})")
        ax.set_ylabel(f"PC2 ({pca.explained_variance_ratio_[1]:.1%})")
        ax.set_title(f"{name} 标准化")    # 标题
        ax.legend()                       # 图例

    plt.suptitle("不同标准化方法的PCA比较", fontsize=14)
    plt.tight_layout()
    plt.savefig("normalization_comparison.png", dpi=150)
    print("比较图已保存: normalization_comparison.png")

五、标准化方法选择决策树

下游分析是什么?
├─ α多样性 → Rarefaction(标准做法)或 不标准化(用原始计数)
├─ β多样性/排序
│   ├─ Bray-Curtis → TSS(相对丰度)
│   ├─ UniFrac → Rarefaction 或 不标准化
│   └─ Aitchison → CLR变换(推荐)
├─ 差异丰度分析
│   ├─ DESeq2 → DESeq2内部标准化(不需额外处理)
│   ├─ edgeR → TMM标准化
│   ├─ metagenomeSeq → CSS标准化
│   ├─ ANCOM-BC → 内部bias校正
│   └─ ALDEx2 → CLR + MC采样
├─ 相关性/网络分析 → CLR变换(避免组成偏差)
└─ 机器学习 → TSS或CLR(视模型而定)

常见报错与解决

报错原因解决方案
CLR变换出现Inf值零值导致log(0)添加伪计数(0.5或最小非零值的一半)
Rarefaction丢失大量样本部分样本测序深度太低设定合理的最小深度阈值,剔除极低深度样本
TMM因子异常(>10或<0.1)样本间差异太大检查是否有异常样本,考虑用CSS替代
DESeq2报"every gene has 0"输入数据已经标准化必须使用原始计数,不能先做TSS
CSS参数选择困难数据分布不典型用cumNormStatFast自动选择百分位

速查表

# 标准化方法速查
TSS: 简单直观,适合初步探索
Rarefaction: 经典方法,适合多样性分析
CSS: 适合差异分析(metagenomeSeq)
TMM: 适合差异分析(edgeR)
DESeq2: 适合差异分析(自动处理)
CLR: 组成数据分析推荐(相关性/网络)
GMPR: 零值多的稀疏数据

# 什么时候不该用Rarefaction?
- 差异丰度分析(浪费统计功效)
- 样本量少时(每条read都珍贵)
- 机器学习特征提取(需要最大信息量)

# 伪计数策略
CLR变换: 0.5 或 min(非零值)/2
DESeq2: 自动处理(poscounts方法)
log变换: 1(log(x+1))

# 2024-2025新方法
TaxaNorm: 考虑批次效应的归一化
ANCOM-BC2: 自带bias校正的差异分析
wrench: 基于分组信息的归一化因子估计

面试高频问题

Q1:为什么微生物组数据需要特殊的标准化方法? A:微生物组数据有三个特殊性:(1) 组成性——测序得到的是比例信息,一个物种"多了"其他就看起来"少了";(2) 高度稀疏——大量零值,且零值含义不明(真的没有 vs 没测到);(3) 过度离散——方差远大于均值。普通的标准化方法无法同时处理这三个问题。

Q2:Rarefaction还推荐使用吗? A:有争议。反对者认为rarefaction浪费数据、降低统计功效(McMurdie 2014)。支持者认为它简单有效、适合多样性分析(Schloss 2024重新辩护)。目前的共识是:α/β多样性分析可以用rarefaction,差异丰度分析不推荐(改用模型化方法如DESeq2、ALDEx2)。

Q3:CLR变换和TSS有什么区别? A:TSS(相对丰度)仍然是组成数据——各物种比例加起来=1,数据在单纯形空间中。CLR变换把数据从单纯形空间"搬到"欧几里得空间,使得标准统计方法(PCA、相关分析等)可以正确应用。TSS的问题是"假负相关"——一个物种增加会造成其他物种看起来减少。

Q4:如何选择标准化方法? A:关键看下游分析:(1) 多样性分析→rarefaction或不标准化;(2) 差异丰度→使用对应工具的内置方法(DESeq2用DESeq2、edgeR用TMM);(3) 相关性/网络→CLR;(4) 机器学习→TSS或CLR。没有万能方法,建议在补充材料中比较多种方法的结果稳健性。