前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >特征工程-使用随机森林填补缺失值

特征工程-使用随机森林填补缺失值

作者头像
ZackSock
发布2021-12-30 16:08:03
1.4K1
发布2021-12-30 16:08:03
举报
文章被收录于专栏:ZackSockZackSock

一、前言

特征工程在传统的机器学习中是非常重要的一个步骤,我们对机器学习算法的优化通常是有限的。如果在完成任务时发现不管怎么优化算法得到的结果都不满意,这个时候就可以考虑回头在做一下特征工程。

二、缺失值填补

在特征工程中,对缺失值的处理是很常见的一个问题。处理方法通常如下:

  1. 删除有缺省值的数据
  2. 使用数据中该特征的均值填充缺失值
  3. 使用数据中该特征的中位数填充缺失值
  4. 使用数据中该特征的众数填充缺失值
  5. 使用机器学习模型对缺失值进行填充

上面的方法各有优点,我们可以根据自己的需求来选择策略。在数据集比较大时,最后一种方式是综合表现比较好的。今天我们就来讲讲使用随机森林来进行缺失值的填补。

三、数据预处理

3.1、处理思路

在我们开始填充数据前,我们还需要对原本的数据进行一些简单的处理。假如我们现在要对下面的数据进行填充:

name

sex

age

target

zack

male

20

1

rudy

male

30

1

alice

female

20

0

atom

male

31

0

alex

female

32

1

kerry

female

0

king

20

1

nyx

male

20

1

petty

female

0

在使用scikit-learn创建随机森林时,不允许我们训练数据的特征值为字符串,因此我们要对name、gender、city这几列进行处理,这里采取one-hot编码的策略。

注意:上面是我捏造的一些数据,至于target是什么含义我也不知道。

首先name特征在很多情况下都不会影响最后的结果,因此我们直接选择删除name特征。然后是gender和city特征,他们都是类型特征,对于gender我们可以用0代表male、用1代表female。而city是多分类的特征,我们也可以采取和gender一样的方法,0代表city_01、1代表city_02、2代表city_03。不过这样会导致city特征权重不一样,如果类别太多对结果会有很大影响。

这个时候我们就可以换一个策略,我们可以把原本的city特征拆分成三个特征,分别是city=city_01、city=city_02、city=city_03,然后特征值只有0或1,这样就可以解决上面的问题了。

比如我们原始数据如下:

name

gender

age

city

target

zack

male

21

city_01

1

alice

female

22

city_02

0

进行转换后数据如下(忽略name特征):

name

gender=male

gender=female

age

city=city_01

city=city_02

city=city_03

target

zack

1

0

21

1

0

0

1

alice

0

1

22

0

1

0

0

在上面我们还有个city=city_03特征,这是因为我们要考虑整个数据集进行拆分。

这里还需要注意一点,就是gender特征可以不这样拆分,这里为了方便就不另外对gender用另外的策略了。

3.2、代码实现

根据上面的思路,我们知道了如何处理多分类的特征。而对于数字特征,我们不需要进行额外处理,因此我们需要遍历特征的列,然后判断是否是我们要处理的列。具体代码如下:

代码语言:javascript
复制
import numpy as np
import pandas as pd
from sklearn.feature_extraction import DictVectorizer

# 创建DictVectorizer
dv = DictVectorizer(sparse=False)
# 读取数据
df = pd.read_csv("test.csv")
# 删除name列
df = df.drop(['name'], axis=1)
# 裁剪出特征值
X = df.iloc[:, 0:-1]

# 遍历特征值的列
for colum in X.iteritems():
    # 对非数值型列进行处理(多类别数据)
    if colum[1].dtype == np.object_:
        # 拆分出列名和数据
        feature_name, data = colum
        
        # ①、将该列转换成字典
        colum = data.map(lambda x: {feature_name: x})
        colum = dv.fit_transform(colum)
        # 多分类特征名转换后的特征名,如gender->[gender=male, gender=female]
        features = dv.get_feature_names_out()
        # 将新创建的列添加进去
        X[features] = colum
        # 删除当前列
        X = X.drop([feature_name], axis=1)
        # ②、如果原先值是空,则吧所以新添加的列设置为nan
        if list(features).__contains__(feature_name):
            features = list(features)
            features.remove(feature_name)
            features = np.array(features)
        
        # 对于特征值是null的数据,转换后的各个特征也应为null
        # 如:gender为null,那gender=male为null,gender=female为null
        mask = X[features].sum(axis=1) == 0
        X.loc[mask, features] = np.nan

