6

Trying to make do.call() work in the context of tidy evaluation:

library(rlang)
library(dplyr)

data <- tibble(item_name = c("apple", "bmw", "bmw"))

mutate(data, category = case_when(item_name == "apple" ~ "fruit",
                                  item_name == "bmw" ~ "car"))

# # A tibble: 3 x 2
#   item_name category
#   <chr>     <chr>   
# 1 apple     fruit   
# 2 bmw       car     
# 3 bmw       car 

What differs between:

category_fn <- function(df, ...){
  # browser()
  cat1 <- quos(...)
  mutate(df, category = case_when(!!! cat1))
}

category_fn(df = data, item_name == "apple" ~ "fruit",
                       item_name == "bmw" ~ "car")

# # A tibble: 3 x 2
#   item_name category
#   <chr>     <chr>   
# 1 apple     fruit   
# 2 bmw       car     
# 3 bmw       car 

and:

cat <- list(item_name == "apple" ~ "fruit", item_name == "bmw" ~ "car")

do.call(category_fn, c(list(df = data), cat), quote = FALSE)
# Or:
do.call(category_fn, c(list(df = data), cat), quote = TRUE)
# Or:
rlang::invoke(category_fn, c(list(df = data), cat))

which all give the same error:

# Error in mutate_impl(.data, dots) : 
#   Evaluation error: object 'item_name' not found.

I stepped into the function with browser(), examined the arguments, ran expr(mutate(df, category = case_when(!!! cat1))) there (as suggested as a universal debugging strategy in http://rpubs.com/lionel-/programming-draft), with the same output in both cases: mutate(df, category = case_when(~(item_name == "apple" ~ "fruit"), ~(item_name == "bmw" ~ "car"))).

I've also tried to tweak the envir or .env arguments to no avail.

My understanding is that it has likely something to do with different quosure environments, but environment(cat1[[1]]) is also identical (<environment: R_GlobalEnv>).

Note:
This is somehow a follow-up of Tidy evaluation programming with dplyr::case_when which I was trying to answer.

> sessioninfo::session_info()
─ Session info ────────────────────────────────────────────────────────
 setting  value                       
 version  R version 3.4.3 (2017-11-30)
 os       Linux Mint 18               
 system   x86_64, linux-gnu           
 [...]                 

─ Packages ────────────────────────────────────────────────────────────
 package     * version    date       source                             
 [...]                
 dplyr       * 0.7.4      2017-09-28 CRAN (R 3.4.3)                     
 [...]                    
 rlang       * 0.1.6      2017-12-21 CRAN (R 3.4.3)                     
 [...]
Aurèle
  • 12,545
  • 1
  • 31
  • 49
  • Interesting answers so far (thanks!) but I'm after one that explains what happens in `do.call()` instead of providing a (good) workaround. – Aurèle Jan 02 '18 at 15:40

3 Answers3

3

We could create 'cat' as a quosure and then do the evaluation with !!!

cat <-  quos(item_name == "apple" ~ "fruit", item_name == "bmw" ~ "car")
category_fn(data, !!!(cat))
# A tibble: 3 x 2
#  item_name category
#  <chr>     <chr>   
#1 apple     fruit   
#2 bmw       car     
#3 bmw       car    
akrun
  • 874,273
  • 37
  • 540
  • 662
2

I think it's a similar issue to the other post; quoting the list itself is not the same as quoting the elements of the list individually.

I have modified the cat definition to quote the elements individually, and the function slightly to remove the quosure statement and explicitly name the argument. In the do.call statements the second argument, the list of arguments to be supplied to the function, I have included the cat element as part of the list.

With these modifications the two do.call statements and the invoke then return the same result as the direct execution in your post:

data <- tibble(item_name = c("apple", "bmw", "bmw"))

cat <- list(quo(item_name == "apple" ~ "fruit"), 
            quo(item_name == "bmw" ~ "car"))

category_fn <- function(df, category){
  mutate(df, category = case_when(!!! category))
}

> do.call(category_fn, list(data, cat), quote = FALSE)
# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car
> # Or:
> do.call(category_fn, list(data, cat), quote = TRUE)
# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car
> # Or:
> rlang::invoke(category_fn, list(df = data, cat))
# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car

The value of the quote argument makes no difference in the two do.call examples.

I find quosures conceptually difficult, and not made a great deal easier by the current programming with dplyr vignette on Cran.

Stewart Ross
  • 1,034
  • 1
  • 8
  • 10
  • 1
    If you are using this way, then `category_fn(data, cat)` should be working – akrun Dec 29 '17 at 17:30
  • @akrun, Yes, category_fn functions directly when supplied the two arguments in this way. I like the simplicity of your solution however - very straightforward! – Stewart Ross Dec 29 '17 at 17:33
  • Unfortunately you’ve now changed the signature of `category_fn`. – Konrad Rudolph Dec 29 '17 at 17:59
  • @Konrad: Agreed, but as far as I know there was no requirement to pass a list of other arguments on for use by sub-functions. The original category_fn function in this question is taken from another example, which in turn replicated the use of the ... argument as shown for the group_by case in the programming with dplyr vignette. In the case of the vignette, the ... argument was needed to set up the ability to use quosures with more than one group_by variable, but it is not needed in these examples - or at least as far as I can tell the OPs have not stated it is needed. – Stewart Ross Dec 29 '17 at 18:21
  • You say "quoting the list itself is not the same as quoting the elements of the list individually", and I'd normally agree but we're passing formulas here (and we're not actually quoting the list anyway). Furthermore, as the OP noted the arguments certainly *seem* identical when inspected, down to their environment. So, *how* do they differ in this case? – Konrad Rudolph Dec 30 '17 at 09:27
  • Taking your examples themselves I find that if I execute do.call using the individually-quoted list I suggested the do.call works, but on the list without it gives the error you've noted, yet the typeof and class of the arguments appear the same. A direct call of the function does not work with either argument. I note from a comment on the original question that there is an open GitHub request to clarify how to provide formula arguments to case_when, so this is clearly a problem being addressed by the package authors. – Stewart Ross Dec 30 '17 at 11:03
2

The answer in part (1a) of my response to Tidy evaluation programming with dplyr::case_when works here too.

If cat, data and category_fn are as in the present question then this works. The first line transforms cat to cat_ which is of a form that will work here.

cat_ <- lapply(cat, function(x) do.call("substitute", list(x))) 
do.call("category_fn", c(list(df = data), cat_))

giving:

# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car

Regarding the question at the end which seems to ask for alternatives to quosures in my answer to the original problem which I have linked to above are solutions to that question using the wrapr package and base R. The seplyr package, by the author of wrapr, may also be an alternative.

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341