本文分别使用均值、0值、回归随机森林对缺失值进行填充,通过比较三种填充方法均方误差,来决定选择哪种方法进行缺失值填充!

# 导包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 导入波士顿房价预测数据集
from sklearn.datasets import load_boston

# 导入K折交叉验证对象
from sklearn.model_selection import cross_val_score

# 缺失值填充对象
from sklearn.impute import SimpleImputer

# 导入回归随机森林
from sklearn.ensemble import RandomForestRegressor

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
# 忽略警告信息
import warnings
warnings.filterwarnings("ignore")
datasets = load_boston()

# 简单探索数据
# datasets.data.shape

# 原始数据集准备1:将数据集、特征列摘出来
x_full = datasets.data
y_full = datasets.target

# 原始数据集准备2:数据集的行数和列数单独摘出来,后面有用的
n_samples = x_full.shape[0] # 行数
n_features = x_full.shape[1] # 列数
# 此时其实导入的波士顿房价数据集已经是完整的,完全符合模型建模标准的预处理后的最终数据集,此时不存在缺失值
# 既然此时没有缺失值,那不如强行给你来点缺失值呗

# 先准备一个随机种子
rng = np.random.RandomState(0)

# 既然现在是模拟回归森林填充缺失值,不如咱玩的大一点!怎么的呢?我们把数据集中一半的特征值全都搞为缺失的!
missing_rate = 0.5 # 表示出现缺失值的比例是原始数据集中所有特征值的一半!

# 也就是说此时需要从506行13列的所有数据中采集一半的数据点,准备将它们全都搞成缺失值
n_missing_samples = int(np.floor(n_samples * n_features * missing_rate))

# 只需指定:在哪些行要具有缺失值 在哪些列要具有缺失值 - 随机选取任意行和任意列对应的数据,将其搞成缺失值!
missing_samples = rng.randint(0,n_samples,n_missing_samples) # 随机在某一行准备生成缺失值
missing_features = rng.randint(0,n_features,n_missing_samples) # 随机在某一列准备生成缺失值
missing_samples
missing_features

# 接下来构造包含缺失值的数据集 - 但是我们不能直接对原始数据集作改变 - 所以我们需要把原始数据集拷贝一下
x_missing = x_full.copy() # 最终的带有缺失值的数据集
y_missing = y_full.copy()

# 根据随机生成缺失值的行列的数组,开始更改原始数据集,随机完成一半数据的缺失值构造
x_missing[missing_samples,missing_features] = np.nan

# 将带有缺失值的数据集构造为dataframe,来方便查看以及后续的处理操作
x_missing = pd.DataFrame(x_missing)
x_missing

运行结果:

利用随机森林进行缺失值填补 随机森林缺失值填充_回归

# 上一步,我们准备完毕了带有一半缺失值的数据集,接下来开始进行三种不同方式的缺失值填充

# 方式1:使用均值填充缺失值

# 实例化缺失值填充对象
imp_mean = SimpleImputer(missing_values = np.nan,strategy = 'mean')

# 将带有缺失值的数据集交由填充对象,完成对应的缺失值填充,并返回最终填充后的完整数据集
x_missing_mean = imp_mean.fit_transform(x_missing)
pd.DataFrame(x_missing_mean)

# 方式2:使用0值填充缺失值
imp_0 = SimpleImputer(missing_values = np.nan,strategy = 'constant',fill_value = 0)
x_missing_0 = imp_0.fit_transform(x_missing)
pd.DataFrame(x_missing_0)

运行结果:

利用随机森林进行缺失值填补 随机森林缺失值填充_机器学习_02


利用随机森林进行缺失值填补 随机森林缺失值填充_机器学习_03

x_missing_reg = x_missing.copy()

# isnull()会找出存在缺失值的列 - 继续调用sum()方法就会计算出该列到底有多少个缺失值

# 所有特征列中缺失值大小由小到大排序后返回的特征列的索引 - 表示:第几列出现了缺失值,
# 最小的特征列是第几列,最大的特征列是第几列
sortIndex = np.argsort(x_missing_reg.isnull().sum().values)

