写点什么

随机森林 - 用随机森林回归填补缺失值

  • 2022-11-07
    山东
  • 本文字数:5057 字

    阅读完需:约 17 分钟

我们现实收集到的数据往往是有缺失值的,我们可以选择含有缺失值的数据,有时候填补缺失值会比直接丢弃样本效果更好,即便我们其实并不知道缺失值的真实样貌。在 sklearn 中,我们可以使用 sklearn.impute.SimpleImputer 来将均值、中值、众数或者其他常用的数值填补到缺失数据中在这个案例,我们将使用均值、0、随机森林回归来填补缺失值,并验证四种状况下的拟合情况,找出对使用的数据集来说最佳的缺失值填补方法


SimpleImputer(    ['missing_values=nan', "strategy='mean'", 'fill_value=None', 'copy=True'],)# 后面章节会更详细的讲
复制代码


import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom sklearn.datasets import load_bostonfrom sklearn.impute import SimpleImputerfrom sklearn.ensemble import RandomForestRegressorfrom sklearn.model_selection import cross_val_score
复制代码


导入数据


dataset = load_boston()dataset.data.shape---(506, 13)
# 总共506*13=6578个数据X_full, y_full = dataset.data, dataset.targetn_samples = X_full.shape[0]n_features = X_full.shape[1]
复制代码


为完整的数据放入缺失值首先我们要确定希望放入缺失数据的比例,在这里我们假设是 50%


rng = np.random.RandomState(0)missing_rate = 0.5n_missing_samples = int(np.floor(n_samples * n_features * missing_rate))n_missing_samples # 总共有3289个数据缺失---3289
复制代码


有数据要随机遍布在数据集的各行各列中,而一个缺失的数据需要一个行索引和一个列索引。如果我们能创造一个数组,包含 3289 个分布在 0-506 中间的行索引,和 3289 个分布在 0-13 之间的列索引,那我们就可以利用索引来为数据中的任意 3289 个位置赋空值,然后我们用 0、均值和随机森林来填写这些缺失值,然后查看回归结果如何


missing_features = rng.randint(0,n_features,n_missing_samples)missing_samples = rng.randint(0,n_samples,n_missing_samples)# randint(下限,上限,n),在[下限,上限)之间随机取出n个整数
复制代码


missing_samples = > rng.choice(n_samples,n_missing_samples,replace=False)

我们现在采样了 3289 个数据,远远超过了我们的样本量 506,所以我们使用随机抽取的函数 randint。但如果我们需要的数据量小于我们的样本量 506,那我们可以采用 no.random.choice 来抽样,choice 会随机抽取不重复的随机数,因此可以帮助我们让数据更加分散,确保数据不会集中在一些行中,它的区间下限固定为 0,上限自己指定,依旧是


X_missing = X_full.copy()y_missing = y_full.copy()
X_missing[missing_samples,missing_features] = np.nan
X_missing = pd.DataFrame(X_missing)# 转换成DataFrame是为了后续方便各种操作
复制代码


numpy 对矩阵的运算快,但在索引等功能上不如 pandas


X_missing.head()---    0  1  2  3  4  5  6  7  8  9  10  11  120  0.00632  18.0  2.31  NaN  NaN  NaN  65.2  NaN  NaN  296.0  15.3  396.9  NaN1  0.02731  NaN  7.07  NaN  0.469  NaN  78.9  4.9671  2.0  242.0  17.8  NaN  9.142  NaN  NaN  NaN  NaN  0.469  7.185  61.1  4.9671  NaN  242.0  NaN  NaN  4.033  NaN  NaN  NaN  NaN  NaN  NaN  45.8  6.0622  NaN  222.0  18.7  NaN  NaN4  NaN  0.0  2.18  NaN  0.458  7.147  NaN  6.0622  3.0  222.0  NaN  NaN  5.33
复制代码


用均值填补缺失值


imp_mean = SimpleImputer(missing_values=np.nan,strategy='mean')# 实例化X_missing_mean = imp_mean.fit_transform(X_missing)# 意为把X_missing中的值导入到模型imp_mean中,计算,完毕后返回给X_missing_mean
复制代码


imp_mean.fit_transform 输入的可以是 DataFrame 也可以是 ndarray

imp_mean.fit_transform(np.array([1,2,np.nan]).reshape(-1,1))
---
array([[1. ],
    [2. ],
    [1.5]])


pd.DataFrame(X_missing_mean).isnull().sum().sum()# 如果都不是空值则都是False,加和就为0(默认以列为单位加和,得到结果为Series,然后再加和)---0
复制代码


用 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).isnull().sum().sum()---0
复制代码


