使用随机森林填补缺失值

导包

1
2
3
4
5
6
7
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

以波士顿数据集为例,导入完整的数据集并探索

1
2
datasets = load_boston()  # 回归数据集
datasets.data.shape, datasets.target.shape # ((506, 13), (506,))
1
2
3
X_full, y_full = datasets.data, datasets.target
n_samples = X_full.shape[0]
n_features = X_full.shape[1]

为完整数据集放入缺失值

1
2
3
4
# 首先确定放入缺失数据的比例
rng = np.random.RandomState(0)
missing_rate = 0.5
n_missing_samples = int(np.floor(n_samples * n_features * missing_rate)) # np.floor() 向下取整,返回.0格式的浮点数
1
2
3
# 确定要填补的行索引 和 列索引
missing_features = rng.randint(0, n_features, n_missing_samples)
missing_samples = rng.randint(0, n_samples, n_missing_samples)
1
2
3
4
5
6
X_missing = X_full.copy()
y_missing = y_full.copy()

X_missing[missing_samples, missing_features] = np.nan

X_missing = pd.DataFrame(X_missing)
1
X_missing.head(10)

1.png

使用0和均值填补缺失值

1
2
3
# 均值填充
imp_mean = SimpleImputer(missing_values=np.nan, strategy="mean")
X_missing_mean = imp_mean.fit_transform(X_missing)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看是否有缺失值
pd.DataFrame(X_missing_mean).isnull().sum()

# output
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
dtype: int64
1
2
3
# 0填充
imp_0 = SimpleImputer(missing_values=np.nan, strategy="constant", fill_value=0)
X_missing_0 = imp_0.fit_transform(X_missing)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看是否有缺失值
pd.DataFrame(X_missing_0).isnull().sum()

# output
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
dtype: int64

使用随机森林填补缺失值

任何回归都是从特征矩阵中学习,然后求解连续型标签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来进行填补了,而我们已经使用回归为其他特征填补了大量有效信息,可以用来填补缺失最多的特征。
遍历所有的特征后,数据就完整,不再有缺失值了。

1
2
3
4
5
6
X_missing_rtf = X_missing.copy()
sortindex = np.argsort(X_missing_rtf.isnull().sum(axis=0)).values
sortindex # 依次从缺失最少的开始填补

# output
# array([ 6, 12, 8, 7, 9, 0, 2, 1, 5, 4, 3, 10, 11], dtype=int64)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
for i in sortindex:

# 构建新特征矩阵和新标签
df = X_missing_rtf
y_new = df.iloc[:, i]
X_new = pd.concat([df.iloc[:, df.columns != i], pd.DataFrame(y_full)], axis=1)

#在新特征矩阵中,对含有缺失值的列,进行0的填补
X_new = SimpleImputer(missing_values=np.nan, strategy="constant", fill_value=0).fit_transform(X_new)

# 找出训练集和测试集
y_train = y_new[y_new.notnull()]
y_test = y_new[y_new.isnull()]
x_train = X_new[y_train.index, :]
x_test = X_new[y_test.index, :]

# 用随机森林回归来填补缺失值
rtf = RandomForestRegressor(n_estimators=100)
rtf = rtf.fit(x_train, y_train)
y_predict = rtf.predict(x_test)

# 将填补好的特征返回到原始矩阵
X_missing_rtf.loc[X_missing_rtf.iloc[:,i].isnull(), i] = y_predict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看是否有缺失值
X_missing_rtf.isnull().sum()

# output
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
dtype: int64

对填补好的数据进行建模

1
2
3
4
5
6
7
8
X = [X_full, X_missing_mean, X_missing_0, X_missing_rtf]

mses = []

for x in X:
rtf = RandomForestRegressor(n_estimators=100, random_state=0)
scores = cross_val_score(rtf, x, y_full, scoring="neg_mean_squared_error", cv=5).mean()
mses.append(scores * -1)
1
2
3
4
5
6
7
[*zip(["X_full", "X_missing_mean", "X_missing_0", "X_missing_rtf"], mses)]  # mse越小越好

# output
[('X_full', 21.62860460743544),
('X_missing_mean', 40.84405476955929),
('X_missing_0', 49.50657028893417),
('X_missing_rtf', 19.714743811861762)]

用所得结果画出条形图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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, mse in enumerate(mses):
ax.barh(i, mse, color=colors[i], alpha=0.6, align='center')

ax.set_title('imputation techniques with Boston Data')
ax.set_xlim(left=np.min(mses) * 0.9, right=np.max(mses) * 1.1)
ax.set_yticks(np.arange(len(mses)))
ax.set_xlabel('MSE')
ax.invert_yaxis()
ax.set_yticklabels(x_labels)
plt.show()

output_23_0.png