DMwR-note-01-预测海藻数量(二)
Tim Chen(motion$) Lv5

写在开头的话


上次的笔记讲了数据的读取,窥探和可视化,这次我们来分析一下数据当中的缺失值。对于缺失值的正确处理也是数据分析当中很重要的一步。

数据缺失


  • 数据缺失是一种非常普遍的情形,但是有些数据分析的方法无法对缺失值进行处理,这样就会带来问题。

  • 当我们处理含有缺失值的数据时,可以运用一下几种最常见的策略:

    • 将含有缺失值的案例剔除
    • 根据变量之间的相关关系填补缺失值
    • 根据案例之间的相似性填补缺失值
    • 使用能够处理缺失值数据的工具
  • 在开始之前,我们先在Rstudio中载入包和数据

    1
    2
    library(DMwR)
    data(algae)

将缺失部分剔除


  • 剔除缺失值的操作非常容易实现,特别当缺失值在数据当中所占的比例比较小的时候,这个选择比较合理。

  • 在剔除缺失值之前,我们勿急,最好先检查观测值,或者至少得到这些观测值的个数,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    algae[!complete.cases(algae),]
    season size speed mxPH mnO2 Cl NO3 NH4 oPO4 PO4 Chla a1
    28 autumn small high 6.80 11.1 9.000 0.630 20 4.000 NA 2.70 30.3
    38 spring small high 8.00 NA 1.450 0.810 10 2.500 3.000 0.30 75.8
    48 winter small low NA 12.6 9.000 0.230 10 5.000 6.000 1.10 35.5
    55 winter small high 6.60 10.8 NA 3.245 10 1.000 6.500 NA 24.3
    56 spring small medium 5.60 11.8 NA 2.220 5 1.000 1.000 NA 82.7
    57 autumn small medium 5.70 10.8 NA 2.550 10 1.000 4.000 NA 16.8
    58 spring small high 6.60 9.5 NA 1.320 20 1.000 6.000 NA 46.8
    59 summer small high 6.60 10.8 NA 2.640 10 2.000 11.000 NA 46.9
    60 autumn small medium 6.60 11.3 NA 4.170 10 1.000 6.000 NA 47.1
    61 spring small medium 6.50 10.4 NA 5.970 10 2.000 14.000 NA 66.9
    62 summer small medium 6.40 NA NA NA NA NA 14.000 NA 19.4
    63 autumn small high 7.83 11.7 4.083 1.328 18 3.333 6.667 NA 14.4
    116 winter medium high 9.70 10.8 0.222 0.406 10 22.444 10.111 NA 41.0
    161 spring large low 9.00 5.8 NA 0.900 142 102.000 186.000 68.05 1.7
    184 winter large high 8.00 10.9 9.055 0.825 40 21.083 56.091 NA 16.8
    199 winter large medium 8.00 7.6 NA NA NA NA NA NA 0.0
    a2 a3 a4 a5 a6 a7
    28 1.9 0.0 0.0 2.1 1.4 2.1
    38 0.0 0.0 0.0 0.0 0.0 0.0
    48 0.0 0.0 0.0 0.0 0.0 0.0
    55 0.0 0.0 0.0 0.0 0.0 0.0
    56 0.0 0.0 0.0 0.0 0.0 0.0
    57 4.6 3.9 11.5 0.0 0.0 0.0
    58 0.0 0.0 28.8 0.0 0.0 0.0
    59 0.0 0.0 13.4 0.0 0.0 0.0
    60 0.0 0.0 0.0 0.0 1.2 0.0
    61 0.0 0.0 0.0 0.0 0.0 0.0
    62 0.0 0.0 2.0 0.0 3.9 1.7
    63 0.0 0.0 0.0 0.0 0.0 0.0
    116 1.5 0.0 0.0 0.0 0.0 0.0
    161 20.6 1.5 2.2 0.0 0.0 0.0
    184 19.6 4.0 0.0 0.0 0.0 0.0
    199 12.5 3.7 1.0 0.0 0.0 4.9

    nrow(algae[!complete.cases(algae),])
    [1] 16
  • 函数complete.cases()产生一个布尔值向量,该向量的元素个数与algae数据框中的行数相同,如果数据框的相应行中不含NA值(即为一个完整的观测值),函数返回值就是TRUE。“!”逻辑非,因此上述指令显示了含有缺失值的水样记录。

  • 为了从数据框中剔除这16个样本,我们可以这样简单的输入:

    1
    algae1 <- na.omit(algae)
  • 这样我们就剔除了源数据中的所有含有缺失值的记录。但同时问题也浮出来了:数据分析的前提是有效数据越多越好。若果数据中含有缺失值的记录比例不低,那么这种做法会导致剩余数据的分析结果出现很大的偏差;或者另外一种情况,虽然缺失值的记录比例低,但是缺失值记录当中有很多是只是含有一个缺失值的,这样的剔除有点浪费数据,所以,下面我们利用函数找到缺失值记录当中含有缺失值最多的来进行剔除。

  • 例如,观察样本中的数据,我们可以看到第62条和第199条记录中的11个解释变量中有6个是缺失值。我们就来剔除它们。

