Pregunta data.table vs dplyr: ¿puede uno hacer algo bien que el otro no puede o no funciona bien?


Visión de conjunto

Estoy relativamente familiarizado con data.table, no tanto con dplyr. He leído algunos dplyr viñetas y ejemplos que han aparecido en SO, y hasta ahora mis conclusiones son que:

  1. data.table y dplyr son comparables en velocidad, excepto cuando hay muchos grupos (es decir,> 10-100K) y en algunas otras circunstancias (vea los puntos de referencia a continuación)
  2. dplyr tiene una sintaxis más accesible
  3. dplyr abstrae (o lo hará) posibles interacciones DB
  4. Hay algunas diferencias menores de funcionalidad (ver "Ejemplos / Uso" a continuación)

En mi mente 2. no soporta mucho peso porque estoy bastante familiarizado con él data.table, aunque entiendo que para los usuarios nuevos en ambos será un factor importante. Me gustaría evitar una discusión sobre cuál es más intuitiva, ya que eso es irrelevante para mi pregunta específica desde la perspectiva de alguien que ya esté familiarizado con data.table. También me gustaría evitar una discusión sobre cómo "más intuitivo" conduce a un análisis más rápido (ciertamente cierto, pero de nuevo, no es lo que más me interesa aquí).

Pregunta

Lo que quiero saber es:

  1. ¿Hay tareas analíticas que sean mucho más fáciles de codificar con uno u otro paquete para las personas familiarizadas con los paquetes (es decir, una combinación de combinaciones de teclas necesarias frente al nivel requerido de esoterismo, donde menos de cada una es una buena opción).
  2. ¿Hay tareas analíticas que se realizan sustancialmente (es decir, más de 2 veces) de manera más eficiente en un paquete frente a otro?

Uno pregunta reciente de SO me hizo pensar un poco más sobre esto, porque hasta ese momento no pensaba dplyr ofrecería mucho más allá de lo que ya puedo hacer en data.table. Aquí está el dplyr solución (datos al final de Q):

dat %.%
  group_by(name, job) %.%
  filter(job != "Boss" | year == min(year)) %.%
  mutate(cumu_job2 = cumsum(job2))

Que era mucho mejor que mi intento de pirateo en un data.table solución. Dicho eso, bueno data.table las soluciones también son bastante buenas (gracias Jean-Robert, Arun, y tenga en cuenta que preferí una sola declaración sobre la solución estrictamente más óptima):

setDT(dat)[,
  .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], 
  by=list(id, job)
]

La sintaxis de este último puede parecer muy esotérica, pero en realidad es bastante sencillo si estás acostumbrado a data.table (es decir, no usa algunos de los trucos más esotéricos).

Idealmente, lo que me gustaría ver es algunos buenos ejemplos fueron los dplyr o data.table manera es sustancialmente más conciso o funciona sustancialmente mejor.

Ejemplos

Uso
  • dplyr no permite operaciones agrupadas que devuelven un número arbitrario de filas (desde la pregunta de eddi, nota: parece que se implementará en dplyr 0.5, también, @beginneR muestra una solución potencial usando do en la respuesta a la pregunta de @eddi).
  • data.table apoyos se une (gracias @dholstius), así como se une a la superposición
  • data.table optimiza internamente expresiones de la forma DT[col == value] o DT[col %in% values] para velocidad mediante indexación automática que usa búsqueda binaria mientras usa la misma sintaxis de base R. Mira aquí para más detalles y un pequeño punto de referencia.
  • dplyr ofrece versiones de evaluación estándar de funciones (p. regroup, summarize_each_) que puede simplificar el uso programático de dplyr (tenga en cuenta el uso programático de data.table es definitivamente posible, solo requiere un pensamiento cuidadoso, sustitución / cotización, etc., al menos que yo sepa)
Puntos de referencia

Datos

Esto es para el primer ejemplo que mostré en la sección de preguntas.

dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))

568
2018-01-29 15:21


origen


Respuestas:


Necesitamos cubrir al menos estos aspectos para proporcionar una respuesta / comparación completa (sin ningún orden de importancia particular): Speed, Memory usage, Syntax y Features.

Mi intención es cubrir cada uno de estos de la forma más clara posible desde la perspectiva de data.table.

