缺失值处理拉格朗日插值法—随机森林算法填充—sklearn填充(均值/众数/中位数)

Posted 卖山楂啦prss

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了缺失值处理拉格朗日插值法—随机森林算法填充—sklearn填充(均值/众数/中位数)相关的知识,希望对你有一定的参考价值。

文章目录

在进行缺失值填充之前,要先对缺失的变量进行业务上的了解,即变量的含义、获取方式、计算逻辑,以便知道该变量为什么会出现缺失值、缺失值代表什么含义。

缺失值的处理

对于缺失值的处理,从总体上来说分为删除存在缺失值的个案缺失值插补

  • 不处理
  • 删除存在缺失值的样本(或特征)
  • 缺失值插补

这里可以阅读以下《美团机器学习实战》中关于缺失值的说明:

一般主观数据不推荐插补的方法,插补主要是针对客观数据,它的可靠性有保证。

附上常用的插补方法

准备数据

使用泰坦尼克数据集:

import seaborn as sns
# 导出泰坦尼克数据集
df = sns.load_dataset('titanic')
df.shape  # 891, 15)
df.info()


数据字段含义网上有解释

可能会存在样本重复,即有超过一行的样本所显示的所有特征都一样

#去除重复值
df.drop_duplicates(inplace=True)
df.shape

#恢复索引
df.index = range(df.shape[0])

缺失情况

df.isnull().sum()
'''
survived         0
pclass           0
sex              0
age            177
sibsp            0
parch            0
fare             0
embarked         2
class            0
who              0
adult_male       0
deck           688
embark_town      2
alive            0
alone            0
dtype: int64
'''

缺失占比

df.isna().mean()
'''
survived       0.000000
pclass         0.000000
sex            0.000000
age            0.198653
sibsp          0.000000
parch          0.000000
fare           0.000000
embarked       0.002245
class          0.000000
who            0.000000
adult_male     0.000000
deck           0.772166
embark_town    0.002245
alive          0.000000
alone          0.000000
dtype: float64
'''

1 sklearn填充

在sklearn当中,使用 impute.SimpleImputerr 来处理缺失值,参数为

sklearn.impute.SimpleImputer (
missing_values=nan, 
strategy=’mean’, 
fill_value=None, 
verbose=0,
copy=True)
参数含义
missing_values数据中缺失值是什么,默认np.nan
strategy填补缺失值的策略,默认均值
输入“mean”使用均值填补(仅对数值型特征可用)
输入“median”使用中位数填补(仅对数值型特征可用)
输入“most_frequent”使用众数填补(对数值型和字符型特征都可用)
输入“constant”表示请参考参数“fill_value”中的值(对数值型和字符型特征都可用)
fill_value当参数strategy为“constant”的时候可用,可输入字符串或数字表示要填充的值,常用0
copy默认为True,将创建特征矩阵的副本,反之则会将缺失值填补到原本的特征矩阵中。

传统地,

  • 如果是分类型特征,采用众数进行填补。
  • 如果是连续型特征,采用均值进行填补。

还要考虑

  • 均值一般适用于近似正态分布数据,观测值较为均匀散布均值周围;
  • 中位数一般适用于偏态分布或者有离群点数据,中位数是更好地代表数据中心趋势;
  • 众数一般用于类别变量,无大小、先后顺序之分。

在现实工作时,使用最多的是易于理解的均值或者中位数。

(1)使用均值进行填补(连续型特征)

import numpy as np
import pandas as pd

# 去掉标签
X_missing = df.drop(['survived'],axis=1)

# 查看缺失情况
missing = X_missing.isna().sum()
missing = pd.DataFrame(data='特征': missing.index,'缺失值个数':missing.values)
#通过~取反,选取不包含数字0的行
missing = missing[~missing['缺失值个数'].isin([0])]
# 缺失比例
missing['缺失比例'] =  missing['缺失值个数']/X_missing.shape[0]
missing
'''
         特征  缺失值个数  缺失比例
2           age    177   0.198653
6      embarked      2   0.002245
10         deck    688   0.772166
11  embark_town      2   0.002245
'''
# 排序
missing.sort_values(by='缺失比例',ascending=False)

#柱形图可视化
import matplotlib.pyplot as plt
import numpy as np
import pylab as pl
fig = plt.figure(figsize=(18,6))
plt.bar(np.arange(missing.shape[0]), list(missing['缺失比例'].values), align = 'center',color=['red','green','yellow','steelblue'])
plt.title('Histogram of missing value of variables')
plt.xlabel('variables names')
plt.ylabel('missing rate')
# 添加x轴标签,并旋转90度
plt.xticks(np.arange(missing.shape[0]),list(missing['特征']))
pl.xticks(rotation=90)
# 添加数值显示
for x,y in enumerate(list(missing['缺失比例'].values)):
    plt.text(x,y+0.12,':.2%'.format(y),ha='center',rotation=90)    
