数据清洗与预处理:把脏数据变成分析就绪态

数据清洗这步,说实在的,占了我实战项目里差不多一半的时间。你想想看,拿到手的丰度表,十个样本里三四个有缺失,低丰度OTU一抓一大把,不做清洗直接跑差异性分析,结果根本没法看。我刚开始做微生物组项目的时候,就吃过这个亏——把原始数据直接塞进LEfSe,出来的结果花里胡哨,可老板一质疑就全垮了。从那以后,我养成了一个习惯:先花一整天清洗,再花一下午分析。

1. 缺失值处理——别让NA毁了你的统计

缺失值在丰度数据里太常见了。测序深度不够、PCR扩增失败、样本标签搞混……原因五花八门。我自己最头疼的是那种“间歇性缺失”:同一处理组的几个重复里,有的OTU有值,有的没有。这时候直接删掉整个OTU,信息损失太大;不删吧,统计检验又报错。

常见的处理策略就三种:

  • 删除:样本缺失率超过50%,直接删样本;OTU在所有样本中缺失率超过80%,删OTU。这招简单粗暴,但我建议别一上来就用。先看看缺失是不是随机出现的。
  • 填补:用均值、中位数、最小值(比如检测限的1/2)或kNN预测。我偏爱用zCompositions包的cmultRepl,它能利用成分数据的比例关系做贝叶斯替换,效果比简单均值好得多。
  • 模型估计:把缺失当作待估参数,用MCMC或极大似然来补。计算量大,但精度最高。小数据集慎用,容易过拟合。

下面是我常用的填补代码:

library(zCompositions)
# 假设你的丰度表是 otu_table,行是OTU,列是样本
# cmultRepl 会自动识别0值,也可以指定处理NA
otu_filled <- cmultRepl(otu_table, label = 0, method = "CZM")
# 如果是NA,可以先用0占位再填补:
otu_table[is.na(otu_table)] <- 0
otu_filled <- cmultRepl(otu_table, label = 0, method = "CZM")
注意: 填补前一定要确认你的数据是否属于成分数据(compositional)。比如16S相对丰度表就是成分数据,直接用均数填补会破坏比例结构,导致下游分析失真。我遇到过一个翻车案例:用简单均值补了缺失,结果β多样性PCoA图里对照组和治疗组完全重叠,换了czm填补后才分开。

2. 低丰度过滤——把噪声踢出去

低丰度OTU(比如平均相对丰度 < 0.01%)多数是测序错误或罕见菌。留着它们,不仅增加多重比较的校正负担,还会让稀疏性加剧,很多多元统计方法(如CCA)都对零过多的数据不友好。我个人的经验是:先画一个丰度分布直方图,看看阈值大概在哪。

常见的过滤策略:

  • 基于丰度值:删除所有样本中平均丰度低于某个阈值(如1e-4)的OTU。
  • 基于出现频率:删除在少于X%样本中出现的OTU(比如只在5%以下样本中出现的去掉)。
  • 两者结合:比如“平均丰度 > 1e-5 且出现频率 > 10%”。

给个代码片段:

# 假设 otu 是矩阵,行OTU,列样本
min_abundance <- 1e-4
min_prevalence <- 0.1  # 至少出现在10%的样本中

# 计算每个OTU的平均丰度
mean_abund <- rowMeans(otu)
# 计算每个OTU的出现频率(非零样本比例)
prevalence <- rowSums(otu > 0) / ncol(otu)

# 过滤
keep <- mean_abund > min_abundance & prevalence > min_prevalence
otu_filtered <- otu[keep, ]
我习惯把过滤后的OTU数量控制在500~2000之间。太少了丢失信息,太多了噪声干扰。你可以在正式分析前,尝试不同阈值,然后观察α多样性(Shannon、Chao1)的变化曲线——曲线突然变陡的地方,就是信号和噪声的分界点。

3. 标准化方法——让样本之间能比

标准化这一步,说白了就是把不同测序深度的样本拉到同一把尺子上。如果直接用原始counts做差异分析,测序深度高的样本天然会有更多高丰度OTU,这可不是生物学差异。

下面我挑三种(相对丰度、CSS、TMM)聊聊,这也是我项目里最常用的。

方法核心思想适用场景R包/函数
相对丰度 (RA)每个OTU count / 总count,得到比例初步探索、可视化、非参数检验prop.table() / decostand(RA)
CSS (Cumulative Sum Scaling)用上分位数(如75%)归一化,抵抗离群样本的影响有离群大总count的样本、准备做距离计算metagenomeSeq::cumNorm()
TMM (Trimmed Mean of M-values)基于加权对数的截尾均值,常用在差异丰度分析前edgeR差异分析、样本间分散度差异大edgeR::calcNormFactors()

RA最简单,但有个大坑:总count的变动会将扰动传导给所有OTU,所以我只在探索性分析时用RA。CSS是Paulson在metagenomeSeq里提出的,它只用了前75%分位的count和来缩放,能有效避免一两个超高丰度OTU带来的偏差。我有个项目,样本总count从1万到10万不等,做RA后β多样性PCoA第一轴全是总count差异;换成CSS,立马洗出生物学信号。

TMM的话,我一般在做差异丰度分析时才请它出场。它通过计算样本间M值的截尾均值来得到缩放因子,比RA更稳健。不过注意,TMM假设大多数OTU是非差异的,如果你的数据里有一半以上的OTU都是差异丰度的,那TMM反而会失效。

代码示例:

# ---- 相对丰度 ----
ra <- prop.table(as.matrix(otu_filtered), 2)  # 按列归一化

# ---- CSS 标准化 ----
library(metagenomeSeq)
# 创建 MRexperiment 对象
obj <- newMRexperiment(otu_filtered)
# 计算归一化因子(默认使用上分位数0.75)
obj <- cumNorm(obj, p = cumNormStat(obj))
# 提取标准化后的counts(不是相对丰度,是缩放后的counts)
css_counts <- cumNormMat(obj)

# ---- TMM 标准化 ----
library(edgeR)
y <- DGEList(counts = otu_filtered)
y <- calcNormFactors(y, method = "TMM")
# 得到的缩放因子存于 y$samples$norm.factors
# 若需要标准化后的logCPM,可以用 cpm(y, log = TRUE)
我的标准流程:
  1. 缺失值处理 → 用cmultRepl填补(成分数据)或kNN(非成分数据)。
  2. 低丰度过滤 → 平均丰度 > 1e-4 & 出现频率 > 10%。
  3. 标准化 → 如果后续要做差异分析(edgeR/DESeq2),用TMM;如果只是β多样性或聚类,用CSS;探索性可视化用RA。

最后,我想强调一点:标准化方法的选择不是一成不变的。我遇到过不同标准化方法导致截然不同的排序图——同一批数据,RA第一轴解释16%,TMM第一轴解释9%。你猜哪个更可靠?答案是:得结合你的生物学假设去验证,比如看分组是否在轴上有明显分离。标准化只是工具,别让它替你做决定。

数据清洗与预处理三步流程 数据清洗与预处理流程 原始丰度表 缺失值处理 删除 / 填补 / 估计 低丰度过滤 丰度阈值 + 频率 标准化 RA / CSS / TMM 相对丰度 (RA) 探索分析 / 可视化 / 非参数检验 CSS 标准化 β多样性 / 聚类 / 有离群样本 TMM 标准化 差异丰度分析(edgeR) 分析就绪的丰度数据