Nota: a menos que se mencione explícitamente lo contrario, al referirnos a dplyr, nos referimos a la interfaz data.frame de dplyr cuyos internos están en C ++ usando Rcpp.


La sintaxis data.table es consistente en su forma: DT[i, j, by]. Mantener i, j y by juntos es por diseño. Al mantener operaciones relacionadas entre sí, permite optimizar fácilmente operaciones para velocidad y más importante uso de memoriay también proporcionar algunos potentes funciones, todo manteniendo la consistencia en sintaxis.

1. Velocidad

Se han agregado bastantes puntos de referencia (aunque principalmente en operaciones de agrupación) a la pregunta que ya muestra data.table gets Más rápido que dplyr como el número de grupos y / o filas a agrupar por incremento, incluyendo puntos de referencia de Matt en la agrupación de 10 millones a 2 mil millones de filas (100GB en RAM) en 100 - 10 millones de grupos y variando las columnas de agrupamiento, que también compara pandas.

En los puntos de referencia, sería genial cubrir estos aspectos restantes también:

  • Agrupación de operaciones que involucran un subconjunto de filas - es decir, DT[x > val, sum(y), by = z] tipo de operaciones.

  • Comparativa de otras operaciones como actualizar y se une.

  • También punto de referencia huella de memoria para cada operación además del tiempo de ejecución.

2. Uso de la memoria

  1. Operaciones que involucran filter() o slice() en dplyr puede ser ineficiente en memoria (tanto en data.frames como en data.tables). Ver esta publicación.

    Tenga en cuenta que El comentario de Hadley habla sobre velocidad (que dplyr es bastante rápido para él), mientras que la mayor preocupación aquí es memoria.

  2. La interfaz data.table en este momento permite modificar / actualizar columnas por referencia (tenga en cuenta que no necesitamos volver a asignar el resultado a una variable).

    # sub-assign by reference, updates 'y' in-place
    DT[x >= 1L, y := NA]
    

    Pero dplyr Nunca actualizar por referencia. El equivalente dplyr sería (tenga en cuenta que el resultado debe ser reasignado):

    # copies the entire 'y' column
    ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))
    

    Una preocupación para esto es transparencia referencial. La actualización de un objeto data.table por referencia, especialmente dentro de una función, puede no ser siempre deseable. Pero esta es una característica increíblemente útil: ver esta y esta publicaciones para casos interesantes. Y queremos mantenerlo.

    Por lo tanto, estamos trabajando para exportar shallow() función en data.table que proporcionará al usuario ambas posibilidades. Por ejemplo, si es deseable no modificar la tabla de datos de entrada dentro de una función, entonces uno puede hacer:

    foo <- function(DT) {
        DT = shallow(DT)          ## shallow copy DT
        DT[, newcol := 1L]        ## does not affect the original DT 
        DT[x > 2L, newcol := 2L]  ## no need to copy (internally), as this column exists only in shallow copied DT
        DT[x > 2L, x := 3L]       ## have to copy (like base R / dplyr does always); otherwise original DT will 
                                  ## also get modified.
    }
    

    Al no usar shallow(), la funcionalidad anterior se conserva:

    bar <- function(DT) {
        DT[, newcol := 1L]        ## old behaviour, original DT gets updated by reference
        DT[x > 2L, x := 3L]       ## old behaviour, update column x in original DT.
    }
    

    Creando un copia superficial utilizando shallow(), entendemos que no desea modificar el objeto original. Nos ocupamos de todo internamente para asegurarnos de que, al mismo tiempo, nos aseguramos de copiar las columnas que modifica solo cuando es absolutamente necesario. Cuando se implemente, esto debería resolver el transparencia referencial problema en conjunto al proporcionar al usuario con ambas posibilidades.

    Además, una vez shallow() se exporta la interfaz data.table de dplyr debe evitar casi todas las copias. Entonces aquellos que prefieren la sintaxis de dplyr pueden usarla con data.tables.

    Pero aún le faltarán muchas características que proporciona data.table, incluida la (sub) -asignación por referencia.

  3. Agregado al unirse:

    Supongamos que tiene dos data.tables de la siguiente manera:

    DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y"))
    #    x y z
    # 1: 1 a 1
    # 2: 1 a 2
    # 3: 1 b 3
    # 4: 1 b 4
    # 5: 2 a 5
    # 6: 2 a 6
    # 7: 2 b 7
    # 8: 2 b 8
    DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y"))
    #    x y mul
    # 1: 1 a   4
    # 2: 2 b   3
    

    Y le gustaría obtener sum(z) * mul para cada fila en DT2 mientras se une por columnas x,y. Podemos:

    • 1) agregado DT1 Llegar sum(z), 2) realizar una unión y 3) multiplicar (o)

      # data.table way
      DT1[, .(z = sum(z)), keyby = .(x,y)][DT2][, z := z*mul][]
      
      # dplyr equivalent
      DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
          right_join(DF2) %>% mutate(z = z * mul)
      
    • 2) hazlo todo de una vez (usando by = .EACHI característica):

      DT1[DT2, list(z=sum(z) * mul), by = .EACHI]
      

    ¿Cuál es la ventaja?

    • No tenemos que asignar memoria para el resultado intermedio.

    • No tenemos que agrupar / hash dos veces (una para la agregación y otra para unir).

    • Y más importante aún, la operación que queríamos realizar es clara al mirar j en 2).

    Comprobar esta publicación para una explicación detallada de by = .EACHI. No se materializan resultados intermedios, y el join + aggregate se realiza de una sola vez.

    Mira esto esta, esta y esta publicaciones para escenarios de uso real.

    En dplyrusted tendría que unirse y agregar o agregar primero y luego unirse, ninguno de los cuales es tan eficiente, en términos de memoria (que a su vez se traduce en velocidad).

  4. Actualiza y une:

    Considere el código data.table que se muestra a continuación:

    DT1[DT2, col := i.mul]
    

    agrega / actualiza DT1la columna de col con mul de DT2 en esas filas donde DT2coincidencias clave de la columna DT1. No creo que haya un equivalente exacto de esta operación en dplyr, es decir, sin evitar un *_join operación, que tendría que copiar la totalidad DT1 solo para agregarle una nueva columna, lo cual es innecesario.

    Comprobar esta publicación para un escenario de uso real.

