I got error when using rlang::call_args
inside dplyr::mutate
. I have already found a workaround without using dplyr::mutate
. My goal here is to find out the actual reason of the error and learn the proper way of using rlang
and dplyr
together.
I have a data frame with one column storing function calls such as
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>
I want to create a new column for arguments list with rlang::call_args
. The expected output should look like
# # A tibble: 3 x 2
# call args
# <list> <list>
# 1 <language> <named list [2]>
# 2 <language> <named list [2]>
# 3 <language> <named list [2]>
with a named list containing function arguments as each line in args
column:
> expected_output$args[[1]]
# $a
# [1] 1
# $b
# [1] 2
and
> expected_output$args[[2]]
# $x
# [1] 5
# $y
# [1] 6
and so on.
When I tried rlang::call_args
with dplyr::mutate
, I got error telling me the input is not a quoted call:
> 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) ...
I have checked the elements of df$call
and confirmed all elements are call objects.
> all(sapply(df$call, rlang::is_call))
# [1] TRUE
I am not sure what x 'call' must be a quoted call
exactly means here in the error message. I thought function arguments in dplyr::mutate
should be automatically treated as quoted expressions since we can do something like
dplyr::mutate(df, NEWVAR = tolower(OLDVAR))
without error of object 'OLDVAR' not found.
I have found a workaround already:
> 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]>
With this thread I am not seeking another way to solve this particular problem but looking for a clear understanding of the error with dplyr::mutate
and learning the proper way of using dplyr
together with rlang
functions.
There are two potential problems, not specific to rlang
:
call_args()
is not vectorized (as mentioned in another comment), which means it can only handle one call at a time and not a list or a vector of calls. Writing df %>% mutate(args = call_args(call))
is equivalent to df$args = call_args(df$call)
, where df$call
is a vector of 3 expressions. Try it and you'll get the same error you describe. Possible solution for this is to use dplyr::rowwise()
call_args()
returns a list, possibly with length > 1. To use it in a rowwise mutate, you'll need to wrap its output in an explicit list()
. Otherwise dplyr complains that you are trying to assign 2 values to a variable in a 1-row data frame.
Solution:
df = df %>% rowwise() %>% mutate(args = list(rlang::call_args(call))) %>% ungroup()
df$args
NB: The lapply()
approach you presented with tibble can also be done with mutate, without rowwise. Maybe a bit less dplyr-ish, though?
df = df %>% mutate(args = lapply(call, rlang::call_args))
df$args