R 为什么是enquo+!!最好是替换+评估

R 为什么是enquo+!!最好是替换+评估,r,dplyr,nse,tidyeval,R,Dplyr,Nse,Tidyeval,在下面的示例中,为什么我们更喜欢使用f1而不是f2?从某种意义上讲,它更有效吗?对于习惯以R为基础的人来说,使用替代+eval选项似乎更为自然 library(dplyr) d = data.frame(x = 1:5, y = rnorm(5)) # using enquo + !! f1 = function(mydata, myvar) { m = enquo(myvar) mydata %>% mutate(two_y = 2 *

在下面的示例中,为什么我们更喜欢使用f1而不是f2?从某种意义上讲,它更有效吗?对于习惯以R为基础的人来说,使用替代+eval选项似乎更为自然

library(dplyr)

d = data.frame(x = 1:5,
               y = rnorm(5))

# using enquo + !!
f1 = function(mydata, myvar) {
  m = enquo(myvar)
  mydata %>%
    mutate(two_y = 2 * !!m)
}

# using substitute + eval    
f2 = function(mydata, myvar) {
  m = substitute(myvar)
  mydata %>%
    mutate(two_y = 2 * eval(m))
}

all.equal(d %>% f1(y), d %>% f2(y)) # TRUE

换句话说,除了这个特殊的例子之外,我的问题是:我能用dplyr NSE函数和良好的ol'base R替代+eval进行编程吗,或者我真的需要学习热爱所有这些rlang函数,因为它有利于速度、清晰度、合成性,…?

想象有一个不同的x要相乘:

> x <- 3
> f1(d, !!x)
  x            y two_y
1 1 -2.488894875     6
2 2 -1.133517746     6
3 3 -1.024834108     6
4 4  0.730537366     6
5 5 -1.325431756     6
!!与替代品相比,它能让您更好地控制范围-使用替代品,您只能轻松地获得第二种方式。

enquo和!!还允许您使用其他dplyr动词(如group_by和select)进行编程。我不确定替补和评估是否能做到这一点。看看这个例子,我稍微修改了一下您的数据帧

library(dplyr)

set.seed(1234)
d = data.frame(x = c(1, 1, 2, 2, 3),
               y = rnorm(5),
               z = runif(5))

# select, group_by & create a new output name based on input supplied
my_summarise <- function(df, group_var, select_var) {

  group_var <- enquo(group_var)
  select_var <- enquo(select_var)

  # create new name
  mean_name <- paste0("mean_", quo_name(select_var))

  df %>%
    select(!!select_var, !!group_var) %>% 
    group_by(!!group_var) %>%
    summarise(!!mean_name := mean(!!select_var))
}

my_summarise(d, x, z)

# A tibble: 3 x 2
      x mean_z
  <dbl>  <dbl>
1    1.  0.619
2    2.  0.603
3    3.  0.292

信用证:

我想给出一个独立于dplyr的答案,因为使用enquo比替代品有一个非常明显的优势。两者都在函数的调用环境中查找,以标识给定给该函数的表达式。不同的是替代品只做一次,而!!enquo将正确地遍历整个调用堆栈

考虑一个使用替换的简单函数:

这就是为什么enquo+!!最好是替换+eval。dplyr只是充分利用这个特性来构建一组连贯的NSE函数

