6

I'm trying to write a function which takes as argument a dataframe and the name of the function. When I try to write the function with the standard R syntax, I can get the good result using eval and substitute as recommanded by @hadley in http://adv-r.had.co.nz/Computing-on-the-language.html

> df <- data.frame(y = 1:10)
> f <- function(data, x) {
+   out <- mean(eval(expr = substitute(x), envir = data))
+   return(out)
+ }
> f(data = df, x = y)
[1] 5.5

Now, when I try to write the same function using the %>% operator, it doesn't work :

> df <- data.frame(y = 1:10)
> f <- function(data, x) {
+   data %>% 
+     eval(expr = substitute(x), envir = .) %>% 
+     mean()
+ }
> f(data = df, x = y)
Show Traceback
Rerun with Debug
 Error in eval(expr, envir, enclos) : objet 'y' introuvable 
> 

How can I using the combine the piping operator with the use of eval and substitute ? It's seems really tricky for me.

PAC
  • 5,178
  • 8
  • 38
  • 62
  • the problem is not in the pipe but rather the [Non Standard Evaluation](http://adv-r.had.co.nz/Computing-on-the-language.html) used by `dplyr` functions – asifzuba Jul 17 '18 at 00:54

3 Answers3

7

A workaround would be

f <- function(data, x) {
  v <- substitute(x)
  data %>% 
    eval(expr = v, envir = .) %>%
    mean()
}

The problem is that the pipe functions (%>%) are creating another level of closure which interferes with the evaluation of substitute(x). You can see the difference with this example

df <- data.frame(y = 1:10)
f1 <- function(data, x) {
  print(environment())
  eval(expr = environment(), envir = data)
}

f2 <- function(data, x) {
  print(environment())
  data %>% 
    eval(expr = environment(), envir = .)
}
f1(data = df, x = y)
# <environment: 0x0000000006388638>
# <environment: 0x0000000006388638>
f2(data = df, x = y)
# <environment: 0x000000000638a4a8>
# <environment: 0x0000000005f91ae0>

Notice how the environments differ in the matrittr version. You want to take care of substitute stuff as soon as possible when mucking about with non-standard evaluation.

I hope your use case is a bit more complex than your example, because it seems like

mean(df$y)

would be a much easier bit of code to read.

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Of course, this is just a toy example and I want to write a much more complex function :) – PAC Feb 12 '16 at 09:42
  • Your solution is elegant. However is there any more general answer to the problem of combining `substitute` with pipes ? – PAC Feb 12 '16 at 09:45
  • Hadley says that this isn't a good solution : https://github.com/hadley/r4ds/issues/45#issuecomment-184855725. So what's the good solution ? – PAC Feb 18 '16 at 13:10
  • Ask Hadley. I would suggest avoiding substitute and eval in magrittr. – MrFlick Feb 18 '16 at 14:03
3

I've been trying to understand my problem.

First, I've written what I want with the summarise() function :

> library(dplyr)
> df <- data.frame(y = 1:10)
> summarise_(.data = df, mean = ~mean(y))
  mean
1  5.5

Then I try to program my own function. I've found a solution which seems to work with the lazyeval package in this post. I use the lazy() and the interp() functions to write what I want.

The first possibility is here :

> library(lazyeval)
> f <- function(data, col) {
+   col <- lazy(col)
+   inter <- interp(~mean(x), x = col)
+   summarise_(.data = data, mean = inter)    
+   }
> f(data = df, col = y)
  mean
1  5.5

I can also use pipes :

> f <- function(data, col) {
+   col <- lazy(col)
+   inter <- interp(~mean(x), x = col)
+   data %>% 
+     summarise_(.data = ., mean = inter)    
+ }
> 
> f(data = df, col = y)
  mean
1  5.5
Community
  • 1
  • 1
PAC
  • 5,178
  • 8
  • 38
  • 62
0

I would not use eval and substitute.

What follows is a simplified version of this great post suited to your question.

df <- data.frame(y = 1:10)
f <- function(data, x) {
  x <- enquo(x)
  df %>% summarise(mean = mean(!!x))
   }
f(data = df, x = y)

There are two things happening here:

  1. Tranforming the column name with enquo()
  2. Prefixing the column with !!

Please see refer to the link for a more complicated example.

Samuel Saari
  • 1,023
  • 8
  • 13