Dplyr 0.5.0

publicado en: rstudio | 0

Me complace anunciar que el dplyr 0.5.0 ya está disponible en CRAN. Consigue la última versión con:

install.packages("dplyr")

dplyr 0.5.0 es un gran lanzamiento con un montón de nuevas características, un montón de mejoras menores, y muchas correcciones de errores, tanto de mí como de la más amplia comunidad de dplyr. En esta entrada del blog, voy a destacar los cambios más importantes:

  • Algunos cambios de ruptura a los verbos de una sola tabla.

  • Nuevos paquetes de tibia y dtplyr.

  • Nuevas funciones vectoriales.

  • Reemplazo de «resumir cada uno» y «mutar cada uno».

  • Mejoras en la traducción del SQL.

Para ver la lista completa, por favor lea las notas de la liberación.

Cambios de ruptura

arrange() una vez más ignora la agrupación, volviendo al comportamiento de dplyr 0.3 y anterior. Esto hace que arrange() sea inconsistente con otros verbos dplyr, pero creo que este comportamiento es generalmente más útil. De todos modos, no va a cambiar de nuevo, ya que más cambios sólo causarán más confusión.

mtcars %...
  group_by(cyl) %>%
  arreglar(desc(mpg))
#Fuente: marco de datos local [32 x 11].
#Grupos: cyl [3]
#...y que no se puede hacer nada..;
#; # Una tibia: 32 x 11
#mpg cyl disp hp drat wt qsec vs am gear carb
#...y la de los niños;
#1 33,9 4 71,1 65 4,22 1.835 19,90 1 1 4 1
#2 32,4 4 78,7 66 4,08 2,200 19,47 1 1 4 1
#3 30,4 4 75,7 52 4,93 1.615 18,52 1 1 4 2
#4 30,4 4 95,1 113 3,77 1.513 16,90 1 1 5 2
#5 27,3 4 79,0 66 4,08 1.935 18,90 1 1 4 1
#...con 27 filas más...

Si se da a Distinguir() una lista de variables, ahora sólo guarda esas variables (en lugar de, como antes, guardar el primer valor de las otras variables). Para preservar el comportamiento anterior, usa .keep_all = TRUE:

df <- data_frame(x = c(1, 1, 1, 2, 2), y = 1:5)
# Ahora sólo mantiene la variable x
df % % distinto(x)
#Una tibia: 2 x 1
#... x... #
# ...y... #
#1 1
#2 2
# El comportamiento anterior preservó todas las variables
df % ... % distinto(x, .keep_all = VERDADERO)
#Una tibia: 2 x 2
#x y
#...y la de los demás..;
#1 1 1
#2 2 4

Las funciones de ayuda select() empieza_con(), termina_con(), etc. son ahora verdaderas funciones exportadas. Esto significa que tienen una mejor documentación, y hay un mecanismo de extensión si quieres escribir tus propios helpers.

Paquetes de Tibble y dtplyr

Las funciones relacionadas con la creación y la coacción de tbl_dfs («tibble» para abreviar), ahora viven en su propio paquete: tibble. Véase la viñeta («tibble») para más detalles.

De manera similar, todo el código relacionado con el código de respaldo de la tabla de datos dplyr se ha separado en un nuevo paquete de dtplyr. Esto separa el desarrollo de la interfaz data.table del desarrollo del paquete dplyr, y espero que impulse mejoras en el backend. Si tanto data.table como dplyr están cargados, recibirá un mensaje recordándole que cargue dtplyr.

Funciones vectoriales

Esta versión de dplyr obtiene una serie de funciones vectoriales inspiradas en SQL. Dos funciones facilitan un poco más la eliminación o la generación de valores perdidos:

  • Dado un conjunto de vectores, coalesce() encuentra el primer valor que no falta en cada posición:
x <- c(1, 2, NA, 4, NA, 6)
y <- c(NA, 2, 3, 4, 5, NA)
# Usa esto para unir un vector completo:
fusionar(x, y)
#[1] 1 2 3 4 5 6
# O simplemente reemplazar el valor perdido con una constante:
coalesce(x, 0)
#[1] 1 2 0 4 0 6
  • El complemento de coalesce() es na_if(): reemplaza un valor especificado con un NA.
