使用随机森林回归来填充数据集中的缺失值
数据的准备
原始数据
- 我们使用的原始数据集如下所示。
- 以下数据集是
SicKit Learn
中,波士顿房价数据的钱 10 列,可以用如下的代码获取到:
1 | from sklearn.datasets import load_boston |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0.000000 | 0.18 | 0.067815 | 0.0 | 0.314815 | 0.577505 | 0.641607 | 0.269203 | 0.000000 | 0.208015 |
1 | 0.000236 | 0.00 | 0.242302 | 0.0 | 0.172840 | 0.547998 | 0.782698 | 0.348962 | 0.043478 | 0.104962 |
2 | 0.000236 | 0.00 | 0.242302 | 0.0 | 0.172840 | 0.694386 | 0.599382 | 0.348962 | 0.043478 | 0.104962 |
3 | 0.000293 | 0.00 | 0.063050 | 0.0 | 0.150206 | 0.658555 | 0.441813 | 0.448545 | 0.086957 | 0.066794 |
4 | 0.000705 | 0.00 | 0.063050 | 0.0 | 0.150206 | 0.687105 | 0.528321 | 0.448545 | 0.086957 | 0.066794 |
… | … | … | … | … | … | … | … | … | … | … |
501 | 0.000633 | 0.00 | 0.420455 | 0.0 | 0.386831 | 0.580954 | 0.681771 | 0.122671 | 0.000000 | 0.164122 |
502 | 0.000438 | 0.00 | 0.420455 | 0.0 | 0.386831 | 0.490324 | 0.760041 | 0.105293 | 0.000000 | 0.164122 |
503 | 0.000612 | 0.00 | 0.420455 | 0.0 | 0.386831 | 0.654340 | 0.907312 | 0.094381 | 0.000000 | 0.164122 |
504 | 0.001161 | 0.00 | 0.420455 | 0.0 | 0.386831 | 0.619467 | 0.889804 | 0.114514 | 0.000000 | 0.164122 |
505 | 0.000462 | 0.00 | 0.420455 | 0.0 | 0.386831 | 0.473079 | 0.802266 | 0.125072 | 0.000000 | 0.164122 |
造一些假数据进去
上面的表格中,共有5060
个数据,而且,表格中的数据是完整的。
1 | TenOfX.isnull().sum() |
我们假设这组数据中,有 50%的数据是缺失的,也就是有2530
个数据是:numpy.NaN
。
所有数据要随机遍布在数据集的各行各列当中,而一个缺失的数据会需要一个行索引和一个列索引,如果能够创造一个数组,包含个2530
分布在0~506
中间的行索引,和2530
个分布在0~13
之间的列索引,那我们就可以利用索引来为数据中的任意2530
个位置赋空值。
确定缺失值总数(n_missing_samples
)
1 | n_samples = TenOfX.shape[0] |
2530 个缺失的特征编号序列
1 | missing_features = rng.randint(0,n_features,n_missing_samples) |
numpy.random.randint(low, high=None, size=None, dtype='l')
- low : int 产生随机数的最小值
- high : int, optional 给随机数设置个上限,即产生的随机数必须小于 high
- size : int or tuple of ints, optional 输出的大小,可以是整数,或者元组
2530 个缺失的样本编号序列
1 | missing_samples = rng.randint(0,n_samples,n_missing_samples) |
填充空值
1 | for i in range(0,n_missing_samples): |
填充缺失值后的数据表格
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | NaN | 0.18 | 0.067815 | 0.0 | 0.314815 | NaN | 0.641607 | NaN | 0.000000 | NaN |
1 | 0.000236 | 0.00 | 0.242302 | 0.0 | 0.172840 | NaN | NaN | 0.348962 | NaN | NaN |
2 | NaN | 0.00 | 0.242302 | NaN | NaN | NaN | NaN | NaN | NaN | 0.104962 |
3 | NaN | 0.00 | NaN | 0.0 | NaN | NaN | 0.441813 | NaN | 0.086957 | NaN |
4 | 0.000705 | NaN | 0.063050 | NaN | NaN | 0.687105 | 0.528321 | 0.448545 | NaN | 0.066794 |
… | … | … | … | … | … | … | … | … | … | … |
501 | 0.000633 | NaN | 0.420455 | NaN | 0.386831 | NaN | 0.681771 | 0.122671 | NaN | 0.164122 |
502 | NaN | 0.00 | 0.420455 | 0.0 | 0.386831 | 0.490324 | 0.760041 | 0.105293 | 0.000000 | 0.164122 |
503 | 0.000612 | NaN | NaN | NaN | 0.386831 | 0.654340 | 0.907312 | NaN | 0.000000 | NaN |
504 | 0.001161 | NaN | NaN | 0.0 | 0.386831 | 0.619467 | 0.889804 | NaN | NaN | 0.164122 |
505 | 0.000462 | 0.00 | 0.420455 | 0.0 | NaN | 0.473079 | NaN | NaN | 0.000000 | 0.164122 |
缺失值数量
1 | Ten_missing.isnull().sum() |
为啥不是 2530 个缺失值啊? 因为,我们的样本数量特征数量都比较少,而且我们要缺失
50%
的数据,所以很容易随机到样本编号和特征编号都一样的数据,重复设置为空了.
使用0
填充缺失值
因为我们要做好几次实验,所以,为了最后评价的公平,我们把这些缺失值复制一下,不要直接在上面修改。
1 | Ten_missing_0 = Ten_missing.copy() |
使用如下的代码填充 0.
1 | imp_0 = SimpleImputer(missing_values=np.nan, strategy="constant",fill_value=0) |
得到如下的结果。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0.000000 | 0.18 | 0.067815 | 0.0 | 0.314815 | 0.000000 | 0.641607 | 0.000000 | 0.000000 | 0.000000 |
1 | 0.000236 | 0.00 | 0.242302 | 0.0 | 0.172840 | 0.000000 | 0.000000 | 0.348962 | 0.000000 | 0.000000 |
2 | 0.000000 | 0.00 | 0.242302 | 0.0 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.104962 |
3 | 0.000000 | 0.00 | 0.000000 | 0.0 | 0.000000 | 0.000000 | 0.441813 | 0.000000 | 0.086957 | 0.000000 |
4 | 0.000705 | 0.00 | 0.063050 | 0.0 | 0.000000 | 0.687105 | 0.528321 | 0.448545 | 0.000000 | 0.066794 |
… | … | … | … | … | … | … | … | … | … | … |
501 | 0.000633 | 0.00 | 0.420455 | 0.0 | 0.386831 | 0.000000 | 0.681771 | 0.122671 | 0.000000 | 0.164122 |
502 | 0.000000 | 0.00 | 0.420455 | 0.0 | 0.386831 | 0.490324 | 0.760041 | 0.105293 | 0.000000 | 0.164122 |
503 | 0.000612 | 0.00 | 0.000000 | 0.0 | 0.386831 | 0.654340 | 0.907312 | 0.000000 | 0.000000 | 0.000000 |
504 | 0.001161 | 0.00 | 0.000000 | 0.0 | 0.386831 | 0.619467 | 0.889804 | 0.000000 | 0.000000 | 0.164122 |
505 | 0.000462 | 0.00 | 0.420455 | 0.0 | 0.000000 | 0.473079 | 0.000000 | 0.000000 | 0.000000 | 0.164122 |
我们要评估这个结果和原来的结果相差有多少。所以,我们定义一个函数,来计算。
我打算使用均方误差来进行评估,使用的公式如下:
$$
Error = \frac{1}{N} \sum_{i} \sum_{j} [TenOfMissing_{ij} - TenOfX_{ij}]^2 \\
i \in missing\_features \\
j \in missing\_samples \\
N = missing\_samples.shape[0]= 2530
$$
具体函数如下:
1 | def evaluator(missFixed, oringe, n_samples): |
结果为:
0.15378726137338652
使用均值来填充
我们新复制一份数据,然后使用如下代码来用均值填充。
1 | #使用Mean进行填补 |
得到了如下的数据:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0.039600 | 0.180000 | 0.067815 | 0.000000 | 0.314815 | 0.527091 | 0.641607 | 0.243633 | 0.000000 | 0.426493 |
1 | 0.000236 | 0.000000 | 0.242302 | 0.000000 | 0.172840 | 0.527091 | 0.685188 | 0.348962 | 0.357701 | 0.426493 |
2 | 0.039600 | 0.000000 | 0.242302 | 0.087413 | 0.353054 | 0.527091 | 0.685188 | 0.243633 | 0.357701 | 0.104962 |
3 | 0.039600 | 0.000000 | 0.391448 | 0.000000 | 0.353054 | 0.527091 | 0.441813 | 0.243633 | 0.086957 | 0.426493 |
4 | 0.000705 | 0.112819 | 0.063050 | 0.087413 | 0.353054 | 0.687105 | 0.528321 | 0.448545 | 0.357701 | 0.066794 |
… | … | … | … | … | … | … | … | … | … | … |
501 | 0.000633 | 0.112819 | 0.420455 | 0.087413 | 0.386831 | 0.527091 | 0.681771 | 0.122671 | 0.357701 | 0.164122 |
502 | 0.039600 | 0.000000 | 0.420455 | 0.000000 | 0.386831 | 0.490324 | 0.760041 | 0.105293 | 0.000000 | 0.164122 |
503 | 0.000612 | 0.112819 | 0.391448 | 0.087413 | 0.386831 | 0.654340 | 0.907312 | 0.243633 | 0.000000 | 0.426493 |
504 | 0.001161 | 0.112819 | 0.391448 | 0.000000 | 0.386831 | 0.619467 | 0.889804 | 0.243633 | 0.357701 | 0.164122 |
505 | 0.000462 | 0.000000 | 0.420455 | 0.000000 | 0.353054 | 0.473079 | 0.685188 | 0.243633 | 0.000000 | 0.164122 |
然后我们使用评估器,来对这份数据进行评估,得到的值为:
0.04693971768814396
使用随机森林来填充
复制新的数据
1 | TenRF = Ten_missing.copy() |
构建我们的新特征矩阵和新标签
1 | df = TenRF |
在新特征矩阵中,对含有缺失值的列,进行 0 的填补
1 | trainWithFakeFill = SimpleImputer(missing_values=np.nan, strategy='constant', fill_value=0) \ |
找出我们的训练集和测试集
1 | YTrain = readyToFill[readyToFill.notnull()] |
用随机森林回归来填补缺失值
1 | rfc = RandomForestRegressor(n_estimators=100) |
将填补好的特征返回到我们的原始的特征矩阵中
1 | TenRF.loc[TenRF.iloc[:,i].isnull(),i] = YPredict |
去循环上述过程,最终得到结果:
如果不调整 RFR 的任何参数,得到的值为:
0.017283231313295726
如果将 RFR 的criterion
参数调整为mse
:
0.0169885810532522
注:
如果有需要的话,可以对决策树进行参数优化,以达到最佳。
结论
- 就我们提到三种填充缺失值的方式,随机森林的效果还是蛮好的,在
simpleImputer
的策略可选项中,还有众数和中位可以选择,效果分别如下:
填充 0 | 中位数 | 众数 | 填充均值 | 随机森林回归器 |
---|---|---|---|---|
0.15378726137338652 |
0.05478160362567096 |
0.10875458022229394 |
0.04693971768814396 |
0.0169885810532522 |
- 如果说,我们需要填充的值是一个离散的量,我们可能需要使用随机森林分类器来解决,其具体位置在:
sklearn.ensemble.RandomForestClassifier
.
使用随机森林回归来填充数据集中的缺失值