1
algae2 <- algae[-c(62,199),]
  • 更进一步的,如果数据量大,单凭肉眼观察是不合理的,所以我们需要找出缺失值较多的记录的下标。下面的代码可以找出海藻数据集中每行数据的缺失值个数:
1
2
3
4
5
6
7
8
algae3 <- apply(algae, 1, function(x) sum(is.na(x)))
algae3
[1] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
[37] 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 2 2 2 2 2 2 2 6 1 0 0 0 0 0 0 0 0 0
[73] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
[109] 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
[145] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
[181] 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0
  • 函数apply()属于R中功能非常强大的一类函数。这类函数又称为元函数,它们可以在某些条件下对对象应用其他函数。对函数apply()而言,它可以把任何其他函数应用到一个多维对象的各个维度上。使用函数apply()时,它把一个函数应用到数据框的每一行。这个被应用的函数在apply()函数的第三个参数给出,对数据框的每一个行都分别调用该函数。

  • 在这个案例中我们使用一个临时函数。它只在调用apply()函数时才存在。另外,函数apply()的第三个也可以是一个“正常”函数的函数名。临时函数的功能室计算对象x中NA的数量。在R中逻辑值TRUE等于数值1,逻辑值FALSE等于0,这意味着当加一个布尔值向量时,得到向量中取值为TRUE的元素的个数。

  • 根据以上代码,可以编写一个程序找出algae中含有给定数目缺失值的行。在本书提供的添加包中有这个函数。

    1
    2
    manyNAs(algae, 0.2)
    [1] 62 199
  • 函数manyNAs()的功能是找出缺失值个数大于20%的行。在第二个参数中可以设置一个精确的列数作为界限。因此,用下面的代码就无须知道含有缺失值较多的行的具体数量:

1
2
algae4 <- algae[-manyNAs(algae),]

  • manyNAs()函数的第二个参数的默认值是0.2。

用最高频率值来填补缺失值


  • 填补含有缺失值的记录的另一个方法就是尝试找到这些缺失值最可能的值。同样的,这里有多种策略可供选择,不同策略对逼近程度和算法复杂度的权衡不同。

  • 填补缺失数据最简单和快捷的方法是使用一些代表中心趋势的值。代表中心趋势的值反映了变量分布的最常见值,因此中心趋势值是最自然的选择。有多个代表数据中心趋势的指标,例如,平均值、中位数、众数等。最合适的选择由变量的分布决定。对于接近正态分布来说,所有的观测值都较好地集中在平均值周围,平均值就是最佳的选择。然而,对于偏态分布,或者有离群值的变量来说,选择平均值不好。偏态分布的大部分值都聚集在变量分布的一侧,因此平均值不能作为最常见值的代表。另一方面,离群值(极值)的存在会扭曲平均值,这就导致了平均值不具有代表性的问题。因此,在对变量分布进行检查之前选择平均值作为中心趋势的代表是不明智的例如,某些R的绘图工具。对偏态分布或者有离群值的分布而言,中位数是更好的代表数据中心趋势的指标。

  • 比如,样本algae[48,]中的变量mxPH有缺失值。由于该变量分布近似正态分布,见DMwR-note-01-预测海藻数量(一),我们可以选用平均值来填补这个“洞”,计算方法如下:

    1
    2
    3
    temp <- algae
    temp[48,"mxPH"] <- mean(algae$mxPH, na.rm=T)

  • 这里,函数mean()计算数值向量的平均值,参数na.rm=T使计算时忽略缺失数据。

  • 大多数时候采用一次填补一列中的所有缺失值而不是像上面那样一行一行地逐个填补。以变量Chla为例,这个变量在第12行上有缺失值。另外,这也是平均值不能代表大多数变量值的一种情况。事实上,Chla的分布偏向较低的数值,并且它有几个极端值,这些都使得平均值不能代表大多数的变量值。因此,我们使用中位数来填补这一类的缺失值:

    1
    2
    3
    temp <- algae
    temp[is.na(algae$Chla), "Chla"] <- median(algae$Chla, na.rm=T)

  • 本书插件包中提供的函数centralInputation()可以用数据的中心趋势值来填补数据集的所有缺失值。岁数值型变量,该函数用中位数;对名义变量,它采用众数。该函数的应用如下:

    1
    2
    3
    4
    temp <- algae
    temp <- temp[-manyNAs(algae),]
    temp <- centralInputation(temp)

  • 使用无偏方法来寻找最佳数据填补值复杂,对于大型数据挖掘问题可能并不适用。

