《sklearn机器学习——数据预处理》估算缺失值

发布于:2025-09-15 ⋅ 阅读:(22) ⋅ 点赞:(0)

插补缺失值是数据预处理中比较常见的缺失值处理方式,如果数据不可或缺的话就必须进行数据缺失值的处理,如果数据缺失值或有或无(例如在大量数据中重复的缺失值)就可以直接删除掉缺失值。
以下是比较常见的插补缺失值的方式:

方法名称 类 (Class) 适用场景 特点
均值/中位数/众数插补 SimpleImputer 数值型或分类型数据的简单填充 - mean: 适用于数值型,对异常值敏感
- median: 适用于数值型,对异常值鲁棒
- most_frequent: 适用于分类型或数值型
- constant: 用指定常数填充
K近邻插补 KNNImputer 数据中存在局部相似性(邻近样本相似) 基于特征空间中K个最近邻的样本的加权或非加权平均值进行插补,适合数值型数据,计算成本较高
迭代插补 IterativeImputer 多变量缺失,缺失机制较复杂(如MAR) 使用其他特征预测缺失特征,迭代进行,功能强大但计算开销大,适用于中小型数据集
基于模型的插补 (通常用 IterativeImputer 或自定义管道) 缺失模式复杂,特征间有较强依赖 可结合回归、随机森林等模型进行预测插补,灵活性高,但需注意过拟合
删除法 pandas.DataFrame.dropna() 缺失比例极低,或缺失完全随机 (MCAR) 简单直接,但会损失数据,可能导致偏差,不推荐在缺失较多时使用
标记法(指示变量) MissingIndicator 分析缺失本身是否有信息 生成一个布尔矩阵,标记哪些值是缺失的,可与 Imputer 结合使用,保留缺失的“信息”

以下我们着重对前三种缺失值处理方式进行讲解,其他的缺失值使用方式也和前三种类似。


SimpleImputer(均值/中位数/众数插补)

核心思想

SimpleImputer 的核心思想是:用一个统计量或指定的常数值来替换数据集中的缺失值。

常用的填充策略包括:

使用均值(mean)
使用中位数(median)
使用众数(most frequent)
使用指定常数(constant)
适用于数值型和类别型特征。

主要参数(Parameters)

参数 说明
missing_values 指定缺失值的形式,默认是 np.nan。也可以是其他值如 0-999
strategy 填充策略,可选:
'mean' (默认)
'median'
'most_frequent'
'constant'
fill_value strategy='constant' 时,用来填充的值。默认为 0。对于字符串或类别数据,可以设为任意字符串。
copy 是否复制原数据进行操作,默认为 True

常用方法(Methods)

方法 说明
fit(X[, y]) 计算填充所需的统计量(如均值、中位数、众数),基于训练数据。
transform(X) 应用 fit 得到的规则,对数据进行填充。
fit_transform(X[, y]) fittransform,常用于训练集。
inverse_transform(X) 如果有逆操作可能,还原填充前的数据(一般不常用)。

简单代码示例

import numpy as np
from sklearn.impute import SimpleImputer

# 创建包含缺失值的数据(np.nan 表示缺失)
data = np.array([[1,      2     ],
                 [np.nan, 3     ],
                 [7,      np.nan],
                 [4,      6     ]])

print("原始数据:")
print(data)

# 创建 imputer 对象:使用均值填充
imputer = SimpleImputer(strategy='mean', missing_values=np.nan)

# 使用 fit_transform 对数据进行拟合并转换
data_imputed = imputer.fit_transform(data)

print("\n填充后的数据(按列计算均值填充):")
print(data_imputed)

结果:

原始数据:
[[ 1.  2.]
 [nan  3.]
 [ 7. nan]
 [ 4.  6.]]

填充后的数据(按列计算均值):
[[1.         2.        ]
 [4.         3.        ]
 [7.         3.66666667]
 [4.         6.        ]]

类别型数据的填充示例

# 类别型数据示例
data_categorical = np.array([['男',   'A'],
                             ['女',   np.nan],
                             [np.nan, 'B'],
                             ['男',   'A']], dtype=object)