Para resumir, es importante darse cuenta de que cada cuestión de optimización importa. Como Grace Hopper diría, Cuidado con tus nanosegundos!

3. Sintaxis

Veamos ahora sintaxis. Hadley comentó aquí:

Las tablas de datos son extremadamente rápidas, pero creo que su concisión lo hace más difícil de aprender y el código que lo usa es más difícil de leer después de haberlo escrito ...

Encuentro esta observación inútil porque es muy subjetiva. Lo que quizás podamos intentar es contrastar consistencia en sintaxis. Compararemos la sintaxis data.table y dplyr side-by-side.

Trabajaremos con los datos ficticios que se muestran a continuación:

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  1. Operaciones básicas de agregación / actualización.

    # case (a)
    DT[, sum(y), by = z]                       ## data.table syntax
    DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax
    DT[, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y))
    
    # case (b)
    DT[x > 2, sum(y), by = z]
    DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y))
    DT[x > 2, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y)))
    
    # case (c)
    DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z]
    DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L])
    DT[, if(any(x > 5L)) y[1L] - y[2L], by = z]
    DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
    
    • La sintaxis data.table es compacta y dplyr es bastante detallada. Las cosas son más o menos equivalentes en el caso (a).

    • En el caso (b), tuvimos que usar filter() en dplyr mientras resumiendo. Pero mientras actualización, tuvimos que mover la lógica dentro mutate(). Sin embargo, en data.table, expresamos ambas operaciones con la misma lógica: operar en filas donde x > 2, pero en el primer caso, consiga sum(y), mientras que en el segundo caso actualiza esas filas para y con su suma acumulativa.

      Esto es lo que queremos decir cuando decimos DT[i, j, by] formar es consistente.

    • Del mismo modo en el caso (c), cuando tenemos if-else condición, somos capaces de expresar la lógica "como es" en ambos data.table y dplyr. Sin embargo, si quisiéramos regresar solo esas filas donde el if la condición satisface y salta de lo contrario, no podemos usar summarise() directamente (AFAICT). Tenemos que filter() primero y luego resuma porque summarise() siempre espera una valor único.

      Mientras devuelve el mismo resultado, usando filter() aquí hace que la operación real sea menos obvia.

      Es muy posible que sea posible usar filter() en el primer caso también (no me parece obvio), pero mi punto es que no deberíamos tener que hacerlo.

  2. Agregación / actualización en múltiples columnas

    # case (a)
    DT[, lapply(.SD, sum), by = z]                     ## data.table syntax
    DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax
    DT[, (cols) := lapply(.SD, sum), by = z]
    ans <- DF %>% group_by(z) %>% mutate_each(funs(sum))
    
    # case (b)
    DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z]
    DF %>% group_by(z) %>% summarise_each(funs(sum, mean))
    
    # case (c)
    DT[, c(.N, lapply(.SD, sum)), by = z]     
    DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
    
    • En el caso (a), los códigos son más o menos equivalentes. data.table usa la función base familiar lapply(), mientras que dplyr introduce *_each() junto con un conjunto de funciones para funs().

    • data.table's := requiere que se proporcionen nombres de columna, mientras que dplyr lo genera automáticamente.

    • En el caso (b), la sintaxis de dplyr es relativamente directa. La mejora de agregaciones / actualizaciones en múltiples funciones está en la lista data.table.

    • En el caso (c) sin embargo, dplyr regresaría n() tantas veces como tantas columnas, en lugar de solo una vez. En data.table, todo lo que tenemos que hacer es devolver una lista en j. Cada elemento de la lista se convertirá en una columna en el resultado. Entonces, podemos usar, una vez más, la función base familiar c() concatenar .N a un list que devuelve un list.

    Nota: una vez más, en data.table, todo lo que tenemos que hacer es devolver una lista en j. Cada elemento de la lista se convertirá en una columna en el resultado. Puedes usar c(), as.list(), lapply(), list() etc ... funciones básicas para lograr esto, sin tener que aprender nuevas funciones.

    Necesitará aprender solo las variables especiales - .N y .SD al menos. El equivalente en dplyr son n() y .

  3. Une

    dplyr proporciona funciones separadas para cada tipo de unión donde data.table permite uniones usando la misma sintaxis DT[i, j, by] (y con razón). También proporciona un equivalente merge.data.table() funcionar como una alternativa.

    setkey(DT1, x, y)
    
    # 1. normal join
    DT1[DT2]            ## data.table syntax
    left_join(DT2, DT1) ## dplyr syntax
    
    # 2. select columns while join    
    DT1[DT2, .(z, i.mul)]
    left_join(select(DT2, x, y, mul), select(DT1, x, y, z))
    
    # 3. aggregate while join
    DT1[DT2, .(sum(z) * i.mul), by = .EACHI]
    DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
        inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul)
    
    # 4. update while join
    DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI]
    ??
    
    # 5. rolling join
    DT1[DT2, roll = -Inf]
    ??
    
    # 6. other arguments to control output
    DT1[DT2, mult = "first"]
    ??
    
    • Algunos pueden encontrar una función separada para cada combinación mucho mejor (izquierda, derecha, interior, anti, semi, etc.), mientras que a otros les gustaría data.table's DT[i, j, by], o merge() que es similar a la base R.

    • Sin embargo, Dplyr se une para hacer eso. Nada mas. Nada menos.

    • data.tables puede seleccionar columnas mientras se une (2), y en dplyr necesitará select() primero en ambos data.frames antes de unirse como se muestra arriba. De lo contrario, materializaría la unión con columnas innecesarias solo para eliminarlas más adelante y eso es ineficiente.

    • data.tables puede agregado al unirse (3) y también actualizar mientras se une (4), utilizando by = .EACHI característica. ¿Por qué materializar el resultado de la combinación completa para agregar / actualizar solo unas pocas columnas?

    • data.table es capaz de se une (5) - rollo adelante, LOCF, retroceder, NOCB, más cercano.

    • data.table también tiene mult = argumento que selecciona primero, último o todas coincidencias (6).

    • data.table tiene allow.cartesian = TRUE argumento para proteger de uniones no válidas accidentales.