通过变量的相关关系来填补缺失值


  • 另一种获取缺失值较少偏差估计值的方法是探寻变量之间的相关关系。比如,听过变量值之间的相关关系,能够发现某变量与mxPH高度相关。这可以使我们得到含有缺失值的第48条样本更可能的填补值。这比之前使用平均值的方法将更胜一筹。

  • 应用如下命令来得到变量间的相关值:

    1
    2
    3
    temp <- algae
    cor(temp[, 4:18], use = "complete.obs")

  • 函数cor()的功能室产生变量之间的相关值矩阵(因为前3个变量时名义变量,所以计算相关值时不考虑它们)。设定参数use = “complete.obs”时,R在计算相关值时忽略含有NA的记录。相关值在1(或-1)周围表示相应的两个变量之间的强正(或负)线性相关关系。然后其他R函数可以得到变量间相关的近似函数形式,它可以让我们通过一个变量的值计算出另一个变量的值

  • 函数cor()的输出结果并不是很清晰,但可以通过symnum()来改善结果的输出形式,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
temp <- algae
symnum(cor(temp[, 4:18], use = "complete.obs"))
mP mO Cl NO NH o P Ch a1 a2 a3 a4 a5 a6 a7
mxPH 1
mnO2 1
Cl 1
NO3 1
NH4 , 1
oPO4 . . 1
PO4 . . * 1
Chla . 1
a1 . . . 1
a2 . . 1
a3 1
a4 . . . 1
a5 1
a6 . . . 1
a7 1
attr(,"legend")
[1] 0 ' ' 0.3 '.' 0.6 ',' 0.8 '+' 0.9 '*' 0.95 'B' 1
  • 这种用符号表示相关值的方法更为清晰,特别是对于大的相关矩阵。
  • 在本案例中,大多数变量之间是不相关的。然而,有2个例外:变量NH4和NO3之间,变量PO4和oPO4之间。后两者之间的相关值很高(大于0.9)。变量NH4和NO3之间的相关性不是特别明显(为0.72),因此根据他们来确定缺失数据是很危险的。此外,因为样本62和样本199有太多的变量含有缺失值,所以如果剔除它们,样本中的变量NH4和NO3就没有缺失值了。至于变量PO4和oPO4,它们之间相关性可以帮助填补这两个变量的缺失值。为了达到这个目标,我们需要找到这两个变量之间的线性关系,方法如下:
1
2
3
4
5
6
7
8
9
10
temp <- algae
temp <- temp[-manyNAs(temp),]
lm(PO4 ~ oPO4, data = temp)

Call:
lm(formula = PO4 ~ oPO4, data = temp)