# 使用众数填充
imputer_cat = SimpleImputer(strategy='most_frequent', missing_values=np.nan)

data_cat_imputed = imputer_cat.fit_transform(data_categorical)

print("类别型数据填充后:")
print(data_cat_imputed)

输出:

类别型数据填充后:
[['男' 'A']
 ['女' 'A']
 ['男' 'B']
 ['男' 'A']]

KNNImputer(K近邻插补)

k近邻插补(KNN Imputation)的核心思想是利用数据点之间的相似性来估计缺失值。对于一个含有缺失值的样本,算法首先在特征空间中计算它与其他样本之间的距离(如欧氏距离),然后找出距离最近的k个相似样本,即“k个最近邻”。接着,根据这k个邻居在缺失特征上的实际取值来填充原始样本的缺失值:对于数值型特征,通常采用加权或非加权的均值;对于分类特征,则常采用众数。这种方法相较于均值或中位数插补,能够更好地保留数据的局部结构和变量间的相关性,因为它只参考与目标样本最相似的那些样本进行填充。k近邻插补适用于数据具有较强局部相似性或聚类结构的情况,但其计算成本较高,尤其在大数据集上;同时,k值的选择对插补效果有较大影响,需通过交叉验证等方法合理确定。此外,该方法对特征的尺度敏感,通常需要先进行标准化或归一化处理。

数学计算

k近邻插补(KNN Imputation)的数学计算过程基于样本之间的距离度量和局部相似性假设。其核心思想是:对于一个含有缺失值的样本,通过在完整数据空间中寻找与其最相似的k个样本(即k个最近邻),并利用这些邻居的信息加权或平均来估计缺失值。整个过程可分为以下几个数学步骤:

首先,假设有数据集 X∈Rn×mX \in \mathbb{R}^{n \times m}XRn×m,其中 nnn 是样本数,$ m $ 是特征数。设目标样本 xix_ixi 在第 jjj 个特征上存在缺失,即 xij=NaNx_{ij} = \text{NaN}xij=NaN。为了计算 xix_ixi 与其他样本的距离以找出k个最近邻,需要处理特征缺失带来的距离计算问题。常用的方法是仅使用两个样本都非缺失的特征来计算距离。例如,使用欧氏距离的变体:
d(xi,xl)=∑j∈Sil(xij−xlj)2 d(x_i, x_l) = \sqrt{ \sum_{j \in S_{il}} (x_{ij} - x_{lj})^2 } d(xi,xl)=jSil(xijxlj)2
其中 SilS_{il}Sil 表示样本 xix_ixixlx_lxl 在相同特征上均非缺失的特征索引集合。这种部分特征距离虽然近似,但允许在不完整样本间进行比较。

接下来,基于计算出的距离,选择距离最小的k个样本构成邻居集合 Nk(xi)N_k(x_i)Nk(xi)。这一步通常通过排序实现。然后,针对目标缺失特征 jjj,从这k个邻居中提取其在第 jjj 个特征上的取值 {xlj∣xl∈Nk(xi)}\{x_{lj} \mid x_l \in N_k(x_i)\}{xljxlNk(xi)}

对于数值型特征,缺失值的估计通常采用简单平均或距离加权平均:

  • 简单平均:x^ij=1k∑xl∈Nk(xi)xlj\hat{x}_{ij} = \frac{1}{k} \sum_{x_l \in N_k(x_i)} x_{lj}x^ij=k1xlNk(xi)xlj
  • 加权平均:x^ij=∑xl∈Nk(xi)wlxlj∑xl∈Nk(xi)wl\hat{x}_{ij} = \frac{ \sum_{x_l \in N_k(x_i)} w_l x_{lj} }{ \sum_{x_l \in N_k(x_i)} w_l }x^ij=xlNk(xi)wlxlNk(xi)wlxlj,其中权重 wl=1d(xi,xl)+ϵw_l = \frac{1}{d(x_i, x_l) + \epsilon}wl=d(xi,xl)+ϵ1ϵ\epsilonϵ 为极小常数以避免除零。

对于类别型特征,则采用众数,即选择k个邻居中出现频率最高的类别作为填充值,也可引入距离权重计算加权投票。

