data.table vs dplyr: ¿puede uno hacer algo bien y el otro no puede o lo hace mal?


760

Visión general

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

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

En mi opinión 2. no tiene mucho peso porque estoy bastante familiarizado con él data.table, aunque entiendo que para los usuarios nuevos en ambos será un gran factor. Me gustaría evitar una discusión sobre cuál es más intuitiva, ya que es irrelevante para mi pregunta específica formulada desde la perspectiva de alguien que ya está familiarizado 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. ¿Existen 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, alguna combinación de pulsaciones de teclas requeridas versus nivel requerido de esoterismo, donde menos de cada una es una buena cosa).
  2. ¿Existen tareas analíticas que se realizan sustancialmente (es decir, más de 2 veces) de manera más eficiente en un paquete en comparación con otro.

Una pregunta reciente de SO me hizo pensar un poco más sobre esto, porque hasta ese momento no pensé dplyrque ofrecería mucho más de lo que ya puedo hacer data.table. Aquí está la dplyrsolución (datos al final de Q):

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

Lo cual fue mucho mejor que mi intento de hackear una data.tablesolución. Dicho esto, las buenas data.tablesoluciones 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 para este último puede parecer muy esotérica, pero en realidad es bastante sencilla si estás acostumbrado data.table(es decir, no usa algunos de los trucos más esotéricos).

Lo ideal sería que lo que me gustaría ver es algunos ejemplos de buenas fueron el dplyro data.tableforma es sustancialmente más concisa o realiza sustancialmente mejor.

Ejemplos

Uso
  • dplyrno permite operaciones agrupadas que devuelven un número arbitrario de filas (de la pregunta de eddi , nota: parece que se implementará en dplyr 0.5 , también, @beginneR muestra una posible solución alternativa doen la respuesta a la pregunta de @ eddi).
  • data.tableadmite uniones continuas (gracias @dholstius) así como uniones superpuestas
  • data.tableoptimiza internamente las expresiones de la forma DT[col == value]o DT[col %in% values]de la velocidad a través de la indexación automática que usa la búsqueda binaria mientras usa la misma sintaxis R base. Vea aquí para más detalles y un pequeño punto de referencia.
  • dplyrofrece versiones de evaluación estándar de funciones (p regroup. ej . summarize_each_) que pueden simplificar el uso programático de dplyr(tenga en cuenta que el uso programático data.tablees definitivamente posible, solo requiere un pensamiento cuidadoso, sustitución / cita, etc., al menos que yo sepa)
Puntos de referencia

Datos

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

99
La solución que es similar en lectura dplyres:as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
eddi

77
Para el # 1, ambos dplyry los data.tableequipos están trabajando en puntos de referencia, por lo que habrá una respuesta en algún momento. # 2 (sintaxis) imO es estrictamente falso, pero eso claramente se aventura en territorio de opinión, por lo que también votaré para cerrar.
eddi

13
bueno, de nuevo en mi opinión, el conjunto de problemas que se expresan más limpiamente (d)plyrtiene la medida 0
eddi

28
@BrodieG lo único que realmente me molesta sobre ambos dplyry plyrcon respecto a la sintaxis y es básicamente la razón principal por la que no me gusta su sintaxis, es que tengo que aprender demasiadas (leer más de 1) funciones adicionales (con nombres que todavía no tiene sentido para mí), recuerda lo que hacen, los argumentos que toman, etc. Eso siempre ha sido un gran desvío para mí de la filosofía plyr.
eddi

43
@eddi [irónico] lo único que realmente me molesta de la sintaxis de data.table es que tengo que aprender cómo interactúan demasiados argumentos de función y qué significan los atajos crípticos (p .SD. ej .). [en serio] Creo que estas son diferencias de diseño legítimas que
atraerán

Respuestas:


532

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

Mi intención es cubrir cada uno de estos lo más claramente posible desde la perspectiva de la tabla de datos.

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


La sintaxis data.table es consistente en su forma - DT[i, j, by]. Para mantener i, jy byjuntos es por diseño. Al mantener juntas las operaciones relacionadas, permite optimizar fácilmente las operaciones para la velocidad y, lo que es más importante , el uso de la memoria , y también proporciona algunas características potentes , todo mientras se mantiene la coherencia en la 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 se vuelve más rápido que dplyr a medida que aumenta el número de grupos y / o filas para agrupar, 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 agrupación variables, que también se compara pandas. Consulte también los puntos de referencia actualizados , que incluyen Sparky pydatatabletambién.

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

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

  • Compare otras operaciones como actualizaciones y uniones .

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

