dplyr::mutate内使用rlang::call_args的报错原因与正确用法
我在dplyr::mutate中使用rlang::call_args时遇到报错,目前已经找到了不依赖dplyr::mutate的临时解决方案,我想要明确该报错的实际原因,同时学习rlang与dplyr搭配使用的正确方法。
我有一个存储函数调用的单列数据框,示例如下:
df <- tibble::tibble( `call` = c(expr(f1(a=1, b=2)), expr(f2(x=5,y=6)), expr(f3(m=9, n=10))) ) # > df # # A tibble: 3 x 1 # call # <list> # 1 <language> # 2 <language> # 3 <language>
我想要用rlang::call_args生成新列存储每个调用的参数列表,预期输出如下:
# # A tibble: 3 x 2 # call args # <list> <list> # 1 <language> <named list [2]> # 2 <language> <named list [2]> # 3 <language> <named list [2]>
args列的每一行都是包含对应函数参数的命名列表:
> expected_output$args[[1]] # $a # [1] 1 # $b # [1] 2
以及:
> expected_output$args[[2]] # $x # [1] 5 # $y # [1] 6
以此类推。
当我尝试在dplyr::mutate中调用rlang::call_args时,收到报错提示输入不是带引号的调用:
> df %>% mutate(args = rlang::call_args(call)) Error: Problem with `mutate()` input `args`. x `call` must be a quoted call ℹ Input `args` is `rlang::call_args(call)`. Run `rlang::last_error()` to see where the error occurred. > rlang::last_error() <error/dplyr:::mutate_error> Problem with `mutate()` input `args`. x `call` must be a quoted call ℹ Input `args` is `rlang::call_args(call)`. Backtrace: Run `rlang::last_trace()` to see the full context. > rlang::last_trace() <error/dplyr:::mutate_error> Problem with `mutate()` input `args`. x `call` must be a quoted call ℹ Input `args` is `rlang::call_args(call)`. Backtrace: █ 1. ├─df %>% mutate(args = rlang::call_args(call)) 2. ├─dplyr::mutate(., args = rlang::call_args(call)) 3. ├─dplyr:::mutate.data.frame(., args = rlang::call_args(call)) 4. │ └─dplyr:::mutate_cols(.data, ...) 5. │ ├─base::withCallingHandlers(...) 6. │ └─mask$eval_all_mutate(dots[[i]]) 7. ├─rlang::call_args(call) 8. │ └─rlang:::abort_call_input_type("call") 9. │ └─rlang::abort(sprintf("`%s` must be a quoted call", arg)) 10. │ └─rlang:::signal_abort(cnd) 11. │ └─base::signalCondition(cnd) 12. └─(function (e) ...
我已经检查了df$call的元素,确认所有元素都是call对象:
> all(sapply(df$call, rlang::is_call)) # [1] TRUE
我不确定报错信息中的x 'call' must be a quoted call具体含义,我原本认为dplyr::mutate中的函数参数会自动被当作带引号的表达式处理,毕竟我们可以正常运行如下代码而不会报OLDVAR对象不存在的错误:
dplyr::mutate(df, NEWVAR = tolower(OLDVAR))
我已经找到了如下临时解决方案:
> tibble::as_tibble(list(call = df$call, args = lapply(df$call, call_args))) # # A tibble: 3 x 2 # call args # <list> <list> # 1 <language> <named list [2]> # 2 <language> <named list [2]> # 3 <language> <named list [2]>
我发起这个提问不是为了寻求该问题的其他解决方法,而是想要清晰理解dplyr::mutate相关报错的根因,学习dplyr与rlang函数搭配使用的正确方式。
报错根本原因
你遇到的报错核心是两个机制叠加导致的:
rlang::call_args自带非标准评估逻辑:它默认会引用(quote)你传入的第一个参数,不会直接对输入的变量求值。你在mutate中写call_args(call)时,call_args拿到的是call这个列名字符符号本身,而不是call列存储的实际call对象列表,检测到输入不是call对象就直接抛出错误。rlang::call_args是单元素处理函数,不支持向量化输入:就算你解决了求值问题,直接把整列的列表向量传给call_args也无法得到逐元素处理的结果,必须配合迭代函数逐行调用。
你之前疑惑的mutate(df, NEWVAR = tolower(OLDVAR))可以正常运行,是因为tolower是普通的向量化函数,没有特殊的引用输入逻辑,会直接对传入的列求值后处理,和call_args的行为逻辑完全不同。
dplyr 与 rlang 搭配的正确用法
针对你的需求,两种符合tidyverse规范的写法如下:
写法1:配合purrr迭代处理
library(dplyr) library(purrr) df %>% mutate(args = map(call, rlang::call_args))
map会逐元素取出call列的每个call对象,传给call_args处理,最终返回的结果就是你需要的列表列。
写法2:使用rowwise逐行处理
library(dplyr) df %>% rowwise() %>% mutate(args = list(rlang::call_args(call))) %>% ungroup()
rowwise会让mutate按行执行,每一行的call都是单个call对象,直接传给call_args处理后用list()包裹,就可以生成符合要求的列表列。
内容的提问来源于stack exchange,提问作者englealuze

