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 algunas viñetas de dplyr 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 (consulte los puntos de referencia a continuación)
  2. dplyr tiene una syntax 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 opinión 2. no tiene mucho peso porque estoy bastante familiarizado con 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 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 2x) de manera más eficiente en un paquete frente a otro.

Una pregunta reciente de SO me hizo pensar un poco más sobre esto, porque hasta ese momento no creía que dplyr ofreciera mucho más allá de lo que ya puedo hacer en data.table . Aquí está la solución dplyr (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 bash de hackear una solución data.table . Dicho esto, las buenas soluciones de data.table también son bastante buenas (gracias Jean-Robert, Arun, y tenga en cuenta que preferí una statement única sobre la solución estrictamente más óptima):

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

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

Idealmente, lo que me gustaría ver son algunos buenos ejemplos en los que la forma dplyr o data.table es sustancialmente más concisa o funciona sustancialmente mejor.

Ejemplos

Uso

  • dplyr no permite operaciones agrupadas que devuelven un número arbitrario de filas (a partir de la pregunta de eddi , nota: parece que se implementará en dplyr 0.5 , también, @beginneR muestra una posible solución alternativa usando do en la respuesta a la pregunta de @ eddi) .
  • data.table admite rolling joins (gracias @dholstius), así como las combinaciones de superposición
  • data.table optimiza internamente las expresiones de la forma DT[col == value] o DT[col %in% values] para la velocidad a través de la indexación automática que utiliza la búsqueda binaria utilizando la misma syntax R base. Vea aquí para más detalles y un pequeño punto de referencia.
  • dplyr ofrece versiones de evaluación estándar de funciones (ej. regroup , summarize_each_ ) que pueden simplificar el uso programático de dplyr (note que 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

  • Ejecuté mis propios puntos de referencia y encontré que ambos paquetes son comparables en el análisis de estilo de “combinación de aplicación dividida”, excepto cuando hay un gran número de grupos (> 100K) en cuyo punto data.table vuelve sustancialmente más rápido.
  • @Arun ejecutó algunos puntos de referencia en las uniones , mostrando que data.table escala mejor que dplyr medida que dplyr el número de grupos (actualizado con las mejoras recientes en ambos paquetes y la versión reciente de R). Además, un punto de referencia cuando se intenta obtener valores únicos tiene data.table ~ 6x más rápido.
  • (Sin verificar) tiene data.table 75% más rápido en las versiones más grandes de un grupo / apply / sort, mientras que dplyr fue 40% más rápido en las más pequeñas ( otra pregunta SO de los comentarios , gracias danas).
  • Matt, el autor principal de data.table , ha comparado las operaciones de agrupación en data.table , dplyr y python pandas en hasta 2 mil millones de filas (~ 100GB en RAM) .
  • Un punto de referencia más antiguo en grupos de data.table tiene data.table ~ 8x más rápido

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)) 

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 syntax data.table es consistente en su forma – DT[i, j, by] . Mantener i , j y junto es por diseño. Al mantener las operaciones relacionadas entre sí, permite optimizar fácilmente las operaciones de velocidad y, lo que es más importante, el uso de la memoria , y también proporciona algunas funciones potentes , a la vez que mantiene la coherencia en la syntax.

1. Velocidad

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

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

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

  • Compare otras operaciones como actualización y uniones .

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

2. Uso de la memoria

  1. Las operaciones que involucran filter() o slice() en dplyr pueden ser ineficientes en la memoria (tanto en data.frames como en data.tables). Ver esta publicación

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

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

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

    Pero dplyr nunca se 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 la 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: vea esta y esta publicación para casos interesantes. Y queremos mantenerlo.

    Por lo tanto, estamos trabajando para exportar la función shallow() en data.table que proporcionará al usuario las dos 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() , se conserva la funcionalidad anterior:

     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. } 

    Al crear una copia superficial usando shallow() , entendemos que no desea modificar el objeto original. Nos ocupamos de todo internamente para asegurarnos de que, al mismo tiempo que garantizamos la copia de las columnas, solo se modifique cuando sea absolutamente necesario . Cuando se implemente, esto debería resolver el problema de transparencia referencial al tiempo que proporciona al usuario ambas posibilidades.

    Además, una vez que se haya exportado shallow() la interfaz data.table de dplyr debe evitar casi todas las copias. Entonces aquellos que prefieren la syntax 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")) # xyz # 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")) # xy mul # 1: 1 a 4 # 2: 2 b 3 

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

    • 1) agregar DT1 para obtener sum(z) , 2) realizar una combinació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 la función by = .EACHI ):

       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 lo más importante, la operación que queríamos realizar es clara al observar j en (2).

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

    Eche un vistazo a esto , esto y esta publicación para escenarios de uso real.

    En dplyr 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 la columna DT1 de DT1 con mul desde DT2 en aquellas filas donde la columna de clave DT2 coincide con DT1 . No creo que exista un equivalente exacto de esta operación en dplyr , es decir, sin evitar una operación *_join , que debería copiar todo el DT1 solo para agregarle una nueva columna, lo cual es innecesario.

    Consulte esta publicación para conocer un escenario de uso real.

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