更新:rlang 0.4.0引入了一个新的操作符{{发音为curly-curly,这实际上是!!enquo的缩写。这允许我们将g2的定义简化为


为了增加一些细微差别,这些东西在BaseR中不一定那么复杂

在正确的环境中计算替换参数时,务必记住使用eval.parent。如果正确使用eval.parent,嵌套调用中的表达式将找到它们的方法。如果不这样做,则可能会发现环境地狱:

我使用的基本工具箱由quote、substitute、bquote、as.call和do.call组成,后者与substitute一起使用时很有用

这里不详细介绍如何在没有任何整齐评估的情况下在base R中解决@Artem和@Tung提出的案例,然后是最后一个示例,不使用quo/enquo,但仍然受益于拼接和取消报价!!!和

我们将看到,拼接和取消引用使代码变得更好,但需要函数来支持它!在目前的情况下,使用quosures并不能显著地改善情况,但仍然可以说确实如此

用基R解Artem的情况
f0我认为,如果dplyr::ppl只允许我们将变量名作为字符串传递,那么世界将会变得更好,就像在mutate_.imo这样的带下划线的旧变体中一样。对于mutate等,一个更好的选择是使用colnames_as_strings=TRUE这样的参数,这将使使用dplyr更简单但在此之前,欢迎来到enquo/!!地狱…@lefft我被告知将列名作为字符传递是“危险和不可靠的”,但我从来没有得到过令人信服的解释来解释为什么会出现这种情况,除了那些对我来说非常罕见的情况。我想如果你经常遇到这些边缘情况,它会更有意义,只是对我来说很奇怪,因为我想我从来没有遇到过。@KonradRudolph我建议允许在一种定义为关于这个惯例的使用…@KonradRudolph在这一点上,我唯一觉得有足够知识来评论的是,你的案例可能没有得到第一句话的帮助。@KonradRudolph fwiw我相信你,如果没有其他原因,我知道你比我知道得多的话。我只是想把语气推向一个不同的方向。我ee。这似乎与以下内容有关:!!更好地处理了使用NSE的函数的组合。不过,这些示例似乎有点笨拙。谢谢!不过,看看替代+eval是否也能在这些情况下工作会很好。最后,我的问题基本上是:我能用goo使用dplyr NSE函数编程吗d ol'substitute+eval,还是我真的需要学习喜欢你提到的所有rlang函数,因为它有好处?@mbiron:我很好奇看到一个使用substitute+eval的解决方案。我想如果你使用了很多tidyverse软件包,那么当Hadley和其他开发人员朝着这个方向努力时,了解tidyeval是值得的。我这是一个将输入字符串解析为dplyr的示例ggplot2@mbiron当然,理论上你可以在这里使用eval和substitute,但是解决方案会非常复杂他的贡献是在现有计算机科学研究的基础上,对解决方案进行概括、形式化和简化。很好的答案,这就是我想要的。非常感谢
.当然,我们也可以使用*_at变体,但除此之外,还可以非常巧妙地使用eval.parent!
library(dplyr)

set.seed(1234)
d = data.frame(x = c(1, 1, 2, 2, 3),
               y = rnorm(5),
               z = runif(5))

# select, group_by & create a new output name based on input supplied
my_summarise <- function(df, group_var, select_var) {

  group_var <- enquo(group_var)
  select_var <- enquo(select_var)

  # create new name
  mean_name <- paste0("mean_", quo_name(select_var))

  df %>%
    select(!!select_var, !!group_var) %>% 
    group_by(!!group_var) %>%
    summarise(!!mean_name := mean(!!select_var))
}

my_summarise(d, x, z)

# A tibble: 3 x 2
      x mean_z
  <dbl>  <dbl>
1    1.  0.619
2    2.  0.603
3    3.  0.292
# example
grouping_vars <- quos(x, y)
d %>%
  group_by(!!!grouping_vars) %>%
  summarise(mean_z = mean(z))

# A tibble: 5 x 3
# Groups:   x [?]
      x      y mean_z
  <dbl>  <dbl>  <dbl>
1    1. -1.21   0.694
2    1.  0.277  0.545
3    2. -2.35   0.923
4    2.  1.08   0.283
5    3.  0.429  0.292


# in a function
my_summarise2 <- function(df, select_var, ...) {

  group_var <- enquos(...)
  select_var <- enquo(select_var)

  # create new name
  mean_name <- paste0("mean_", quo_name(select_var))

  df %>%
    select(!!select_var, !!!group_var) %>% 
    group_by(!!!group_var) %>%
    summarise(!!mean_name := mean(!!select_var))
}

my_summarise2(d, z, x, y)

# A tibble: 5 x 3
# Groups:   x [?]
      x      y mean_z
  <dbl>  <dbl>  <dbl>
1    1. -1.21   0.694
2    1.  0.277  0.545
3    2. -2.35   0.923
4    2.  1.08   0.283
5    3.  0.429  0.292
f <- function( myExpr ) {
  eval( substitute(myExpr), list(a=2, b=3) )
}

f(a+b)   # 5
f(a*b)   # 6
g <- function( myExpr ) {
  val <- f( substitute(myExpr) )
  ## Do some stuff
  val
}

g(a+b)
# myExpr     <-- OOPS
library( rlang )

f2 <- function( myExpr ) {
  eval_tidy( enquo(myExpr), list(a=2, b=3) )
}

g2 <- function( myExpr ) {
  val <- f2( !!enquo(myExpr) )
  val
}

g2( a+b )    # 5
g2( b/a )    # 1.5
g2 <- function( myExpr ) {
  val <- f2( {{myExpr}} )
  val
}
my_summarise0 <- function(df, group_var, select_var) {

  group_var  <- substitute(group_var)
  select_var <- substitute(select_var)

  # create new name
  mean_name <- paste0("mean_", as.character(select_var))

  eval.parent(substitute(
  df %>%
    select(select_var, group_var) %>% 
    group_by(group_var) %>%
    summarise(mean_name := mean(select_var))))
}

library(dplyr)
set.seed(1234)
d = data.frame(x = c(1, 1, 2, 2, 3),
               y = rnorm(5),
               z = runif(5))
my_summarise0(d, x, z)
#> # A tibble: 3 x 2
#>       x mean_z
#>   <dbl>  <dbl>
#> 1     1  0.619
#> 2     2  0.603
#> 3     3  0.292
grouping_vars <- c(quote(x), quote(y))
eval(as.call(c(quote(group_by), quote(d), grouping_vars))) %>%
  summarise(mean_z = mean(z))
#> # A tibble: 5 x 3
#> # Groups:   x [3]
#>       x      y mean_z
#>   <dbl>  <dbl>  <dbl>
#> 1     1 -1.21   0.694
#> 2     1  0.277  0.545
#> 3     2 -2.35   0.923
#> 4     2  1.08   0.283
#> 5     3  0.429  0.292
my_summarise02 <- function(df, select_var, ...) {

  group_var  <- eval(substitute(alist(...)))
  select_var <- substitute(select_var)

  # create new name
  mean_name <- paste0("mean_", as.character(select_var))

  df %>%
    {eval(as.call(c(quote(select),quote(.), select_var, group_var)))} %>% 
    {eval(as.call(c(quote(group_by),quote(.), group_var)))} %>%
    {eval(bquote(summarise(.,.(mean_name) := mean(.(select_var)))))}
}

my_summarise02(d, z, x, y)
#> # A tibble: 5 x 3
#> # Groups:   x [3]
#>       x      y mean_z
#>   <dbl>  <dbl>  <dbl>
#> 1     1 -1.21   0.694
#> 2     1  0.277  0.545
#> 3     2 -2.35   0.923
#> 4     2  1.08   0.283
#> 5     3  0.429  0.292

grouping_vars <- c(quote(x), quote(y))

d %>%
  group_by(!!!grouping_vars) %>%
  summarise(mean_z = mean(z))
#> # A tibble: 5 x 3
#> # Groups:   x [3]
#>       x      y mean_z
#>   <dbl>  <dbl>  <dbl>
#> 1     1 -1.21   0.694
#> 2     1  0.277  0.545
#> 3     2 -2.35   0.923
#> 4     2  1.08   0.283
#> 5     3  0.429  0.292
my_summarise03 <- function(df, select_var, ...) {

  group_var  <- eval(substitute(alist(...)))
  select_var <- substitute(select_var)

  # create new name
  mean_name <- paste0("mean_", as.character(select_var))

  df %>%
    select(!!select_var, !!!group_var) %>% 
    group_by(!!!group_var) %>%
    summarise(.,!!mean_name := mean(!!select_var))
}

my_summarise03(d, z, x, y)
#> # A tibble: 5 x 3
#> # Groups:   x [3]
#>       x      y mean_z
#>   <dbl>  <dbl>  <dbl>
#> 1     1 -1.21   0.694
#> 2     1  0.277  0.545
#> 3     2 -2.35   0.923
#> 4     2  1.08   0.283
#> 5     3  0.429  0.292