缺失值处理
数据缺失的原因
缺失值的产生的原因多种多样,主要分为机械原因和人为原因。机械原因是由于机械原因导致的数据收集或保存的失败造成的数据缺失,比如数据存储的失败,存储器损坏,机械故障导致某段时间数据未能收集(对于定时数据采集而言)。人为原因是由于人的主观失误、历史局限或有意隐瞒造成的数据缺失,比如,在市场调查中被访人拒绝透露相关问题的答案,或者回答的问题是无效的,数据录入人员失误漏录了数据
数据缺失的类型
将数据集中不含缺失值的变量称为 完全变量
,数据集中含有缺失值的变量称为 不完全变量
完全随机缺失(MCAR)
指的是数据的缺失是随机的,不依赖于任何不完全变量或完全变量,不影响样本的无偏性,如家庭地址缺失
随机缺失(MAR)
指的是数据的缺失不是完全随机的,即该类数据的缺失依赖于其他完全变量,如财务数据缺失情况与企业的大小有关
非完全随机缺失(MNAR)
指的是数据的缺失依赖于不完全变量自身,如高收入人群不愿意提供家庭收入
数据缺失的处理方法
一、不处理
有一些模型自身能够处理数据缺失的情况(如随机森林、xgboost),在这种情况下不需要对缺失数据做任何的处理,这种做法的缺点是在模型的选择上有局限
二、删除
import pandas as pd
import numpy as np
from sklearn.impute import KNNImputer
from sklearn.ensemble import RandomForestClassifier
df = pd.read_csv('person.csv')
df
删除有缺失数据的样本
df = pd.read_csv('person.csv')
df = df.dropna(how='any')
df
删除有过多缺失数据的特征
df = pd.read_csv('person.csv')
df = df.drop(['height','weight'], axis=1)
df
三、填充
固定值填充
df = pd.read_csv('person.csv')
df['sex'] = df['sex'].fillna('female')
df
均值填充
df = pd.read_csv('person.csv')
df['height'] = df['height'].fillna(df['height'].mean())
df
中位数填充
df = pd.read_csv('person.csv')
df['height'] = df['height'].fillna(df['height'].median())
df
众数填充
df = pd.read_csv('person.csv')
df['age'] = df['age'].fillna(df['age'].mode()[0]) # The mode is the value that appears most often
df
用上一个值填充
df = pd.read_csv('person.csv')
df['height'] = df['height'].ffill()
df
用下一个值填充
df = pd.read_csv('person.csv')
df['weight'] = df['weight'].bfill()
df
随机数填充
df = pd.read_csv('person.csv')
r = np.random.randint(2) % 2 # [0,2)
df['sex'] = df['sex'].fillna('female' if r == 1 else 'male')
df
插值法填充
使用插值法可以计算缺失值的估计值,所谓的插值法就是通过两点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) , ( x 1 , y 1 ) (x_1,y_1) (x1,y1) 估计中间点的值,假设 y = f ( x ) y=f(x) y=f(x) 是一条直线,通过已知的两点来计算函数 f ( x ) f(x) f(x) ,然后只要知道 x x x 就能求出 y y y ,以此方法来估计缺失值。当然我们也可以假设 f ( x ) f(x) f(x) 不是直线,而是其他函数
df = pd.read_csv('person.csv')
# 默认为线性插值,即在两个数据点之间连接直线,计算给定的插值点在直线上的值作为插值结果
df['age'] = df['age'].interpolate() # nan=(24+26)/2=25
df
KNN 填充
df = pd.read_csv('person.csv')
# 标签编码
df['sex'] = df['sex'].map({'female': 1
,'male': 0})
df
# 可以使用交叉验证来选择k值
imputer = KNNImputer(n_neighbors=1) # 默认每个邻域中的所有点的权重均相等
df.iloc[:,1:] = imputer.fit_transform(df.iloc[:,1:])
df
建模预测
将缺失的属性作为预测目标来预测,将数据集按照是否含有特定属性的缺失值分为两类,利用现有的机器学习算法对预测数据集的缺失值进行预测。该方法的根本的缺陷是如果其他属性和缺失属性无关,则预测的结果毫无意义;但是若预测结果相当准确,则说明这个缺失属性是没必要纳入数据集中的;一般的情况是介于两者之间
下面的案例中将选择 sex 特征作为预测目标,采用随机森林进行拟合得到预测值,其他缺失特征采用均值进行填充
# 读入数据
df = pd.read_csv('person.csv')
# 标签编码
df['sex'] = df['sex'].map({'female': 1
,'male': 0})
# 分离特征和标签
feature = df.iloc[:,2:]
df.iloc[:,2:] = feature.fillna(feature.mean()) # 其他缺失特征采用均值进行填充
feature = df.iloc[:,2:] # age height weight
label = df.iloc[:,1] # sex
print(feature)
print(label)
# 将sex非空划分为训练集
sex_is_null = df['sex'].isnull()
train_indices = sex_is_null == False
X_train = feature[train_indices]
y_train = label[train_indices]
# 将sex为空划分为测试集
X_test = feature[sex_is_null]
print(X_train)
print(y_train)
print(X_test)
# 构建模型,训练并预测
rf = RandomForestClassifier(n_estimators=10, class_weight='balanced', random_state=1)
rf.fit(X_train, y_train)
predictions = rf.predict(X_test)
df.loc[sex_is_null,'sex'] = predictions
df
高维映射
将属性映射到高维空间,采用独热编码(one-hot)技术对属性进行编码。将包含 K 个离散取值范围的属性值扩展为 K+1 个属性值,若该属性值缺失,则扩展后的第 K+1 个属性值置为1。这种做法是最精确的做法,保留了所有的信息,也未添加任何额外信息,若预处理时把所有的变量都这样处理,会大大增加数据的维度。这样做的好处是完整保留了原始数据的全部信息,不用考虑缺失值;缺点是计算量大大提升,且只有在样本量非常大的时候效果才好
df = pd.read_csv('person.csv')
dummy_sex = pd.get_dummies(df['sex'], prefix='sex', dummy_na=True)
df = pd.concat([df, dummy_sex], axis=1)
df = df.drop('sex', axis=1)
df
热卡填充
对于一个包含空值的对象,热卡填充法(Hot deck imputation)在完整数据中找到一个与它最相似的对象,然后用这个相似对象的值来进行填充。不同的问题可能会选用不同的标准来对相似进行判定。该方法概念上很简单,且利用了数据间的关系来进行空值估计,但缺点在于难以定义相似标准,主观因素较多
极大似然估计
在缺失类型为随机缺失的条件下,假设模型对于完整的样本是正确的,那么通过观测数据的边际分布可以对未知参数进行极大似然估计(Little and Rubin)。这种方法也被称为忽略缺失值的极大似然估计,对于极大似然的参数估计实际中常采用的计算方法是期望值最大化(Expectation Maximization,EM)。该方法比删除个案和单值插补更有吸引力,它一个重要前提:适用于大样本。有效样本的数量足够以保证极大似然估计值是渐近无偏的并服从正态分布。但是这种方法可能会陷入局部极值,收敛速度也不是很快,并且计算很复杂
多重插补
多值插补的思想来源于贝叶斯估计,认为待插补的值是随机的,它的值来自于已观测到的值。具体实践上通常是估计出待插补的值,然后再加上不同的噪声,形成多组可选插补值。根据某种选择依据,选取最合适的插补值
总结
当我们决定采用何种缺失值处理方式时,要综合考虑数据缺失的原因、数据缺失的类型、数据缺失的处理方法的特点、样本量等因素
本文到此结束,后续将会不断更新,如果发现上述有误,请各位大佬及时指正!如果觉得写得还可以,欢迎点赞收藏加关注,谢谢!