在清洗数据的阶段,我们面对缺失值有三种方式
1:直接将少量具有缺失值的样本删除。
2:将大量缺失值的特征删除。
3:中等含有缺失值的特征进行填补。
在scikit-learn中,有一个专门填充的类,专门将均值,中值,或其他数值填充,比如还有0值填充,随机森林回归填充,来分别验证拟合状况,来找出此数据集最佳的缺失值填补方式。
假如我们还是使用回归数据来做测试,使用boston房价数据。
第一步:加载数据
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import SimpleImputer # 用来填补缺失值
from sklearn.model_selection import cross_val_score
# ====1:加载数据
boston = load_boston()
这里不展开介绍了,简单说是506个样本,13个特征。
第二步:人为地构造一些缺失值,
# ====2:人为地,随机地,让一些数据缺失掉
x_src = boston.data # 原始特征矩阵数据
y_src = boston.target # 原始目标数据
n_samples = x_src.shape[0] # 样本个数
n_features = x_src.shape[1] # 特征个数
rnd = np.random.RandomState(1) # 定义一个随机数种子
n_samples_missing = int(n_samples * n_features * 0.3) # 大概有30%的数据将要被我们擦除掉。计算出要擦除的个数
print('we will drop {} values.'.format(n_samples_missing))
missing_samples_list = rnd.randint(0, n_samples, n_samples_missing) # 从0 ~ n_samples 随机采样出n_samples_missing个整数
missing_features_list = rnd.randint(0, n_features, n_samples_missing) # 从0 ~ n_features 随机采样出n_samples_missing个整数
x_src[missing_samples_list, missing_features_list] = np.nan # 现在我们就把一些随机的位置给抹去了,俩列表的长度必须是一致的
x_missing = pd.DataFrame(x_src)
print(x_missing.head()) # 至此,我们人为丢弃一些数据,造成缺失数据就完成了。
我们把特征矩阵的大概30%的数据置为空值,来模拟我们的缺失值。大约有1973个值要被置为空。
随机抽出这些值,使用随机数产生器来帮我寻找。
第三步:用不同的策略来生生填充后的特征矩阵,这里用到了SimpleImputer这个类
1:使用特征均值来填充
# 填充方法一:使用均值填充全部的缺失值
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean') # 实例化一个填充均值的处理器
x_missing_mean = imp_mean.fit_transform(x_missing) # 使用fit_transform来实现填充
print('fill mean.....')
print(pd.DataFrame(x_missing_mean).head())
2:使用特征的中值填充
# 填充方法二:使用中值填充全部的缺失值
imp_median = SimpleImputer(missing_values=np.nan, strategy='median') # 实例化一个填充中值的处理器
x_missing_median = imp_median.fit_transform(x_missing) # 使用fit_transform来实现填充
print('fill median.....')
print(pd.DataFrame(x_missing_median).head())
3:使用常数0来填充
# 填充方法三:使用0填充全部的缺失值
imp_zero = SimpleImputer(missing_values=np.nan, strategy='constant', fill_value=0) # 实例化一个填充0的处理器
x_missing_zero = imp_zero.fit_transform(x_missing) # 使用fit_transform来实现填充
print('fill zero.....')
print(pd.DataFrame(x_missing_zero).head())
4:使用模型学习出缺失值
这块也是我们今天学习的关键,如何学习出这么多缺失值呢?
我们想一想贝叶斯的思想,先验概率和后验概率,也就是特征和目标之间是可以互换的,目标也能当做是特征,特征也可以当做是目标。举个例子:我们可以用“地区”,“交通”,“学区数量”来预测“房价”,反过来我们也可以用“房价”,“交通”,“学区数量”来预测“地区”啊。
具体就是,数据集X有N个特征,当我们只有一个特征F有缺失的时候,我们就把目标Y和F进行互换,形成新的特征矩阵,F就是新的目标,Y此时就是作为特征了。
新目标F中不缺失的数据,就是y_train。
y_train对应的特征矩阵,就是x_train。
新目标F中缺失的数据,就是y_test。
y_ test对应的特征矩阵,就是x_test。
我们可以根据x_train,y_train,训练出一个回归/分类模型,再对x_test进行predict预测,得到的结果y_predict就是我们学习到的y_test,替换特征F一整列中缺失的值即可。
那如果存在多列有缺失值的?不着急,一个个慢慢来,我们按照缺失值的数量多少从少到多排个序,每一个特征都进行这样的互换、学习、替换的过程。
从少到多是因为,缺失值越少,信息熵越低,越容易处理,准确度也是越高。
需要注意的是,每次学习某个单个特征之前呢,先把其他特征临时赋值,比如是常数值,平均值,中值等,保证只有唯一一个特征是含有缺失值。
# 填充方法四:使用随机森林回归,用学习的方式来填充,逐步分特征来填充。
x_missing_reg = x_missing
print('before learning.....')
print(x_missing_reg.head())
print(x_missing_reg.isnull().sum(axis=0))
# 计算各个特征的缺失值的个数,并且按照从少到多的顺序来一个个学习。从少到多,所需要的信息量也是从简单到难,一步步迭代式。
# argsort 返回从小大值的下标,这才是我们需要的咧。
sorted_indexes = np.argsort(x_missing_reg.isnull().sum(axis=0)).values # True是1,FALSE是0
print('sorted_indexes:', sorted_indexes)
for i in sorted_indexes:
# 构建新的特征矩阵和新的目标,也就是将第i个特征作为新的目标,原来的目标作为特征。
feature_i = x_missing_reg.iloc[:, i]
tmp_df = pd.concat([x_missing_reg.iloc[:, x_missing_reg.columns != i], pd.DataFrame(y_src)], axis=1)
# 在新组装的特征矩阵里面,将所有的的缺失值进行填充平均值 处理
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean') # 实例化一个填充均值的处理器
tmp_df_mean = imp_mean.fit_transform(tmp_df) # 使用fit_transform来实现填充,返回numpy.ndarray类型
# 找出我们的训练集和测试集
y_train = feature_i[feature_i.notnull()] # 新的目标中,非空的作为y_train
y_test = feature_i[feature_i.isnull()] # 新的目标中,为空的作为y_test,本身不需要,而是需要知道其所在的索引。
x_train = tmp_df_mean[y_train.index, :] # .index可以获得下标号,新的目标非空值对应的特征
x_test = tmp_df_mean[y_test.index, :] # 新的目标空值对应的特征
# 建立随机森林回归树进行训练
rfc = RandomForestRegressor(n_estimators=100)
rfc = rfc.fit(x_train, y_train)
y_predict = rfc.predict(x_test)
# 将学习到了Y赋值给原始数据。
x_missing_reg.loc[x_missing_reg.iloc[:, i].isnull(), i] = y_predict
print('after learning.....')
print(x_missing_reg.head())
print(x_missing_reg.isnull().sum(axis=0)) # 计算每个特征下还是 nan 的数量
第四步:对多种数据集进行多次交叉验证测试打分,比较优劣。
# ====4:进数据准备就绪,来分别看看效果。不同的填充的数据集在相同的随机森林回归下,预测的效果。
datas = [x_src, x_missing, x_missing_mean, x_missing_median, x_missing_zero, x_missing_reg]
label = ['x_src', 'no any fill', 'x_missing_mean', 'x_missing_median', 'x_missing_zero', 'x_missing_reg']
res_cmp = []
for x in datas: # 遍历每个已经填充了的数据集
scores_rfc = []
for c in range(1, 11): # 对于每一填充数据集,重复执行10次交叉验证,看看效果。
rfc = RandomForestRegressor(criterion='mse', n_estimators=10)
score_tfc = cross_val_score(rfc, x, y_src, cv=10, scoring='neg_mean_squared_error').mean()
scores_rfc.append(score_tfc)
res_cmp.append(scores_rfc)
# 画图画出来展示下
print(res_cmp)
for i in range(len(label)):
plt.plot(range(1, 11), res_cmp[i], label=label[i])
plt.legend() # 显示图例
plt.show()
结果如下:
结论是:
说明采取不同的缺失值填充措施效果是有差距的,采取学习后的填充值,是最接近于原始数据的,效果极好,其他地“一刀切”式的填充会带来不怎么好的结果。
奇怪的是,没有进行任何缺失值填充的数据集也会表现的很好,跟原始数据还有学习填充的数据不分上下,看来随机森林内部应该是有一定的很好的缺失值填充处理策略了。
最后,之所以要花大力气学习,这里还有一些常用的数据操作的方法,也算是学习python了。