Paso a paso en TidyEval

Entendiendo Tidyeval gradualmente

Illustration from unsplash

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! Pasos de bebé

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!

Maria Dermit
Maria Dermit
Investigadora postdoctoral

Postdoctoral researcher interested in translation control and data science for biomedical research.

Relacionado