How to pass (optional) quosures to a tibble?

Hi there,

I struggle with quosures on something that must have a trivial solution.

I have a function that has an argument, below called a, that may take a vector or a (non-quoted) column name.
Such argument work flawlessly within mutate() calls, but in my case I would like to directly use the argument to build a tibble, so sometimes the argument must be read as a quosure to be evaluated in the initial table, and othertimes it should be evaluated directly. OK, I struggle to explain but the example below should help:

foo <- function(table, a) {
  tibble(a = {{a}})
  }

tibble(b = 1) %>% foo(a = 1)
tibble(b = 1) %>% foo(a = b) ## does not work... I would like it to give the same output as the line above...

How can I thus modify the code below so that it works as expected?
Thanks!

1 Like

I am not sure this is a best way but it works like this

library(tibble)
library(magrittr)
foo <- function(table, a) {
  a <- rlang::enquo(a)
  tibble(a = rlang::eval_tidy(a, table))
}

tibble(b = 2) %>% 
  foo(a = 1)
#> # A tibble: 1 x 1
#>       a
#>   <dbl>
#> 1     1
tibble(b = 2) %>% 
  foo(a = b) 
#> # A tibble: 1 x 1
#>       a
#>   <dbl>
#> 1     2
c <- 4
tibble(b = 2) %>% 
  foo(a = c) 
#> # A tibble: 1 x 1
#>       a
#>   <dbl>
#> 1     4

Created on 2019-07-02 by the reprex package (v0.3.0.9000)

You need to use rlang functions to manage something like that. The new {{}} syntax won't help here because you need something more than just !!enquo(a)

eval_tidy allows to evaluate using tidyeval with some data as mask to the environements. I let you read the help page to learn a bit more about data masks.

1 Like

Many thanks @cderv, it helps a lot.
I read about data mask but the following reveals I still do fully grasp the use of data mask:


library(tibble)
library(magrittr)

foo <- function(table, a) {
  a <- rlang::enquo(a)
  tibble(a = rlang::eval_tidy(a, table))
}

tibble(a = 2) %>% 
  foo(a = 1)
#> # A tibble: 1 x 1
#>       a
#>   <dbl>
#> 1     1

Created on 2019-07-03 by the reprex package (v0.3.0)

Why would a be 1 in the output? If I understand it correctly, eval_tidy should use the data mask build from table to override the environment defined in the quosure before evaluating a. Thus to me, a should be 2 and not 1. Obviously I am wrong, but then what's happening... Could you please clarify? I did read both help files ?eval_tidy and ?as_data_mask but perhaps I missed something.

Yes but it does not mask the function environment. here, a is defined inside the function so it is found before the data mask and before the global environment.

library(tibble)
library(magrittr)

foo <- function(table, a) {
  a <- rlang::enquo(a)
  a <- 3
  tibble(a = rlang::eval_tidy(a, table))
}

tibble(a = 2) %>% 
  foo(a = 1)
#> # A tibble: 1 x 1
#>       a
#>   <dbl>
#> 1     3

Created on 2019-07-03 by the reprex package (v0.3.0.9000)

That is why you get 1 because it is define inside the function as a <- rlang::enquo(a).
I think it is: function env > data mask > parent env

I don't know what you are trying to achieve but playing with names, symbols and environment is not something easy and can be the source of some mistake. one should go with caution.

Thanks @cderv, the goal I am trying to achieve is to understand how the tidyverse really works and how to program tidy functions not by trials and errors. From the R documentation, I thought that both tibble() and mutate() were quoting functions that would behave in the same way. As you showed me, it turns out they behave quite differently as tibble() requires an explicit call to eval_tidy(). Now, I gather that the key for a better understanding is probably to grasp how the scoping really works during a tidy evaluation. I know it is difficult. I will keep digging.

Many thanks!

PS: I am aware of the rlang vignette, https://tidyeval.tidyverse.org and @hadley's new chapter on the topic. I will go back to those.

This feels like it might be a bug to me. At a minimum the error message here is confusing:

foo <- function(table, a) {
  tibble::tibble(a = {{a}})
}

x <- 1:10
foo(x)
#> Error in eval_tidy(xs[[i]], unique_output): object '' not found

I'd suggest filing an issue on tibble.

@hadley, pardon me if I am wrong but I think you missed something: your are passing x to table and nothing to a, so I think that the error message is not particularly surprising, unless you think it should specify that a is missing. Please clarify and I will file the issue if needed.
Best,
Alex

Oh I see. I didn’t understand what you were trying to do with your function.

The error message still isn’t great though.

@hadley: I reported the issue there: https://github.com/tidyverse/tibble/issues/619
Best,
Alex

1 Like

Thanks, it’s appreciated!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.