筛选行:逻辑掩码
在 R 中筛选数据框,就是把条件放在方括号的行位置上:df[condition, ]。条件会求值为一个由 TRUE/FALSE 组成的向量,每行一个,R 保留为 TRUE 的行:
从里往外读 scores[scores$score > 80, ]:scores$score > 80 生成 TRUE FALSE TRUE TRUE FALSE,方括号保留第 1、3、4 行。末尾的逗号不能省。 数据框的方括号接受 [rows, columns];列的位置留空表示“所有列”。忘了逗号,你索引的就变成了列,这是 R 初学者最常犯的错误之一。
注意打印出来的行号是 1 3 4,而不是 1 2 3:筛选会保留原来的行标签。这没什么害处,但常常让人意外。
多个条件:& 和 |(不是 &&)
用 &(与)和 |(或)组合条件。每个条件都要写成完整的比较:
陷阱在于:R 还有 && 和 ||,但在这里它们和 &、| 不能互换。双写形式只适用于单个值(它们是为 if 条件准备的);传入整列时,新版 R(4.3 及以上)会报类似 'length = 5' in coercion to 'logical(1)' 的错误并停止,而旧版 R 会悄悄只比较第一行,这可能更糟。在方括号里,永远用单个的 & 和 |。两者的完整区别请看运算符文档。
%in%:匹配一组值
当某列应该匹配多个值中的任意一个时,不要用 | 串起一堆 ==,而要用 %in%:
只要 x 是 set 中的某个值,x %in% set 就返回 TRUE。它比 status == "pending" | status == "shipped" 更好读,能扩展到任意数量的值,取反也很干净:!(orders$status %in% c("refunded"))。
选择列
方括号的列位置可以接受列名或位置:
有两点要注意。按名称选择,即使列的顺序变了也不受影响;按位置选择(scores[, c(1, 3)]),一旦有人插入一列就会出错。另外,只选一列时返回的是普通向量,而不是数据框,所以第二个 print() 显示的是 91 88,外面没有表格。如果需要保持数据框的形状,就加上 drop = FALSE:scores[, "score", drop = FALSE]。
subset():友好的 base R 一行写法
base R 自带一个包装函数,一次可读的调用就能同时处理行和列,不需要 $,也不用操心逗号:
在 subset() 里你直接写裸列名(score,而不是 scores$score),它会在数据框中对这些名字求值。这叫作非标准求值,它有一个文档中写明的注意事项:它在运行时解析名称的方式,在你自己写的函数里会出问题(函数中名为 score 的变量可能被名为 score 的列遮蔽)。经验法则直接来自 ?subset:交互使用时很好,编程时要避免,那时就用方括号索引。
NA 陷阱
和 NA 比较的结果是 NA,而不是 FALSE;而方括号筛选会把掩码为 NA 的行保留为全是 NA 的行:
第一个结果中有一行全是 NA 的垃圾行,因为第 2 行的条件既不是 TRUE 也不是 FALSE。解决方法是显式要求 !is.na()。subset() 和 dplyr 的 filter() 都会悄悄丢掉条件为 NA 的行,这很方便,但你要知道它在这样做。NA 为什么会这样传播,请看缺失值文档。
dplyr 的写法:filter() 和 select()
dplyr 包把这项工作拆成两个动词:filter() 处理行,select() 处理列,直接写裸列名,不用一遍遍重复 df$(下面是静态代码,在线环境只能运行 base R):
library(dplyr)
scores |> filter(score > 80)
scores |> filter(score > 80, team == "red") # comma = AND
scores |> filter(status %in% c("pending", "shipped"))
scores |> select(name, score)
scores |> filter(team == "red") |> select(name, score)
filter() 会自动丢掉条件为 NA 的行,并且总是返回数据框(从不返回单纯的向量),上面 base R 的两个坑都被它消除了。代价是多了一个包依赖,以及和 subset() 一样的非标准求值注意事项。所有动词的完整介绍请看 dplyr 入门。
要点总结
df[condition, ]是最基本的写法,而且逗号必不可少。- 用单个的
&和|组合条件;&&用于向量会报错。 - 要匹配一组值时,
%in%比串起来的==更好。 - 按名称选择列,并记住单独一列会退化成向量,除非加上
drop = FALSE。 subset()是交互使用时顺手的一行写法;方括号则适合在程序中使用。- 在方括号中和
NA比较会产生幽灵行,要用!is.na()防范。
下一篇:添加和变换列,包括 df$new <- ...、ifelse() 和 dplyr 的 mutate()。
常见问题
在 R 中怎么筛选数据框的行?
把一个逻辑条件放在方括号的行位置上:df[df$score > 80, ]。条件会生成一个 TRUE/FALSE 向量,R 保留为 TRUE 的行。逗号很重要,它把行筛选和(空的)列筛选分开。subset(df, score > 80) 能用更少的代码做同样的事。
在 R 中怎么按多个条件筛选?
用 &(与)和 |(或)组合条件:df[df$score > 80 & df$team == "red", ]。要用单个的 &,不要用 &&:双写形式只适用于单个值,在新版 R 中用于向量会报错。
R 中 subset() 和方括号筛选有什么区别?
两者保留的行相同,但有两点不同:subset() 会悄悄丢掉条件为 NA 的行(方括号则会把它们保留为全是 NA 的行);subset() 使用非标准求值,你可以直接写裸列名,交互使用时很方便,但在你自己写的函数里不可靠。
怎么筛选某列匹配一组值的行?
用 %in%:df[df$team %in% c("red", "blue"), ] 保留 team 是所列任一值的行。它可以代替一串用 | 连接的 == 比较,而且和 == 不同,它从不返回 NA。