Menu

R 语言 NA:缺失值处理(is.na、na.rm、na.omit)

NA 表示“未知”,而且它会扩散到它接触的每一次计算中。如何用 is.na() 检测它,用 na.rm = TRUE 跳过它,以及有意识地删除或替换它。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

NA 表示“未知”,而且会扩散

真实的数据集总有空洞:问卷上没填的题目,传感器漏掉的读数。R 用 NA(not available,不可用)表示每一个空洞。关键的思维模型是:NA 不是零,不是空字符串,也不是某个特殊的数字。它的意思是“这里有一个值,但我们不知道它是多少”。

认真对待这一点,R 的行为就变得合乎逻辑了。一个未知的数加一等于多少?未知。4、8 和某个未知数的平均值是多少?未知,因为缺失的值可能是任何数,所以平均值也可能是任何数:

三行都打印 NA。这种传染性是特性,而不是 bug:R 拒绝悄悄地假装自己知道一个它并不知道的答案。悄悄跳过空白单元格的电子表格可能会掩盖半列数据都缺失的事实;R 则会迫使你注意到,并决定缺失值对你的分析意味着什么。本文余下的部分,讲的就是如何有意识地做出这个决定。

检测 NA:用 is.na(),永远不要用 ==

这是每个人都会掉进去的第一个陷阱。你想找出缺失值,于是写了一个比较,结果它不起作用:

x == NA 返回 NA NA NA,一个 TRUE 都没有。为什么?顺着“未知”的逻辑想一想:4 等于某个未知值吗?说不准,那个未知值可能就是 4。就连 NA 所在的位置,比较的结果也是 NA:一个未知值等于另一个未知值吗?未知。与 NA 的比较永远不可能返回 TRUE 或 FALSE,所以拿它做检查毫无用处;更糟的是,在 [ ] 中使用全是 NA 的掩码,选出来的东西也不是你想要的。

正确的工具是 is.na(),它就是为回答这个问题而设计的,会返回诚实的逻辑值:FALSE TRUE FALSE。在此基础上,有两个你会经常用到的惯用法:

sum(is.na(x)) 统计缺失值的个数(TRUE 求和时算作 1),拿到新数据集时,先对每一列都运行一遍。which(is.na(x)) 找出它们的位置。而 x[!is.na(x)] 只保留观测到的值:通过逻辑掩码手动删除,和一直以来的向量筛选写法一样。

在汇总中跳过 NA:na.rm = TRUE

你通常不需要手动删除 NA,因为 R 的汇总函数内置了一个出口:na.rm(NA remove,移除 NA)参数:

加上 na.rm = TRUE 后,函数会去掉缺失值,用剩下的值来计算:4 和 8 的平均值是 6。sum()、sd()、median()、min()、max()、var(),整个描述性统计函数家族都接受这个参数。

注意它的默认值是 FALSE。这是 R 在替你坚持立场:它希望忽略缺失数据是你明确写下的选择,而不是一个悄无声息的默认行为。当你写下 na.rm = TRUE 时,你是在声明“这里的缺失值可以放心忽略”。如果传感器只是随机漏掉了几个读数,这是对的;如果是收入最低的人跳过了收入问题,这就是危险的错误。这个参数让你为这个判断负责。

删除不完整的行:na.omit() 与 complete.cases()

在数据框中,缺失值存在于单元格里,但分析往往是逐行进行的,所以常见的操作是删除含有任何 NA 的行:

na.omit(df) 返回删掉所有至少含有一个 NA 的行之后的数据框,这里只有 Rosa 保留了下来。complete.cases(df) 返回同一思路背后的逻辑行掩码(TRUE FALSE FALSE),用它来索引会得到相同的结果,但有两个优点:你可以先数一数将要丢掉多少行(sum(!complete.cases(df))),还可以限定哪些列才重要:df[complete.cases(df[, "age"]), ] 只删除缺少 age 的行,即使 Mia 的分数未知,她也会被保留。

这种限定列的写法比看上去更重要:对一个很宽的数据框使用 na.omit(),可能会因为那些你根本没打算分析的列中的 NA,悄悄丢掉大部分行。在删除之前,先弄清楚要删掉多少行,以及为什么删。

替换 NA

有时正确的做法是填补空洞,而不是删除行。惯用写法是把 is.na() 和赋值结合起来:

