R语言传递dplyr代码为函数参数时出现报错的解决咨询
解决dplyr代码作为函数参数的执行错误
问题背景
需求是创建一个函数,允许传入dplyr代码作为参数,根据数据框是否包含category列自动执行对应逻辑:无category列执行code1,有则执行code2。
已构建的数据集:
library(dplyr) start_date <- as.Date("2024-01-01") end_date <- Sys.Date() dates <- seq(from = start_date, to = end_date, by = "week") states <- c("California", "Texas", "Florida", "New York", "Illinois", "Ohio", "Georgia", "North Carolina", "Michigan", "Pennsylvania") category <- c("A", "B", "C") df1 <- expand.grid(date = dates, state = sample(states, length(dates), replace = TRUE)) %>% mutate( units = sample(100:1000, nrow(.), replace = TRUE), amount = round(runif(nrow(.), min = 1000, max = 10000), 2) ) df2 <- expand.grid(date = dates, state = sample(states, length(dates), replace = TRUE), category = sample(category, length(dates), replace = TRUE)) %>% mutate( units = sample(100:1000, nrow(.), replace = TRUE), amount = round(runif(nrow(.), min = 1000, max = 10000), 2) )
原函数及调用代码:
# 原函数 apply_conditional_summary <- function(data, code1, code2) { if ("category" %in% colnames(data)) { result <- eval(substitute(data %>% code2)) } else { result <- eval(substitute(data %>% code1)) } return(result) } # 调用时触发错误 apply_conditional_summary( df1, group_by(date, state) %>% summarise(units = mean(units), total_amt = sum(amount), .groups = "drop"), group_by(date, state, category) %>% summarise(units = mean(units), total_amt = sum(amount), .groups = "drop") %>% group_by(date, state) %>% mutate(share = total_amt / sum(total_amt)) %>% ungroup() )
报错信息:
Error in `%>%`(., group_by(date, state), summarise(units = mean(units), : unused argument (summarise(units = mean(units), total_amt = sum(amount), .groups = "drop"))
错误原因
原函数中,传入的code1/code2是已经用%>%连接的完整表达式,但执行eval(substitute(data %>% code1))时,相当于把data传入code1的第一个管道步骤后,又把整个后续链当作额外参数传递,违背了管道操作的语法规则,导致group_by等函数收到未定义的参数,触发报错。
解决方法
推荐使用tidyverse的**整洁评估(tidyeval)**机制处理这类场景,比直接用eval(substitute())更稳定可靠。
方案1:使用enquo捕获表达式(推荐)
library(dplyr) apply_conditional_summary <- function(data, code1, code2) { # 捕获传入的dplyr表达式 code1 <- enquo(code1) code2 <- enquo(code2) if ("category" %in% colnames(data)) { data %>% !!code2 } else { data %>% !!code1 } }
正确调用示例
传入的code1/code2只需写从dplyr操作开始的部分(不需要带开头的data %>%):
# 调用无category列的df1 apply_conditional_summary( df1, group_by(date, state) %>% summarise(units = mean(units), total_amt = sum(amount), .groups = "drop"), group_by(date, state, category) %>% summarise(units = mean(units), total_amt = sum(amount), .groups = "drop") %>% group_by(date, state) %>% mutate(share = total_amt / sum(total_amt)) %>% ungroup() ) # 调用有category列的df2 apply_conditional_summary( df2, group_by(date, state) %>% summarise(units = mean(units), total_amt = sum(amount), .groups = "drop"), group_by(date, state, category) %>% summarise(units = mean(units), total_amt = sum(amount), .groups = "drop") %>% group_by(date, state) %>% mutate(share = total_amt / sum(total_amt)) %>% ungroup() )
方案2:使用rlang::expr包裹传入代码
如果不想用enquo,也可以让调用者用expr()包裹代码,函数内用eval_tidy执行:
library(dplyr) library(rlang) apply_conditional_summary <- function(data, code1, code2) { if ("category" %in% colnames(data)) { eval_tidy(expr(data %>% !!code2), data = list(data = data)) } else { eval_tidy(expr(data %>% !!code1), data = list(data = data)) } } # 调用示例:用expr包裹代码 apply_conditional_summary( df1, expr(group_by(date, state) %>% summarise(units = mean(units), total_amt = sum(amount), .groups = "drop")), expr(group_by(date, state, category) %>% summarise(units = mean(units), total_amt = sum(amount), .groups = "drop") %>% group_by(date, state) %>% mutate(share = total_amt / sum(total_amt)) %>% ungroup()) )
内容的提问来源于stack exchange,提问作者prdel99
相关产品推荐
相关产品推荐

