欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

R极简教程-8:缺失值与异常值

程序员文章站 2022-07-14 20:17:31
...

OK,数据都进来了,下一步要做的是查看数据质量怎么样?尤其要关注的两个问题是,有没有缺失值和异常值。

缺失值

这个很好理解,就是数据不见了呗。比如采集了5000行500列数据,其中某几个数据因为采集或者记录原因失败了,就造成了缺失值。缺失值在R语言中一般用NA来代替。(比如读取文件的时候,如果有些行有些列没有数值,就会自动填为NA)。

R使用NA(不可得)代表缺失值、NaN(不是一个数)代表不可能的值、Inf和-Inf代表正无穷和负无穷,函数is.na()、is.nan()、is.infinite()可分别用于识别缺失值、不可能值和无穷值。

再读取了一批文件以后,我们可以用is.na()函数来查看缺失值:

> A <- matrix(1:36,6,6)
> A
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    7   13   19   25   31
[2,]    2    8   14   20   26   32
[3,]    3    9   15   21   27   33
[4,]    4   10   16   22   28   34
[5,]    5   11   17   23   29   35
[6,]    6   12   18   24   30   36
> A[1,3] <- NA
> A[5,2] <- NA
> A[6,6] <- NA
> A
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    7   NA   19   25   31
[2,]    2    8   14   20   26   32
[3,]    3    9   15   21   27   33
[4,]    4   10   16   22   28   34
[5,]    5   NA   17   23   29   35
[6,]    6   12   18   24   30   NA
> is.na(A)
      [,1]  [,2]  [,3]  [,4]  [,5]  [,6]
[1,] FALSE FALSE  TRUE FALSE FALSE FALSE
[2,] FALSE FALSE FALSE FALSE FALSE FALSE
[3,] FALSE FALSE FALSE FALSE FALSE FALSE
[4,] FALSE FALSE FALSE FALSE FALSE FALSE
[5,] FALSE  TRUE FALSE FALSE FALSE FALSE
[6,] FALSE FALSE FALSE FALSE FALSE  TRUE
> 

再上面的程序中,我们首先建立了一个矩阵A,然后在其中人工加入了三个缺失值。然后用is.na()可以判断,每一个数据是不是缺失值。

在这里which函数是非常有用的。(其实which函数再R语言中是一个非常重要的函数。)可以用which看出是那几个位置有错误。

> which(is.na(A))
[1] 11 13 36

我们定义的是一个矩阵,但是which出来的结果确实数字,这是因为which会将Matrix或者DataFrame展开成Vector,然后计算缺失值。就类似于as.vector(A)一样。如果想要确切地知道哪行哪列的数据是缺失值,可以在which函数中加入参数:

> which(is.na(A),arr.ind = T)
     row col
[1,]   5   2
[2,]   1   3
[3,]   6   6
> 

可以看出,我们准确地找到了NA值所在的行列。

缺失值可视化

简而言之就是,想要直观地看看你的数据中缺失值在哪里。最直观的看法就是使用热图(heatmap)(不是地图上红一块绿一块那种热力图!),我之前的教程里用到的pheatmap函数就是这方面的好工具。

在之前,我们用命令is.na()的时候,其实已经看出缺失值的,但是目前我们数据缺省值很少,如果缺失值多怎么办?

> #随机产生一个40行50列的矩阵
> A <- matrix(rnorm(2000,0,1),40,50)
> #再其中增加200个NA值。
> A[sample(1:2000,200)] <- NA
> #生成一个全部是1,维度与A相同的矩阵
> B <- matrix(1,nrow=nrow(A),ncol=ncol(A))
> #将B中A的NA值,设置为0
> B[is.na(A)] <- 0
> #用pheatmap函数plot所有的缺失值
> pheatmap(B,cluster_rows = F,cluster_cols = F)

R极简教程-8:缺失值与异常值
上图中的蓝点就是这批数据中的缺失值。

下面的话出自某网络博客。我觉得说的很对:

识别缺失数据的数目、分布和模式有两个目的:分析生成缺失数据的潜在机制,评价缺失数据对回答实质性问题的影响。我们需要弄清楚以下几个问题:

缺失数据的比例多大?
缺失数据是否集中在少数几个变量上,或者广泛存在?
缺失是随机产生的吗?
缺失数据间的相关性或与可观测数据间的相关性,是否可以表明产生缺失值的机制?