Coefficients:
(Intercept) oPO4
42.897 1.293
  • 函数lm()可以用来获取形如Y=β0+β1χ1+…+βnχn的线性模型。上述结果得到的线性模型是:PO4 = 42.897 + 1.293 * oPO4。如果这两个变量不是同是有缺失值,那么通过这个模型计算这些变量的缺失值。

  • 在剔除样本62和样本199后,还剩下一个样本(样本28)在变量PO4上有缺失值,可以简单地使用上面的线性关系计算缺失值的填补值:

    1
    2
    temp <- algae
    temp[28, "PO4"] <- 42.897 + 1.293 * temp[28, "oPO4"]
  • 然而,为了说明这个方法,我们假设变量PO4有很多个缺失值。如何使用上述的线性关系计算所有的缺失值呢?最好的方法就是构造一个函数,它可以根据给定的oPO4的值就是PO4的值,然后对所有缺失值应用这个函数。

    1
    2
    3
    4
    5
    6
    7
    8
    temp <- algae
    temp <- temp[-manyNAs(temp),]
    fillPO4 <- function(oP) {
    if(is.na(oP))
    return(NA)
    else return(42.897+1.293*oP)
    }
    temp[is.na(temp$PO4), "PO4"] <- sapply(temp[is.na(temp$PO4),"PO4"], fillPO4)
  • 上面代码中创建了一个叫做fillPO4()的函数,该函数有一个参数来接受变量oPO4的值,通过这个值,根据模型计算对应的PO4的值。然后,将这个函数应用到变量PO4有缺失值的所有样本中。这个过程可以通过另外一个元函数sapply()来实现。函数sapply()的第一个参数是一个向量,第二个参数为一个函数,返回的结果是另一个向量,该向量和第一个参数有相同的长度,向量中的元素为sapply()中第一个向量中的每一个元素作为参数传递到第二个函数后返回的结果。这意味着sapply()的结果将是填补变量PO4缺失值的向量。

  • 对线性关系的研究使我们能够填充一些新的缺失值。然而,还有几个观测值含有缺失值。可以试探着探索案例数据中含有缺失值的变量和名义变量之间的关系。这可以通过应用R添加包lattice中的函数来绘制条件直方图来进行。代码如下:

    1
    2
    temp <- algae
    histogram(~mxPH|season, data = temp)

    Pot:

  • 上面代码绘制在不同季节变量mxPH的直方图。每个直方图对应于某个季节的观测值数据。注意,上图的季节顺序不是按照自然的时间顺序,可以转换数据框中因子季节标签的顺序,这样可以使图形中的季节值为自然顺序。代码如下:

    1
    2
    3
    temp <- algae
    temp$season <- factor(temp$season, levels = c("spring", "summer", "autumn", "winter"))
    histogram(~mxPH|season, data = temp)

    Pot:

  • 由图看出,季节对mxPH的值没有显著的影响。

通过探索案例之间的相似性来填补缺失值

  • 不同于探索数据集列(变量)之间的相关性,本节尝试使用行(观测值)之间的相似性来填补缺失值。我们可以使用这种方法来填补除去那两个含有太多NA值的样本外的其他缺失数据。

    1
    2
    temp <- algae
    temp <- temp[-manyNAs(temp),]
  • 本节所描述的方法假设如果两个水样是相似的,其中一个水样在某些变量上有缺失值,那么该缺失值很可能与另外一个水样的值是相似的。为了使用这种直观的方法,首先定义相似性的概念。相似性经常由描述观察值的多元度量空间的变量所定义。在文献中有许多度量相似性的指标,常用的就是欧氏距离。这个距离可以非正式地定义为任何两个案例的观测值之差的平方和。计算公式如下:

  • 下面描述的方法将使用这种度量来寻找与任何含有缺失值的案例最相似的10个水样,并用它们来填补缺失值。我们考虑两种应用这些值的方法。第一种方法简单地计算着10个最相近的案例的中位数并用这个中位数来填补缺失值。如果缺失值是名义变量(本案例的algae数据不存在这种情况),我们采用这10个最相似数据中出现次数最多的值(众数)。第二种方法采用这些相似数据的加权均值。权重的大小随着距待填补缺失值的个案的距离增大而减小。这里用高斯核函数从距离获得权重。如果相邻个案距待填补缺失值的个案的距离为d,则它的值在加权平均重的权重为:

  • 上面的方法可以通过本书添加包中的函数knnImputation()来实现。这个函数用一个欧氏距离的变种来找到距任何个案最近的k个邻居。这个变种的欧氏距离可以应用于同时含有名义变量和数值变量的数据集中。计算公式如下:

  • 其中δi是变量i的两个值之间的距离,即

  • 在计算距离时,一般要对数值变量进行标准化,即

  • 下面说明如何使用knnImputation()函数。

    1
    2
    temp <- algae
    temp <- knnImputation(temp, k -10)
  • 吐过用中位数来填补缺失值,可以使用如下代码

    1
    2
    temp <- algae
    temp <- knnImputation(temp, k = 10, meth = "median")
  • 总之,通过这些简单的操作,数据集中不在含有NA值(缺失值),为使用R的其他函数进行分析做好充分的准备工作。

后记


  • 本节主要讨论了数据缺失值的处理,缺失值少的时候可以直接删除,但是当缺失值比较多的时候,然后会影响到整个数据集的正确性时,就要考虑用相关性或相似性来处理了。
  • 本次博文是根据《数据挖掘与R语言(Luis Torgo著)》这本书的内容而写的笔记,本博文中所涉及的内容版权均归原作者所有。
  • 下节讨论的主题是数据的建模。
 评论