HOWTO · R

使用 Dplyr 从数据框中删除多列

本文展示了如何使用 dplyr 从数据框中删除多个列。示例代码演示了如何使用不同的选择辅助函数和运算符通过一行代码删除多个列。

本页内容

要使用 dplyr 删除多列,可在 dplyr::select() 中使用补集运算符 -(或 !),保留除所选删除列以外的所有列。

如何使用 dplyr 删除多列

如果要删除的列固定,可直接按名称选择;如果名称存储在字符向量中,可用 all_of() 或 any_of();删除连续列可用范围;按命名模式选择可用辅助函数;根据数据规则选择则可用 where()。将变换后的数据用于后续操作前,请检查结果中的列名。

设置 R 会话和示例数据

dplyr 提供了 select() 以及下文使用的 tidyselect 辅助函数。在 select() 中,不加引号的列名是表达式,而存储在对象中的列名必须是字符字符串。示例展示 dplyr 语法;下面这个独立的 base R 示例也演示了相同的按名称排除方式,无需安装程序包。

运行此示例:

本节中的代码以及显示的标签或输出仍为英文。

records <- data.frame(
  id = 1:3,
  name = c("Ada", "Lin", "Sam"),
  score = c(91, 84, 88),
  debug_note = c("first", "second", "third"),
  group = c("A", "B", "A")
)

to_remove <- c("id", "debug_note")
remaining <- records[setdiff(names(records), to_remove)]
cat(paste(names(remaining), collapse = ", "), "\n", sep = "")
name, score, group

直接按名称删除列

如果要删除的列已提前确定,可在 select() 中写出不加引号的列名。例如,select(records, -c(id, debug_note)) 会删除这两列并保留其余列。也可以写成 select(records, -id, -debug_note);两种写法表达的是相同的排除操作。

补集也可以写成并集的补集:select(records, -(id | debug_note))。运算符 | 将多个选择合并,前面的 - 则排除合并后的列集合。选择最容易清楚表达目标列集合的写法即可。也可以按位置排除,例如 select(records, -c(1, 4)),但位置取决于列顺序;如果输入数据结构可能变化,优先按名称选择。

使用字符向量删除列

如果列名来自配置、用户选择或先前的计算,请将它们存为字符串,再通过辅助函数传入。select(records, -all_of(to_remove)) 要求所有指定名称都存在。当缺失名称意味着预期结构与实际结构不一致时,这种写法很有用。

如果某些名称可能不存在,而目标是“删除其中实际存在的列”,请使用 select(records, -any_of(to_remove))。any_of() 能让删除操作适应数据结构变化,但不会删除其他列。要让数据管道可靠,应根据缺少名称究竟是问题还是正常情况来选择。

按范围删除列

冒号运算符会选择一个包含两端的连续序列。select(records, -(name:score)) 会按照数据当前的列顺序,排除从 name 到 score 的列。select(records, -(2:4)) 则按位置执行相同操作。当范围端点稳定时,按名称指定范围很方便;按位置指定范围较简洁,但列重新排序后可能会选中不同的列。这两种语法都不表示按字母顺序排列。

按名称模式删除列

使用 starts_with()、ends_with() 或 contains(),按共同前缀、后缀或子字符串选择列名,再对选择结果取反。下面这个独立数据框包含各辅助函数所需的列名:

本节中的代码以及显示的标签或输出仍为英文。

pattern_records <- data.frame(
  id = 1:2,
  name = c("Ada", "Lin"),
  raw_payload = c("x", "y"),
  tmp_cache = c("p", "q"),
  score_old = c(7, 8),
  temp_flag = c(TRUE, FALSE),
  measure_1 = c(10, 20),
  measure_2 = c(30, 40),
  group = c("A", "B")
)

列名为 id、name、raw_payload、tmp_cache、score_old、temp_flag、measure_1、measure_2 和 group 时,select(pattern_records, -starts_with("raw_")) 会保留 id、name、tmp_cache、score_old、temp_flag、measure_1、measure_2 和 group。select(pattern_records, -starts_with(c("raw_", "tmp_"))) 还会删除 tmp_cache,保留 id、name、score_old、temp_flag、measure_1、measure_2 和 group。select(pattern_records, -ends_with("_old")) 会保留 id、name、raw_payload、tmp_cache、temp_flag、measure_1、measure_2 和 group;select(pattern_records, -contains("temp")) 会保留 id、name、raw_payload、tmp_cache、score_old、measure_1、measure_2 和 group。

这些辅助函数默认忽略大小写;若要区分大小写,可设置 ignore.case = FALSE。要使用正则表达式,可写成 select(pattern_records, -matches("^measure_[0-9]+$"));这会删除 measure_1 和 measure_2,保留 id、name、raw_payload、tmp_cache、score_old、temp_flag 和 group。该模式匹配以 measure_ 开头、后接一个或多个数字且不含其他字符的名称。matches() 灵活性较高,但只有在所有维护代码的人都能看懂所用模式时才适合使用。

按数字后缀范围删除列

对于前缀一致且带有数字后缀的列名,num_range() 可以选择一段后缀序列,无需逐一写出完整名称。对于上文列出的 pattern_records 列,select(pattern_records, -num_range("measure_", 1:2)) 会删除 measure_1 和 measure_2,保留 id、name、raw_payload、tmp_cache、score_old、temp_flag 和 group。第一个参数是共同前缀,第二个参数是数字序列。如果后缀采用固定宽度补零格式,例如 measure_01,请设置 width,如 width = 2。这个辅助函数处理的是数字后缀;它不会根据列中存储的值进行选择。

按谓词删除列

where() 会对每一列应用谓词,并选择返回 TRUE 的列;对选择结果取反即可删除这些列。例如,select(records, -where(is.character)) 会删除所有字符列。如果要删除的列由某种数据属性决定,而不是由特定名称决定,可以使用这种方法。

谓词必须适用于它可能检查的每一列。mean() 这样的数值汇总函数不适用于任意混合类型的数据,过于宽泛的规则也可能删除超出预期的列。如果只想删除部分数值列,可以将类型检查与适用于这些列的条件结合,或者直接选择具体列名。

排查 select() 问题并获取帮助

如果不带命名空间的 select() 调用行为异常,可能是其他已加载的程序包屏蔽了它。试试 dplyr::select(data, ...);如果这样能正常工作,就使用命名空间前缀,或调整程序包的加载顺序,以调用预期的函数。

dplyr select() 参考文档介绍了列选择,并链接到相关辅助函数。tidyselect 选择语言参考说明了选择表达式和运算符。在 R 中查看帮助时,可使用 ?select,或在 Help 面板中搜索不带括号的 select。

一般来说,选择一种清晰且适用的名称、向量、范围、模式或谓词来描述要删除的列,然后取其补集。这样,代码逻辑会明确体现最终保留的列,也更容易在数据结构变化时调整选择。