使用随机森林回归填补缺失值任何回归都是从特征矩阵中学习,然后求解连续型标签 y 的过程,之所以能够实现这个过程,是因为回归算法认为,特征矩阵和标签之间存在着某种联系。实际上,标签和特征是可以相互转换的,比如说,在一个“用地区,环境,附近学校数量”预测“房价”的问题中,我们既可以用“地区”,“环境”,“附近学校数量”的数据来预测“房价”,也可以反过来,用“环境”,“附近学校数量”和“房价”来预测“地区”。而回归填补缺失值,正是利用了这种思想。对于一个有 n 个特征的数据来说,其中特征 T 有缺失值,我们就把特征 T 当做标签,其他的 n-1 个特征和原来的特征标签组成的特征矩阵。那对于 T 来说,它没有缺失的部分,就是 Y_test,这部分数据既有标签也有特征,而它缺失的部分,只有特征没有标签,就是我们需要预测的部分


  • 特征 T 不缺失的值对应的其他 n-1 个特征 + 本来的标签组成 X_train

  • 特征 T 不缺失的值组成 Y_train

  • 特征 T 缺失的值对应的其他 n-1 个特征 + 本来的标签组成 X_test

  • 特征 T 缺失的值因为是未知的,组成我们需要预测的 Y_test


这种做法,对某一个特征大量缺失,其他特征却很完整的情况,非常适用。


如果数据中除了特征 T,还有其他特征也有缺失值,我们从缺失最少的开始填补,因为填补缺失最少的特征需要的准确信息最小。填补一个特征时,其他特征的缺失值先用 0 代替,每完成一次回归预测,就将预测值放到原来的特征矩阵中,然后继续填补下一个特征。每次填补完毕,有缺失值的特征会减少一个,所以每次循环后,需要用 0 来填补的特征就越来越少。当进行到最后一个特征时(这个特征应该是所有特征中缺失值最多的),已经没有任何的其他特征需要用 0 来进行填补了,而我们已经使用回归为其他特征填补了大量有效信息,可以用来填补缺失最多的特征。遍历所有的特征后,数据就完整,不再有缺失值了。


X_missing_reg =X_missing.copy()
# 找出数据集中,缺失值从少到多排列的特征的顺序sortindex = np.argsort(X_missing_reg.isnull().sum(axis=0)).values# X_missing_reg.isnull().sum(axis=0)返回Series# np.sort返回array,会丢失索引# np.argsort返回从小到大排序的顺序所对应的索引
复制代码


np.argsort(X_missing_reg.isnull().sum(axis=0))
---
0      6
1      8
2      0
3      9
4     12
5      2
6     10
7      4
8      7
9      5
10     1
11     3
12    11
dtype: int64


这里我们先拿出缺失数据最少的一列 i 来说构建我们的新特征矩阵(没有被选中去填充的特征+原始的标签)和标签(被选中去填充的特征)每次预测完一个特征后用得到的列替换 X_missing_reg


df = X_missing_reg # 一开始也就是我们构造的X_missing# 这里给不给copy()都一样,下面pd.concat的inplace=True,也会创建副本
复制代码


fillc = df.iloc[:,i]df = pd.concat([df.iloc[:,df.columns != i],pd.DataFrame(y_full)],axis=1)# pd.concat([],axis),列表中写要连的DataFram,axis选择轴线,axis=0延长,axis=1增宽
复制代码


如果之前使用了

df = X_missing_reg.copy()

那么我们这里可以用,所以我们也可以直接df.drop(i,axis=1),或者del df[:,i]来替代df.iloc[:,df.columns != i]这里由于不是 copy,所以选择切片并让 concat 返回副本的方式避免更改原数据


实例化并用 0 填充空值


df_0 = SimpleImputer(missing_values=np.nan,strategy='constant',fill_value=0).fit_transform(df)
复制代码


找出训练集和测试集,训练集是被选中要填充的特征中(现在是我们的标签)的非空值


Ytrain = fillc[fillc.notnull()]Ytest = fillc[fillc.isnull()]
复制代码


我们需要的不是 Ytest 的值,需要的是 Ytest 的索引,用 Ytest 的索引找到 Xtest 然后用 Xtrain、Ytrain 训练出来的模型预测得到 Ytest,填补改列的缺失值


# 在新特征矩阵上,被选出来的要填充的特征的非空值所对应的数据Xtrain = df_0[Ytrain.index,:]# 在新特征矩阵上,被选出来的要填充的特征的空值所对应的记录Xtest = df_0[Ytest.index,:]
复制代码


用随机森林回归来填补缺失值


rfc = RandomForestRegressor(n_estimators=50) #实例化rfc = rfc.fit(Xtrain,Ytrain)Ypredict = rfc.predict(Xtest)
复制代码


