dplyr中的列式操作:across()函数详解

dplyr中的列式操作:across()函数详解

【免费下载链接】dplyr 【免费下载链接】dplyr 项目地址: https://gitcode.com/gh_mirrors/dpl/dplyr

还在为需要对数据框的多个列重复相同操作而烦恼吗?复制粘贴不仅枯燥乏味,还容易出错。dplyr的across()函数正是解决这一痛点的利器,它让你能够轻松地对多个列应用相同的转换操作。

通过本文,你将掌握:

  • across()函数的核心语法和工作原理
  • 多种列选择方式和函数应用技巧
  • 输出列命名的灵活控制方法
  • if_any()if_all()的配合使用
  • 实际场景中的最佳实践和常见陷阱

across()函数基础

across()函数是dplyr包中用于列式操作的核心函数,它允许你在summarise()mutate()等数据掩码函数中使用select()的选择语义。

基本语法结构

across(.cols, .fns, ..., .names = NULL, .unpack = FALSE)
参数说明示例
.cols要转换的列选择c(Sepal.Length, Sepal.Width)
.fns要应用的函数mean, ~ mean(.x, na.rm = TRUE)
.names输出列命名模板"mean_{.col}"
.unpack是否解包数据框列TRUE/FALSE

基础使用示例

library(dplyr)

# 对字符型列计算唯一值数量
starwars %>% 
  summarise(across(where(is.character), n_distinct))

# 对数值型列应用多个函数
min_max <- list(
  min = ~min(.x, na.rm = TRUE), 
  max = ~max(.x, na.rm = TRUE)
)
starwars %>% summarise(across(where(is.numeric), min_max))

列选择技巧

across()支持多种列选择方式,与select()函数的选择语义完全一致。

按位置选择

iris %>% mutate(across(1:2, round))  # 选择第1-2列

按名称选择

iris %>% mutate(across(c(Sepal.Length, Sepal.Width), round))

按条件选择

# 选择所有数值型列
iris %>% mutate(across(where(is.numeric), round))

# 选择名称包含"Sepal"的列
iris %>% mutate(across(starts_with("Sepal"), round))

# 复合选择:数值型且名称以"x"开头
iris %>% mutate(across(where(is.numeric) & starts_with("Sepal"), round))

使用外部向量选择

cols <- c("Sepal.Length", "Petal.Width")
iris %>% mutate(across(all_of(cols), round))

# 命名向量可以控制输出列名
names(cols) <- tolower(cols)
iris %>% mutate(across(all_of(cols), round))

函数应用方式

.fns参数支持多种函数定义方式,满足不同场景需求。

单个函数

# 使用内置函数
iris %>% summarise(across(where(is.numeric), mean))

# 使用匿名函数
iris %>% summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)))

多个函数列表

# 命名函数列表
iris %>% summarise(across(where(is.numeric), 
                         list(mean = mean, sd = sd, n_miss = ~ sum(is.na(.x)))))

# 输出结果包含 mean_Sepal.Length, sd_Sepal.Length, n_miss_Sepal.Length 等列

访问当前列信息

在函数内部可以使用cur_column()获取当前列名:

df <- tibble(x = 1:3, y = 3:5, z = 5:7)
mult <- list(x = 1, y = 10, z = 100)

df %>% mutate(across(all_of(names(mult)), ~ .x * mult[[cur_column()]]))

输出列命名控制

.names参数使用glue语法模板来控制输出列名,提供灵活的命名方式。

基本命名模板

# 默认命名:{列名}_{函数名}
iris %>% summarise(across(where(is.numeric), list(mean = mean, sd = sd)))

# 自定义命名模板
iris %>% summarise(across(where(is.numeric), mean, .names = "mean_{.col}"))
iris %>% summarise(across(where(is.numeric), list(mean = mean, sd = sd), 
                         .names = "{.col}.{.fn}"))

使用局部变量

local({
  prefix <- "summary"
  iris %>% summarise(across(where(is.numeric), mean, .names = "{prefix}_{.col}"))
})

与其他dplyr动词配合

across()可以与大多数dplyr动词配合使用,但需要注意一些特殊用法。

与mutate()配合

# 对数值型列进行标准化
rescale01 <- function(x) {
  rng <- range(x, na.rm = TRUE)
  (x - rng[1]) / (rng[2] - rng[1])
}
df <- tibble(x = 1:4, y = rnorm(4))
df %>% mutate(across(where(is.numeric), rescale01))

与summarise()配合

# 分组汇总统计
starwars %>% 
  group_by(species) %>% 
  filter(n() > 1) %>% 
  summarise(across(c(sex, gender, homeworld), n_distinct))

与filter()配合的特殊函数

across()不能直接用于filter(),需要使用专门的辅助函数:

# if_any(): 任一列满足条件
starwars %>% filter(if_any(everything(), ~ !is.na(.x)))

# if_all(): 所有列都满足条件  
starwars %>% filter(if_all(everything(), ~ !is.na(.x)))

# 具体条件筛选
df <- tibble(x = c("a", "b"), y = c(1, 1), z = c(-1, 1))
df %>% filter(if_all(where(is.numeric), ~ .x > 0))  # 所有数值列都大于0
df %>% filter(if_any(where(is.numeric), ~ .x > 0))  # 任一数值列大于0