plt.ylim([0,1.2])
plt.show()

X_missing[missing['特征']].info()
'''
RangeIndex: 891 entries, 0 to 890
Data columns (total 4 columns):
 #   Column       Non-Null Count  Dtype   
---  ------       --------------  -----   
 0   age          714 non-null    float64 
 1   embarked     889 non-null    object  
 2   deck         203 non-null    category
 3   embark_town  889 non-null    object 
'''

X_missing['embarked'].unique() # array(['S', 'C', 'Q', nan], dtype=object)
X_missing['deck'].unique() # [NaN, C, E, G, D, A, B, F]
X_missing['embark_town'].unique() # array(['Southampton', 'Cherbourg', 'Queenstown', nan], dtype=object)

deck 列缺失比例达到77%,这里考虑直接删除该列

X_missing.drop(['deck'],axis=1,inplace=True)
# 把各个类型  分离出来   方便采用不同的填补方法
# 数值型(即数据类型为int、float的列)
X_missing[missing['特征']].select_dtypes(include='number').columns
# 除数据类型为number外其他的列
X_missing[missing['特征']].select_dtypes(exclude='number').columns
# 
X_missing[missing['特征']].select_dtypes(include='object').columns
X_missing[missing['特征']].select_dtypes(include=['int', 'datetime', 'object']).columns

使用均值进行填补,仅对数值型特征可用,这里对age进行填补

#填补年龄
Age = X_missing.loc[:,"age"].values.reshape(-1,1) #sklearn当中特征矩阵必须是二维

from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer() #实例化,默认均值填补
#imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')

imp_mean = imp_mean.fit_transform(Age) #fit_transform一步完成调取结果
X_missing.loc[:,"age"] = imp_mean
X_missing.info()
'''
 #   Column       Non-Null Count  Dtype   
---  ------       --------------  -----   
 0   pclass       891 non-null    int64   
 1   sex          891 non-null    object  
 2   age          891 non-null    float64 
 3   sibsp        891 non-null    int64   
 4   parch        891 non-null    int64   
 5   fare         891 non-null    float64 
 6   embarked     889 non-null    object  
 7   class        891 non-null    category
 8   who          891 non-null    object  
 9   adult_male   891 non-null    bool    
 10  embark_town  889 non-null    object  
 11  alive        891 non-null    object  
 12  alone        891 non-null    bool 
'''

(2)使用中位数、0进行填补(连续型特征)

也可以用中位数填补或用0填补

imp_median = SimpleImputer() #实例化
imp_median = SimpleImputer(strategy="median") #用中位数填补
imp_median = imp_median.fit_transform(Age)

imp_0 = SimpleImputer() #实例化
imp_0 = SimpleImputer(strategy="constant",fill_value=0) #用0填补
imp_0 = imp_0.fit_transform(Age)

最需要注意的一点是SimpleImputer传入的参数至少要是二维,如果将直接索引出的一列特征传入的话,是会发生报错的,所以必须利用reshape()将一维转化为二维。之后的操作就是先实例化、然后训练模型,最后用填充后的数据覆盖之前的数据。


(3)使用众数进行填补(离散型特征)

使用众数填补embarked、embark_town

# 查看缺失情况
missing = X_missing.isna().sum()
missing = pd.DataFrame(data='特征': missing.index,'缺失值个数':missing.values)
#通过~取反,选取不包含数字0的行
missing = missing[~missing['缺失值个数'].isin([0])]
# 缺失比例
missing['缺失比例'] =  missing['缺失值个数']/X_missing.shape[0]
missing
'''
         特征  缺失值个数  缺失比例
6      embarked      2  0.002245
10  embark_town      2  0.002245
'''

X_missing['embarked'].unique() # array(['S', 'C', 'Q', nan], dtype=object)
X_missing['embark_town'].unique() # array(['Southampton', 'Cherbourg', 'Queenstown', nan], dtype=object)
imp_most = SimpleImputer() #实例化
imp_most = SimpleImputer(strategy = "most_frequent")
X_missing[missing['特征']] = imp_most.fit_transform(X_missing[missing['特征']])
X_missing.isnull().sum()
'''
pclass         0
sex            0
age            0
sibsp          0
parch          0
fare           0
embarked       0
class          0
who            0
adult_male     0
embark_town    0
alive          0
alone          0
dtype: int64
'''

SimpleImputer优于fillna()之处在于前者可以一行语句指定填充值的形式,而利用fillna()需要多行重复语句才能实现,或者需要提前计算某列的平均值、中位数或者众数。