在整个过程中,特征通常需要标准化(如Z-score或Min-Max),以消除量纲影响,确保距离度量的公平性。此外,k值的选择至关重要:k过小易受噪声影响,k过大则可能引入不相关样本。通常通过交叉验证选择最优k。KNN插补能捕捉局部结构,但计算复杂度为 O(n2)O(n^2)O(n2),适合中小规模数据。

k近邻在sklearn中的类或方法介绍

在 scikit-learn(sklearn)中,k近邻插补功能由 sklearn.impute.KNNImputer 类实现。该类通过寻找每个样本的k个最近邻,并利用这些邻居的值来填充缺失数据,是一种基于相似性的插补方法。KNNImputer 能够自动处理数值型数据中的缺失值(NaN),并支持多种距离度量和加权策略。

其主要参数包括:n_neighbors 指定用于插补的最近邻数量,默认为5;weights 控制邻居的权重方式,可选 'uniform'(等权重)或 'distance'(距离的倒数作为权重);metric 定义距离度量方式,默认为 'nan_euclidean',该度量能忽略缺失值并基于共现特征计算欧氏距离;copy 决定是否复制数据,默认为 True;add_indicator 可选是否添加一个缺失值指示矩阵,用于后续建模时保留缺失信息。

KNNImputer 的使用方式与其他 sklearn 预处理器一致:调用 fit() 方法在训练数据上学习邻居结构,transform() 方法对数据进行插补,或直接使用 fit_transform() 一步完成。它支持对二维数值数组进行操作,适用于特征矩阵的缺失值填补。需要注意的是,该方法对数据的尺度敏感,因此在使用前通常建议对数据进行标准化处理(如 StandardScaler),以避免某些特征因量纲过大而主导距离计算。此外,当数据集较大时,KNNImputer 的计算开销较高,因其需计算样本间的成对距离。它是处理结构化数据中缺失值的一种有效且直观的方法,尤其适用于数据具有局部相关性或聚类结构的场景。

代码示例

import numpy as np
from sklearn.impute import KNNImputer
from sklearn.preprocessing import StandardScaler

# 创建示例数据(包含缺失值)
data = np.array([[1.0, 2.0],
                 [np.nan, 3.0],
                 [7.0, np.nan],
                 [4.0, 6.0],
                 [5.0, 5.0]])

print("原始数据:")
print(data)

# 方法一:直接使用 KNNImputer(推荐)
# KNNImputer 默认使用欧氏距离,k=5,自动处理缺失值计算
imputer = KNNImputer(n_neighbors=2, weights='uniform')  # 可选 weights='distance'
data_imputed = imputer.fit_transform(data)

print("\nKNN插补后的数据:")
print(data_imputed)

# 方法二:结合标准化(更佳实践)
# 因为KNN基于距离,建议先标准化
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)  # 注意:这里会传播NaN,实际中可先粗略填充再标准化

# 重新使用KNNImputer(它能处理NaN)
imputer_scaled = KNNImputer(n_neighbors=2)
data_imputed_scaled = imputer_scaled.fit_transform(data)

# 若需要,可将结果反标准化(但通常插补后直接使用)
print("\n标准化+KNN插补后的数据:")
print(data_imputed_scaled)

# 解释:第一行缺失值会参考第4、5行(最近邻)
# 第二行缺失值会参考第1、4行等,根据特征距离自动计算

IterativeImputer(迭代插补)

迭代插补(Iterative Imputation)的核心思想是将每个含有缺失值的特征依次视为回归问题,利用其他特征预测其缺失值。它通过多轮迭代逐步优化填充值:每一轮中,使用已填充的数据作为输入,对每个特征建立回归模型(如线性回归、贝叶斯回归)来预测其缺失部分。随着迭代进行,填充值不断更新,直至收敛或达到最大迭代次数。该方法能捕捉特征间的复杂关系,比单次插补更精确。在 sklearn 中由 IterativeImputer 实现,适用于中等规模数据集。

迭代插补的数学计算过程

