Menu

R 数据筛选与取子集(subset、[ ]、dplyr filter)

保留你想要的行和列的所有方法:方括号加逻辑掩码、一行搞定的 subset()、%in%、NA 陷阱,以及 dplyr 的 filter() 和 select()。

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

筛选行:逻辑掩码

在 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。

Coddy 编程语言插图

和 Coddy 一起学编程

开始