次の例では、なぜ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
関数には利点があります(速度、明確さ、構成性など)?
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関数の一貫したセットを構築します。
enquo()
および!!
では、group_by
やdplyr
などの他のselect
動詞を使用してプログラミングすることもできます。 substitute
とeval
がそれを実行できるかどうかはわかりません。データフレームを少し変更するこの例を見てください。
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によるプログラミング
乗算したい別の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番目の方法しか簡単に取得できません。