这些就是不那么容易解决的问题了,比如说,每一列缺失数据比例多大,可以很简单的用一行代码算出来:

> colMeans(is.na(A))

至于第二个问题,缺失值是否存在某一些变量上,这样可以通过做barplot来看。

> barplot(colMeans(is.na(A)),names.arg = 1:50)

R极简教程-8:缺失值与异常值
可以看出,大部分的列都有缺失值(那当然,我们是随机生成的缺失值。)

缺失值处理

一般来说,处理缺失值有两种办法:直接把数据删掉,或者用补足。删掉的意思就是,如果某一行出现了缺失值,无论这一行中缺失值有多少,直接将其删除,整行删除。补充的意思是,用附近的一些数据的值,计算一个估计值去填补这一个数。

直接删除的优缺点分别是:方便快捷,代码好写,但是有可能太多信息都会被遗漏掉。
补足(impute):的优缺点分别是:尽量保持了信息,但是如果一行数据10个,有9个是NA,那么补出来的数据也不会有多高的质量。

所以,缺失值处理是一个比较需要经验的问题,一般来说我的处理办法是,如果样本数据很多,比如列有上百个,那就先对列做缺失值比例计算,将缺失达到10%的样本直接删除。然后再剩下的数据中,按行做缺失值比例调查,将比例高达一定阈值(比如20%)的删掉,然后对剩下的数据做impute过程。

impute过程可以使用imputeR包,其中的impute.knn()函数很好用:

完整代码如下:

> A <- matrix(rnorm(2000,0,1),40,50)
> A[sample(1:2000,200)] <- NA

> colRatio <- colMeans(is.na(A))
> col_remain <- which(colRatio < 0.2)
> length(col_remain)
[1] 49
> C <- A[,col_remain]
> 
> rowRatio <- rowMeans(is.na(C))
> row_remain <- which(rowRatio < 0.2)
> length(row_remain)
[1] 40
> 
> D <- C[row_remain,]
> dim(D)
[1] 40 49
> library(impute)
> Final <- impute.knn(D)

异常值

异常值顾名思义就是偏离了“寻常值”的数据。但是多“异常”的值才能成为异常值,这就得看研究项目而定了。有些时候,异常值才是一个科研项目中应该去研究的问题。一般来说,有一个想对通用的检测异常值的标准,就是均值±三倍标准差。这个很好理解,你的均值是数据大部分“寻常值”的所在位置,标准差是其差异程度。那么3倍标准差很明显就说明一个数据严重地偏离了均值了。

下面我们用代码来写一下:

A <- rnorm(1000,0,1)
outlier_above <- which(A > mean(A) + 3*sd(A))
outlier_below <- which(A < mean(A) - 3*sd(A))

R极简教程-8:缺失值与异常值

我们可以看到,在我随机生成了1000个随机数以后(正态分布,均值为0,标准差为1),只有4个数达到了异常值上届,也只有3个数达到了异常值的下届。

下面我们尝试把这些异常值画出来,其实代码特别简单:

boxplot(A)

R极简教程-8:缺失值与异常值
该图的中文名叫做箱线图,其中上下的小圆点就是异常值的意思。最上边的横线是上边界,最下边是下边界,中间的箱体,上边缘是上四分位数,下边缘是下四分位数,中间最粗的横线是中位数(不是均值)。

个人觉得,有一个特别简单找出异常值的办法,就是使用boxplot函数:

> tmp <- boxplot(A)
> tmp$out
 [1]  3.338426  2.854525  3.500505  3.071915  2.853435  3.009685 -3.315813 -3.564299
 [9] -3.374682  2.744321
> 

直接将boxplot存储成为一个变量tmp,tmp中的out子元素就是异常值。

对于异常值的处理也是花样繁多,有的人就直接把所有异常值设置成为NA,然后就回到了上边的缺失值部分。也有人保留他们,总之这方面算法很多,不一而足。异常值产生的原因很多,有很多时候,异常值其实不仅有意义,而且很重要:

比如,探查人流涌动,就是通过异常值来看那些地方突然人数激增?探查宇宙射线一类的科研,最关注的东西永远都是异常值。再大数定律横行其道的今天,几乎各种数据都是需要量化来达到规律总结的,这恰好令了“天鹅”一类事件越来越难以估计。所以对于异常值的处理和解读,其实正是数据分析人员的水平高下所在。

在这一部分,我简单介绍了一下R语言中的缺失值和异常值。