user51966
user51966

Reputation: 1057

Using `expr()` inside the function

The Chapter 19 of Advanced R explains that expr() is not useful inside the function.

However, in the following case, I could not make a function work without expr().

Let's suppose I want to group a tibble in a function.

data(iris)
iris %>% group_by(Species)

The obvious approach is to use "curly curly".

func_a <- function(data, grouping) {
  data %>% group_by({{grouping}})
}
func_a(iris, Species)

However, "curly curly" does not work if I allow an arbitrarily supplied expressions.

func_b <- function(data, ...) {
  data %>% group_by({{...}})
}
func_b(iris, Species)
# Error in (function (x)  : object 'Species' not found

In the end, I found I need to expr() to make it work.

func_c <- function(data, ...) {
  grouping <- expr(...)
  data %>% group_by(!!grouping)
}
func_c(iris, Species)

The example of expr() in Advanced R is:

f1 <- function(x) expr(x)
f1(a + b + c)
#> x

My main question is why func_c works. Does expr() take ... as it is and evaluate it with !!? Why we have to take a different approach for ...?

Then, I am not sure why this does not work.

func_d <- function(data, grouping) {
  grouping <- expr(grouping)
  data %>% group_by(!!grouping)
}
func_d(iris, Species)

I also checked rlang manual, but the explanation is too brief for me.

Upvotes: 2

Views: 1086

Answers (2)

Artem Sokolov
Artem Sokolov

Reputation: 13691

Think of it as expr() and !! effectively negating each other.

func_c <- function(data, ...) {
  grouping <- expr(...)
  data %>% group_by(!!grouping)
}

is equivalent to

func_c <- function(data, ...) {
  data %>% group_by(...)            # Proper way to handle dots, by the way
}

(It's not an exact equivalence, because the former implementation expands dots in expr, while the latter does it in group_by. But both implementations will produce the same output when a single column symbol is supplied to ....)

Likewise,

func_d <- function(data, grouping) {
  grouping <- expr(grouping)
  data %>% group_by(!!grouping)
}

is equivalent to

func_d <- function(data, grouping) {
  data %>% group_by(grouping)         # No column `grouping` in iris
}

To get func_d working, you need to replace expr() with enexpr(). This will capture the expression provided to the function, as opposed to the expression grouping itself.

Upvotes: 0

Count Orlok
Count Orlok

Reputation: 1007

expr prevents the evaluation of code. For instance, attempting to run x by itself will fail unless x is a variable you've previously declared - R will go looking for the value in x when you evaluate it, and will issue an error if no such value is found. In contrast, expr(x) will never fail (even if x hasn't been declared yet), because the expr tells R "take this at face value and don't go looking for something else it might represent". expr(x) will return something of the type "name", which is basically just that - a name. You can think of a name as the interface between you and R - it's what you type in, and how you communicate your instructions. eval(expr(x)) is the same as just doing x.

Now taking your examples in order:

func_c <- function(data, ...) {
  grouping <- expr(...)
  data %>% group_by(!!grouping)
}
func_c(iris, Species)

This works because Species will be directly passed to expr through the ..., and the return type will be a "name", which gets stored in the variable grouping. A name can be evaluated with !! as you did it, or with eval. Either way, doing !!grouping will cause R to first go looking for what the grouping variable represents, finding Species. The variable gets replaced with its value, and finally !!Species will tell R to go looking for the variable called Species, which within the context of the group_by function will give you the column called "Species".

Moving on to your next example:

f1 <- function(x) expr(x)
f1(a + b + c)

This doesn't work simply because expr(x) blocks any evaluation. R doesn't go looking for what's inside x, so it never finds a + b + c, it takes the x at face value, and that's what you get.

Finally, we have your last example:

func_d <- function(data, grouping) {
  grouping <- expr(grouping)
  data %>% group_by(!!grouping)
}
func_d(iris, Species)

This is similar to your first example, but there's an extra variable at play here - the parameter called grouping. In your first example, Species entered the function directly (through ...), so it wasn't bound to any parameter name. In this third example, Species enters the function through a named parameter, i.e. bound to the variable grouping. However, expr(grouping) tells R "don't bother looking for what grouping represents, I have everything I need right here"... so it never finds Species at all. expr(grouping) just gives you the name grouping, regardless of whatever's in the variable itself. So then when you try to evaluate that using !!grouping within group_by, R tries to look for a column name called grouping... Needless to say, it doesn't find it, and you get a Column grouping is not found error.

Upvotes: 2

Related Questions