第3章 数据预处理概述
好,咱们直接进入正题。很多人一上来就丢数据跑PCA、MDS,结果图丑得没法看,还怪方法不行。我自己早年也犯过这个错,折腾半天发现是原始数据里一堆垃圾没清理。数据预处理,说白了就是你手里拿到的那张表,不能直接喂给算法。你得先把它洗洗干净。
为什么我这么强调这步?因为PCA和MDS对数据分布、缺失值、量纲差异极其敏感。哪怕有一坨异常值,或者某个基因的表达量比其他大几个数量级,最终投影出来的图就会完全变形。你以为看到的是批次效应,其实只是数据没处理好导致的假象。
核心原则:预处理质量决定了可视化结果的真实性与可解释性。 跳过这步,后面的分析就是个笑话。
3.1 为什么要做数据预处理?
嗯,这里先问个问题:你拿到的原始数据长什么样?
- 存在缺失值 (NA/NaN): 有些样本的某些特征没测到。PCA里的协方差矩阵一遇到缺失值就直接罢工,或者给你算出一个离谱的结果。
- 量纲不统一: 基因A的表达量是几千,基因B可能只有零点几。PCA天然偏向方差大的变里,如果不做缩放,结果就是被高表达量的基因主导,低表达量的信息被淹没。
- 存在极端异常值: 可能是测量误差,也可能是样本污染。一个异常值就能把主成分的方向带偏,就像团队里有人瞎指挥。
- 数据分布不对称: PCA理论上对正态分布的数据效果最好。如果数据偏态很严重,投影效果会打折扣。
我当年在做单细胞RNA-seq数据时,就遇到过样本不小心混入了死细胞,表达量整体异常高。如果不先过滤掉这些异常样本和基因,后面的批次效应识别根本就无从谈起。
3.2 数据清洗:从缺失值到异常值
咱们一步一步来。我习惯按这个顺序操作,你可以参照着来。
- 处理缺失值:
- 删除法:如果某个基因或样本缺失率超过10%-20%,直接删掉。
- 插补法:我常用中位数填充(对耐异常值)或KNN插补(利用相似样本估算)。
- 过滤低表达或低变异基因:
- 如果所有样本里某个基因表达量都很低,它基本不贡献变异,留着只会增加噪音。
- 检测并处理异常值:
- 用Z-score或者IQR(四分位距)方法标记。
- 我曾经有一次在转录组数据里发现一个样本的全局表达模式与其他样本完全不一样,后来查出是文库建错了。这种异常样本一定要剔除。
警告: 标准化前一定要先做好缺失值和异常值处理。否则标准化后的结果会被这些异常点严重扭曲,甚至把错误模式放大。
3.3 数据归一化:让不同测量标尺站在一起
这一步是关键。就像英制尺寸和公制尺寸不能直接比,不同基因的表达水平也得拉到同一个度量轴才能做PCA。我个人常用以下几种方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score (标准化) | (x - mean) / std | 数据大致符合正态分布,想要消除量纲影响。PCA最常用。 |
| Min-Max 缩放 | (x - min) / (max - min) | 数据有已知的上下界,需要缩放到[0,1]区间。对异常值敏感。 |
| 分位数归一化 | 将各样本的分布强制对齐 | 多组学数据、芯片数据,假设各样本整体分布一致。我常用这个去除技术误差。 |
举个例子,在Python里做Z-score标准化很简单:
from sklearn.preprocessing import StandardScaler
# 假设data是一个numpy数组,shape为(样本数, 特征数)
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)
# 注意:fit_transform是在所有样本上做的,测试集和训练集要分开做transform
小技巧: 在分批次的数据中,我建议先合并所有批次的数据一起计算scaler参数,再做标准化。这样可以保证所有批次在同一个量纲下处理,避免引入新的计算偏差。
3.4 构造输入矩阵:PCA和MDS前的最后一步
完成上述清洗和标准化后,数据终于可以喂给PCA或MDS了。不过,你想想看,PCA的输入是什么?它接收的是一个样本 x 特征的二维矩阵。
通常情况下,如果你的数据是类似这样的格式:
| 样本ID | 批次 | 基因A | 基因B | 基因C |
|---|---|---|---|---|
| S1 | Batch1 | 12.5 | 5.1 | 0.8 |
| S2 | Batch2 | 13.1 | 4.9 | 0.9 |
你需要把“基因A、基因B、基因C”作为特征列,让每个样本变成一个高维向量。PCA就是对这个特征空间做降维。MDS则是从样本间的距离矩阵出发。所以,你要确保这个距离矩阵是基于标准化后的数据计算的。
我个人通常在最后一步会检查数据的形状,确保没有NaN,并且所有数值都在合理的范围内。然后才开始运行PCA。
# 检查数据
print("数据形状:", data_scaled.shape)
print("缺失值数量:", np.isnan(data_scaled).sum())
print("数据均值近似0:", np.round(np.mean(data_scaled, axis=0), 2))
print("数据标准差近似1:", np.round(np.std(data_scaled, axis=0), 2))
嗯,到这里,数据预处理的大致框架就拿下了。别小看这几步,它直接决定了你后面花大功夫做的可视化靠谱不靠谱。