Una vez más, la sintaxis es consistente con DT[i, j, by] con argumentos adicionales que permiten controlar más la salida.

  1. do()...

    El resumen de dplyr está especialmente diseñado para funciones que devuelven un único valor. Si su función devuelve valores múltiples / desiguales, tendrá que recurrir a do(). Tienes que saber de antemano acerca de todas tus funciones devuelven valor.

    DT[, list(x[1], y[1]), by = z]                 ## data.table syntax
    DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax
    DT[, list(x[1:2], y[1]), by = z]
    DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1]))
    
    DT[, quantile(x, 0.25), by = z]
    DF %>% group_by(z) %>% summarise(quantile(x, 0.25))
    DT[, quantile(x, c(0.25, 0.75)), by = z]
    DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75))))
    
    DT[, as.list(summary(x)), by = z]
    DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
    
    • .SDes equivalente .

    • En data.table, puedes lanzar prácticamente cualquier cosa en j - Lo único que debe recordar es devolver una lista para que cada elemento de la lista se convierta en una columna.

    • En dplyr, no puedo hacer eso. Tienes que recurrir a do() dependiendo de qué tan seguro esté de si su función siempre devolverá un solo valor. Y es bastante lento.

Una vez más, la sintaxis de data.table es consistente con DT[i, j, by]. Podemos seguir lanzando expresiones en j sin tener que preocuparse por estas cosas.