用 predict 接口将 Xtest 导入,得到我们预测的结果(回归结果),就是我们要用来填补空值的这些值


X_missing_reg.loc[X_missing_reg.iloc[:,i].isnull(),i] = Ypredict# iloc不支持使用boolSeries进行索引,但loc可以,可以取Series的values,或者ix.[](官方不建议使用ix)
复制代码


布尔索引会直接去除掉 False 的值

pd.Series([1,2,1,1,3])[pd.Series([1,2,1,1,3])==1]

取 Series 的 values,或者 ix.

a = pd.DataFrame(rng.randint(0,10,(4,5)))
a.iloc[pd.Series([True,True,False,True]).values,1],a.ix[pd.Series([True,True,False,True]),1]
---
(0    0
1    5
3    8
Name: 1, dtype: int32, 0    0
1    5
3    8
Name: 1, dtype: int32)


到此对于 i 的操作就完毕了,整合在一起循环即可填补所有缺失值


for i in sortindex:    df = X_missing_reg     fillc = df.iloc[:,i]    df = pd.concat([df.iloc[:,df.columns != i],pd.DataFrame(y_full)],axis=1)    df_0 = SimpleImputer(missing_values=np.nan,strategy='constant',fill_value=0).fit_transform(df)    Ytrain = fillc[fillc.notnull()]    Ytest = fillc[fillc.isnull()]    Xtrain = df_0[Ytrain.index,:]    Xtest = df_0[Ytest.index,:]    rfc = RandomForestRegressor(n_estimators=50)     rfc = rfc.fit(Xtrain,Ytrain)    Ypredict = rfc.predict(Xtest)    X_missing_reg.loc[X_missing_reg.iloc[:,i].isnull(),i] = Ypredict
复制代码


填补结果


X_missing_reg.head()---0  1  2  3  4  5  6  7  8  9  10  11  120  0.006320  18.00  2.3100  0.06  0.500220  6.37186  65.20  4.118102  3.88  296.0  15.300  396.9000  10.14761  0.027310  11.83  7.0700  0.06  0.469000  6.20716  78.90  4.967100  2.00  242.0  17.800  390.7672  9.14002  0.129802  14.05  3.1026  0.02  0.469000  7.18500  61.10  4.967100  4.42  242.0  16.556  393.1716  4.03003  0.053528  16.90  3.3406  0.20  0.452401  7.24684  45.80  6.062200  3.50  222.0  18.700  391.7752  5.26144  0.062054  0.00  2.1800  0.16  0.458000  7.14700  28.64  6.062200  3.00  222.0  16.854  389.4170  5.3300
X_missing_reg.isnull().sum().sum()---0
复制代码


对填充好的数据进行建模预测


X = [X_full,X_missing_mean,X_missing_0,X_missing_reg]mse = []for x in X:    estimator = RandomForestRegressor(random_state=0,n_estimators=100)#实例化    scores = cross_val_score(estimator,x,y_full,scoring='neg_mean_squared_error',cv=5).mean()    mse.append(scores * -1)
mse # mse越小越好---[21.62860460743544, 47.342475892156855, 44.379830132149095, 15.169715537895552]
[*zip(['X_full','X_missing_mean','X_missing_0','X_missing_reg'],mse)]---[('X_full', 21.62860460743544), ('X_missing_mean', 47.342475892156855), ('X_missing_0', 44.379830132149095), ('X_missing_reg', 15.169715537895552)]
复制代码


用所得结果画出条形图


x_labels = ['Full data','Mean Imputation','Zero Imputation','Regressor Imputation']colors = ['r','g','b','orange']
plt.figure(figsize=(12,6))# 画出画布,并指定使用改画布ax = plt.subplot(111)# 添加子图# 111第一个1指第一行,第二个1指第一列,第三个1指该子图的第一个图
for i in np.arange(len(mse)): ax.barh(i,mse[i],color=colors[i],alpha=0.6,align='center') # 因为都是ax.所以都画在subplot(111),也就在同一个图里
ax.set_title('Imputation Techniques with Boston Data')ax.set_xlim(left=np.min(mse)*0.9,right=np.max(mse)*1.1)# 不从0显示x轴,显示最小值的0.9倍到最大值的1.1倍ax.set_yticks(np.arange(len(mse)))# y轴显示序号ax.set_yticklabels(x_labels)ax.invert_yaxis()# 翻转y轴正方向,有没有没啥关系plt.show()
复制代码


视频作者:菜菜TsaiTsai链接:【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili


发布于: 刚刚阅读数: 4
用户头像

还未添加个人签名 2022-09-14 加入

还未添加个人简介

评论

发布
暂无评论
随机森林-用随机森林回归填补缺失值_Python_烧灯续昼2002_InfoQ写作社区