Dans l'exemple suivant, pourquoi devrions-nous privilégier l'utilisation de f1
plus de f2
? Est-il plus efficace dans un certain sens? Pour quelqu'un qui avait l'habitude de baser R, il semble plus naturel d'utiliser l'option "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
En d'autres termes, et au-delà de cet exemple particulier, ma question est: puis-je me débrouiller avec la programmation en utilisant les fonctions dplyr
NSE avec une bonne vieille base R comme substitut + eval, ou ai-je vraiment besoin d'apprendre à aimer toutes ces fonctions rlang
parce qu'il y a un avantage (vitesse, clarté, compositionnalité, ...)?
Je veux donner une réponse indépendante de dplyr
, car il y a un avantage très clair à utiliser enquo
par rapport à substitute
. Les deux regardent dans l'environnement appelant d'une fonction pour identifier l'expression qui a été donnée à cette fonction. La différence est que substitute()
ne le fait qu'une seule fois, tandis que !!enquo()
remontera correctement toute la pile d'appel.
Considérons une fonction simple qui utilise substitute()
:
f <- function( myExpr ) {
eval( substitute(myExpr), list(a=2, b=3) )
}
f(a+b) # 5
f(a*b) # 6
Cette fonctionnalité est interrompue lorsque l'appel est imbriqué dans une autre fonction:
g <- function( myExpr ) {
val <- f( substitute(myExpr) )
## Do some stuff
val
}
g(a+b)
# myExpr <-- OOPS
Considérons maintenant les mêmes fonctions réécrites en utilisant 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
Et c'est pourquoi enquo()
+ !!
Est préférable à substitute()
+ eval()
.
dplyr
tire simplement pleinement parti de cette propriété pour construire un ensemble cohérent de fonctions NSE.
enquo()
et !!
vous permet également de programmer avec d'autres dplyr
verbes tels que group_by
et select
. Je ne sais pas si substitute
et eval
peuvent le faire. Jetez un oeil à cet exemple où je modifie un peu votre bloc de données
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
Modifier: également enquos
& !!!
facilite la capture de la liste des variables
# 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
Crédit: Programmation avec dplyr
Imaginez qu'il y ait un x différent que vous souhaitez multiplier:
> 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
vs sans le !!
:
> 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
!!
vous donne plus de contrôle sur la portée que substitute
- avec un substitut, vous ne pouvez obtenir la 2e voie facilement.