去年我们组接了一个ICU患者数据建模的活儿,目标是根据入院头24小时的生理指标预测患者出院后的再入院风险。数据拉下来一看,大部分特征缺失率在15%到30%,其中几项护理评分甚至超过40%。一开始图省事,直接用均值把缺的口填了,交叉验证结果那叫一个飘:第一次跑AUC 0.78,第二次换随机种子直接掉到0.73,换掉几个脏样本又跳回0.76。怎么调模型都不对劲,后来才意识到问题根本不在模型,而在缺失值这一环。
后来我把缺失值处理换成了Scikit-learn的KNNImputer,思路很简单:用其他特征维度上与每个缺失样本最相似的K个样本,把它们的观测值拿来做加权平均,填充到缺失位置。就这么一个改动,模型AUC稳定在0.81上下,交叉验证的方差明显变小。这篇文章就把这套流程完整写下来:为什么医疗数据要插补而不是直接删,KNN插补的原理和三个关键参数,以及一套可以直接照抄的实操代码。
为了把问题说透,我用Scikit-learn自带的糖尿病数据集模拟一个带缺失的医疗场景,完整演示从数据清洗、标准化、KNN插补到分类预测的每一步,也把坑都标出来。无论你是做学术研究还是工业落地,这套方法都能直接用。
1. 为什么医疗数据绕不开“缺失值插补”
1.1 医疗数据的缺失不是随机的
医疗记录和电商、社交数据不太一样。患者入院后,各项检查、检验、护理评分是“按需开具”的,不是每项指标对每个患者都一定做。很多生理指标只有病情到一定程度才会额外检测,这直接导致缺失本身携带了病情信息。换句话说,ICU里某个患者如果没测某项指标,有时候不是“忘了记录”,而是医生判断当前场景下没必要测。
缺失机制一般分成三类:
- MCAR(完全随机缺失):缺失和任何观测/未观测变量都无关,比如设备临时故障导致部分记录丢失。
- MAR(随机缺失,但跟已观测变量有关):缺失倾向依赖于已观测到的特征,比如年龄更大的患者更容易漏填某项量表。
- MNAR(非随机缺失):缺失值和缺失值本身有关,比如某种检查结果太差,反而不被记录。
医疗数据里最多的情况是MAR,也可能是MCAR,但几乎不会是完全的MNAR。这一点很关键:MAR和MCAR都意味着我们可以用已有观测信息去估计缺失值,这也是KNN插补能成立的前提。如果数据是MNAR,光靠插补救不回来,那是另一个层面的问题,后文会专门讲。
1.2 直接删样本或均值填充,损失到底在哪
很多人处理缺失的第一步就是 dropna(),在缺失率不高、样本量很大的场景下确实可以;但医疗数据往往样本量稀缺,动辄几百条到几千条,删掉30%带缺失的记录,直接损失的是统计功效和模型对罕见类别患者的覆盖。更麻烦的是,如果缺失和病情严重程度有关,删掉这些样本等于把最难预测的高危病人全删了,模型在真实场景里就是“睁眼瞎”。
均值/中位数填充是另一个常见做法,成本低、代码短,但问题非常明显:填充值不会带来“样本特定”的信息。用列均值填出来的值,不管患者本身多特殊,都被拉到了人群平均水平。这样做首先会压低该特征的方差,导致线性模型和距离类模型估计不准确;其次会破坏特征之间的相关结构。举个简单例子:BMI和血压在真实数据里是相关的,均值填充后这种局部相关被稀释,后续模型学到的特征关系就是错的。
我在初版模型里用均值填充,交叉验证AUC波动特别大,后来查了很多特征重要性的变化,发现关键特征的交互作用完全乱掉了。换成KNN插补后,每个缺失样本的填充值不再是一个全校平均值,而是“跟这个患者最像的那批患者的实际测量值”,这样特征间的协同关系就被保留了下来。这也是KNN插补在预测任务里比均值填充稳的根本原因。
这里我放一个最简单的对比实验,用的是模拟数据,数值不算漂亮,但趋势很典型。
| 处理方式 | AUC(RF, 5折CV) | 5折标准差 |
|---|---|---|
| 均值填充 | 0.743 | 0.031 |
| 删除缺失样本 | 0.702 | 0.045 |
| KNN插补(k=5, weights='distance') | 0.812 | 0.016 |
KNN插补不仅拉高了平均分,还压低了方差。“稳”这个词说的就是这件事——做交叉验证、换训练集种子的时候,模型表现不会一会儿天堂一会儿地狱。当然,表格只是结果展示,原理要往下看。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. KNN插补的核心原理:邻居平均怎么在医学场景里生效
2.1 核心思想是“相似样本给相似答案”
KNN最早是用于分类和回归的监督学习算法,KNN插补则借用了同一套直觉:如果两个病人的特征
