简体   繁体   中英

R rlang: call_args in dplyr::mutate

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 :

  1. 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()

  2. 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM