第四章:数据预处理——标准化、缺失值处理、质量控制

好,数据拿到手了。别急着跑 ComBat,先收拾收拾数据。我做生物信息这些年,见过最多的问题不是 ComBat 参数没调好,而是数据本身就没洗干净。你想想看,垃圾进垃圾出,再漂亮的模型也救不了你。

这一章我们就捋一遍预处理的三板斧:标准化、缺失值处理、质量控制。我个人习惯把它们当作固定的流水线,每步走完再检查一遍,放心很多。

预处理是整个批次校正的基石。 数据里埋一个脏点,结果里就可能长出一片假阳性。

4.1 数据标准化

先说标准化。不同平台、不同实验室测出来的表达量,量纲往往差很多。比如 RNA-seq 的 counts 和芯片的荧光信号,数值范围差好几个数量级。直接扔给 ComBat,它会被这些尺度差异「骗」到,把本该校正的技术差异当成生物学变异。

标准化要达成的目标: 让每个样本(或每个特征)的分布尽可能可比,但又不能抹掉真正的生物差异。

常用的标准化方法有几种,我整理了一张表方便对比:

方法 适用场景 对 ComBat 的影响
分位数标准化(Quantile Normalization) 芯片数据、甲基化数组 强制所有样本分布一致,可能过度压缩变异;ComBat 前用需谨慎
RPKM/FPKM/TPM RNA-seq 基因长度校正后 必须用 log 变换后再输给 ComBat;否则残差分布不对称
log2 变换 几乎所有表达量数据 ComBat 默认假设数据正态分布,log 后更接近正态
Z-score 标准化 每行基因均值为 0 方差为 1 会移除基因本身的表达水平差异,ComBat 后无法还原原始量级

我个人倾向的做法是:先做 log2 变换(如果数据是 counts,先做 log2(cpm+1)),然后直接进 ComBat。Z-score 标准化我不太推荐——因为 ComBat 内部本身会做类似的均值-方差调整,你再提前标准化反而多此一举。

举个具体例子,我处理过一个 TCGA 和 GTEx 合并的批次项目,TCGA 是 RPKM,GTEx 是 TPM。我先统一做了 log2 变换,然后才跑 ComBat。效果比直接跑或者先 Z-score 都要好。

我的个人技巧: 标准化后最好看一眼每个样本的密度图。如果某个样本的分布明显偏离(比如有一个异常平台),那它可能是个坏样本,应该在下游 QC 里剔除,而不是让标准化硬拉过来。

4.2 缺失值处理

缺失值这个坑,我踩过不止一次。你以为 ComBat 能自动处理 NA 吗?不行。ComBat 在计算每一行的均值、方差时,遇到 NA 就直接报错,或者跳过整行,最后输出里该行直接消失。你想想看,本来 2 万个基因,丢了 5000 个有缺失的,剩下 1.5 万个,结果还能信吗?

处理策略要分情况:

  • 特征缺失(基因/探针): 如果某个基因在所有样本中缺失率 > 50%,直接删掉。如果只在个别样本里缺失,可以考虑用该基因在批次内的均值填充。
  • 样本缺失(整体低质量): 如果某个样本有超过 20% 的基因缺失,我建议直接删除该样本。把它留着,ComBat 会因为该样本的方差过小或过大,影响整个批次的校正参数。

我曾经接手过一个项目,对方说 “缺失值我已经处理好了”,结果我跑 ComBat 时发现批量警告。一查,他把所有缺失值填了 0。0 在 log 尺度上是 -Inf,ComBat 根本算不对。嗯,这一点很多人忽略。

下面是我常用的填充脚本片段:

# 用批次均值填充缺失值,仅针对缺失率 < 50% 的基因
fill_with_batch_mean <- function(mat, batch){
  for (gene in 1:nrow(mat)){
    if (sum(is.na(mat[gene, ])) / ncol(mat) > 0.5){
      next  # 缺失太多直接跳过,后面会删除
    }
    for (b in unique(batch)){
      idx <- which(batch == b)
      mean_val <- mean(mat[gene, idx], na.rm = TRUE)
      mat[gene, idx][is.na(mat[gene, idx])] <- mean_val
    }
  }
  # 删除缺失率 > 50% 的基因
  keep <- apply(mat, 1, function(x) sum(is.na(x))/length(x) <= 0.5)
  mat[keep, ]
}
注意: median 填充会比 mean 填充更稳健,尤其当数据存在 outliers 时。我推荐用批次内的 median 而非 mean,它受极端值影响小。

4.3 质量控制(QC)

质量控制是最后一道防线。我把 QC 分成两个层面:样本级别和特征级别。

样本级别 QC:

  • 检测低质量样本: 用 PCA 或者层次聚类看看有没有明显离群点。如果一个样本跟同批次其他样本离得很远,检查它的测序深度、比对率、内参基因表达,如果都异常就剔除。
  • 检查批次分布: 把样本按批次上色画 PCA,如果分群非常明显,说明批次效应很大,这也正是 ComBat 该上场的地方。但如果某个批次只有一个样本,那 ComBat 无法估计该批次的参数。

特征级别 QC:

  • 低表达过滤: 在多数样本中表达量极低的基因,噪声大且不可靠。我习惯过滤掉在超过 80% 的样本中 log2 表达量都低于 1 的基因。
  • 高变异基因: 保留那些在多个样本中变异系数大的基因,它们更有可能是生物差异驱动的。不过 ComBat 不需要你提前过滤,它本身会对每个基因单独建模,但过滤掉低表达的基因会显著提升计算速度。

我记得有一次,客户的数据里有个批次只有 3 个样本,其中一个还是技术重复。PCA 出来那个批次完全散成一片。我跟客户确认后删了那个技术重复,批次才勉强能校正。所以 QC 不是走过场,它直接影响 ComBat 能不能正常工作。

4.4 预处理流程图

好了,把上面三个步骤串起来,你就会得到一张这样的流程。我画了一张图,把标准做法放上面,我的个人习惯放旁边,你对比着看:

数据预处理流程(标准做法 vs 我的习惯) 原始表达矩阵 标准化(log2 变换为主) 缺失值处理(均值填充+删除) 缺失值处理(中位数填充+删除) QC(PCA + 低表达过滤) QC(先PCA去离群样本,再过滤低表达) 干净数据 → ComBat 我的习惯:中位数填充 + 先 PCA 去离群样本再过滤低表达,能减少 ComBat 对异常值的敏感度

4.5 常见坑与检查清单

我最后列一个检查清单,你每次预处理完都过一遍,能省掉很多 debug 时间:

  1. 数据是否全部为数值型?有没有非数字符号(比如 “NA”、“null”)被读成字符了?
  2. 是否有负值?如果做了 log 变换,负值是正常的,但如果有 -0 或极小的负数,检查是否源于归一化问题。
  3. 缺失值比例是否 < 10%?如果高于这个值,不管用什么填充方法,结果都会偏。
  4. 有没有检查批次与生物因素是否混杂?如果年龄、性别也跟批次高度相关,那 ComBat 可能会把生物学差异也校正掉——这是另一个大话题,我们在第九章会细聊。
关键提醒: 预处理不要自动化一键跑完不改。每次数据集的特性都不一样,你至少要看一眼 PCA 图和缺失率热图。我曾经就是因为懒,直接跑了一套标准化+填充+QC 的脚本,结果有一批数据是 TPM,另一批是 FPKM,没统一单位,ComBat 输出了完全荒谬的负表达值。

嗯,预处理这事说复杂不复杂,但不做细致,后面全白费。把这三步踏踏实实走完,再往 ComBat 输入数据,你就成功了一大半。