大数跨境

导师:“缺失值直接填平均数?这咋上线?”我:“Codex这么补!”导师:“那你知道为什么R²会掉、数据分布还会被悄悄改掉吗?”

导师:“缺失值直接填平均数?这咋上线?”我:“Codex这么补!”导师:“那你知道为什么R²会掉、数据分布还会被悄悄改掉吗?” 机器学习和人工智能AI
2026-11-09
9

哈喽,大家好~

最近,有同学提到:数据里有空值,直接填个平均数不就行了吗?

能填,但填完之后,模型学到的规律还靠谱吗?

缺失值看起来只是几个空格,实际上会改变特征分布、变量关系,甚至让模型产生错误判断~

今天,我们就从原理到完整案例,把机器学习中的缺失值处理讲清楚~

01缺失值到底从哪来?

假设我们分析用户消费数据,有些用户没有填写年龄。这里先别急着填,需要判断“为什么没填”。

缺失通常分为三类:

  • MCAR,完全随机缺失:系统偶发故障,与任何变量无关。
  • MAR,随机缺失:是否缺失与其他已知变量有关。例如年轻用户更不愿填写收入。
  • MNAR,非随机缺失:缺失与字段本身有关。例如高收入用户更倾向隐藏收入。

也就是,处理缺失值不是找个数字补进去,而是尽量保留数据原来的关系。

02常见处理方法

缺失很少,并且接近完全随机时,可以删除对应样本。但缺失比例较高时,直接删除会浪费信息。

最简单的是均值填补:

其中, 表示第 个样本的第 个特征已观测, 是该特征的有效样本数。

它速度快,但会把数据往中心拉,削弱方差和特征之间的关系。存在异常值时,中位数通常更稳。

KNN填补会寻找相似样本,再根据邻居估计缺失值;迭代填补则把每个缺失特征当成预测目标:

表示除第 列之外的其他特征。你可以这样理解:它在反复训练小模型,用已知字段推测未知字段。

03完整案例

我们构造一组非线性回归数据,并让缺失概率受到 和 影响,模拟MAR场景。

下游模型使用随机森林。我们同时观察两个指标:

  1. 填充值与真实值之间的RMSE,越低越好。
  2. 随机森林在测试集上的 ,越高越好。
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom sklearn.experimental import enable_iterative_imputerfrom sklearn.impute import SimpleImputer, KNNImputer, IterativeImputerfrom sklearn.ensemble import RandomForestRegressorfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import mean_squared_error, r2_scorenp.random.seed(42)# 1. 构造完整数据X = np.random.normal(size=(1500, 5))y = 3*X[:, 0]**2 + 2*np.sin(X[:, 1]) - 2*X[:, 2]*X[:, 3] \    + X[:, 4] + np.random.normal(0, .6, 1500)X_train, X_test, y_train, y_test = train_test_split(    X, y, test_size=.3, random_state=42)# 缺失风险受x1、x3共同影响def add_missing(data, rate):    risk = 1/(1 + np.exp(-(0.9*data[:, 0] - 0.7*data[:, 2])))    p = np.clip(rate * (0.4 + 1.2*risk), 0, 0.9)    mask = np.random.rand(*data.shape) < p[:, None]    result = data.copy()    result[mask] = np.nan    return result, mask# 2. 图一:展示缺失机制X_demo, mask_demo = add_missing(X_train, .35)fig, ax = plt.subplots(figsize=(8, 6))gx, gy = np.meshgrid(np.linspace(-3, 3, 100),                     np.linspace(-3, 3, 100))risk = 1/(1 + np.exp(-(0.9*gx - 0.7*gy)))ax.contourf(gx, gy, risk, levels=15, cmap="turbo", alpha=.65)sc = ax.scatter(X_train[:, 0], X_train[:, 2],                c=mask_demo.sum(axis=1), cmap="plasma",                s=24, edgecolor="white", linewidth=.2)fig.colorbar(sc, ax=ax, label="每行缺失字段数")ax.set(xlabel="x1", ylabel="x3", title="缺失风险区域与实际缺失数量")plt.tight_layout()plt.show()# 3. 比较三种填补方法methods = {    "均值填补": SimpleImputer(),    "KNN填补": KNNImputer(n_neighbors=5),    "迭代填补": IterativeImputer(max_iter=10, random_state=42)}records = []for rate in [.1, .2, .35, .5]:    A, train_mask = add_missing(X_train, rate)    B, _ = add_missing(X_test, rate)    for name, imputer in methods.items():        A_fill = imputer.fit_transform(A)        B_fill = imputer.transform(B)        rmse = mean_squared_error(            X_train[train_mask], A_fill[train_mask]        ) ** .5        model = RandomForestRegressor(            n_estimators=150, min_samples_leaf=3,            random_state=42, n_jobs=-1        )        model.fit(A_fill, y_train)        r2 = r2_score(y_test, model.predict(B_fill))        records.append([rate, name, rmse, r2])df = pd.DataFrame(records, columns=["缺失率", "方法", "RMSE", "R2"])# 4. 图二:填补误差与模型效果联动分析colors = {"均值填补": "#ff2d55",          "KNN填补": "#00b8d9",          "迭代填补": "#7c4dff"}fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))for name, group in df.groupby("方法"):    axes[0].plot(group["缺失率"], group["RMSE"], "o-",                 lw=3, ms=7, color=colors[name], label=name)    axes[1].plot(group["缺失率"], group["R2"], "s-",                 lw=3, ms=7, color=colors[name], label=name)axes[0].set(title="缺失率与填补误差", xlabel="缺失率", ylabel="RMSE")axes[1].set(title="缺失率与模型效果", xlabel="缺失率", ylabel="$R^2$")for ax in axes:    ax.grid(alpha=.25)    ax.legend()plt.tight_layout()plt.show()print(df.round(3))

第一张图把缺失风险背景和实际缺失数量叠在一起。如果右上或左下区域颜色明显不同,说明缺失并非完全随机,而是受到 、 影响。此时直接删除样本,会让保留下来的数据产生偏差。

第二张图需要左右结合着看。左侧RMSE低,说明填补结果更接近真实值;右侧 高,说明处理后的数据更有利于下游模型。

这里要注意,填补误差最低,不代表模型效果一定最好。我们最终要根据业务目标选择方案,而不是只盯着填补值本身。

实际项目中,还要把填补器放进训练流程,只在训练集上执行fit,再对验证集和测试集执行transform,否则会发生数据泄漏。

最后

处理缺失值的关键,是先判断缺失机制,再比较不同方法对数据关系和模型效果的影响。均值填补适合快速基线,KNN适合局部结构明显的数据,迭代填补更擅长利用特征之间的关联。

接下来大家可以继续尝试增加缺失指示变量,告诉模型“这个位置原本为空”;也可以使用交叉验证联合调整邻居数量、迭代次数和下游模型参数。

总之就是说,填补只是手段,保证模型稳定泛化才是最终目标。

最后
最近准备了16大块的内容,124个算法问题的总结,完整的机器学习小册,免费领取~
领取:备注「算法小册」即可~
扫码如有问题,记得添加微信号:xiaobai_ml012

【声明】内容源于网络
0
0
机器学习和人工智能AI
让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
内容 449
粉丝 0
机器学习和人工智能AI 让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
总阅读10.2k
粉丝0
内容449