为什么是enquo + !! 最好替换+ eval

mbi*_*ron 26 r dplyr nse tidyeval

在下面的例子中,我们为什么要赞成使用f1f2?从某种意义上说它更有效吗?对于习惯使用R的人来说,使用"substitute + 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
Run Code Online (Sandbox Code Playgroud)

换句话说,除了这个特殊的例子之外,我的问题是:我可以使用dplyr具有良好的基础R的替代+ eval的NSE函数编程,或者我真的需要学会喜欢所有这些rlang函数,因为有它的好处(速度,清晰度,组合性......)?

Art*_*lov 8

我想给出一个独立的答案dplyr,因为使用enquo结束有一个非常明显的优势substitute.两者都查看函数的调用环境以识别赋予该函数的表达式.区别在于substitute()它只会执行一次,而!!enquo()会正确地向上移动整个调用堆栈.

考虑一个使用的简单函数substitute():

f <- function( myExpr ) {
  eval( substitute(myExpr), list(a=2, b=3) )
}

f(a+b)   # 5
f(a*b)   # 6
Run Code Online (Sandbox Code Playgroud)

当调用嵌套在另一个函数中时,此功能会中断:

g <- function( myExpr ) {
  val <- f( substitute(myExpr) )
  ## Do some stuff
  val
}

g(a+b)
# myExpr     <-- OOPS
Run Code Online (Sandbox Code Playgroud)

现在考虑使用enquo()以下方法重写相同的函数:

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
Run Code Online (Sandbox Code Playgroud)

这就是为什么enquo()+ !!优于substitute()+ eval().

dplyr 只需充分利用此属性即可构建一组连贯的NSE函数.

  • 很好的答案,这就是我要找的。非常感谢。 (2认同)

Tun*_*ung 6

enquo()并且!!还允许您使用其他dplyr动词编程,例如group_byselect.我不知道,如果substituteeval能做到这一点.看看这个例子,我稍微修改了你的数据框

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
Run Code Online (Sandbox Code Playgroud)

编辑:还enquos!!!更容易捕捉到的变量列表

# 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
Run Code Online (Sandbox Code Playgroud)

信用:用dplyr编程

  • @mbiron当然,理论上你可以在这里使用`eval`和`substitute`.但解决方案将是痛苦复杂和复杂的.{rlang}的贡献是通过在现有计算机科学研究的基础上推广,形式化和简化解决方案. (2认同)

Nea*_*ltz 5

想象一下,你想要乘以不同的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
Run Code Online (Sandbox Code Playgroud)

vs没有!!:

> f1(d, x)
  x            y two_y
1 1 -2.488894875     2
2 2 -1.133517746     4
3 3 -1.024834108     6
4 4  0.730537366     8
5 5 -1.325431756    10
Run Code Online (Sandbox Code Playgroud)

!!让你更好地控制范围而不是substitute- 用替代你只能轻松获得第二种方式.