1 数据缺失的原因
- 无意的:采集遗漏等;
- 有意的:有些数据集在特征描述中会规定将缺失值也作为一种特征值,这时候缺失值就可以看作是一种特殊的特征值;
- 不存在:有些特征属性根本就是不存在的,比如一个未婚者的配偶名字就没法填写,再如一个孩子的收入状况也无法填写;
2 数据缺失的类型
数据集中不含缺失值的变量称为完全变量,含有缺失值的变量称为不完全变量。
从缺失的分布来将缺失可以分为完全随机缺失,随机缺失和完全非随机缺失。
- 完全随机缺失(missing completely at random,MCAR):指的是数据的缺失是完全随机的,不依赖于任何不完全变量或完全变量,不影响样本的无偏性,如家庭地址缺失;
- 随机缺失(missing at random,MAR):指的是数据的缺失不是完全随机的,即该类数据的缺失依赖于其他完全变量,如财务数据缺失情况与企业的大小有关;
- 非随机缺失(missing not at random,MNAR):指的是数据的缺失与不完全变量自身的取值有关,如高收入人群不原意提供家庭收入;
对于随机缺失和非随机缺失,直接删除记录是不合适的。随机缺失可以通过已知变量对缺失值进行估计,而非随机缺失的非随机性还没有很好的解决办法。
3 数据缺失的处理方法
1. 删除记录
优点:
- 最简单粗暴;
缺点:
- 牺牲了大量的数据,通过减少历史数据换取完整的信息,这样可能丢失了很多隐藏的重要信息;
当缺失数据比例较大时,特别是缺失数据非随机分布时,直接删除可能会导致数据发生偏离,比如原本的正态分布变为非正太; 这种方法在样本数据量十分大且缺失值不多的情况下非常有效,但如果样本量本身不大且缺失也不少,那么不建议使用。 Python中的使用:
可以使用 pandas 的 dropna 来直接删除有缺失值的特征。#删除数据表中含有空值的行
df.dropna(how='any')
2. 数据填补
对缺失值的插补大体可分为两种:替换缺失值,拟合缺失值,虚拟变量。替换是通过数据中非缺失数据的相似性来填补,其核心思想是发现相同群体的共同特征,拟合是通过其他特征建模来填补,虚拟变量是衍生的新变量代替缺失值。
替换缺失值均值插补
对于定类数据:使用 众数(mode)填补,比如一个学校的男生和女生的数量,男生500人,女生50人,那么对于其余的缺失值我们会用人数较多的男生来填补。
对于定量(定比)数据:使用平均数(mean)或中位数(median)填补,比如一个班级学生的身高特征,对于一些同学缺失的身高值就可以使用全班同学身高的平均值或中位数来填补。一般如果特征分布为正太分布时,使用平均值效果比较好,而当分布由于异常值存在而不是正太分布的情况下,使用中位数效果比较好。 注:此方法虽然简单,但是不够精准,可能会引入噪声,或者会改变特征原有的分布。下图左为填补前的特征分布,图右为填补后的分布,明显发生了畸变。因此,如果缺失值是随机性的,那么用平均值比较适合保证无偏,否则会改变原分布。Python中的使用:
#使用price均值对NA进行填充
df['price'].fillna(df['price'].mean())
df['price'].fillna(df['price'].median())
热卡填补(Hot deck imputation)
热卡填充法是在完整数据中找到一个与它最相似的对象,然后用这个相似对象的值来进行填充。通常会找到超出一个的相似对象,在所有匹配对象中没有最好的,而是从中随机的挑选一个作为填充值。这个问题关键是不同的问题可能会选用不同的标准来对相似进行判定,以及如何制定这个判定标准。该方法概念上很简单,且利用了数据间的关系来进行空值估计,但缺点在于难以定义相似标准,主观因素较多。
K最近距离邻法(K-means clustering)
另外一种方法就是利用无监督机器学习的聚类方法。通过K均值的聚类方法将所有样本进行聚类划分,然后再通过划分的种类的均值对各自类中的缺失值进行填补。归其本质还是通过找相似来填补缺失值。 注:缺失值填补的准确性就要看聚类结果的好坏了,而聚类结果的可变性很大,通常与初始选择点有关,并且在下图中可看到单独的每一类中特征值也有很大的差别,因此使用时要慎重。
拟合缺失值
拟合就是利用其它变量做模型的输入进行缺失变量的预测,与我们正常建模的方法一样,只是目标变量变为了缺失值。 注:如果其它特征变量与缺失变量无关,则预测的结果毫无意义。如果预测结果相当准确,则又说明这个变量完全没有必要进行预测,因为这必然是与特征变量间存在重复信息。一般情况下,会介于两者之间效果为最好,若强行填补缺失值之后引入了自相关,这会给后续分析造成障碍。 利用模型预测缺失变量的方法有很多,这里仅简单介绍几种。
回归预测:
如我们之前提到的房价预测项目一样数据分析实战—北京二手房房价分析(建模篇),基于完整的数据集,建立回归方程。对于有缺失值的特征值,将已知特征值代入模型来估计未知特征值,以此估计值来进行填充,以下图为例。当然关于回归的方法有很多,这里就不详细介绍了。 缺失值是连续的,即定量的类型,才可以使用回归来预测。
- 极大似然估计(Maximum likelyhood):
在缺失类型为随机缺失的条件下,假设模型对于完整的样本是正确的,那么通过观测数据的边际分布可以对未知参数进行极大似然估计(Little and Rubin)。这种方法也被称为忽略缺失值的极大似然估计,对于极大似然的参数估计实际中常采用的计算方法是期望值最大化(Expectation Maximization,EM)。该方法比删除个案和单值插补更有吸引力,它一个重要前提:适用于大样本。有效样本的数量足够以保证ML估计值是渐近无偏的并服从正态分布。但是这种方法可能会陷入局部极值,收敛速度也不是很快,并且计算很复杂,且仅限于线性模型。
- 多重插补(Mutiple imputation):
多值插补的思想来源于贝叶斯估计,认为待插补的值是随机的,它的值来自于已观测到的值。具体实践上通常是估计出待插补的值,然后再加上不同的噪声,形成多组可选插补值。根据某种选择依据,选取最合适的插补值。
我们看到,以上提出的拟合和替换方法都是单一的插补方法,而多重插补弥补了单一插补的缺陷,它并没有试图去通过模拟值去估计每个缺失值,而是提出缺失数据值的一个随即样本(这些样本可以是不同的模型拟合结果的组合)。这种程序的实施恰当地反映了由于缺失值引起的不确定性,使得统计推断有效。多重插补推断可以分为以下3个步骤:
- 为每个缺失值产生一套可能的插补值,这些值反映了无响应模型的不确定性;
- 每个插补数据集合都用针对完整数据集的统计方法进行统计分析;
- 对来自各个插补数据集的结果,根据评分函数进行选择,产生最终的插补值;
根据数据缺失机制、模式以及变量类型,可分别采用回归、预测均数匹配( predictive mean matching, PMM )、趋势得分( propensity score, PS )、Logistic回归、判别分析以及马尔可夫链蒙特卡罗( Markov Chain Monte Carlo, MCMC) 等不同的方法进行填补。 假设一组数据,包括三个变量Y1,Y2,Y3,它们的联合分布为正态分布,将这组数据处理成三组,A组保持原始数据,B组仅缺失Y3,C组缺失Y1和Y2。在多值插补时,对A组将不进行任何处理,对B组产生Y3的一组估计值(作Y3关于Y1,Y2的回归),对C组作产生Y1和Y2的一组成对估计值(作Y1,Y2关于Y3的回归)。> 当用多值插补时,对A组将不进行处理,对B、C组将完整的样本随机抽取形成为m组(m为可选择的m组插补值),每组个案数只要能够有效估计参数就可以了。对存在缺失值的属性的分布作出估计,然后基于这m组观测值,对于这m组样本分别产生关于参数的m组估计值,给出相应的预测,这时采用的估计方法为极大似然法,在计算机中具体的实现算法为期望最大化法(EM)。对B组估计出一组Y3的值,对C将利用Y1,Y2,Y3它们的联合分布为正态分布这一前提,估计出一组(Y1,Y2)。
上例中假定了Y1,Y2,Y3的联合分布为正态分布。这个假设是人为的,但是已经通过验证(Graham和Schafer于1999),非正态联合分布的变量,在这个假定下仍然可以估计到很接近真实值的结果。 注:使用多重插补要求数据缺失值为随机性缺失,一般重复次数20-50次精准度很高,但是计算也很复杂,需要大量计算。
- 随机森林:
另一种比较常用的拟合方法就是随机森林,这也是Kaggle竞赛中大佬们经常使用的一个办法,具体实现方式与正常一样,只是将缺失值作为目标变量即可。以下知识星球项目(一)中一段代码,仅供参考。
def set_missing_ages(df):
# 把已有的数值型特征取出来丢进Random Forest Regressor中
age_df = df[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]
# 乘客分成已知年龄和未知年龄两部分
known_age = age_df[age_df.Age.notnull()].as_matrix()
unknown_age = age_df[age_df.Age.isnull()].as_matrix()
# y即目标年龄
y = known_age[:, 0]
# X即特征属性值
X = known_age[:, 1:]
# fit到RandomForestRegressor之中
rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
rfr.fit(X, y)
# 用得到的模型进行未知年龄结果预测
predictedAges = rfr.predict(unknown_age[:, 1:])
# print predictedAges
# 用得到的预测结果填补原缺失数据
df.loc[ (df.Age.isnull()), 'Age' ] = predictedAges
return df, rfr
- 虚拟变量
虚拟变量其实就是缺失值的一种衍生变量。具体做法是通过判断特征值是否有缺失值来定义一个新的二分类变量。比如,特征为A含有缺失值,我们衍生出一个新的特征B,如果A中特征值有缺失,那么相应的B中的值为1,如果A中特征值没有缺失,那么相应的B中的值为0。
下面是知识星球项目(一)中的一段程序:
data_train['CabinCat'] = data_train['Cabin'].copy()
data_train.loc[ (data_train.CabinCat.notnull()), 'CabinCat' ] = "No"
data_train.loc[ (data_train.CabinCat.isnull()), 'CabinCat' ] = "Yes"
fig, ax = plt.subplots(figsize=(10,5))
sns.countplot(x='CabinCat', hue='Survived',data=data_train)
plt.show()
下面可以通过一行代码清楚看到衍生的虚拟变量。
data_train[['Cabin','CabinCat']].head(10)
3. 不处理
补齐处理只是将未知值补以我们的主观估计值,不一定完全符合客观事实,在对不完备信息进行补齐处理的同时,我们或多或少地改变了原始的信息系统。而且,对空值不正确的填充往往将新的噪声引入数据中,使挖掘任务产生错误的结果。因此,在许多情况下,我们还是希望在保持原始信息不发生变化的前提下对信息系统进行处理。
在实际应用中,一些模型无法应对具有缺失值的数据,因此要对缺失值进行处理。然而还有一些模型本身就可以应对具有缺失值的数据,此时无需对数据进行处理,比如Xgboost,rfr等高级模型。
4 总结
总而言之,大部分数据挖掘的预处理都会使用比较方便的方法来处理缺失值,比如均值法,但是效果上并一定好,因此还是需要根据不同的需要选择合适的方法,并没有一个解决所有问题的万能方法。具体的方法采用还需要考虑多个方面的:
- 数据缺失的原因;
- 数据缺失值类型;
- 样本的数据量;
- 数据缺失值随机性等;
关于数据缺失值得思维导图:
如果大家有任何好的其他方法,欢迎补充。