第 44 章 tidyverse中的缺失值

今天我们聊聊数据处理中的缺失值。

44.1 什么是缺失值?

我们先导入企鹅数据

library(tidyverse)

penguins <- read_csv(here::here("demo_data", "penguins.csv"))
penguins
## # A tibble: 344 × 8
##   species island    bill_length_mm bill_depth_mm
##   <chr>   <chr>              <dbl>         <dbl>
## 1 Adelie  Torgersen           39.1          18.7
## 2 Adelie  Torgersen           39.5          17.4
## 3 Adelie  Torgersen           40.3          18  
## 4 Adelie  Torgersen           NA            NA  
## 5 Adelie  Torgersen           36.7          19.3
## 6 Adelie  Torgersen           39.3          20.6
## # … with 338 more rows, and 4 more variables:
## #   flipper_length_mm <dbl>, body_mass_g <dbl>,
## #   sex <chr>, year <dbl>

我们看到第4行第3列开始,出现若干NA,这里的NA就是缺失值NA的意思就是 not available.

注意区分 “NA” 和 NA

  • “NA” 有引号的是字符串
  • NA 是R里的特殊标记

在R console中执行 ?"NA",我们看到第一行

NA is a logical constant of length 1 which contains a missing value indicator.

也就说,NA代表了数据缺失的一种逻辑状态(另外两个逻辑值是TRUE, FALSE),NA有两层含义:

  • 它是逻辑值
  • 代表着缺失值

44.2 有关NA的计算

  • 数值运算时

一般情况下,与 NA 的数值计算结果也是 NA

c(NA, 1) + 2
## [1] NA  3
  • 逻辑运算时

逻辑运算中TRUE为真,FALSE为假,而NA会被认为未知(即真假难辨)7

isTRUE(NA)
## [1] FALSE
## [1] FALSE

既不是真,也不是假,真假难辨。因此,在逻辑运算时,可以按下表指引

TRUE NA FALSE
不能确定真假
# Some logical operations do not return NA
c(TRUE, FALSE) & NA
## [1]    NA FALSE
c(TRUE, FALSE) | NA
## [1] TRUE   NA

可以看到,TRUE & NA 的结果为 NA(而不是FALSE),是因为NA的意思是“不能确定真假”,即有可能真也有可能假,介于真和假之间。因此TRUE 与 NA的逻辑和(即TRUE & NA)返回NA;而FALSE 与 NA的逻辑和(即FALSE & NA) 则返回FALSE。逻辑或的情形也是类似的。

44.3 如何判断NA?

找出数据中的缺失值,可以用is.na()函数

c(1, 2, NA, 4) %>% is.na()
## [1] FALSE FALSE  TRUE FALSE

44.4 强制转换

前面提到 NA 是一个与TRUEFALSE并列的逻辑值,比如

c(TRUE, FALSE, NA) %>% class()
## [1] "logical"

它的结果变成了”logical”.

但如果 NA 放在数值型的向量中,

c(1, 2, NA, 4) %>% class()
## [1] "numeric"

它的结果却变成了”numeric”

如果 NA 放在字符串的向量中,

c("1", "2", NA, "4") %>% class()
## [1] "character"

它的结果却变成了”character”

为什么会出现这种诡异的现象?究其原因,还在于NA的属性上,代表数据缺失的逻辑值,即数据类型是逻辑型。

c(TRUE, NA, FALSE) 
## [1]  TRUE    NA FALSE
c(TRUE, NA, FALSE) %>% class()
## [1] "logical"

在第 4 章中,我们提到,把不同类型的数据用c()组合成向量时,因为c() 函数要求数据类型必须一致,因此就会发生强制转换。比如当逻辑型变量和数值型变量组合在一起时,逻辑型会强制转换成数值型

c(1, 2, TRUE, 4) 
## [1] 1 2 1 4
c(1, 2, TRUE, 4) %>% class()
## [1] "numeric"

TRUE会转换成1,FALSE会转换成0. 那么此时逻辑型的 NA 会转换成数值型的 NA_real_

逻辑型 转换成数值型
TRUE 1
NA NA_real_
FALSE 0
c(1, 2, NA, 4) 
## [1]  1  2 NA  4
c(1, 2, NA, 4) %>% class()
## [1] "numeric"
c(1, 2, NA_real_, 4)
## [1]  1  2 NA  4
c(1, 2, NA_real_, 4) %>% class()
## [1] "numeric"

当逻辑型变量和字符串型变量组合在一起时,逻辑型会强制转换成字符串型

逻辑型 转换成字符串型
TRUE “TRUE”
NA NA_character_
FALSE “FALSE”
c("1", "2", TRUE, "4")
## [1] "1"    "2"    "TRUE" "4"
c("1", "2", NA, "4")
## [1] "1" "2" NA  "4"
c("1", "2", NA_character_, "4") 
## [1] "1" "2" NA  "4"

除了逻辑型NA, 数值型NA_real_, 字符串型NA_character_外, 还有整数型NA_integer_, 和复数型NA_complex. 我们再看下面的例子

