数据处理-数据清洗
在数据分析中,海量的原始数据中存在着大量不完整(有缺失值)、不一致、有异常的数据,严重影响到数据分析建模的执行效率,甚至可能导致分析结果的偏差,所以进行数据清洗显得尤为重要,数据清洗完成后,对数据进行集成、变换、规约等一系列操作,这个过程就是数据处理。数据处理一是要提高数据的质量,二是要让数据更好地适应特定的分析技术或工具。
数据处理的主要内容包括数据清洗、数据集成、数据变换和数据规约。数据清洗主要是删除原始数据集中的无关数据、重复数据、平滑噪声数据,筛选掉与挖掘主题无关的数据,处理缺失值、异常值。接下来我们具体来看一看数据清洗的内容。
1 缺失值处理
1.1 方法理论
数据缺失主要包括记录的缺失和记录中某个字段信息的缺失,两者都会造成分析结果的不准确。那么缺失值是怎么产生的呢?原因有三点,一是有些信息暂时无法获取,或者获取信息的代价太大;二是有些信息是被遗漏的,可能是输入时认为不重要、忘记填写或对数据理解错误等一些人为因素而遗漏,也可能是由于数据采集设备的故障、存储介质的故障、传输媒体的故障等非人为因素的原因而丢失;三是属性值不存在,比如一个未婚者的配偶姓名、一个儿童的固定收入等。
那数据中存在缺失值对数据分析有什么影响呢?总的来说还是体现在三个方面。一是数据分析中将会缺少大量的有效信息;二是由于数据不完整,模型所表现出来的结果不确定性会更加显著;三是包含空值的数据会使建模过程陷入混乱,导致不可靠的输出。
处理缺失值时我们常用的方法有删除法、替换法和插补法。
- 删除法:最简单的缺失值处理方法,根据数据处理的不同角度可分为删除观测样本和删除变量两种。删除观测样本是以减少样本量为代价来换取信息完整性的方法,适用于缺失值所占比例较小的情况;删除变量适用于变量有较大缺失且对研究目标影响不大的情况,意味着要删除整个变量。
- 替换法:变量按属性可分为数值型和非数值型,如果缺失值所在变量为数值型,一般用该变量在其他所有对象取值的均值来替换变量的缺失值;如果为非数值型,则使用该变量其他全部有效观测值的中位数或者众数进行替换。
- 插补法:删除法虽然简单易行,但会存在信息浪费的问题且数据结构会发生变动,以致最后得到的统计结果偏差太大。而插补法则会大大改善这种情况,常用的插补法有回归插补、多重插补等。回归插补法是利用回归模型,将需要插值补缺的变量作为因变量,其他相关变量作为自变量,通过线性回归模型预测出因变量的值来对缺失变量进行补缺;多重插补法的原理则是从一个包含缺失值的数据集中生成一组完整的数据,每个数据集中的缺失数据用蒙特卡洛方法来填补。如此进行多次,从而产生缺失值的一个随机样本。
1.2 R的实现
现在通过R语言具体实例来直观地分析三种方法的不同之处。首先,载入VIM
包中的sleep
数据集,该数据集中包含62种哺乳动物的睡眠、生态学变量和体质变量之间的关系。
#install.packages("VIM")#安装VIM软件包
library(VIM)#加载VIM软件包
## Warning: package 'VIM' was built under R version 3.4.4
## Loading required package: colorspace
## Warning: package 'colorspace' was built under R version 3.4.4
## Loading required package: grid
## Loading required package: data.table
## Warning: package 'data.table' was built under R version 3.4.4
## VIM is ready to use.
## Since version 4.0.0 the GUI is in its own package VIMGUI.
##
## Please use the package to use the new (and old) GUI.
## Suggestions and bug-reports can be submitted at: https://github.com/alexkowa/VIM/issues
##
## Attaching package: 'VIM'
## The following object is masked from 'package:datasets':
##
## sleep
data(sleep)#调用sleep数据集
dim(sleep)#查看数据集维度
## [1] 62 10
initial.data=sleep#将sleep数据集赋值给initial.data
summary(initial.data)#汇总数据集信息
## BodyWgt BrainWgt NonD Dream
## Min. : 0.005 Min. : 0.14 Min. : 2.100 Min. :0.000
## 1st Qu.: 0.600 1st Qu.: 4.25 1st Qu.: 6.250 1st Qu.:0.900
## Median : 3.342 Median : 17.25 Median : 8.350 Median :1.800
## Mean : 198.790 Mean : 283.13 Mean : 8.673 Mean :1.972
## 3rd Qu.: 48.203 3rd Qu.: 166.00 3rd Qu.:11.000 3rd Qu.:2.550
## Max. :6654.000 Max. :5712.00 Max. :17.900 Max. :6.600
## NA's :14 NA's :12
## Sleep Span Gest Pred
## Min. : 2.60 Min. : 2.000 Min. : 12.00 Min. :1.000
## 1st Qu.: 8.05 1st Qu.: 6.625 1st Qu.: 35.75 1st Qu.:2.000
## Median :10.45 Median : 15.100 Median : 79.00 Median :3.000
## Mean :10.53 Mean : 19.878 Mean :142.35 Mean :2.871
## 3rd Qu.:13.20 3rd Qu.: 27.750 3rd Qu.:207.50 3rd Qu.:4.000
## Max. :19.90 Max. :100.000 Max. :645.00 Max. :5.000
## NA's :4 NA's :4 NA's :4
## Exp Danger
## Min. :1.000 Min. :1.000
## 1st Qu.:1.000 1st Qu.:1.000
## Median :2.000 Median :2.000
## Mean :2.419 Mean :2.613
## 3rd Qu.:4.000 3rd Qu.:4.000
## Max. :5.000 Max. :5.000
##
可以看到数据集中的数据为数值类型。接下来,我们用函数is.na()
识别缺失值,函数返回值是逻辑值,TRUE表示是缺失值。
#install.packages("DT")#安装DT软件包
library(DT)#加载DT软件包
## Warning: package 'DT' was built under R version 3.4.4
n=is.na(sleep)#判断是否存在缺失值
datatable(n)#展示判断结果
这样的方式一目了然,但是当数据量很大时,不方便观察,因此我们可对每列TRUE求和,就可以得到所有变量的缺失值数目和总体的缺失值数目,根据缺失值数目占总体的比例选择合适的处理方法。
N=sum(is.na(sleep))#返回数据集中缺失值的个数
a=rep(0,10)#生成一个长度为10的数组,并初始化为0
for(i in 1:10){
a[i]=sum(n[,i])#将第i列TRUE的个数相加并赋值给数组a的第i个元素
}
a
## [1] 0 0 14 12 4 4 4 0 0 0
可以看到,数据集一共有38个缺失值,其中,第3-7个变量分别有14,12,4,4,4个缺失值。
1.2.1 删除法
用删除法处理缺失数据,用函数na.omit()
删除含有缺失值的行
data=na.omit(sleep)#将删除缺失值后的数据集命名为data
dim(data)#查看data的维度
## [1] 42 10
最终我们删除了20行样本,占总样本量的32%,对于仅有62条样本的数据来说,这个比例是非常大的,为了获取数据完整性而损失巨大的信息是非常不值得的,所以尝试使用替换法处理缺失值。
1.2.2 替换法
用替换法处理缺失数据,首先刚开始就知道数据集中的数据都是数值型的,所以一般用均值替换缺失值,如果数据集中某些变量是非数值型的,则使用该变量的其他全部有效观测值的中位数或众数进行替换。在描述性分析中已经讲过求数据框均值要用函数apply()
x=apply(data,2,mean)#对列求均值
x
## BodyWgt BrainWgt NonD Dream Sleep Span
## 100.813905 218.683810 8.742857 1.900000 10.642857 19.371429
## Gest Pred Exp Danger
## 129.940476 2.952381 2.357143 2.690476
从返回结果中可以可看到每个变量的均值,接下来我们就用每列的均值去替换NA值。首先把判断sleep每列数据是否含有缺失值的结果保存在变量y中,所以y的每个元素都是逻辑值,如果值为真,则把那一列的均值赋给原始数据集,代码如下。
for(i in 1:10){
y=is.na(sleep[,i])#判断sleep第i列数据是否含有缺失值的结果
for(j in 1:62){
if(y[j]){
sleep[j,i]=x[i]#用第i列的均值替换处于第j行第i列的NA值
}
}
}
datatable(sleep)
可以看到,数据集中的缺失值已被替换。
1.2.3 插补法
接下来用插补法处理缺失值,回归插补的思想是把需要插补的变量作为响应变量,其他变量作为预测变量,以未缺失数据的集合为训练集训练多元回归模型,然后预测相应变量的缺失值,回归插补一般用于NA值不同行的情况,先来看一个回归插补法的例子。我们以数据集中有缺失值的NonD为预测变量,以无缺失值的BodyWgt为响应变量,构造数据框。
A=initial.data$BodyWgt#为了保证用于训练和预测的变量名一致,在此重新命名变量
B=initial.data$NonD
data.new=data.frame(A,B)#构造数据框
datatable(data.new)
新生成的数据框data.new含有两个变量,只有B有缺失值,我们分割数据集,无缺失值的行组成训练集,又缺失的行组成测试集。
sub=which(is.na(data.new$B))#识别缺失值所在行数
train=data.new[-sub,]#以无缺失值数据样本为训练数据集
test=data.new[sub,]#以缺失值数据样本为测试数据集
fit=lm(train$B~.,data=train)#以A为预测变量,B为响应变量作线性回归
test$B=predict(fit,test)#模型预测
data.new1=rbind(train,test)#按行合并数据集
datatable(data.new1)
可以看到,数据集缺失项已经被插补了。
多重插补是一种基于重复模拟的处理缺失值的方法,它从一个包含缺失值的数据集中生成一组完整的数据集,每个数据集的缺失数据用蒙特卡洛方法来填补。使用R语言中的mice()
包来执行这些操作,首先我们看看操作思路。
mice()
函数首先从一个包含缺失数据的数据库开始,返回一个包含多个(默认为5个)完整数据集的对象。每个完整数据集都是通过对原始数据框中的缺失数据进行插补而生成的。由于插补有随机的成分,因此每个完整数据集都略有不同;with()
函数可一次对每个完整数据集应用统计模型(如线性模型或广义线性模型);pool()
函数将这些单独的分析结果整合为一组结果。最终模型的标准误和p值都将准确地反映出由于缺失值和多重插补而产生的不确定性。
基于mice
包的分析过程为
library(mice)
imp=mice(data,m)
fit=with(imp,analysis)
pooled=pool(fit)
summary(pooled)
imp:一个包含m个插值数据集的列表对象,同时含有完成插补过程的信息,默认为5;
analysis:用来设定应用于m个插补数据集的统计分析方法;
fit:一个包含m个单独统计分析结果的列表对象;
pooled:一个包含m个统计分析平均结果的列表对象;
data: 包含缺失值的矩阵或数据框。
#install.packages("mice")#安装mice软件包
library(mice)#加载mice软件包
## Warning: package 'mice' was built under R version 3.4.4
## Loading required package: lattice
imp=mice(initial.data,m=5,seed=1234)#一个包含5个插补数据集的列表对象,同时含有完成插补过程的信息,设置随机种子以保证每次结果一致
##
## iter imp variable
## 1 1 NonD Dream Sleep Span Gest
## 1 2 NonD Dream Sleep Span Gest
## 1 3 NonD Dream Sleep Span Gest
## 1 4 NonD Dream Sleep Span Gest
## 1 5 NonD Dream Sleep Span Gest
## 2 1 NonD Dream Sleep Span Gest
## 2 2 NonD Dream Sleep Span Gest
## 2 3 NonD Dream Sleep Span Gest
## 2 4 NonD Dream Sleep Span Gest
## 2 5 NonD Dream Sleep Span Gest
## 3 1 NonD Dream Sleep Span Gest
## 3 2 NonD Dream Sleep Span Gest
## 3 3 NonD Dream Sleep Span Gest
## 3 4 NonD Dream Sleep Span Gest
## 3 5 NonD Dream Sleep Span Gest
## 4 1 NonD Dream Sleep Span Gest
## 4 2 NonD Dream Sleep Span Gest
## 4 3 NonD Dream Sleep Span Gest
## 4 4 NonD Dream Sleep Span Gest
## 4 5 NonD Dream Sleep Span Gest
## 5 1 NonD Dream Sleep Span Gest
## 5 2 NonD Dream Sleep Span Gest
## 5 3 NonD Dream Sleep Span Gest
## 5 4 NonD Dream Sleep Span Gest
## 5 5 NonD Dream Sleep Span Gest
summary(imp)#查看imp汇总信息
## Multiply imputed data set
## Call:
## mice(data = initial.data, m = 5, seed = 1234)
## Number of multiple imputations: 5
## Missing cells per column:
## BodyWgt BrainWgt NonD Dream Sleep Span Gest Pred
## 0 0 14 12 4 4 4 0
## Exp Danger
## 0 0
## Imputation methods:
## BodyWgt BrainWgt NonD Dream Sleep Span Gest Pred
## "" "" "pmm" "pmm" "pmm" "pmm" "pmm" ""
## Exp Danger
## "" ""
## VisitSequence:
## NonD Dream Sleep Span Gest
## 3 4 5 6 7
## PredictorMatrix:
## BodyWgt BrainWgt NonD Dream Sleep Span Gest Pred Exp Danger
## BodyWgt 0 0 0 0 0 0 0 0 0 0
## BrainWgt 0 0 0 0 0 0 0 0 0 0
## NonD 1 1 0 1 1 1 1 1 1 1
## Dream 1 1 1 0 1 1 1 1 1 1
## Sleep 1 1 1 1 0 1 1 1 1 1
## Span 1 1 1 1 1 0 1 1 1 1
## Gest 1 1 1 1 1 1 0 1 1 1
## Pred 0 0 0 0 0 0 0 0 0 0
## Exp 0 0 0 0 0 0 0 0 0 0
## Danger 0 0 0 0 0 0 0 0 0 0
## Random generator seed value: 1234
Number of multiple imputations:5表示多重插补的数量是5次
Missing cells per column:表示每列变量缺失值包含的数量,如NonD包含14个缺失值
Imputation methods:可以看出对于有缺失值的变量采用了pmm(预测均值)的方法来插补,其他变量没有缺失值所以没有插补
Visit Sequence:从左往右展示了插补的变量,这里进行插补的分别是sleep数据集中的3-7列变量
Predictor Matrix:是预测变量矩阵,行代表插补变量,列代表为插补提供信息的变量,1和0表示使用和未使用。NonD到Gest这5行有缺失值,所以只有这5行进行了插补,每个含有缺失值的变量都利用了其他变量提供的信息来进行插补。
利用complete()
函数可以观察m个插补数据集中的任意一个,查看完整数据集中的第2个数据集,并将其赋值给原数据集。
data.new2=complete(imp,action=2)
datatable(data.new2)#查看data.new2数据集
可以看到,插补后的第2个数据集中包含62行完整的数据。针对不同的数据结构采用不同的处理方法可以节省时间,也为之后的建模工作扫除了一些障碍。
2 异常值处理
2.1 方法
异常值包括缺失值、离群值、重复值、数据不一致等。异常值的诊断和处理有很多种方法,但是当数据中有多个重复的值时,应该先删除重复数据。
诊断
通过箱线图呈现噪声值,这种方法可以查看明显的噪声值,通过简单的代码也可以准确定位噪声的位置;
聚类方法诊断,为每个观测值聚类,计算与聚类中心的距离,距离最大的N类(自己设定)被诊断为异常值(聚类方法在聚类分析一章中有详细讲到)
回归方法诊断噪声值,处理缺失值后,建立线性回归模型,通过观察模型返回的的残差拟合图、 QQ图、标准化后的残差拟合值图、cook距离判断噪声值图来判断异常点(看图方法在统计学-回归分析一节中有详细分析)
处理
把异常值视为缺失值,那么处理缺失值的方法就可以用于处理异常值,依然有删除、替换、插补三种方法;
用前后两个观测值的平均值修正该异常值,或者用盖帽法修正异常值;
如果确认数据正确,可以直接在有异常值的数据集上进行建模。
2.2 R的实现
以R自带数据集iris为例。
2.2.1 数据去重
data(iris)#加载数据集
datatable(iris)#展示数据集
index=duplicated(iris)#返回某一行是不是有重复的逻辑值
index
## [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [12] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [23] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [34] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [45] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [56] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [67] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [78] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [89] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [100] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [111] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [122] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [133] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE
## [144] FALSE FALSE FALSE FALSE FALSE FALSE FALSE
可以看到,第143行数据是重复数据,现在将其删掉
iris=iris[!index,]#筛选出非重复数据
还有另一种方法,用unique
函数可以直接返回已经删除重复了的数据。
datatable(unique(iris))#返回已经删除了重复元素或者重复行的数据
2.2.2 箱形图检验异常值
iris=iris[,-5]#去除类别数据
boxplot(iris)
title("鸢尾花数据异常值检测箱线图")
箱线图中的散点表示溢出的异常点,可以看到花萼宽度数据有异常值
sp=boxplot(iris$Sepal.Width,data=iris)
sp
## $stats
## [,1]
## [1,] 2.2
## [2,] 2.8
## [3,] 3.0
## [4,] 3.3
## [5,] 4.0
##
## $n
## [1] 149
##
## $conf
## [,1]
## [1,] 2.935281
## [2,] 3.064719
##
## $out
## [1] 4.4 4.1 4.2 2.0
##
## $group
## [1] 1 1 1 1
##
## $names
## [1] ""
sp$out
## [1] 4.4 4.1 4.2 2.0
text(sp$group,sp$out,labels=sp$out,col="red")
title("鸢尾花花萼宽度异常值检测箱线图")
sp的返回值中,out表示异常值,group表示异常值所在的类别。可以看到,鸢尾花花萼宽度有四个异常值且同属于第一类。接下来,编写简单的函数返回异常值的位置。
l=length(sp$out)#查看有多少个异常值
m=rep(0,l)
for(i in 1:l){
m[i]=which(iris$Sepal.Width==sp$out[i])
}
m
## [1] 16 33 34 61
结果表明,第异常值4.4在第16行,4.2在第33行,4.1在第34行,2.0在第61行。知道了异常值的位置就可以对它进行处理了。
2.2.3 剔除
剔除已识别的异常值
将用箱线图识别出来的异常值直接剔除
iris.new=iris[-m,]#剔除掉m中保存的各个行
datatable(iris.new)
2.2.4 修正
盖帽法处理异常值
盖帽法默认数据中大于99.9%分位数点的值和小于0.1%分位数点的值为异常值,分别用两个分位数补齐。
q1=quantile(iris$Sepal.Width, 0.001) #该函数用于获取百分位数点的值
q99=quantile(iris$Sepal.Width, 0.999)
iris.new1=iris
iris.new1[iris.new1$Sepal.Width<q1,]$Sepal.Width<-q1
iris.new1[iris.new1$Sepal.Width>q99,]$Sepal.Width<-q99
summary(iris.new1$Sepal.Width) #盖帽法之后,查看数据情况
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 2.03 2.80 3.00 3.06 3.30 4.37
datatable(iris.new1)
均值修正
用异常值前后两个观测值的平均值修正该异常值
for(i in 1:l){
iris$Sepal.Width[m[i]]=1/2*(iris$Sepal.Width[m[i]-1]+iris$Sepal.Width[m[i]+1])
}
boxplot(iris$Sepal.Width)#画箱线图观察异常点有没有被处理
2.2.5 转为缺失值
将异常值视为缺失值,用处理缺失值的方法来处理异常值。
iris.new2=iris
iris.new2$Sepal.Width[m]=NA
2.2.6 聚类法处理异常值
聚类法通常用来处理离群点,离群点是一种特殊的异常值,异常值一般针对单一变量,而离群点则是综合考虑之后的异常值,方法是:先对数据标准化——聚类——求每一类每一指标的均值点——每一类每一指标生成一个矩阵——计算欧式距离——画图判断。具体的聚类操作在聚类分析一章中有详细讲到。
3 参考文献
[1]张良均. R语言数据分析与挖掘实战[M]. 机械工业出版社, 2015.