Motivación
Provide some examples of using Tidy evaluation (also called non-standard evaluation (NSE) or delayed evaluation).
ℹ️ Este post te puede resultar beneficioso si has leído los capítulos 17-20 del libro de Advanced R y estás buscando encontrar más ejemplos adicionales y en español al gran recurso de Mara.
Librerias que necesitaremos para este post:
library(rlang)
library(tidyverse)
library(testthat)
Vamos a crear una función para calcular el coeficiente de variación
cv <- function(var) {
sd(var) / mean(var)
}
Podemos probar que la función se está comportando correctamente:
testthat::expect_equal( cv(c(3,3)), 0)
testthat::expect_equal( round(cv(c(3,6)),2), 0.47)
¡Excelente! ¡La función parece estar haciendo lo que queremos!
Capturar y descubrir expresión
Podemos capturar y hacer expresiones (entrecomillar) de lo que el usuario ha pasado como argumento con enexpr
. Podemos quitar los entrecomillados con bang-bang !!
. Si le pasamos el resultado de esto a expr lo seguimos manteniendo como una expresión. Seguimos estando en evaluación retrasada, nada se ha evaluado todavia.
cv <- function(var) {
var <- enexpr(var)
expr(sd(!!var) / mean(!!var))
}
Veamos qué pasa:
testthat::expect_equal(eval(cv(c(3,3))), 0)
testthat::expect_type((cv(c(3,3))), "language")
Si evaluamos la función, el cv es igual a 0. Tenga en cuenta que hemos retrasado la evaluación hasta el punto cuando usamos eval.
Si no evaluamos la función, permanece como un objeto de llamada o call object.
Modificando el medio ambiente, sin problemas
Modifiquemos un poco el enviroment
o entorno de la función:
cv <- function(var) {
x=6
var <- enexpr(var)
expr(sd(!!var) / mean(!!var))
}
Veamos qué pasa:
x=3
testthat::expect_equal( eval(cv(c(3,x))), 0)
x=6
testthat::expect_equal(round(eval(cv(c(3,x))),2), 0.47)
Esto funciona incluso si ponemos x en el entorno donde se escribe la función porque x no es un argumento de la función cv.
Modificando el ambiente - PROBLEMAS!
adding_cv <- function(df,var) {
x=c(3,6)
var <- enexpr(var)
mutate(df, sd(!!var) / mean(!!var))
}
Veamos que pasa:
df<- tibble(n=3)
x=c(3,3)
adding_cv(df,x)
## # A tibble: 1 x 2
## n `sd(x)/mean(x)`
## <dbl> <dbl>
## 1 3 0.471
¡¿Cómo?! La nueva columna contiene el coeficiente de variación. Si x = c (3,3), el valor en la nueva columna debería haber sido 0. Sin embargo, agregar_cv está usando x = c (3,6) incluido en el entorno de la función y no x = c (3,3).
Capturando con enquo
Para capturar la función y el entorno, necesitamos enquo
adding_cv <- function(df,var) {
x=c(3,6)
var <- enquo(var)
mutate(df, sd(!!var) / mean(!!var))
}
Veamos que pasa ahora:
df<- tibble(n=3)
x=c(3,3)
adding_cv(df,x)
## # A tibble: 1 x 2
## n `sd(x)/mean(x)`
## <dbl> <dbl>
## 1 3 0
Cambiando el nombre con with :=
Podemos hacer que el nombre de la nueva columna sea más bonito con : =
adding_cv <- function(df,var,nm) {
x=c(3,3)
nm_name <- quo_name(nm)
var <- enquo(var)
mutate(df, !!nm_name:= sd(!!var) / mean(!!var))
}
Veamos si eso permite cambiar el nombre:
x=c(3,6)
df<- tibble(n=3)
adding_cv(df,x,"nombre_bonito")
## # A tibble: 1 x 2
## n nombre_bonito
## <dbl> <dbl>
## 1 3 0.471
Insertando una lista de expresiones en una llamada
¿Qué pasa si queremos hacer un filtrado detraído? Necesitamos quitar las comillas de varios argumentos. Para esto podemos usar !!!.
adding_cv <- function(df,var,nm, ...) {
x=c(3,6)
nm_name <- quo_name(nm)
var <- enquo(var)
filtering <- enquos(...)
df %>% filter(!!!filtering) %>%
mutate( !!nm_name:= sd(!!var) / mean(!!var))}
Veamos si podemos filtrar las filas 3 y 6 de la columna de nuestro df.
df<- tibble(n=c(3,6,9),m=c(3,6,9))
x=c(3,3)
adding_cv(df,x,"pretty_name", n %in% c(3,6))
## # A tibble: 2 x 3
## n m pretty_name
## <dbl> <dbl> <dbl>
## 1 3 3 0
## 2 6 6 0
Conclusiones
Estos fueron solo algunos ejemplos para ilustrar por qué tidyeval puede ser útil y cuándo podría ser necesario. ¡Espero que te haya ayudado!