c(TRUE, NA) %>%
  purrr::map(., ~is.logical(.))
## [[1]]
## [1] TRUE
## 
## [[2]]
## [1] TRUE
c("a", NA, NA_character_) %>% 
  purrr::map(., ~is.character(.))
## [[1]]
## [1] TRUE
## 
## [[2]]
## [1] TRUE
## 
## [[3]]
## [1] TRUE
c(123, NA, NA_real_) %>% 
  purrr::map(., ~is.numeric(.))
## [[1]]
## [1] TRUE
## 
## [[2]]
## [1] TRUE
## 
## [[3]]
## [1] TRUE
c(NA_real_, NA_complex_, NA_character_, NA_integer_, NA) %>% # coercion to character type
  purrr::map(., ~is.character(.))
## [[1]]
## [1] TRUE
## 
## [[2]]
## [1] TRUE
## 
## [[3]]
## [1] TRUE
## 
## [[4]]
## [1] TRUE
## 
## [[5]]
## [1] TRUE

44.5 如果统计有多少NA?

在实际的数据处理中,没有人愿意把问题搞复杂,一般情况下会先预处理,比如,剔除掉或者用其他值替换掉。不管是一删了之,还是采用插值替换,都有必要了解下数据中多少NA,这样才决定采用什么样的预处理办法。

常用的方法:

先用is.na()判断出是否为缺失值,缺失值是TRUE,不是缺失值为FALSE;然后TRUE/FALSE转换成数值,即TRUE -> 1; FALSE -> 0;最后把所有的1加起来,就知道数据中有多少个缺失值。

具体代码为

c(1, 2, NA, 4) %>% is.na() %>% as.integer() %>% sum()
## [1] 1

偷懒可以这样写

c(1, 2, NA, 4) %>% is.na() %>% sum()
## [1] 1

当然也可以自定义一个函数,

sum_of_na <- function(x){
  sum(is.na(x))
}

c(1, 2, NA, 4) %>% sum_of_na()
## [1] 1

44.6 应用到tidyverse中

回到本章开始的企鹅数据

penguins$bill_length_mm %>% sum_of_na()
## [1] 2

用到dplyr函数中

penguins %>% summarise(
  N1 = sum_of_na(bill_length_mm),
  N2 = sum_of_na(bill_depth_mm)
)
## # A tibble: 1 × 2
##      N1    N2
##   <int> <int>
## 1     2     2

一次性统计所有列

penguins %>% summarise(
  across(everything(), sum_of_na)
)
## # A tibble: 1 × 8
##   species island bill_length_mm bill_depth_mm
##     <int>  <int>          <int>         <int>
## 1       0      0              2             2
## # … with 4 more variables: flipper_length_mm <int>,
## #   body_mass_g <int>, sex <int>, year <int>

更偷懒的办法,也更直观(再次感受到R的美!)

penguins %>% summarise(
  across(everything(), ~sum(is.na(.x)))
)
## # A tibble: 1 × 8
##   species island bill_length_mm bill_depth_mm
##     <int>  <int>          <int>         <int>
## 1       0      0              2             2
## # … with 4 more variables: flipper_length_mm <int>,
## #   body_mass_g <int>, sex <int>, year <int>

数据框的一列中每个元素的数据类型是要求相同的,这是构建数据框的基本要求。因此,在dplyr中mutate()函数创建数据框的新列时, 这一列的元素必须是同一种类型,如果遇到新列中包含NA,也要确保NA的类型与其它元素的类型要一致,比如其它元素是字符串,那么就应该使用字符串类型的缺失值,即NA_character_.

我来看下面这个例子:

d <- tibble(x = c(1, 3, 6, NA, 8, NA))
d
## # A tibble: 6 × 1
##       x
##   <dbl>
## 1     1
## 2     3
## 3     6
## 4    NA
## 5     8
## 6    NA
d %>% mutate(
  is_even = case_when(
    x %% 2 == 0 ~ "even",
    x %% 2 == 1 ~ "not even",
    TRUE ~ NA                 # wrong
  )
)

上面这个代码中,本意是希望构建一个新列存储(“even”, “not even”)字符串;而NA是逻辑型的,类型不一致,因此会报错。 正确的写法是使用NA_character_

d %>% mutate(
  is_even = case_when(
    x %% 2 == 0 ~ "even",
    x %% 2 == 1 ~ "not even",
    TRUE ~ NA_character_
  )
)
## # A tibble: 6 × 2
##       x is_even 
##   <dbl> <chr>   
## 1     1 not even
## 2     3 not even
## 3     6 even    
## 4    NA <NA>    
## 5     8 even    
## 6    NA <NA>

44.7 思考

44.8 更多

注意区分NA 和 Inf, NaN, NULL

  • Inf = 无穷大,比如 pi / 0 %>% is.infinite()
  • NaN = 不是一个数(Not a Number), 比如 0 / 0 %>% is.nan(), sqrt(-1) %>% is.nan()
  • NULL = 空值,比如 c() %>% is.null()