在数据清洗的过程中,还有一个非常重要的问题就是处理缺失的数据,尤其是在我们拿到样本比较小的数据时,能够通过一些合理的方式去插补这些缺失的数据对我们接下来的分析尤为重要。这篇将会介绍一些处理数据缺失的处理方法和手段。
一般情况下,缺失的数据通常会以NA, nan, 0, .等来表示,造成缺失数据的原因一般也是两种,一个是人为错误,还有就是技术错误。在遍历缺失数据时,有一个非常好用的可视化库missingno。接下来会经常使用这个库进行可视化我们的缺失数据,以便更好的去理解数据缺失的原因:
|
|
首先我们需要了解处理缺失数据的一个基本流程:
转化所有缺失值为空 –>分析缺失值的数量及类型–>合理删除或插补缺失数据 –>比较评估表现
这篇将会使用datacamp中的diabetes数据。这个链接下载下来的数据是一个xls文件,可能是文件类型出现了问题,可自行修改成csv后缀。其中我对原数据进行了一些修改从而更好的完成这篇文章的例子(该文使用的数据下载地址):点击下载
关于数据的解释可以在kaggle中找到。
|
|
通过.head()
我们大致对数据有了一个了解,通过.info()
我们可以判断出变量Glucose
,Diastolic_BP
,Serum_Insulin
这三个变量是有数据缺失的。除了这三个可以很直观的看出有缺失值的变量外,很多时候数据还会存在一些隐藏的缺失情况。我们观察发现,所有变量类型都是数字类型,只有Skin_Fold是一个对象(object)类型,虽然它在.info()
中包含了768条完整的数据,但是这要引起我们的重视,所以要对其进行重点观察:
|
|
转化所有缺失值为空
通过.unique()
对Skin_Fold
唯一值的选取和排序,我们看到Skin_Fold有的数据为”.”,我们知道”.”也是常见的一种缺失数据标记,所以我们需要对其进行处理。根据处理缺失数据的流程,第一步是把所有的缺失数据找出来并转化为空值:
|
|
由于出现空值的Skin_Fold
变量情况比较简单,我们可以只需在读取数据的时候在.read_csv()
中加入na_value
参数即可把所有”.”转化为nan
。
我们还可以使用.describe()函数去观察一下我们int和float类型的数据,确保没有异常数据出现:
|
|
通过观察我们发现,变量BMI有一些异常值的出现,BMI出现了最小值为0的情况,我们知道BMI是不可能为0的。所以我们需要对其进行处理:
|
|
分析缺失值的数量及类型
在合理的把缺失数据转化为Nan之后,我们在第二步需要分析缺失值的数量和类型。有两个函数可以筛选出缺失值,它们分别是.isnull()
和.isna()
|
|
接下来我们将会使用在文章最开始提到的缺失数据可视化包missingno,来对缺失的数据进行更加直观的可视化处理:
|
|
缺失值是随机发生的吗?是如何发生的呢?不同的变量缺失值之间是否存在一些相关性?
一般来讲,发生缺失值有三种情况:
- 完全随机缺失(MCAR):缺失值与其他变量没有任何关联;
- 随机缺失(MAR):缺失值与其他变量但不是与其他缺失变量纯在相关性;
- 非随机缺失(MNAR):缺失值之间存在相关性。
理解缺失值的存在原因以及它们之间的相关性对于对缺失值的处理至关重要。我们通过对df的matrix观察可以发现,Glucose
变量和BMI
变量为完全随机缺失,Diastolic_BP
变量为随机缺失,Skin_Fold
和Serum_Insulin
两个变量的缺失值存在相关行,所以它们是非随机缺失。
为了更加直观的观察缺失值之间的关系,我们还可以使用missingno包中的热力图及树形决策图来进行可视化:
|
|
通过这两张图,我们可以非常直观的看到Skin_Fold
和Serum_Insulin
两个变量的缺失值存在强相关性。
合理删除或插补缺失数据
合理删除缺失数据
在分析完缺失值的情况之后,我们可以根据实际情况对缺失数据进行一些处理。对于缺失数据,一般的处理方式有两种:1. 删除缺失数据; 2. 插补缺失数据。
对于完全随机缺失(MCAR)的情况一般处理方法就是直接删除,其中删除方式有两种:1. 成对删除(pairwise); 2. 列表删除(listwise)。
两种删除方式各自有其优点和缺点。一般情况推荐使用成对删除方式,但是当数据确实量较小的时候,为了得到更完整的分析结果,可以考虑使用列表删除。
|
|
Glucose
变量和BMI
由于是完全随机缺失且缺失数据量较少,所以我们这边对它们采用列表删除整行数据:
|
|
再次绘制matrix观察对缺失值的删除情况,我们发现Glucose
变量和BMI
的缺失值已经被完全删除了,这里再次强调一下,仅仅删除缺失数据当我们可以确定数据是完全随机缺失的时候。
## 一般插补方法
在对完全随机缺失的数据进行删除操作之后,我们需要对那些因为其他原因缺失的数据进行插补操作。目前我们还有以下变量存在缺失数据:
Diastolic_BP
Skin_Fold
Serum_Insulin
缺失数据插补一般有两种方式,一种为一般方式还有一种为高级方式。一般方式通常是为缺失数据插补平均数、中位数、众数或者常量,高级插补方式中需要使用到机器学习的一些算法来进行插补,比如KNN,MICE等。使用哪种方式要根据不同的情况进行合理的选择,需要对数据进行合理的插补又不能过度拟合模型。一般方式插补数据需要用到一个python包sklearn:
|
|
平均数插补:
|
|
中位数插补:
|
|
众数插补:
|
|
常量插补:
|
|
可视化一下我们的插补情况:
|
|
Serum_Insulin
变量和Glucose
变量从图中的紫色点可以看出存在一定的正相关性,但是插补的数据(红色点)并没有呈现应有的相关性,所以该插补方式可能不是特别合理。
我们观察一下另外其他的几种插补情况:
|
|
出现了与mean插补方式类似的情况。为了更好的提升插补效果,我们还可以使用更高级的插补方法,例如KNN或者MICE。
## 高级插补方法
KNN是K-Nearest Neighbor的缩写,其运行基本原理是选择缺失的数据点最接近的K个数据点的完整数据,然后取这K个点的平均数,并用这个平均数插补该缺失数据。KNN百度百科。下图展示了KNN的基本原理,其中“五角星”所表示的就是要估算的值:
KNN属于机器学习的算法,在python中使用KNN插补需要安装fancyimpute库。根据不同的操作环境,安装方式略有不同。如果你使用的是Windows操作系统,在安装的时候遇到了困难可以参考这篇文章。当然,这篇文章里的问题可能只是众多问题中的一小部分,所以遇到其它问题时,可以去寻求更多的帮助。
下面是使用KNN插补方法的演示及其插补效果可视化的情况:
|
|
可以看出,KNN插补提升了对缺失数据的插补效果,体现出了相应的相关性。
除了KNN插补方式,还有一种常见的高级插补方式MICE,全称Multiple Imputations by Chained Equations 。与KNN不同的是,MICE使用的是多重回归的方式对缺失数据进行插补,对随机样本数据进行多重回归之后,然后取回归值得平均数对缺失值插补,其使用方式与之前的插补方式大同小异,除了需要多导入一个IterativeImputer :
|
|
这么多插补方法,该使用哪种?哪种更好呢?其实对于这个问题并没有标准答案,我们需要根据实际情况来选择最合适的插补方法。这时,我们就进入了处理缺失值的最后一个步骤,比较和评估插补缺失数据后的表现。
比较评估表现
数据插补的终极目的是为了提升模型的表现,所以在插补缺失数据时这一点是首要考虑的。一般来说使用机器学习模型来插补的数据表现可能会更好,我们需要选择表现最好的插补情况。在评估表现的过程中,使用密度图(density plot)可以很直观的看到数据的分布情况,并且还是一个很好的检查插补偏差的方式。接下来,我们将使用线性模型来验证以下插补后的数据框df与去除所有缺失值的数据框df_cc各自的表现:
|
|
接下来我们比较以下采用不同插补方式过后模型的表现,首先我们选用adjust R-square来评估平均数插补、KNN插补和MICE插补:
|
|
我们根据adjust R-square得到的结论是使用KNN插补方法获得的模型拟合最佳。同时我们还可以通过绘制密度图来观察插补后的数据与去除所有缺失数据的完整数据框的分布,这样可以直观的看到哪种插补方式的分布与完整数据框的分布最接近,这样可以避免我们选取的插补方式存在较大的偏差:
|
|
通过密度图,可以看出使用平均数插补(mean imputation)的结果蓝线比完整数据框Baseline红线的偏差较大,KNN和MICE插补与原完整数据较为接近,所以这里我们就可以淘汰平均数插补了。结合之前的adjust R-square的结果,KNN可能是最佳的插补方式。
借此,我们就完成了对缺失数据处理的整个基本流程。
插补分类变量中的缺失值
有些时候,我们的数据中还有可能出现一些分类变量数据缺失的现象,我们需要对其进行插补。对分类变量中的缺失数据进行插补更加复杂,因为一般分类数据通常是字符串类型。所以一般我们需要先对其进行编码,比如用0,1代表不同的分类,然后再用一些技巧对缺失数据进行插补。这个例子我不打算引入新的dataset,所以就直接修改一下原本的diabetes的‘Class’列,并用它来进行插补演示:
|
|
我们先看一下被处理过的数据长什么样子:
然后我们对这一列的缺失值进行插补:
|
|
插补之后的数据如下图所示:
由于我们的Class
变量其实是完整的,我们在例子最开始在Outcome
中特意删除了50个数据,把这50个数据变为了缺失数据,所以当我们使用KNN插补之后,我们可以把插补后的结果跟原’Class’的真实数据进行一下比较,看下插补的效果如何:
|
|
准确率大概有80%以上,效果可以说还算不错。