Aurèle
Aurèle

Reputation: 12819

do.call() and tidy evaluation

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)                     
 [...]

Upvotes: 6

Views: 1267

Answers (3)

G. Grothendieck
G. Grothendieck

Reputation: 269556

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.

Upvotes: 2

Stewart Ross
Stewart Ross

Reputation: 1044

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.

Upvotes: 2

akrun
akrun

Reputation: 887098

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    

Upvotes: 3

Related Questions