把它读作:“在 visits 缺失的位置,填上 0。”当 NA 实际上代表一个已知的值时,这样做恰好是合理的:一位没有访问记录的客户,确实就是访问了零次。

但要老实判断什么时候才是这样。如果 NA 的意思是“我们没能测量到”,用 0 替代就是在捏造数据:把缺失的考试分数换成 0,会把平均分拉低,好像那些学生都考了零分一样,而事实上你并不知道他们考了多少。比较一下上面的平均值(2.4)和原始数据用 na.rm 求得的平均值(4):同样的数据,却得出不同的结论。用平均数或中位数替换造成的扭曲较小,但仍然会低估变异性。规则是:只有当你能用大白话说清楚,为什么缺失的那一项实际上就是这个值时,才去填补它。否则就保留 NA,并使用 na.rm。“未知”往往是数据集中最真实的那个值。

NA、NULL、NaN 与 Inf

R 有四个看起来很像、含义却截然不同的特殊值:

值含义长度常见来源
NA值存在,但未知1(占据一个位置)缺失数据
NULL根本没有对象0(不占位置)被删除的列表元素、空结果
NaN答案无定义的数学运算10 / 0、log(-1)
Inf超出表示范围的数11 / 0、溢出

在实践中真正重要的区别是:NULL 在向量中会消失(c(1, NULL, 3) 只有两个元素,它无法表示一个缺失的观测值),而 NA 会占住自己的位置。NaN 算作缺失(is.na(NaN) 为 TRUE,所以 na.rm 也会移除它),但反过来不成立:is.nan(NA) 为 FALSE。而 Inf 不是缺失值,它是一个真实的、可以比较的数(Inf > 1e300 为 TRUE),所以 na.rm 不会移除它;要筛选出普通的数,请用 is.finite()。

补充一点:NA 悄悄地分为几种带类型的版本(NA_integer_、NA_real_、NA_character_),这样它就能放进任何向量,而不会破坏“只有一种类型”的规则。你很少需要亲手输入它们,但会在包的代码和 dplyr 的错误信息中看到它们。

要点回顾

  • NA 表示“未知”,而未知是会传染的:任何涉及 NA 的计算都会返回 NA,这是有意设计的。
  • 用 is.na() 检测,永远不要用 == NA;用 sum(is.na(x)) 统计空洞。
  • na.rm = TRUE 让汇总函数跳过 NA:这是在每次调用时明确做出的决定,表示这些缺失值可以忽略。
  • na.omit() 整行删除不完整的行;complete.cases() 给你掩码,可以计数,也可以限定在重要的列上。
  • 只有当你能说清楚缺失值实际上是什么时,才替换 NA(x[is.na(x)] <- value)。
  • NA ≠ NULL ≠ NaN ≠ Inf:分别是缺失值、不存在的对象、无定义的数学运算、无界的数。

下一篇:函数,把你的逻辑打包成可复用、有名字的模块。

常见问题

R 语言中 mean() 为什么返回 NA?

因为向量中至少有一个值是 NA,而 NA 具有传染性:只要有任何一个输入是未知的,R 就会说答案也是未知的。传入 na.rm = TRUE,即 mean(x, na.rm = TRUE),就能计算现有值的平均数。大多数汇总函数(sum、sd、median、min、max)都接受同样的参数。

R 语言怎么检查 NA?

用 is.na(x),它会在每个缺失值的位置返回 TRUE。千万不要用 x == NA 来检查:任何东西和一个未知值比较,结果都是 NA,而不是 TRUE 或 FALSE,所以这个检查会悄悄失效。sum(is.na(x)) 统计缺失值的个数;which(is.na(x)) 找出它们的位置。

R 语言怎么删除数据框中含有 NA 的行?

na.omit(df) 会删除每一个至少含有一个 NA 的行。如果想要更多控制,complete.cases(df) 返回一个标记完整观测行的逻辑向量,所以 df[complete.cases(df), ] 能显式地完成同样的事情;你还可以只针对重要的列使用它,例如 df[complete.cases(df[, c("age", "score")]), ]。

R 语言中 NA 和 NULL 有什么区别?

NA 是一个缺失的值:它在向量中占据一个位置,长度为 1。NULL 表示没有对象:它的长度为 0,放进向量时会直接消失:c(1, NULL, 3) 只有两个元素。缺失的数据点用 NA;NULL 出现在删除列表元素时,或者作为空的返回值。

Coddy 编程语言插图

和 Coddy 一起学编程

开始