x <- c(1, 5, 2, -99, -99, 10)
na_if(x, -99)
#[1] 1 5 2 NA NA 10

Tres funciones proporcionan formas convenientes de reemplazar los valores. En orden de lo más simple a lo más complicado, son:

  • if_else(), una sentencia if vectorizada, toma un vector lógico (normalmente creado con un operador de comparación como ==, <, o %in%) y reemplaza TRUEs con un vector y FALSEs con otro.
x1 ;- muestra(5)
if_else(x1 < 5, "pequeño", "grande")
#... [1] "pequeño" "pequeño" "grande" "pequeño" "pequeño"

if_else() es similar a la base::ifelse(), pero tiene dos mejoras útiles.
Primero, tiene un cuarto argumento que reemplazará los valores perdidos:

x2 <- c(NA, x1)
if_else(x2 < 5, "pequeño", "grande", "desconocido")
#...desconocido...pequeño...pequeño...grande...pequeño...pequeño...pequeño...

En segundo lugar, también tiene una semántica más estricta que ifelse(): los argumentos verdaderos y falsos deben ser del mismo tipo. Esto da un tipo de retorno menos sorprendente, y conserva los vectores S3 como fechas y factores:

x <- factor(sample(letters[1:5], 10, replace = TRUE))
ifelse(x %in% c("a", "b", "c"), x, factor(NA))
#[1] NA NA 1 NA 3 2 3 NA 3 2
if_else(x %in% c("a", "b", "c"), x, factor(NA))
#c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c
#; Niveles: a b c d e

Actualmente, if_else() es muy estricto, por lo que tendrás que emparejar cuidadosamente los tipos de verdadero y falso. Es más probable que esto te pique cuando uses valores perdidos, y necesitarás usar una NA específica: NA_integer_, NA_real_, o NA_character_:

if_else(TRUE, 1, NA)
#; Error: "falso" tiene el tipo "lógico" no "doble
if_else(TRUE, 1, NA_real_)
#... [1] 1
  • recode(), un conmutador vectorial(), toma un vector numérico, un vector de carácter o un factor y sustituye elementos en función de sus valores.
x <- muestra(c("a", "b", "c", NA), 10, reemplazar = VERDADERO)
# El valor por defecto es dejar los valores no reemplazados como están
recodificar(x, a = "Manzana")
#; [1] "c" "Manzana" NA "c" NA "b" NA
#; [9] "c" "Manzana"
# Pero puedes elegir anular el valor por defecto:
recode(x, a = "Apple", .default = NA_character_)
#; [1] NA "Manzana" NA NA NA NA NA NA NA
#; [9] NA "Manzana"
# También puede elegir qué valor se utiliza para los valores perdidos
recode(x, a = "Apple", .default = NA_character_, .missing = "Unknown")
#; [1] NA "Manzana" "Desconocido" "Desconocido" NA "Desconocido" NA
#; [8] "Desconocido" NA "Manzana"
  • case_when(), es un conjunto vectorizado de if y else ifs. Le proporcionas un conjunto de pares de resultados de prueba como fórmulas: El lado izquierdo de la fórmula debe devolver un vector lógico, y el lado derecho debe devolver un valor único, o un vector de la misma longitud que el lado izquierdo. Todos los resultados deben ser del mismo tipo de vector.
x <- 1:40
case_when(
  x %% 35 == 0 ~ "fizz buzz",
  x %% 5 == 0 ~ "fizz",
  x %% 7 == 0 ~ "buzz",
  VERDADERO ~ as.character(x)
)
#...1...2...3...4...fizz...#
#...6...6...zumbido...8...9...efervescencia...
#...11...12...13...zumbido...efervescencia...
#... [16] "16" "17" "18" "19" "efervescencia"
#; [21] "buzz" "22" "23" "24" "fizz"
#... [26] "26" "27" "zumbido" "29" "efervescencia"
#... [31] "31" "32" "33" "34" "fizz buzz"
#... [36] "36" "37" "38" "39" "efervescencia"

case_when() es todavía un experimento y no funciona actualmente dentro de mutate(). Eso se arreglará en una futura versión.

