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 | 答案无定义的数学运算 | 1 | 0 / 0、log(-1) |
Inf | 超出表示范围的数 | 1 | 1 / 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 出现在删除列表元素时,或者作为空的返回值。