3. Sintaxis

Veamos ahora la syntax . Hadley comentó aquí :

Las tablas de datos son extremadamente rápidas, pero creo que su concisión hace que sea 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 la consistencia en la syntax . Compararemos la syntax 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 syntax 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 al resumir . Pero durante la actualización , tuvimos que mover la lógica dentro de 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, obtener sum(y) , mientras que en el segundo caso actualizar esas filas para y con su sum acumulativa.

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

    • De manera similar en el caso (c), cuando tenemos la condición if-else , podemos express la lógica “tal cual” en ambos data.table y dplyr. Sin embargo, si queremos devolver solo aquellas filas donde la condición if satisface y omite lo contrario, no podemos usar summarise() directamente (AFAICT). Primero tenemos que filter() y luego resumir porque summarise() siempre espera un único valor .

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

      También podría ser posible usar filter() en el primer caso (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 utiliza la función base familiar lapply() , mientras que dplyr presenta *_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 syntax de dplyr es relativamente directa. La mejora de agregaciones / actualizaciones en múltiples funciones está en la lista data.table.

    • Sin embargo, en el caso (c), dplyr devolvería n() tantas veces como columnas, en lugar de una sola 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() para concatenar .N a una list que devuelve una 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. Puede usar las as.list() c() , as.list() , lapply() , list() etc … base 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 syntax DT[i, j, by] (y con razón). También proporciona una función merge.data.table() equivalente como 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 enlace mucho más agradable (izquierda, derecha, interior, anti, semi, etc.), mientras que a otros les gustaría DT[i, j, by] de data.table 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 tendrá que 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 agregarse mientras se une (3) y también se actualiza al unirse (4), usando la función by = .EACHI . ¿Por qué materializar el resultado de la combinación completa para agregar / actualizar solo unas pocas columnas?

    • data.table es capaz de lanzar combinaciones (5) – avanzar, LOCF , retroceder, NOCB , más cercano .

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

    • data.table tiene allow.cartesian = TRUE argument para proteger de uniones inválidas accidentales.

Una vez más, la syntax es consistente con DT[i, j, by] con argumentos adicionales que permiten controlar aún 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)))) 
    • .SD es equivalente .

    • En data.table, puede lanzar casi 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. Tiene 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 syntax de data.table es consistente con DT[i, j, by] . Podemos seguir lanzando expresiones en j sin tener que preocuparnos por estas cosas.

Echa un vistazo a esta pregunta SO y esta . Me pregunto si sería posible express la respuesta como simple usando la syntax de dplyr …

