视频作者:[菜菜TsaiTsai] 链接:[【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili]
我们现实收集到的数据往往是有缺失值的,我们可以选择含有缺失值的数据,有时候填补缺失值会比直接丢弃样本效果更好,即便我们其实并不知道缺失值的真实样貌。 在sklearn中,我们可以使用sklearn.impute.SimpleImputer来将均值、中值、众数或者其他常用的数值填补到缺失数据中 在这个案例,我们将使用均值、0、随机森林回归来填补缺失值,并验证四种状况下的拟合情况,找出对使用的数据集来说最佳的缺失值填补方法
SimpleImputer(
['missing_values=nan', "strategy='mean'", 'fill_value=None', 'copy=True'],
)
# 后面章节会更详细的讲
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestRegressor
from 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.target
n_samples = X_full.shape[0]
n_features = X_full.shape[1]
为完整的数据放入缺失值 首先我们要确定希望放入缺失数据的比例,在这里我们假设是50%
rng = np.random.RandomState(0)
missing_rate = 0.5
n_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,上限自己指定,依旧是$[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 12
0 0.00632 18.0 2.31 NaN NaN NaN 65.2 NaN NaN 296.0 15.3 396.9 NaN
1 0.02731 NaN 7.07 NaN 0.469 NaN 78.9 4.9671 2.0 242.0 17.8 NaN 9.14
2 NaN NaN NaN NaN 0.469 7.185 61.1 4.9671 NaN 242.0 NaN NaN 4.03
3 NaN NaN NaN NaN NaN NaN 45.8 6.0622 NaN 222.0 18.7 NaN NaN
4 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]
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 12
0 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.1476
1 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.1400
2 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.0300
3 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.2614
4 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()