高级功能:数据框解包

.unpack参数允许解包函数返回的数据框列,将其展开为多个单独列。

# 定义返回数据框的函数
quantile_df <- function(x, probs = c(0.25, 0.5, 0.75)) {
  tibble(quantile = probs, value = quantile(x, probs))
}

# 默认不解包
iris %>% reframe(across(starts_with("Sepal"), quantile_df))

# 自动解包
iris %>% reframe(across(starts_with("Sepal"), quantile_df, .unpack = TRUE))

# 自定义解包命名
iris %>% reframe(across(starts_with("Sepal"), quantile_df, 
                       .unpack = "{outer}.{inner}"))

实际应用场景

场景1:数据质量检查

# 检查每列的缺失值情况
missing_summary <- function(x) {
  list(
    n_missing = sum(is.na(x)),
    pct_missing = mean(is.na(x)) * 100,
    unique_values = n_distinct(x, na.rm = TRUE)
  )
}

starwars %>% summarise(across(everything(), missing_summary))

场景2:多变量标准化

# 对多个变量进行Z-score标准化
standardize <- function(x) {
  (x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}

iris %>% mutate(across(where(is.numeric), standardize))

场景3:批量创建衍生变量

# 为每个数值变量创建对数变换版本
iris %>% mutate(across(where(is.numeric), list(log = ~ log(.x + 1))))

常见陷阱与解决方案

陷阱1:分组变量被意外操作

df <- data.frame(g = c(1, 1, 2), x = c(-1, 1, 3), y = c(-1, -4, -9))
df %>% 
  group_by(g) %>% 
  summarise(across(where(is.numeric), sum))
# 结果:g列不会被操作,因为它是分组变量

陷阱2:新创建的列被后续操作选中

# 错误示例:新创建的min_*列会被第二个across()选中
starwars %>% summarise(
  across(where(is.numeric), ~min(.x, na.rm = TRUE), .names = "min_{.col}"),
  across(where(is.numeric), ~max(.x, na.rm = TRUE), .names = "max_{.col}")
)

# 正确解决方案1:使用tibble包装
starwars %>% summarise(
  tibble(
    across(where(is.numeric), ~min(.x, na.rm = TRUE), .names = "min_{.col}"),
    across(where(is.numeric), ~max(.x, na.rm = TRUE), .names = "max_{.col}")  
  )
)

# 正确解决方案2:重新排列列顺序
starwars %>% 
  summarise(across(where(is.numeric), min_max, .names = "{.fn}.{.col}")) %>% 
  relocate(starts_with("min"))

陷阱3:常量列的统计计算

df <- data.frame(x = c(1, 2, 3), y = c(1, 4, 9))

# n列会被误操作
df %>% summarise(n = n(), across(where(is.numeric), sd))

# 解决方案:显式排除或调整顺序
df %>% summarise(across(where(is.numeric) & !n, sd), n = n())

性能优化建议

评估时机的重要性

across()内部的代码会对每个列和组的组合都进行一次评估,这在生成随机变量等场景中需要特别注意:

gdf <- tibble(g = c(1, 1, 2, 3), v1 = 10:13, v2 = 20:23) %>% group_by(g)

# 外部:1个正态变量
n <- rnorm(1)
gdf %>% mutate(across(v1:v2, ~ .x + n))

# 内部across():6个正态变量(列数×组数)
gdf %>% mutate(across(v1:v2, ~ .x + rnorm(1)))

使用pick()进行纯列选择

如果只需要选择列而不需要应用函数,推荐使用pick()函数:

# 查找包含特定模式的所有组合
starwars %>% count(pick(contains("color")), sort = TRUE)

# 查找所有不同的颜色组合
starwars %>% distinct(pick(contains("color")))

迁移指南:从旧函数到across()

dplyr之前的_if_at_all函数族已被across()取代,迁移方法如下:

# 旧代码
df %>% mutate_if(is.numeric, ~mean(.x, na.rm = TRUE))
df %>% mutate_at(vars(c(x, starts_with("y"))), mean)
df %>% mutate_all(mean)

# 新代码
df %>% mutate(across(where(is.numeric), ~mean(.x, na.rm = TRUE)))
df %>% mutate(across(c(x, starts_with("y")), mean))
df %>% mutate(across(everything(), mean))

总结

across()函数是dplyr中处理列式操作的革命性工具,它:

  1. 统一了语法:取代了多个旧函数,简化了学习曲线
  2. 提供了灵活性:支持多种列选择方式和函数应用模式
  3. 增强了表达能力:能够实现之前无法表达的复杂汇总操作
  4. 保持了性能:通过智能的评估时机控制确保效率

通过掌握across()函数,你将能够更加高效地处理数据清洗、转换和分析任务,显著提升数据处理的效率和代码的可读性。

记住实践是最好的学习方式,尝试在你的下一个数据项目中应用across()函数,体验它带来的便利和强大功能。

【免费下载链接】dplyr 【免费下载链接】dplyr 项目地址: https://gitcode.com/gh_mirrors/dpl/dplyr

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值