Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

do.call() and tidy evaluation

Tags:

r

dplyr

nse

rlang

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)                     
 [...]
like image 886
Aurèle Avatar asked Dec 29 '17 16:12

Aurèle


2 Answers

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    
like image 126
akrun Avatar answered Sep 29 '22 13:09

akrun


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.

like image 39
Stewart Ross Avatar answered Sep 29 '22 14:09

Stewart Ross