对于大部分代码,相信读者都能理解。这里来解释下代码中①、②两个部分。

3.3、代码解析

(1)问题①

在①处我们将当前列的数据转换成了字典,然后再调用DictVectorizer对象的fit_transform方法,我们直接看DictVectorizer的作用。来看下面这段代码:

代码语言:javascript
复制
from sklearn.feature_extraction import DictVectorizer
# 待处理字典列表
data = [
    {"gender": "male"},
    {"gender": "female"},
    {"gender": "unknow"},
    {"gender": "male"},
    {"gender": "male"}
]
dv = DictVectorizer(sparse=False)
# 转换数据
data = dv.fit_transform(data)
print(dv.get_feature_names_out())
print(data)

上面代码输出如下:

代码语言:javascript
复制
['gender=female' 'gender=male' 'gender=unknow']
[[0. 1. 0.]
 [1. 0. 0.]
 [0. 0. 1.]
 [0. 1. 0.]
 [0. 1. 0.]]

可以看到,这个和我们上面思路提到的转换是一样的。因为dv接收的是字典序列,因此我们需要先使用下面代码:

代码语言:javascript
复制
colum = data.map(lambda x: {feature_name: x})

这样就可以将当前列转换成字典序列类型。然后调用dv.fit_transform就可以实现转换。

(2)问题②

这部分代码是为了让原本gender为nan的数据转换后gender=female和gender=male也应为nan。但是对于存在缺失值的数据,转换过程中会出现下面的问题:

代码语言:javascript
复制
from sklearn.feature_extraction import DictVectorizer
data = [
    {"gender": "male"},
    {"gender": "female"},
    {"gender": "unknow"},
    {"gender": "male"},
    {"gender": None}
]
dv = DictVectorizer(sparse=False)
data = dv.fit_transform(data)
print(dv.get_feature_names_out())
print(data)

上面我们添加了一个带有缺失值的数据,输出结果如下:

代码语言:javascript
复制
['gender' 'gender=female' 'gender=male' 'gender=unknow']
[[ 0.  0.  1.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0.  0.  1.]
 [ 0.  0.  1.  0.]
 [nan  0.  0.  0.]]

可以发现,我们原本只期望有三列,但是却出现了四列。因此我们需要将dv.get_feature_names_out()中的多余列删除。

到此,我们的数据就处理完了。下面我们可以使用随机森林来填补缺失值。

四、使用随机森林填补缺失值

4.1、实现思路

填补缺失值的过程就是不断建立模型预测的过程。我们还是来看一组简单的数据:

height

weight

age

181

70

20

178

18

160

50

170

60

19

上面的数据有两个特征存在缺失值,我们都需要进行填充。当我们要填充weight时,我们可以考虑选取weight不为空的数据。然后将其余列作为特征值,而weight作为目标值。这样我们就可以训练出一个可以预测weight的模型。

但是上面的方法有个问题,就是我们选取的是weight不为空的数据,但是这些数据的其它特征可能为空。这个时候我们就可以考虑用其它简单方法先对其余缺失值进行填充,然后训练模型填充weight的缺失值。

在填补weight的缺失值后,再用同样的方法来填补其余有缺失值的特征。

为了效果好,我们会优先选择填补缺失值数量少的列,因为这样我们就可以拿到较多的数据,可以更好地填充该列数据。然后依次类推。

4.2、代码实现

这部分是在实现上面对多分类的处理后进行的,完整代码如下:

代码语言:javascript
复制
y = df.iloc[:, [-1]]

