缺失值处理:别让“空”毁了你的模型
做RNA-seq分析,缺失值是个绕不开的坑。我当年刚接触转录组数据时,拿到表达矩阵第一件事就是直接丢给机器学习模型跑——结果准确率惨不忍睹。后来一查,发现矩阵里飘着一堆NaN和0。嗯,并不是基因不表达,而是测序没测到。你想想看,模型看到一堆缺失值,能不懵吗?
这一章我就聊聊缺失值怎么来的,以及我常用的几种填补策略。坦白说,没有万能药,但选对了方法能让你的模型至少提升5-10个点。
基因表达数据为什么会有缺失?
很多人以为表达矩阵里出现0或NaN就是基因没表达。其实大部分缺失是技术原因造成的,跟生物学无关。我总结了几个常见“凶手”:
- 测序深度不足:低表达基因的read count本来就少,抽样时可能恰好没抽到。这就是所谓的“dropout事件”。
- 低表达基因:表达量太低,低于检测阈值,仪器报不出来。
- 批次效应:不同实验批次、不同试剂、不同操作员——我曾在一个项目中,两批样本同一基因的缺失率差了30%,原因竟然是换了一台测序仪。
- PCR扩增偏好:高GC含量的区域难扩增,容易漏掉。
- 比对质量问题:低质量read被过滤掉,对应基因就空了。
常见填补策略:从粗暴到精细
填补方法很多,但核心就两个字:推和猜。推是用统计量替代,猜是利用其他基因或样本的关系来预测。下面几个策略我都在真实项目里踩过坑,一个一个说。
均值/中位数填补(最简单,但小心翻车)
把基因在所有样本中的表达值取平均(或中位数),填到空位置上。代码就两行:
from sklearn.impute import SimpleImputer
import numpy as np
imputer = SimpleImputer(strategy='median')
matrix_filled = imputer.fit_transform(expression_matrix)
我在处理一个乳腺癌数据集时试过均值填补,结果聚类分析直接把所有样本分成了两团——后来发现是因为均值被几个高表达样本带偏了。换成中位数就好多了。我个人习惯:有异常值用中位数,否则用均值。
KNN填补(利用“邻居”帮忙)
找k个与缺失基因表达模式最相似的基因(按非缺失位置算欧氏距离),用它们的加权平均来填。sklearn直接支持:
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=5, weights='distance')
matrix_filled = imputer.fit_transform(expression_matrix)
效果比均值好很多,但有两个坑:一是计算量大——如果你有2万个基因,跑一次KNN要几分钟;二是k值怎么选?我试过5、10、15,在TCGA的肝癌数据上k=10效果最好。建议用交叉验证来调。
随机森林填补(迭代预测,效果最稳)
先用均值初始化,然后每次选一个含缺失值的基因作为“目标变量”,其他基因做特征,训练随机森林来预测缺失值。迭代个10轮,结果就收敛了。sklearn的IterativeImputer就是这个逻辑:
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.ensemble import RandomForestRegressor
imputer = IterativeImputer(
estimator=RandomForestRegressor(n_estimators=100, max_depth=10),
max_iter=10,
random_state=42)
matrix_filled = imputer.fit_transform(expression_matrix)
这个是我最常用的方法,尤其在要建预测模型时。随机森林本身对噪声不敏感,而且能捕捉基因之间的非线性关系。不过迭代10次的话,2万基因×500样本的数据集可能要跑30分钟。嗯,时间换精度,值不值你自己掂量。
方法对比:一张表说清楚
| 方法 | 速度 | 适用场景 | 我踩过的坑 |
|---|---|---|---|
| 均值/中位数 | 极快 | 缺失率<10%,且数据分布对称 | 异常值会把均值带偏;中位数更扛噪 |
| KNN填补 | 中等 | 缺失率10%-30%,样本量适中 | k选不好,邻居里全是空的就翻车 |
| 随机森林 | 慢 | 缺失率30%-50%,追求模型精度 | 迭代次数多了过拟合,10轮足够 |
核心流程:一张SVG图讲清楚
下面我画了一个流程图,概括了从缺失诊断到策略选择再到效果验证的完整思路。你可以把它贴在工位旁边,做RNA-seq分析时心里有个谱:
避坑指南:这些年我交过的学费
填补不是终点,填完一定要验证。我分享几个真实教训:
- 别对所有基因无差别填补:我曾图省事,把缺失率95%的基因也用KNN填了——结果模型把那些基因当作重要特征了。后来发现,填出来的值基本是噪声。建议先过滤掉缺失率>50%的基因。
- 填完后检查分布:均值填补会让表达分布出现一个尖锐的峰,峰顶正好是均值。你画个核密度图就能看出来。如果看到这种“假峰”,赶紧换方法。
- 不要忽视零值:有些软件把0当作缺失,有些当作真实表达。我就被坑过一次——用DESeq2时把0当作缺失填了,结果差异基因分析全错了。先弄清楚你数据里0的意义。
我的经验总结:如果最终目的是建分类模型,优先用随机森林填补;如果是做差异分析或降维,KNN更安全。但无论选哪个,**先用原始非缺失数据做一次小实验**——随机挑10%的基因人为“造缺失”,然后看你的填补方法能恢复多少。这个步骤花10分钟,能帮你省下未来几周的重做时间。