# 遍历每一个具有缺失值的列
for i in sortIndex:
    
    # 开始实现迭代填充逻辑,此时不能先直接对目标数据集x_missing_reg作操作 - 必须先copy个副本出来
    
    df = x_missing_reg.copy()
    
    # 提取需要进行预测填补的那一列,将该列特征作为标签
    fillc = df.loc[:,i]
    
    # 将原本的标签作为特征,建立新的矩阵 - 怎么建立? - 直接concat!
    df = pd.concat([df.loc[:,df.columns != i],pd.DataFrame(y_missing)],axis = 1)
    
    # 但是注意!此时df还不能立刻用来预测fillc - 因为还有其他的特征列存在缺失值!
    # 接下来,暂时将剩余还有缺失值的特征列,暂时将其缺失值进行补0
    df_0 = SimpleImputer(missing_values = np.nan,strategy = 'constant',fill_value = 0).fit_transform(df)
    # 此时,这个df_0,能否用于真正的缺失值预测的数据集? - 可以了!
    # 那么请问,可以直接将df_0丢给回归模型开始训练了吗? - 不行!为什么? - 你还没划分训练集和测试集呢!
    # 为啥这里要划分训练集和测试集???????
    # 因为:训练集即为所有特征列中的非缺失值的样本 - 测试集即为缺失值的样本 - 训练集负责训练数据! - 测试集负责预测缺失值!
    # 测试集获得预测结果后,即可直接进行缺失值的填充!
    
    # 1.先划分训练集标签 - 需要填补缺失值的列中不为空的样本作为训练集标签
    Ytrain = fillc.loc[fillc.notnull()]
    
    # 2.再划分测试集标签 - 需要填补缺失值的列中为空的样本作为测试集标签 - 即待填充的值 - 即直接作为测试集标签进行预测啦!
    Ytest = fillc.loc[fillc.isnull()]
    
    # 3.再划分训练集数据 - 不为空的样本
    Xtrain = df_0[Ytrain.index,:]
    
    # 4.最后划分测试集数据 - 为空的样本
    Xtest = df_0[Ytest.index,:]
    
    # 核心预测逻辑:构建回归随机森林对象,基于训练集数据训练模型 - 输入测试集数据获取预测结果 - 预测结果即为待填充的值!
    Ypredict = RandomForestRegressor(n_estimators = 100).fit(Xtrain,Ytrain).predict(Xtest)
    
    # 最后一步:填充缺失值了!
    x_missing_reg.loc[x_missing_reg.loc[:,i].isnull(),i] = Ypredict
# 最后的步骤:模型验证!
X = [x_full,x_missing_mean,x_missing_0,x_missing_reg]

# 接下来即将使用K折交叉验证评估每种数据集所建立的回归随机森林的模型拟合的评估分数 - 谁最小,说明对应的缺失值填充方法就是最好的方法
mse = []

# 针对每一组所得数据集,进行10折交叉验证,获取mse均方误差的模型评估分数
for i in X:
    
    rfc = RandomForestRegressor(n_estimators = 100,random_state = 0)
    mse.append(cross_val_score(rfc,i,y_full,cv = 10,scoring = 'neg_mean_squared_error').mean())

[*zip(['x_full','x_missing_mean','x_missing_0','x_missing_reg'],mse)]

运行结果:

利用随机森林进行缺失值填补 随机森林缺失值填充_机器学习_04

x_labels = ['Full data','Zero Imputation','Mean Imputation','Regressor Imputation']
colors = ['r', 'g', 'b', 'orange']
plt.figure(figsize=(12, 6))
ax = plt.subplot(111)
for i in np.arange(len(mse)):
    ax.barh(i, mse[i],color=colors[i], alpha=0.6, align='center')
ax.set_title('Imputation Techniques with Boston Data')
ax.set_yticks(np.arange(len(mse)))
ax.set_xlabel('MSE')
ax.set_yticklabels(x_labels)
plt.show()

运行结果:

利用随机森林进行缺失值填补 随机森林缺失值填充_利用随机森林进行缺失值填补_05


利用随机森林进行缺失值填补 随机森林缺失值填充_回归_06


结论:从图可以看出,回归随机森林均方误差最小,说该方法效果最好,应该选择使用回归随机森林进行数据集的缺失值填充。