迭代插补(Iterative Imputation)是一种多轮迭代的缺失值填补方法,其数学核心在于将每个特征依次作为目标变量,利用其余特征构建预测模型来估计其缺失值。整个过程从一个简单的初始填充(如均值)开始,然后通过多轮迭代逐步优化填充值,直至收敛。

设数据集为X∈Rn×mX \in \mathbb{R}^{n \times m}XRn×m,其中nnn为样本数,mmm为特征数,部分元素为缺失。令X(t)X^{(t)}X(t)表示第ttt轮迭代后的数据矩阵。初始化时,对每个特征的缺失值用其均值或中位数填充,得到X(0)X^{(0)}X(0)

在第ttt轮迭代中,对每个特征j=1,2,...,mj = 1,2,...,mj=1,2,...,m,执行以下步骤:将特征jjj作为目标变量yj=X:,j(t−1)y_j = X_{:,j}^{(t-1)}yj=X:,j(t1),其余特征作为协变量X∖j(t−1)X_{\setminus j}^{(t-1)}Xj(t1)。在完整样本(即yjy_jyj非缺失的行)上训练一个回归模型fjf_jfj,例如线性回归:yj=X∖jβj+ϵjy_j = X_{\setminus j}\beta_j + \epsilon_jyj=Xjβj+ϵj,通过最小二乘法估计参数βj\beta_jβj。然后,使用该模型预测jjj特征在缺失位置的值:y^jmiss=fj(X∖jmiss)\hat{y}_j^{\text{miss}} = f_j(X_{\setminus j}^{\text{miss}})y^jmiss=fj(Xjmiss),并用预测值更新X(t)X^{(t)}X(t)中的对应元素。

此过程对所有特征循环一次构成一轮迭代。重复多轮,直到填充值的变化小于预设阈值(如∥X(t)−X(t−1)∥<ϵ\|X^{(t)} - X^{(t-1)}\| < \epsilonX(t)X(t1)<ϵ)或达到最大迭代次数。由于每次更新都基于当前最优估计,模型能逐步捕捉特征间的依赖结构。sklearn中的IterativeImputer默认使用贝叶斯岭回归,因其对多重共线性鲁棒且能提供不确定性估计。该方法能建模非线性关系(若使用非线性回归器),但计算成本较高,且收敛性依赖于数据结构和模型选择。

sklearn中常用的迭代插补类或方法sklearn.impute.IterativeImputer

在 scikit-learn 中,迭代插补通过 sklearn.impute.IterativeImputer 类实现。该类提供了一种高级的缺失值填补策略,适用于具有复杂特征依赖关系的数据集。

核心思想
IterativeImputer 的核心思想是将缺失值填补建模为一个迭代的回归问题。它假设数据在特征之间存在潜在的统计依赖关系,因此可以利用这些关系逐步优化缺失值的估计。具体而言,算法对每个含有缺失值的特征依次作为目标变量,使用其他特征作为输入变量,构建回归模型来预测其缺失部分。这一过程在多轮迭代中重复进行:每一轮中,所有特征的缺失值都被重新预测和更新,随着迭代的进行,填充值逐渐逼近真实值,直至收敛。这种方法比简单的单次插补(如均值或KNN)更能捕捉变量间的复杂关系,尤其适用于数据满足“缺失完全随机”(MCAR)或“随机缺失”(MAR)假设的场景。

主要参数

  • estimator:用于拟合回归模型的基础估计器,默认为 BayesianRidge()。也可指定其他回归器如 Ridge(), RandomForestRegressor() 等,以适应线性或非线性关系。
  • missing_values:指定缺失值的标记,默认为 np.nan
  • max_iter:最大迭代次数,默认为10。若填充值在达到此轮数前已收敛,则提前停止。
  • random_state:控制随机性,确保结果可重现,特别在使用随机算法(如随机森林)时有效。
  • imputation_order:插补顺序,可选 'ascending'(缺失少的先填)、'descending''roman'(从左到右)、'reverse''random'
  • n_nearest_features:仅使用与目标特征最相关的前k个特征进行建模,以提高效率并减少噪声干扰。
  • initial_strategy:初始填充策略,用于第一轮迭代前的预填充,可选 'mean''median''most_frequent''constant'
  • tol:收敛阈值,当连续两轮插补结果的平均绝对变化小于该值时停止迭代。

