如何在R的函数内使用dplyr/magrittr的管道操作符?

6
我正在尝试编写一个函数,该函数的参数是数据框和函数名称。当我尝试使用标准R语法编写函数时,我可以通过使用@hadley在http://adv-r.had.co.nz/Computing-on-the-language.html中推荐的evalsubstitute来获得良好的结果。
> 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

现在,当我尝试使用%>%运算符编写相同的函数时,它不起作用:
> 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 
> 

如何在使用管道操作符的同时结合eval和substitute函数?这对我来说似乎非常棘手。

问题不在管道中,而是由dplyr函数使用的非标准评估(Non Standard Evaluation)引起的。 - asifzuba
3个回答

7
一个解决方法是:
f <- function(data, x) {
  v <- substitute(x)
  data %>% 
    eval(expr = v, envir = .) %>%
    mean()
}

问题在于管道函数(%>%)创建了另一个闭包层,这会干扰对substitute(x)的求值。您可以通过以下示例看到区别。
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>

请注意matrittr版本中环境的不同。当涉及非标准评估时,您需要尽快处理substitute内容。
我希望您的使用情况比您的示例更复杂,因为它似乎是这样的。
mean(df$y)

这段代码将会更易于阅读。


当然,这只是一个玩具例子,我想编写一个更复杂的函数 :) - PAC
你的解决方案很优雅。但是,是否有更一般的答案来解决将 substitute 与管道结合的问题? - PAC
Hadley说这不是一个好的解决方案:https://github.com/hadley/r4ds/issues/45#issuecomment-184855725。那么什么是好的解决方案呢? - PAC
问问 Hadley。我建议在 magrittr 中避免使用 substitute 和 eval。 - MrFlick

3

我一直在努力理解我的问题。

首先,我使用了summarise()函数来表达我的意图:

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

然后我尝试编写自己的函数。在这篇文章中,我找到了一种似乎可以使用lazyeval包的解决方案。我使用lazy()interp()函数编写我想要的内容。

第一个可能性如下:

> 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

我也可以使用管道:

> 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

0

我不会使用eval和替换。

接下来是一个简化版本的这篇伟大的文章,适合你的问题。

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

这里发生了两件事情:

  1. 使用 enquo() 转换列名
  2. 在列名前加上 !!

请参考链接以获取更复杂的示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接