Mira esto esta pregunta SO y éste. Me pregunto si sería posible expresar la respuesta como simple usando la sintaxis de dplyr ...

Para resumir, he destacado particularmente varios instancias donde la sintaxis de dplyr es ineficiente, limitada o no hace que las operaciones sean sencillas. Esto es particularmente así porque data.table recibe un poco de reacción sobre la sintaxis "más difícil de leer / aprender" (como la que se pegó / enlazó arriba). La mayoría de las publicaciones que cubren a dplyr hablan sobre las operaciones más directas. Y eso es genial. Pero también es importante tener en cuenta sus limitaciones de sintaxis y funciones, y aún no he visto una publicación en él.

data.table también tiene sus peculiaridades (algunas de las cuales he señalado que estamos intentando corregir). También estamos intentando mejorar las uniones de data.table como he resaltado aquí.

Pero también se debe considerar la cantidad de características que dplyr carece en comparación con data.table.

4. Características

He señalado la mayoría de las características aquí y también en este post. En adición:

  • fread - el lector de archivos rápido ha estado disponible durante mucho tiempo ahora.

  • fwrite - NUEVO en el desarrollo actual, v1.9.7, a paralelizado el escritor rápido de archivos ahora está disponible. Ver esta publicación para una explicación detallada sobre la implementación y # 1664 para realizar un seguimiento de nuevos desarrollos.

  • Indexación automática - Otra característica útil para optimizar la sintaxis R base como es, internamente.

  • Agrupación Ad-hoc: dplyr ordena automáticamente los resultados agrupando variables durante summarise(), que puede no ser siempre deseable

  • Numerosas ventajas en las uniones de data.table (para la velocidad / la eficiencia de la memoria y la sintaxis) mencionadas anteriormente.

  • Non-equi se une: es una característica NUEVA disponible desde v1.9.7 +. Permite uniones usando otros operadores <=, <, >, >= junto con todas las demás ventajas de data.table joins.

  • Uniones de rango superpuestas se implementó en data.table recientemente. Comprobar esta publicación para una visión general con puntos de referencia.

  • setorder() función en data.table que permite un reordenamiento muy rápido de data.tables por referencia.

  • dplyr proporciona interfaz a las bases de datos usando la misma sintaxis, que data.table no contiene en este momento.

  • data.table proporciona equivalentes más rápidos de establecer operaciones de v1.9.7 + (escrito por Jan Gorecki) - fsetdiff, fintersect, funion y fsetequal con adicional all argumento (como en SQL).

  • data.table carga limpiamente sin advertencias de enmascaramiento y tiene un mecanismo descrito aquí para [.data.frame compatibilidad cuando se pasa a cualquier paquete R dplyr cambia las funciones básicas filter, lag y [ que puede causar problemas; p.ej. aquí y aquí.


Finalmente:

  • En las bases de datos: no hay ninguna razón por la cual data.table no pueda proporcionar una interfaz similar, pero ahora esto no es una prioridad. Se puede aumentar si a los usuarios les gusta mucho esa función ... no estoy seguro.

  • En el paralelismo: todo es difícil, hasta que alguien se adelante y lo haga. Por supuesto, requerirá un gran esfuerzo (al estar seguro de subprocesos).

    • Actualmente se está progresando (en v1.9.7 desarrollo) hacia la paralelización de las partes que consumen mucho tiempo para aumentar el rendimiento incremental usando OpenMP.

387
2018-01-08 12:39



Aquí está mi intento de una respuesta integral desde la perspectiva dplyr, siguiendo el esquema general de la respuesta de Arun (pero algo reorganizado) basado en prioridades diferentes).

Sintaxis

Hay cierta subjetividad a la sintaxis, pero estoy de acuerdo con mi afirmación de que la concisión de data.table hace que sea más difícil de aprender y más difícil de leer. ¡Esto se debe en parte a que dplyr está resolviendo un problema mucho más fácil!

Una cosa realmente importante que Dplyr hace por usted es que restricciones sus opciones. Yo afirmo que la mayoría de los problemas de mesa individual pueden se resuelve con solo cinco verbos clave, filtra, selecciona, muta, organiza y resumir, junto con un adverbio "por grupo". Esa restricción es de gran ayuda cuando estás aprendiendo manipulación de datos, porque ayuda a ordenar tu pensando en el problema En dplyr, cada uno de estos verbos está mapeado a sola función. Cada función hace un trabajo, y es fácil de entender aisladamente.

Usted crea complejidad conectando estas operaciones simples junto con %>%. Aquí hay un ejemplo de una de las publicaciones Arun vinculado a:

diamonds %>%
  filter(cut != "Fair") %>%
  group_by(cut) %>%
  summarize(
    AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = n()
  ) %>%
  arrange(desc(Count))

Incluso si nunca has visto dplyr antes (¡o incluso R!), Aún puedes obtener la esencia de lo que sucede porque las funciones son todas inglesas verbos La desventaja de los verbos en inglés es que requieren más tipeo que [, pero creo que se puede mitigar en gran medida con una mejor autocompletar.

Aquí está el código data.table equivalente:

diamondsDT <- data.table(diamonds)
diamondsDT[
  cut != "Fair", 
  .(AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = .N
  ), 
  by = cut
][ 
  order(-Count) 
]

Es más difícil seguir este código a menos que ya estés familiarizado con tabla de datos. (Tampoco pude descifrar cómo sangrar las repetidas [ de una manera que se ve bien a mi ojo). Personalmente, cuando miro el código I escribió hace 6 meses, es como mirar un código escrito por un extraño, así que he llegado a preferir el código directo, aunque detallado.

Otros dos factores menores que creo que disminuyen ligeramente la legibilidad:

  • Dado que casi todas las operaciones de tabla de datos utilizan [ necesitas más contexto para descubrir lo que está sucediendo. Por ejemplo, es x[y] unir dos tablas de datos o extraer columnas de un marco de datos? Esto es solo un pequeño problema, porque en el código bien escrito los nombres variables deberían sugerir lo que está sucediendo.

  • me gusta eso group_by() es una operación separada en dplyr. Eso Cambia fundamentalmente el cálculo, así que creo que debería ser obvio al rozar el código, y es más fácil detectar group_by() que el by argumento para [.data.table.

También me gusta que el el tubo no solo se limita a un solo paquete. Puede comenzar ordenando su datos con tidyry terminar con una trama en ggvis. Y tú eres no limitado a los paquetes que escribo - cualquiera puede escribir una función eso forma una parte sin fisuras de una tubería de manipulación de datos. en efecto, yo prefiero el código data.table anterior reescrito con %>%:

diamonds %>% 
  data.table() %>% 
  .[cut != "Fair", 
    .(AvgPrice = mean(price),
      MedianPrice = as.numeric(median(price)),
      Count = .N
    ), 
    by = cut
  ] %>% 
  .[order(-Count)]

Y la idea de canalizar con %>% no se limita solo a los marcos de datos y se puede generalizar fácilmente a otros contextos: web interactiva gráficos, web raspado, gists, tiempo de ejecución contratos, ...)

Memoria y rendimiento

He agrupado estos, porque, para mí, no son tan importantes. La mayoría de los usuarios R trabajan con menos de 1 millón de filas de datos, y dplyr es lo suficientemente rápido como para ese tamaño de datos que no conoce Tiempo de procesamiento. Optimizamos dplyr para la expresividad en datos medianos; Siéntase libre de usar data.table para la velocidad bruta en datos más grandes.

La flexibilidad de dplyr también significa que puede ajustar fácilmente el rendimiento características que usan la misma sintaxis Si el rendimiento de dplyr con el backend del marco de datos no es lo suficientemente bueno para ti, puedes usar backend de data.table (aunque con un conjunto de funcionalidades un tanto restringido). Si la información con la que está trabajando no cabe en la memoria, puede usar un back-end de base de datos.

Dicho todo esto, el rendimiento de Dplyr mejorará a largo plazo. Bien definitivamente implemente algunas de las grandes ideas de data.table como radix ordenar y usar el mismo índice para combinaciones y filtros. También somos trabajando en la paralelización para que podamos aprovechar los múltiples núcleos.

Caracteristicas

Algunas cosas en las que planeamos trabajar en 2015:

  • el readr paquete, para que sea más fácil sacar los archivos del disco y en a la memoria, análoga a fread().

  • Uniones más flexibles, incluido el soporte para combinaciones no equi.

  • Agrupación más flexible como muestras de bootstrap, rollups y más

También estoy invirtiendo tiempo en mejorar las R base de datos conectores, la capacidad de hablar con apis weby haciéndolo más fácil raspe páginas html.


305
2017-11-16 22:39



En respuesta directa a la Título de la pregunta...

dplyr  seguro hace cosas que data.table no poder.

Tu punto n. ° 3

dplyr abstrae (o lo hará) posibles interacciones DB

es una respuesta directa a su propia pregunta, pero no se eleva a un nivel lo suficientemente alto. dplyr es realmente un front-end extensible a múltiples mecanismos de almacenamiento de datos donde data.table es una extensión de uno solo.

Mirar dplyr como interfaz agnóstica de back-end, con todos los objetivos usando la misma gramática, donde puede ampliar los objetivos y los manejadores a voluntad. data.table es, desde el dplyr perspectiva, uno de esos objetivos.

Nunca (espero) veré un día que data.table intenta traducir sus consultas para crear declaraciones SQL que operan con almacenes de datos en red o en disco.

dplyr posiblemente puede hacer cosas data.table no lo hará o no podría hacer tan bien.

Basado en el diseño de trabajo en memoria, data.table podría tener un tiempo mucho más difícil de extenderse en el procesamiento paralelo de consultas que dplyr.


En respuesta a las preguntas en el cuerpo ...

Uso

¿Hay tareas analíticas que sean mucho más fáciles de codificar con uno u otro paquete? para personas familiarizadas con los paquetes (es decir, una combinación de combinaciones de teclas requeridas con el nivel requerido de esoterismo, donde menos de cada una es buena).

Esto puede parecer una punt pero la respuesta real es no. Gente familiar con las herramientas parece utilizar el más familiar para ellos o el que es realmente el adecuado para el trabajo en cuestión. Habiendo dicho eso, a veces quieres presentar una legibilidad particular, a veces un nivel de rendimiento, y cuando necesitas un nivel lo suficientemente alto de ambos puedes necesitar otra herramienta para estar de acuerdo con lo que ya tienes para hacer abstracciones más claras .

Actuación

¿Hay tareas analíticas que se realizan sustancialmente (es decir, más de 2 veces) de manera más eficiente en un paquete frente a otro?

Nuevamente, no. data.table sobresale por ser eficiente en todo eso hace donde dplyr obtiene la carga de estar limitado en algunos aspectos al almacén de datos subyacente y a los manejadores registrados.

Esto significa que cuando se encuentra con un problema de rendimiento con data.table puede estar bastante seguro de que está en su función de consulta y si es en realidad un cuello de botella con data.table entonces te has ganado la alegría de presentar un informe. Esto también es cierto cuando dplyr esta usando data.table como el back-end; tú mayo ver algunos sobrecarga desde dplyr pero las probabilidades son que es tu consulta.

Cuando dplyr tiene problemas de rendimiento con los back-ends que puede sortear al registrar una función para la evaluación híbrida o (en el caso de las bases de datos) manipular la consulta generada antes de la ejecución.

Ver también la respuesta aceptada para ¿Cuándo es plyr mejor que data.table?


42