返回值与方法
IterativeImputer 是一个转换器,调用 fit_transform(X) 后返回一个完整的、无缺失值的数组 X_new,其形状与输入 X 相同。该类遵循 sklearn 的统一接口:

  • fit(X, y=None):在数据 X 上训练插补模型,学习特征间的依赖结构。
  • transform(X):应用已学习的模型对数据进行插补,通常用于测试集。
  • fit_transform(X, y=None):先 fittransform,常用于训练集。
  • inverse_transform(X):尝试将插补后的数据还原(不常用)。

使用示例

import numpy as np
from sklearn.impute import IterativeImputer
from sklearn.linear_model import BayesianRidge
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import make_regression

# 创建示例数据(含缺失值)
X, _ = make_regression(n_samples=100, n_features=5, noise=10, random_state=42)
X = X.astype(np.float64)

# 随机引入缺失值 (设为NaN)
rng = np.random.RandomState(0)
missing_rate = 0.1
n_missing = int(X.size * missing_rate)
missing_idx = rng.choice(X.size, n_missing, replace=False)
X.flat[missing_idx] = np.nan

print("数据形状:", X.shape)
print("缺失值总数:", np.isnan(X).sum())

# 示例1:使用默认的 BayesianRidge 作为估计器
print("\n--- 使用 BayesianRidge 进行迭代插补 ---")
imputer1 = IterativeImputer(
    random_state=42,
    max_iter=10,
    tol=1e-3
)
X_imputed1 = imputer1.fit_transform(X)
print("插补完成,前5行数据示例:")
print(X_imputed1[:5])

# 示例2:使用 RandomForestRegressor(可捕捉非线性关系)
print("\n--- 使用 RandomForestRegressor 进行迭代插补 ---")
rf_estimator = RandomForestRegressor(n_estimators=100, random_state=42)
imputer2 = IterativeImputer(
    estimator=rf_estimator,
    max_iter=5,
    random_state=42,
    n_nearest_features=4,  # 只使用最相关的4个特征
    initial_strategy='median'
)
X_imputed2 = imputer2.fit_transform(X)
print("插补完成,前5行数据示例:")
print(X_imputed2[:5])

# 示例3:指定插补顺序
print("\n--- 按缺失值从少到多的顺序插补 ---")
imputer3 = IterativeImputer(
    estimator=BayesianRidge(),
    imputation_order='ascending',  # 缺失少的特征先插补
    random_state=42
)
X_imputed3 = imputer3.fit_transform(X)
print("插补完成。")

输出:

数据形状: (100, 5)
缺失值总数: 50

--- 使用 BayesianRidge 进行迭代插补 ---
插补完成,前5行数据示例:
[[-78.47730754 -45.91018342  49.86429946  23.88051971  75.90350028]
 [-66.25752847 -44.90790193  39.32407676  21.75456144  55.02861352]
 [-81.45750098 -43.64129887  32.46904595  18.75099757  85.35297552]
 [-83.85630998 -51.82522953  49.61871728  16.20524996  97.17506948]
 [-81.1710475  -46.76848787  48.76558238  20.28455949  82.16829043]]

--- 使用 RandomForestRegressor 进行迭代插补 ---
插补完成,前5行数据示例:
[[-79.55404138 -47.10818398  50.61735035  23.9607133   76.24067419]
 [-65.70774146 -44.98180626  38.88447833  21.72911865  55.47114617]
 [-80.40825848 -43.85005367  33.38802717  18.57819498  84.98678477]
 [-83.40597077 -51.78977536  49.58798052  16.37075375  97.72328356]
 [-81.76476001 -46.7489016   48.55869993  20.19476443  82.60605685]]

--- 按缺失值从少到多的顺序插补 ---
插补完成。

注意事项

由于 IterativeImputer 涉及多轮模型训练,计算开销较大,尤其在高维或大数据集上。此外,它可能在小样本或强共线性数据中不稳定,因此推荐使用正则化回归器(如贝叶斯岭回归)以增强鲁棒性。该方法适用于中等规模数据,是处理复杂缺失模式的强有力工具。