2. Uso de 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 este post .

    Tenga en cuenta que el comentario de Hadley habla de la velocidad (que dplyr es abundante 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 necesitamos reasignar 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 de dplyr sería (tenga en cuenta que el resultado debe reasignarse):

    # 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 shallow()función en data.table que proporcionará al usuario ambas posibilidades . Por ejemplo, si es deseable no modificar la entrada data.table dentro de una función, se 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 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 implementa, esto debería resolver el problema de transparencia referencial por completo al tiempo que proporciona al usuario ambas posibilidades.

    Además, una vez que 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 todavía le faltarán muchas características que proporciona data.table, incluida la (sub) asignación por referencia.

  3. Agregue mientras se une:

    Suponga 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 te gustaría obtener sum(z) * mulcada fila DT2mientras te unes por columnas x,y. Podemos:

    • 1) agregar DT1para obtener 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 la by = .EACHIfunción):

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

    Cual es la ventaja?

    • No tenemos que asignar memoria para el resultado intermedio.

    • No tenemos que agrupar / hash dos veces (uno para la agregación y otro para unirse).

    • Y lo que es más importante, la operación que queríamos realizar es clara al mirar jen (2).

    Consulte esta publicación para obtener una explicación detallada de by = .EACHI. No se materializan resultados intermedios, y la unión + agregado se realiza todo de una vez.

    Eche un vistazo a esto , esto y esto para ver escenarios de uso real.

    En dplyrtendría que unirse y agregado o agregada primero y luego unirse , ninguno de los cuales son tan eficientes, en términos de memoria (que a su vez se traduce en velocidad).

  4. Actualización y une:

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

    DT1[DT2, col := i.mul]

    agrega / actualiza DT1la columna colcon muldesde DT2en aquellas filas donde DT2coincide la columna clave DT1. No creo que haya un equivalente exacto de esta operación dplyr, es decir, sin evitar una *_joinoperación, que tendría que copiar todo DT1para agregarle una nueva columna, lo cual es innecesario.

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

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

3. Sintaxis

Veamos ahora la sintaxis . 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 ...

Este comentario me parece inútil porque es muy subjetivo. Lo que quizás podamos intentar es contrastar la consistencia en la sintaxis . Compararemos la sintaxis de data.table y dplyr una al lado de la otra.

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 al resumir . Pero durante la actualización , tuvimos que mover la lógica dentro mutate(). Sin embargo, en data.table expresamos ambas operaciones con la misma lógica: operamos en filas donde x > 2, pero en el primer caso, obtenemos sum(y), mientras que en el segundo caso actualizamos esas filas ycon su suma acumulativa.

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

    • De manera similar, en el caso (c), cuando tenemos una if-elsecondición, podemos expresar la lógica "tal como está" tanto en data.table como en dplyr. Sin embargo, si quisiéramos devolver solo aquellas filas donde la ifcondición satisface y omitir lo contrario, no podemos usar summarise()directamente (AFAICT). filter()Primero tenemos que resumir, porque summarise()siempre se espera un único valor .

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

      Es muy posible que también sea posible usarlo 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 usa una función base familiar lapply(), mientras que dplyrpresenta *_each()junto con un montón de funciones para funs().

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

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

    • Sin embargo, en el caso (c), dplyr devolvería n()tantas veces más columnas, en lugar de solo una vez. En data.table, todo lo que tenemos que hacer es devolver una lista 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 .Na a listque devuelve a list.

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

    Necesitará aprender solo las variables especiales, .Ny .SDal menos. El equivalente en dplyr son n()y.

  3. Uniones

    dplyr proporciona funciones separadas para cada tipo de combinación donde data.table permite combinaciones utilizando la misma sintaxis DT[i, j, by](y con razón). También proporciona una merge.data.table()función 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 combinación mucho más agradable (izquierda, derecha, interior, anti, semi, etc.), mientras que a otros les pueden gustar data.table's DT[i, j, by], o merge()que es similar a la base R.

    • Sin embargo, dplyr se une a hacer exactamente eso. Nada mas. Nada menos.

    • data.tables puede seleccionar columnas al unir (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 tarde y eso es ineficiente.

    • data.tables puede agregarse mientras se une (3) y también actualizarse mientras se une (4), usando la by = .EACHIfunción. ¿Por qué materializar todo el resultado de la combinación para agregar / actualizar solo unas pocas columnas?

    • data.table es capaz de unir (5): avanzar, LOCF , retroceder, NOCB , el más cercano .

    • data.table también tiene un mult =argumento que selecciona primero , último o todos los partidos (6).

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

Una vez más, la sintaxis es consistente con DT[i, j, by]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 solo valor. Si su función devuelve valores múltiples / desiguales, tendrá que recurrir a do(). Tiene que saber de antemano acerca de todas sus funciones de valor de retorno.

    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))))
    • .SDel equivalente es .

    • En data.table, puede agregar casi cualquier cosa j: lo único que debe recordar es que devuelva una lista para que cada elemento de la lista se convierta en una columna.

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

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