Para resumir, he destacado en particular varias instancias en las que la syntax 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 syntax “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 syntax 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 esta publicación. En adición:

  • fread – fast file reader ha estado disponible durante mucho tiempo.

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

  • Indexación automática : otra característica práctica para optimizar la syntax R de la base tal como es, internamente.

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

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

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

  • Las combinaciones de rango superpuestas se implementaron recientemente en data.table. Consulte esta publicación para obtener una descripción general con los puntos de referencia.

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

  • dplyr proporciona una interfaz para las bases de datos con la misma syntax, que data.table no posee en este momento.

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

  • data.table carga limpiamente sin advertencias de enmascaramiento y tiene un mecanismo descrito aquí para la compatibilidad con [.data.frame cuando se pasa a cualquier paquete R. dplyr cambia las funciones de base filter , lag y [ que pueden causar problemas; por ejemplo, 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 boost 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 piezas conocidas que consumen mucho tiempo para aumentos de rendimiento incrementales utilizando OpenMP .

Aquí está mi bash de una respuesta integral desde la perspectiva dplyr, siguiendo el amplio esquema de la respuesta de Arun (pero algo reorganizado en base a las diferentes prioridades).

Sintaxis

Hay cierta subjetividad a la syntax, 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 restringe sus opciones. Yo afirmo que la mayoría de los problemas de una sola tabla se pueden resolver con solo cinco verbos clave que filtran, seleccionan, modifican, arreglan y resumen, junto con un adverbio “por grupo”. Esa restricción es de gran ayuda cuando está aprendiendo manipulación de datos, porque ayuda a ordenar su pensamiento sobre el problema. En dplyr, cada uno de estos verbos se asigna a una sola función. Cada función hace un trabajo y es fácil de entender de forma aislada.

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

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

Incluso si nunca ha visto dplyr antes (¡o incluso R!), Aún puede obtener la esencia de lo que sucede porque las funciones son todos verbos en inglés. 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é familiarizado con data.table. (Tampoco pude descifrar cómo sangrar las repeticiones [ de una forma que se ve bien a mi ojo). Personalmente, cuando miro el código que escribí hace 6 meses, es como mirar un código escrito por un extraño, así que prefiero el código simple, aunque detallado.

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

  • Como casi todas las operaciones de la tabla de datos usan [ necesita un contexto adicional para descubrir qué está sucediendo. Por ejemplo, ¿está x[y] uniendo dos tablas de datos o extrayendo columnas de un dataframe? Este es solo un pequeño problema, porque en un código bien escrito, los nombres de las variables deberían sugerir lo que está sucediendo.

  • Me gusta que group_by() es una operación separada en dplyr. Cambia fundamentalmente el cálculo, por lo que creo que debería ser obvio al descodificar el código, y es más fácil detectar group_by() que el argumento por [.data.table .

También me gusta que el tubo no se limite solo a un paquete. Puede comenzar ordenando sus datos con tidyr y finalizar con un diagtwig en ggvis . Y no está limitado a los paquetes que escribo: cualquiera puede escribir una función que forme parte integrante de un conducto de manipulación de datos. De hecho, 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 a solo marcos de datos y se puede generalizar fácilmente a otros contextos: gráficos web interactivos , raspado web , gists , contratos de tiempo de ejecución , ...)

Memoria y rendimiento

He agrupado estos, porque, para mí, no son tan importantes. La mayoría de los usuarios de R trabajan con menos de 1 millón de filas de datos, y dplyr es lo suficientemente rápido para ese tamaño de datos del que desconoce el 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 modificar fácilmente las características de rendimiento utilizando la misma syntax. Si el rendimiento de dplyr con el backend del dataframe no es lo suficientemente bueno para usted, puede usar el back-end de data.table (aunque con un conjunto de funcionalidades un tanto restringido). Si los datos con los que está trabajando no caben en la memoria, puede usar un back-end de base de datos.

Dicho todo esto, el rendimiento de Dplyr mejorará a largo plazo. Definitivamente implementaremos algunas de las mejores ideas de data.table como ordenar radix y usar el mismo índice para combinaciones y filtros. También estamos 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 paquete readr , para facilitar la readr archivos del disco y 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 los conectores de la base de datos de R, la capacidad de hablar con aplicaciones web y facilitar el análisis de páginas html .

En respuesta directa al Título de pregunta

dplyr definitivamente hace cosas que data.table no puede.

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.

Considere a dplyr como una interfaz dplyr back-end, con todos los objectives usando la misma gramática, donde puede ampliar los objectives y los manejadores a voluntad. data.table es, desde la perspectiva dplyr , uno de esos objectives.

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

dplyr posiblemente puede hacer cosas data.table no 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 parallel processing 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 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).

Esto puede parecer una punt pero la respuesta real es no. People familiar with tools seem to use the either the one most familiar to them or the one that is actually the right one for the job at hand. With that being said, sometimes you want to present a particular readability, sometimes a level of performance, and when you have need for a high enough level of both you may just need another tool to go along with what you already have to make clearer abstractions.

Performance

Are there analytical tasks that are performed substantially (ie more than 2x) more efficiently in one package vs. another.

Nuevamente, no. data.table excels at being efficient in everything it does where dplyr gets the burden of being limited in some respects to the underlying data store and registered handlers.

This means when you run into a performance issue with data.table you can be pretty sure it is in your query function and if it is actually a bottleneck with data.table then you’ve won yourself the joy of filing a report. This is also true when dplyr is using data.table as the back-end; you may see some overhead from dplyr but odds are it is your query.

When dplyr has performance issues with back-ends you can get around them by registering a function for hybrid evaluation or (in the case of databases) manipulating the generated query prior to execution.

Also see the accepted answer to when is plyr better than data.table?