También añadí un pequeño ayudante para tratar con las comparaciones de punto flotante: near() pruebas de igualdad con la tolerancia numérica (abs(x – y) < tolerancia).

x <- sqrt(2) ^ 2
x == 2
#FALSO
near(x, 2)
#... [1] VERDADERO

Funciones de predicción

Gracias a las ideas y el código de Lionel Henry, una nueva familia de funciones mejoran al resumir cada uno y mutar cada uno:

  • resumir_todos() y mutar_todos() aplican una función a todas las columnas (no agrupadas):

%>% group_by(cyl) %>% summarise_all(mean)
#> # Una tibia: 3 x 11
#> cyl mpg disp hp drat wt qsec vs
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#1 4 26.66364 105.1364 82.63636 4.070909 2.285727 19.13727 0.9090909
#> 2 6 19.74286 183.3143 122.28571 3.585714 3.117143 17.97714 0.5714286
#> 3 8 15.10000 353.1000 209.21429 3.229286 3.999214 16.77214 0.0000000
#> … con 3 variables más: am <dbl>, gear <dbl>, carb <dbl>
…y, por último..;

  • resumir_at() y mutar_at() operan en un subconjunto de columnas. Puede seleccionar columnas con:

    • un vector de caracteres de nombres de columnas,

    • un vector numérico de posiciones de columna, o

    • una especificación de columna con semántica select() generada con el nuevo ayudante vars().

    mtcars %>% group_by(cyl) %>% summary_at(c("mpg", "wt"), mean)
    #> # Una tibia: 3 x 3
    #> cyl mpg wt
    #>
    #> 1 4 26.66364 2.285727
    #> 2 6 19.74286 3.117143
    #> 3 8 15.10000 3.999214
    mtcars %>% group_by(cyl) %>% summary_at(vars(mpg, wt), mean)
    #> # Una tibia: 3 x 3
    #> cyl mpg wt
    #>
    #> 1 4 26.66364 2.285727
    #> 2 6 19.74286 3.117143
    #> 3 8 15.10000 3.999214

  • resumir_if() y mutar_if() toman una función predicada (una función que devuelve VERDADERO o FALSO cuando se le da una columna). Esto facilita la aplicación de una función sólo a las columnas numéricas:

pre_______________ iris %>% summary_if(is.numérico, media)
#> Sepal.Largo Sepal.Ancho Petal.Largo Petal.Ancho
#> 1 5.843333 3.057333 3.758 1.199333
…y que no se puede hacer nada..;

Todas estas funciones pasan… a las diversiones individuales:

pre_______________ iris %>% summary_if(is.numeric, mean, trim = 0.25)
#> Sepal.Largo Sepal.Ancho Petal.Largo Petal.Ancho
#> 1 5.802632 3.032895 3.934211 1.230263
…y que no se puede hacer nada..;

Un nuevo select_if() permite elegir columnas con una función de predicado:

df <- data_frame(x = 1:3, y = c("a", "b", "c"))
df %>% select_if(is.numeric)
#> # Una tibia: 3 x 1
#> x
#> <int>
#> 1 1
#> 2 2
#> 3 3
df %>% select_if(is.character)
#> # Una tibia: 3 x 1
#> y
#> <chr>
#> 1 a
#> 2 b
#> 3 c
…y que no se puede hacer nada..;

resumir_cada() y mutar_cada() será desaprobado en una futura publicación.

Traducción SQL

He revisado completamente la traducción de los verbos dplyr en las sentencias SQL. Anteriormente, dplyr usaba un enfoque bastante ad-hoc que trataba de adivinar cuando se necesitaba una nueva subconsulta. Desafortunadamente este enfoque estaba lleno de errores, así que ahora he implementado un modelo de datos interno más rico. A corto plazo, es probable que esto conduzca a algunas disminuciones menores de rendimiento (ya que el SQL generado es más complejo), pero es mucho más probable que el dplyr genere un SQL correcto. A largo plazo, estas abstracciones permitirán escribir un optimizador/compilador de consultas en dplyr, lo que permitiría generar consultas mucho más sucintas. Si usted sabe algo acerca de escribir optimizadores o compiladores de consultas y está interesado en trabajar en este problema, por favor hágamelo saber!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *