web-dev-qa-db-ja.com

なぜenquo + !! + evalに置き換えるのが望ましい

次の例では、なぜf1以上f2?ある意味でより効率的ですか? 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

つまり、この特定の例を超えて、私の質問は次のとおりです。dplyr NSE関数を使用して、代替+評価のような古いベースRのNSE関数をプログラミングから解放できますか、それとも本当に愛することを学ぶ必要がありますか?これらのすべてのrlang関数には利点があります(速度、明確さ、構成性など)?

28
mbiron

dplyrよりもenquoを使用する方が明確な利点があるため、substituteとは無関係の答えを出したいと思います。どちらも関数の呼び出し環境を調べて、その関数に与えられた式を識別します。違いは、substitute()は1回しか実行しないのに対し、!!enquo()は呼び出しスタック全体を正しく処理することです。

substitute()を使用する単純な関数を考えてみます。

_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
_

次に、同じ関数を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
_

そして、それがenquo() + __!!_がsubstitute() + eval()よりも望ましい理由です。

dplyrは、このプロパティを最大限に活用して、NSE関数の一貫したセットを構築します。

8
Artem Sokolov

enquo()および!!では、group_bydplyrなどの他のselect動詞を使用してプログラミングすることもできます。 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

編集: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

クレジット: dplyrによるプログラミング

6
Tung

乗算したい別の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

!!なし:

> 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

!!を使用すると、substituteよりもスコープをより詳細に制御できます。代わりに使用すると、2番目の方法しか簡単に取得できません。

5
Neal Fultz