# 按照当前列缺失值的数量进行升序排列
sortindex = np.argsort(X.isnull().sum(axis=0)).values
for i in sortindex:
    # 将当前列作为目标值
    feature_i = X.iloc[:, i]
    
    # 将其余列作为特征值(包括目标值)
    tmp_df = pd.concat([X.iloc[:, X.columns != i], y], axis=1)
    # 使用众数填充其余列缺失值
    imp_mf = SimpleImputer(missing_values=np.nan, strategy='most_frequent')
    tmp_df_mf = imp_mf.fit_transform(tmp_df)

    # 将feature_i中非空的样本作为训练数据
    y_notnull = feature_i[feature_i.notnull()]
    y_null = feature_i[feature_i.isnull()]   
    X_notnull = tmp_df_mf[y_notnull.index, :]
    X_null = tmp_df_mf[y_null.index, :] 
    
    # 如果没有缺失值则填充下一列
    if y_null.shape[0] == 0:
        continue

    # 建立随机森林回归树进行训练
    rfc = RandomForestRegressor(n_estimators=100)
    rfc = rfc.fit(X_notnull, y_notnull)
    
    # 对缺失值进行预测
    y_predict = rfc.predict(X_null)

    # 填充缺失值
    X.loc[X.iloc[:, i].isnull(), X.columns[i]] = y_predict

这样我们就实现了随机森林填充缺失值的操作。完整代码如下:

代码语言:javascript
复制
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.feature_extraction import DictVectorizer
from sklearn.impute import SimpleImputer

dv = DictVectorizer(sparse=False)
df = pd.read_csv("test.csv")
name = df['name']
df = df.drop(['name'], axis=1)
X = df.iloc[:, 0:-1]

# 遍历数据的列
for colum in X.iteritems():
    # 对非数值型列进行处理
    if colum[1].dtype == np.object_:
        # 拆分出列名和数据
        feature_name, data = colum

        # 将该列转换成字典
        colum = data.map(lambda x: {feature_name: x})
        colum = dv.fit_transform(colum)
        features = dv.get_feature_names_out()

        # 将新创建的列添加进去
        X[features] = colum

        # 删除当前列
        X = X.drop([feature_name], axis=1)

        # 如果原先值是空,则吧所以新添加的列设置为nan
        if list(features).__contains__(feature_name):
            features = list(features)
            features.remove(feature_name)
            features = np.array(features)

        mask = X[features].sum(axis=1) == 0
        X.loc[mask, features] = np.nan

y = df.iloc[:, [-1]]

# 按照当前列缺失值的数量进行升序排列
sortindex = np.argsort(X.isnull().sum(axis=0)).values
for i in sortindex:
    # 将当前列作为目标值
    feature_i = X.iloc[:, i]

    # 将其余列作为特征值(包括目标值)
    tmp_df = pd.concat([X.iloc[:, X.columns != i], y], axis=1)
    # 使用众数填充其余列缺失值
    imp_mf = SimpleImputer(missing_values=np.nan, strategy='most_frequent')  
    tmp_df_mf = imp_mf.fit_transform(tmp_df)

    # 将feature_i中非空的样本作为训练数据
    y_notnull = feature_i[feature_i.notnull()]  
    y_null = feature_i[feature_i.isnull()]   
    X_notnull = tmp_df_mf[y_notnull.index, :]  
    X_null = tmp_df_mf[y_null.index, :] 

    # 如果没有缺失值则下一列
    if y_null.shape[0] == 0:
        continue

    # 建立随机森林回归树进行训练
    rfc = RandomForestRegressor(n_estimators=100)
    rfc = rfc.fit(X_notnull, y_notnull)

    # 对缺失值进行预测
    y_predict = rfc.predict(X_null)

    # 填充缺失值
    X.loc[X.iloc[:, i].isnull(), X.columns[i]] = y_predict

今天的内容就是这些

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-12-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 新建文件夹X 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、缺失值填补
  • 三、数据预处理
    • 3.1、处理思路
      • 3.2、代码实现
        • 3.3、代码解析
          • (1)问题①
          • (2)问题②
      • 四、使用随机森林填补缺失值
        • 4.1、实现思路
          • 4.2、代码实现
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档