Me gustaría entender cómo pasar cadenas que representan expresiones en dplyr, para que las variables mencionadas en la cadena se evalúen como expresiones en columnas en el marco de datos. La viñeta principal sobre este tema cubre la aprobación de las posiciones, y no trata las cadenas en absoluto .

Está claro que las quosures son más seguras y claras que las cadenas cuando se representan expresiones, por lo que, por supuesto, debemos evitar las cadenas cuando se pueden usar quosures. Sin embargo, cuando se trabaja con herramientas fuera del ecosistema R, como javascript o archivos de configuración YAML, a menudo se tendrá que trabajar con cadenas en lugar de quosures.

Por ejemplo, supongamos que quiero una función que haga un recuento agrupado utilizando expresiones que el usuario / llamante haya transmitido. Como se esperaba, el siguiente código no funciona, ya que dplyr usa una evaluación no estándar para interpretar los argumentos a group_by.

library(tidyverse)

group_by_and_tally <- function(data, groups) {
  data %>%
    group_by(groups) %>%
    tally()
}

my_groups <- c('2 * cyl', 'am')
mtcars %>%
  group_by_and_tally(my_groups)
#> Error in grouped_df_impl(data, unname(vars), drop): Column `groups` is unknown

En dplyr 0.5 usaríamos una evaluación estándar, como group_by_(.dots = groups), para manejar esta situación. Ahora que los verbos de subrayado están en desuso, ¿cómo deberíamos hacer este tipo de cosas en dplyr 0.7?

En el caso especial de expresiones que son solo nombres de columna, podemos usar las soluciones para esta pregunta, pero no funcionan para expresiones más complejas como 2 * cyl que no son solo un nombre de columna.

5
Paul 16 jun. 2017 a las 19:06

3 respuestas

La mejor respuesta

Es importante tener en cuenta que, en este simple ejemplo, tenemos control de cómo se crean las expresiones. Entonces, la mejor manera de pasar las expresiones es construir y pasar las cotizaciones directamente usando quos():

library(tidyverse)
library(rlang)

group_by_and_tally <- function(data, groups) {
  data %>%
    group_by(UQS(groups)) %>%
    tally()
}

my_groups <- quos(2 * cyl, am)
mtcars %>%
  group_by_and_tally(my_groups)
#> # A tibble: 6 x 3
#> # Groups:   2 * cyl [?]
#>   `2 * cyl`    am     n
#>       <dbl> <dbl> <int>
#> 1         8     0     3
#> 2         8     1     8
#> 3        12     0     4
#> 4        12     1     3
#> 5        16     0    12
#> 6        16     1     2

Sin embargo, si recibimos las expresiones de una fuente externa en forma de cadenas, simplemente podemos analizar las expresiones primero, lo que las convierte en quosures:

my_groups <- c('2 * cyl', 'am')
my_groups <- my_groups %>% map(parse_quosure)
mtcars %>%
  group_by_and_tally(my_groups)
#> # A tibble: 6 x 3
#> # Groups:   2 * cyl [?]
#>   `2 * cyl`    am     n
#>       <dbl> <dbl> <int>
#> 1         8     0     3
#> 2         8     1     8
#> 3        12     0     4
#> 4        12     1     3
#> 5        16     0    12
#> 6        16     1     2

Nuevamente, solo deberíamos hacer esto si obtenemos expresiones de una fuente externa que las proporcione como cadenas; de lo contrario, deberíamos realizar reservas directamente en el código fuente R.

10
Paul 18 jun. 2017 a las 19:51

El paquete friendlyeval puede ayudarlo con esto:

library(tidyverse)
library(friendlyeval)

group_by_and_tally <- function(data, groups) {
  data %>%
    group_by(!!!friendlyeval::treat_strings_as_exprs(groups)) %>%
    tally()
}

my_groups <- c('2 * cyl', 'am')
mtcars %>%
  group_by_and_tally(my_groups)

# # A tibble: 6 x 3
# # Groups:   2 * cyl [?]
# `2 * cyl`    am     n
# <dbl> <dbl> <int>
# 1         8     0     3
# 2         8     1     8
# 3        12     0     4
# 4        12     1     3
# 5        16     0    12
# 6        16     1     2
2
MilesMcBain 24 jun. 2018 a las 00:01

Es tentador usar cadenas, pero casi siempre es mejor usar expresiones. Ahora que tiene cuasiquotación, puede construir fácilmente expresiones de una manera flexible:

lhs <- "cyl"
rhs <- "disp"
expr(!!sym(lhs) * !!sym(rhs))
#> cyl * disp

vars <- c("cyl", "disp")
expr(sum(!!!syms(vars)))
#> sum(cyl, disp)
5
Lionel Henry 13 nov. 2018 a las 10:45