Echa un vistazo a esta pregunta SO y esta . Me pregunto si sería posible expresar la respuesta como sencilla utilizando la sintaxis de dplyr ...

Para resumir, he destacado especialmente varias instancias en las que la sintaxis de dplyr es ineficiente, limitada o no logra hacer que las operaciones sean sencillas. Esto se debe particularmente a que data.table recibe bastante reacción sobre la sintaxis "más difícil de leer / aprender" (como la que se pegó / enlazó anteriormente). La mayoría de las publicaciones que cubren dplyr hablan sobre la mayoría de las operaciones directas. Y eso es genial. Pero también es importante darse cuenta de su sintaxis y limitaciones de características, y aún no he visto una publicación al respecto.

data.table también tiene sus peculiaridades (algunas de las cuales he señalado que estamos intentando solucionar). También estamos intentando mejorar data.table's se une como lo he destacado aquí .

Pero también se debe considerar la cantidad de características que carece de dplyr 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. Adicionalmente:

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

  • fwrite : ahora hay disponible un escritor de archivos rápido en paralelo . Consulte esta publicación para obtener una explicación detallada sobre la implementación y el número 1664 para realizar un seguimiento de los desarrollos posteriores.

  • Indexación automática : otra característica útil para optimizar la sintaxis base R tal como está, internamente.

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

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

  • Uniones no equi : permite uniones utilizando otros operadores <=, <, >, >=junto con todas las otras ventajas de las uniones data.table.

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

  • setorder() función en data.table que permite una reordenación realmente rápida de data.tables por referencia.

  • dplyr proporciona una interfaz para bases de datos que usan la misma sintaxis, que data.table no tiene en este momento.

  • data.tableproporciona equivalentes más rápidos de operaciones de conjuntos (escrito por Jan Gorecki) - fsetdiff, fintersect, funiony fsetequalcon adicional de allargumento (como en SQL).

  • data.table se carga limpiamente sin advertencias de enmascaramiento y tiene un mecanismo descrito aquí para [.data.framecompatibilidad cuando se pasa a cualquier paquete R. dplyr cambia funciones de base filter, lagy [que puede causar problemas; Por ejemplo, aquí y aquí .


Finalmente:

  • En bases de datos: no hay ninguna razón por la que data.table no pueda proporcionar una interfaz similar, pero esto no es una prioridad ahora. Podría aumentar si los usuarios quisieran esa característica ... no estoy seguro.

  • Sobre el paralelismo: todo es difícil, hasta que alguien sigue adelante y lo hace. Por supuesto que tomará esfuerzo (ser seguro para subprocesos).

    • Actualmente, se está avanzando (en el desarrollo de v1.9.7) hacia la paralelización de partes conocidas que consumen mucho tiempo para aumentar el rendimiento mediante el uso OpenMP.

99
@bluefeet: No creo que le hayas hecho ningún buen servicio al resto de nosotros al trasladar esa discusión al chat. Tenía la impresión de que Arun era uno de los desarrolladores y esto podría haber resultado en ideas útiles.
IRTFM

2
Cuando fui a chatear usando su enlace, parecía que todo el material que seguía el comentario que comenzaba "Deberías usar un filtro" ... había desaparecido. ¿Me estoy perdiendo algo sobre el mecanismo SO-chat?
IRTFM

66
Creo que en todos los lugares donde se usa la asignación por referencia ( :=), el dplyrequivalente también se debe usar <-como en DF <- DF %>% mutate...lugar de soloDF %>% mutate...
David Arenburg

44
En cuanto a la sintaxis. Creo que dplyrpuede ser más fácil para los usuarios que solían plyrsintaxis, pero data.tablepuede ser más fácil para los usuarios que solían consultar la sintaxis de los idiomas SQL, y el álgebra relacional detrás de esto, que se trata de la transformación de datos tabulares. @Arun debe tener en cuenta que los operadores de conjuntos son muy fáciles de realizar mediante la data.tablefunción de ajuste y, por supuesto, trae una aceleración significativa.
Jangorecki

99
He leído esta publicación muchas veces, y me ayudó mucho a comprender data.table y poder usarla mejor. Yo, para la mayoría de los casos, prefiero data.table sobre dplyr o pandas o PL / pgSQL. Sin embargo, no podía dejar de pensar en cómo expresarlo. La sintaxis no es fácil, clara o detallada. De hecho, incluso después de haber usado data.table mucho, a menudo todavía me cuesta comprender mi propio código, escribí literalmente hace una semana. Este es un ejemplo de vida de un lenguaje de solo escritura. es.wikipedia.org/wiki/Write-only_language Entonces, esperemos que algún día podamos usar dplyr en data.table.
Ovnis

385

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

Sintaxis

Hay cierta subjetividad a la sintaxis, pero mantengo 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. Afirmo que la mayoría de los problemas de una sola tabla se pueden resolver con solo cinco verbos clave filtrar, seleccionar, mutar, organizar y resumir, junto con un adverbio "por grupo". Esa restricción es de gran ayuda cuando aprende la manipulación de datos, ya que 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 realiza un trabajo y es fácil de entender de forma aislada.

Puede crear complejidad canalizando estas operaciones simples junto con %>%. Aquí hay un ejemplo de una de las publicaciones vinculadas a 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 has visto dplyr antes (¡o incluso R!), Aún puedes entender la esencia de lo que está sucediendo 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 eso puede mitigarse en gran medida con un autocompletado mejor.

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 la repetición [ de una manera que me parezca bien). Personalmente, cuando miro el código que escribí hace 6 meses, es como mirar un código escrito por un extraño, por lo que he llegado a preferir el código directo, si es detallado.

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

  • Dado que casi todas las operaciones de la tabla de datos utilizan [, necesita contexto adicional para descubrir qué está sucediendo. Por ejemplo, ¿se x[y] unen dos tablas de datos o se extraen columnas de un marco de datos? Esto 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 cuando se descrema el código, y es más fácil de detectar group_by()que el byargumento [.data.table.

También me gusta que la tubería no se limite a un solo paquete. Puede comenzar ordenando sus datos con tidyr y terminar con un diagrama en ggvis . Y no está limitado a los paquetes que escribo: cualquiera puede escribir una función que forme una parte perfecta de una tubería 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 %>%no se limita solo a marcos de datos y se generaliza fácilmente a otros contextos: gráficos web interactivos , scraping web , gist , contratos de tiempo de ejecución , ...)

Memoria y rendimiento

Los he agrupado 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 que no conoce el tiempo de procesamiento. Optimizamos dplyr para expresividad en datos medios; siéntase libre de usar data.table para obtener una velocidad sin procesar 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 sintaxis. Si el rendimiento de dplyr con el backend del marco de datos no es lo suficientemente bueno para usted, puede usar el backend data.table (aunque con un conjunto de funcionalidades algo restringido). Si los datos con los que está trabajando no caben en la memoria, puede usar un servidor de base de datos.

Dicho todo esto, el rendimiento de dplyr mejorará a largo plazo. Definitivamente implementaremos algunas de las grandes ideas de data.table como el pedido de radix y el uso del mismo índice para uniones y filtros. También estamos trabajando en paralelización para poder aprovechar múltiples núcleos.

Caracteristicas

Algunas cosas en las que estamos planeando trabajar en 2015:

  • el readrpaquete, para facilitar la extracción de archivos del disco y la memoria, de forma análoga fread().

  • Uniones más flexibles, incluido el soporte para uniones 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 las API web y facilitar el raspado de páginas html .


27
Solo una nota al margen, estoy de acuerdo con muchos de sus argumentos (aunque prefiero la data.tablesintaxis yo mismo), pero puede usarlos fácilmente %>%para canalizar data.tableoperaciones si no le gusta el [estilo. %>%no es específico de dplyr, sino que proviene de un paquete separado (del cual usted también es coautor), por lo que no estoy seguro de entender lo que está tratando de decir en la mayoría de sus párrafos de sintaxis .
David Arenburg

11
@DavidArenburg buen punto. He reescrito la sintaxis para poder aclarar cuáles son mis puntos principales, y para resaltar que puedes usar %>%con data.table
hadley

55
Gracias Hadley, esta es una perspectiva útil. Re sangría que normalmente hago DT[\n\texpression\n][\texpression\n]( gist ) que en realidad funciona bastante bien. Mantengo la respuesta de Arun como la respuesta, ya que responde más directamente a mis preguntas específicas que no se refieren tanto a la accesibilidad de la sintaxis, pero creo que esta es una buena respuesta para las personas que intentan tener una idea general de las diferencias / puntos en común entre dplyry data.table.
BrodieG

33
¿Por qué trabajar en fastread cuando ya existe fread()? ¿No se dedicaría mejor tiempo a mejorar fread () o trabajar en otras cosas (subdesarrolladas)?
EDi

10
La API de data.tablese basa en un abuso masivo de la []notación. Esa es su mayor fortaleza y su mayor debilidad.
Paul

65

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

dplyr definitivamente hace cosas que data.tableno pueden.

Tu punto # 3

dplyr resume (o lo hará) posibles interacciones de DB

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

Míralo dplyrcomo una interfaz agnóstica de fondo, con todos los objetivos usando la misma gramática, donde puedes extender los objetivos y controladores a voluntad. data.tablees, desde la dplyrperspectiva, uno de esos objetivos.

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

dplyrposiblemente puede hacer cosas data.tableque no lo harán o podrían no hacerlo tan bien.

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


En respuesta a las preguntas en el cuerpo ...

Uso

¿Existen 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, alguna combinación de pulsaciones de teclas requeridas versus nivel requerido de esoterismo, donde menos de cada una es una buena cosa).

Esto puede parecer un despeje, pero la verdadera respuesta es no. Las personas familiarizadas con las herramientas parecen usar la que les es más familiar o la que realmente es la adecuada para el trabajo en cuestión. Dicho esto, a veces desea presentar una legibilidad particular, a veces un nivel de rendimiento, y cuando necesita un nivel lo suficientemente alto de ambos, puede necesitar otra herramienta para acompañar lo que ya tiene para hacer abstracciones más claras. .

Actuación

¿Existen tareas analíticas que se realizan sustancialmente (es decir, más de 2 veces) de manera más eficiente en un paquete en comparación con otro.

De nuevo no. data.tablesobresale en ser eficiente en todo lo que hace donde se dplyrobtiene la carga de estar limitado en algunos aspectos al almacén de datos subyacente y a los controladores registrados.

Esto significa que cuando se encuentra con un problema de rendimiento data.table, puede estar bastante seguro de que está en su función de consulta y si realmente es un cuello de botella, data.tableentonces se ha ganado la alegría de presentar un informe. Esto también es cierto cuando dplyrse usa data.tablecomo back-end; Es posible que vea algunos gastos generales, dplyrpero es probable que sea su consulta.

Cuando dplyrtiene problemas de rendimiento con back-end, puede solucionarlos registrando una función para la evaluación híbrida o (en el caso de las bases de datos) manipulando la consulta generada antes de la ejecución.

Consulte también la respuesta aceptada de cuándo es mejor plyr que data.table?


3
¿No puede envolver un data.table con tbl_dt? ¿Por qué no simplemente obtener lo mejor de ambos mundos?
aaa90210

22
Olvida mencionar la declaración inversa "data.table definitivamente hace cosas que dplyr no puede", que también es cierto.
Jangorecki

25
La respuesta de Arun lo explica bien. Lo más importante (en términos de rendimiento) sería fread, actualización por referencia, combinaciones continuas, combinaciones superpuestas. Creo que no hay ningún paquete (no solo dplyr) que pueda competir con esas características. Un buen ejemplo puede ser la última diapositiva de esta presentación.
Jangorecki

15
Totalmente, data.table es la razón por la que todavía uso R. De lo contrario, usaría pandas. Es incluso mejor / más rápido que los pandas.
marbel

8
Me gusta data.table debido a su simplicidad y semejanza con la estructura de sintaxis SQL. Mi trabajo consiste en realizar análisis de datos y gráficos ad hoc muy intensos todos los días para el modelado estadístico, y realmente necesito una herramienta lo suficientemente simple como para hacer cosas complicadas. Ahora puedo reducir mi kit de herramientas a solo data.table para datos y celosía para gráficos en mi trabajo diario. Dé un ejemplo, incluso puedo hacer operaciones como esta: $ DT [group == 1, y_hat: = predict (fit1, data = .SD),] $, que es realmente bueno y lo considero una gran ventaja de SQL en Clásico R ambiente.
xappppp
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.