比如,用fillna()填充

# 用中位数填充
X_missing.loc[:,"age"] = X_missing.loc[:,"age"].fillna(X_missing.loc[:,"age"].median())

或者更一般的
# 均值填充
data['col'] = data['col'].fillna(data['col'].means())
# 中位数填充
data['col'] = data['col'].fillna(data['col'].median())
# 众数填充
data['col'] = data['col'].fillna(stats.mode(data['col'])[0][0])

(4)KNN填补

官网:https://scikit-learn.org/stable/modules/generated/sklearn.impute.KNNImputer.html

使用sklearn的impute 模块中的KNNImputer 函数。KNNImputer通过欧几里德距离矩阵寻找最近邻,帮助估算观测中出现的缺失值。

每个样本的缺失值都是使用n_neighbors训练集中找到的最近邻的平均值估算的 。如果两个都不缺失的特征都接近,则两个样本接近。

class sklearn.impute.KNNImputer(*, 
missing_values=nan, 
n_neighbors=5, 
weights='uniform', 
metric='nan_euclidean', 
copy=True, 
add_indicator=False)

import numpy as np
from sklearn.impute import KNNImputer
X = [[1, 2, np.nan], [3, 4, 3], [np.nan, 6, 5], [8, 8, 7]]
imputer = KNNImputer(n_neighbors=2)
imputer.fit_transform(X)
'''
array([[1. , 2. , 4. ],
       [3. , 4. , 3. ],
       [5.5, 6. , 5. ],
       [8. , 8. , 7. ]])
'''
# 对于离散数据
data = [['a1',12,'M'],['a2',10,np.nan],['a3',14,'F'],['a4',8,'M'],['a5',7,'F']
        ,['a6',20,'F'],['a7',7,'M']]
df = pd.DataFrame(data,columns=['name','age','gender'])
'''
  name  age gender
0   a1   12      M
1   a2   10    NaN
2   a3   14      F
3   a4    8      M
4   a5    7      F
5   a6   20      F
6   a7    7      M
'''
# 将离散数据转化为数值型
df['gender_map'] = df.gender.map('F':1,'M':0)
df 
'''
  name  age gender  gender_map
0   a1   12      M         0.0
1   a2   10    NaN         NaN
2   a3   14      F         1.0
3   a4    8      M         0.0
4   a5    7      F         1.0
5   a6   20      F         1.0
6   a7    7      M         0.0
'''
df_fill = df[['age','gender_map']]
'''
    age  gender_map
0   12         0.0
1   10         NaN
2   14         1.0
3    8         0.0
4    7         1.0
5   20         1.0
6    7         0.0
'''
# 填补
from sklearn.impute import KNNImputer
impute = KNNImputer(n_neighbors = 2)
df_filled = impute.fit_transform(df_fill)
df_filled
'''
array([[12.,  0.],
       [10.,  0.],
       [14.,  1.],
       [ 8.,  0.],
       [ 7.,  1.],
       [20.,  1.],
       [ 7.,  0.]])
'''

另外,还有其他的实现KNN填充方式

利用knn算法填充,其实是把目标列当做目标标量,利用非缺失的数据进行knn算法拟合,最后对目标列缺失进行预测。(对于连续特征一般是加权平均,对于离散特征一般是加权投票)

fancyimpute 类

from fancyimpute import KNN
fill_knn = KNN(k=3).fit_transform(data)
data = pd.DataFrame(fill_knn)

sklearn类

from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor

def knn_filled_func(x_train, y_train, test, k = 3, dispersed = True):
    # params: x_train 为目标列不含缺失值的数据(不包括目标列)
    # params: y_train 为不含缺失值的目标列
    # params: test 为目标列为缺失值的数据(不包括目标列)
    if dispersed:
        knn= KNeighborsClassifier(n_neighbors = k, weights = "distance")
    else:
        knn= KNeighborsRegressor(n_neighbors = k, weights = "distance")

    knn.fit(x_train, y_train)
    return test.index, knn.predict(test)

2 随机森林回归进行填补

现实中,很少用算法(如随机森林)填补缺失值,因为算法填补很慢,不如均值或者0。另外,算法插补方法,领导不一定能理解,造成不必要的麻烦。

具体的

在现实中,其实非常少用到算法来进行填补,有以下几个理由:

  1. 算法是黑箱解释性不强。如果你是一个数据挖掘工程师,你使用算法来填补缺失值后,你不懂机器学习的老板或者同事问你的缺失值是怎么来的,你可能需要从头到尾帮他/她把随机森林解释一遍,这种效率过低的事情是不可能做的,而许多老板和上级不会接受他们无法理解的东西。
  2. 算法填补太过缓慢,运行–次森林需要有至少100棵树才能够基本保证森林的稳定性,而填补一个列就需要很长的时间。在我们并不知道森林的填补结果是好是坏的情况下,填补一个很大的数据集风险非常高,有可能需要跑好几个小时,但填补出来的结果却不怎么优秀,这明显是–个低效的方法。

但这种方法还是值得学习的

随机森林插补法原理

对于一个有n个特征的数据来说,其中特征T有缺失值,我们就把特征T当作标签,其他的

n-1个特征 + 原本的标签 = 新的特征矩阵

那对于T来说,它没有缺失的部分,就是我们的 Y_train,这部分数据既有标签也有特征,而它缺失的部分,只有特征没有标签,就是我们需要预测的部分。

特征T不缺失的值对应的其他n-1个特征 + 本来的标签:X_train
特征T不缺失的值:Y_train

特征T缺失的值对应的其他n-1个特征 + 本来的标签:X_test
特征T缺失的值:未知,我们需要预测的 Y_test

那如果数据中除了特征T之外,其他特征也有缺失值怎么办?

答案是遍历所有的特征,从缺失最少的开始进行填补(因为填补缺失最少的特征所需要的准确信息最少)。填补一个特征时,先将其他特征的缺失值用0代替,每完成一次回归预测,就将预测值放到原本的特征矩阵中,再继续填补下一个特征。每一次填补完毕,有缺失值的特征会减少一个,所以每次循环后,需要用0来填补的特征就越来越少。当进行到最后一个特征时(这个特征应该是所有特征中缺失值最多的),已经没有任何的其他特征需要用0来进行填补了,而我们已经使用回归为其他特征填补了大量有效信息,可以用来填补缺失最多的特征。遍历所有的特征后,数据就完整,不再有缺失值了。

代码

换一个数据集

(1)构造缺失数据集

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()
# 总共506*13=6578个数据,不含缺失值
# 完整的数据
X_full, y_full = dataset.data, dataset.target
n_samples = X_full.shape[0] # 样本 506
n_features = X_full.shape[1] # 特征 13

rng = np.random.RandomState(0) # 种子
# 缺失率为0.2,即构造的缺失值占20%
missing_rate = 0.2
n_missing_samples = int(np.floor(n_samples * n_features * missing_rate))
# 缺失值索引
missing_col_index = rng.randint(0,n_features,n_missing_samples)
missing_row_index = rng.randint(0,n_samples,n_missing_samples)

X_missing = X_full.copy()
y_missing = y_full.copy()

# 替换,构造缺失值数据集
X_missing[missing_row_index,missing_col_index] = np.nan
X_missing = pd.DataFrame(X_missing,columns=dataset.feature_names)

用随机森林回归来填补缺失值

X_missing_reg = X_missing.copy()
# 查看缺失情况
missing = X_missing_reg .isna().sum()
missing = pd.DataFrame(data='特征': missing.index,'缺失值个数':missing.values)
#通过~取反,选取不包含数字0的行
missing = missing[~missing['缺失值个数'].isin([0])]
# 缺失比例
missing['缺失比例'] =  missing['缺失值个数']/X_missing_reg .shape[0]
missing
'''
       特征  缺失值个数 缺失比例
0      CRIM     91  0.179842
1        ZN     88  0.173913
2     INDUS     93  0.183794
3      CHAS    109  0.215415
4       NOX     96  0.189723
5        RM    100  0.197628
6       AGE     93  0.183794
7       DIS     85  0.167984
8       RAD     87  0.171937
9       TAX     92  0.181818
10  PTRATIO     88  0.173913
11        B     93  0.183794
12    LSTAT     88  0.173913
'''


X_df = X_missing_reg.isnull().sum()
# 得出列名 缺失值最少的列名 到 缺失值最多的列名
colname= X_df[~X_df.isin([0])].sort_values().index.values
# 缺失值从小到大的特征顺序
sortindex =[]
for i in colname:
    sortindex .append(X_missing_reg.columns.tolist().index(str(i)))

# 遍历所有的特征,从缺失最少的开始进行填补,每完成一次回归预测,就将预测值放到原本的特征矩阵中,再继续填补下一个特征
for i in sortindex:
    #构建我们的新特征矩阵和新标签
    df = X_missing_reg 以上是关于缺失值处理拉格朗日插值法—随机森林算法填充—sklearn填充(均值/众数/中位数)的主要内容,如果未能解决你的问题,请参考以下文章

数据插补—拉格朗日插值法

python数据挖掘分析清洗——缺失值处理方法汇总

数据分析&数据挖掘拉格朗日插值法

拉格朗日插值方法

拉格朗日(Lagrange)插值算法

拉格朗日插值法求2的平方根