data.table vs dplyr: ¿puede uno hacer algo bien que el otro no puede o lo hace mal?
Frecuentes
Visto 159,210 equipos
892
Descripción
estoy relativamente familiarizado con data.table
, no tanto con dplyr
. He leído a través de algunos dplyr
viñetas y ejemplos que han aparecido en SO, y hasta ahora mis conclusiones son que:
data.table
ydplyr
son comparables en velocidad, excepto cuando hay muchos (es decir, >10-100K) grupos, y en algunas otras circunstancias (consulte los puntos de referencia a continuación)dplyr
tiene una sintaxis más accesibledplyr
abstrae (o hará) posibles interacciones DB- Hay algunas diferencias de funcionalidad menores (ver "Ejemplos/Uso" a continuación)
En mi mente 2. no tiene 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 intuitivo, ya que eso es irrelevante para mi pregunta específica formulada 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 nuevamente, no es lo que más me interesa aquí).
Pregunta
Lo que quiero saber es:
- ¿Hay tareas analíticas que son 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 frente al nivel requerido de esoterismo, donde menos de cada es algo bueno)?
- ¿Hay tareas analíticas que se realizan sustancialmente (es decir, más de 2 veces) más eficientemente en un paquete que en otro?
Uno pregunta SO reciente me hizo pensar un poco más en 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))
Lo cual fue mucho mejor que mi intento de hackear un data.table
solución. Dicho esto, bien data.table
las soluciones también son bastante buenas (gracias Jean-Robert, Arun, y tenga en cuenta que aquí prefiero 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 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 donde el dplyr
or data.table
forma es sustancialmente más concisa o funciona sustancialmente mejor.
Ejemplos
Usodplyr
no permite operaciones agrupadas que devuelven un número arbitrario de filas (de la pregunta de eddi, nota: esto parece que se implementará en dplyr 0.5, también, @beginneR muestra una posible solución alternativa usandodo
en la respuesta a la pregunta de @eddi).data.table
apoya uniones rodantes (gracias @dholstius) así como uniones superpuestasdata.table
optimiza internamente las expresiones de la formaDT[col == value]
orDT[col %in% values]
en velocidad a indexación automática que usa búsqueda binaria mientras usa la misma sintaxis básica de R. Mira aquí para más detalles y un pequeño punto de referencia.dplyr
ofrece versiones estándar de evaluación de funciones (p. ej.regroup
,summarize_each_
) que puede simplificar el uso programático dedplyr
(nótese el uso programático dedata.table
es definitivamente posible, solo requiere una reflexión cuidadosa, sustitución/cita, etc., al menos que yo sepa)
- Corrí mis propios puntos de referencia y encontró que ambos paquetes son comparables en el análisis de estilo "dividir aplicar combinar", excepto cuando hay una gran cantidad de grupos (> 100K) en cuyo punto
data.table
se vuelve sustancialmente más rápido. - @Arun ejecutó algunos puntos de referencia en uniones, Mostrando que
data.table
escalas mejor quedplyr
a medida que aumenta la cantidad de grupos (actualizado con mejoras recientes en ambos paquetes y la versión reciente de R). Además, un punto de referencia cuando se trata de obtener valores únicos tienedata.table
~6 veces más rápido. - (No verificado) tiene
data.table
75% más rápido en versiones más grandes de un grupo/aplicar/clasificar mientrasdplyr
fue un 40% más rápido en los más pequeños (otra pregunta SO de los comentarios, gracias Danas). - Matt, el autor principal de
data.table
el gobierno federal estadounidense ha operaciones de agrupación comparadas endata.table
,dplyr
y pitónpandas
en hasta 2 mil millones de filas (~100 GB en RAM). - An punto de referencia más antiguo en grupos de 80K tiene
data.table
~ 8 veces más rápido
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))
4 Respuestas
642
Necesitamos cubrir al menos estos aspectos para proporcionar una respuesta/comparación completa (sin ningún orden de importancia en particular): Speed
, Memory usage
, Syntax
y Features
.
Mi intención es cubrir cada uno de estos con la mayor claridad posible desde la perspectiva de la tabla de datos.
Nota: a menos que se indique 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 de data.table es consistente en su forma: DT[i, j, by]
. Mantener i
, j
y by
juntos es por diseño. Al mantener juntas las operaciones relacionadas, permite optimizar fácilmente operaciones para velocidad y más importante uso de memoria, y también proporcionar algunos características de gran alcance, todo mientras se mantiene la consistencia 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 gets más rápida 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 (100 GB en RAM) en 100 - 10 millones de grupos y varias columnas de agrupación, que también comparan pandas
. Vea también la puntos de referencia actualizados, Que incluyen Spark
y pydatatable
.
En los puntos de referencia, sería genial cubrir estos aspectos restantes también:
Operaciones de agrupación que implican un subconjunto de filas - es decir,
DT[x > val, sum(y), by = z]
operaciones de tipo.Compare otras operaciones como actualización y une.
También punto de referencia huella de memoria para cada operación además del tiempo de ejecución.
2. Uso de memoria
Operaciones que involucran
filter()
orslice()
en dplyr puede ser ineficiente para la memoria (tanto en data.frames como en data.tables). Ver este post.Tenga en cuenta que comentario de hadley habla de velocidad (que dplyr es abundante rápidamente para él), mientras que la principal preocupación aquí es memoria.
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 actualización 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 por esto es transparencia referencial. Actualizar un objeto data.table por referencia, especialmente dentro de una función, puede no ser siempre deseable. Pero esta es una función increíblemente útil: consulte este y este Publicaciones de 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, 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 antigua funcionalidad: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 un copia superficial usar
shallow()
, entendemos que no desea modificar el objeto original. Nos encargamos de todo internamente para garantizar que, al mismo tiempo que se copian las columnas que modifica solo cuando es absolutamente necesario. Cuando se implemente, esto debería resolver el transparencia referencial problema por completo al mismo tiempo que proporciona al usuario ambas posibilidades.También, una vez
shallow()
se exporta La interfaz data.table de dplyr debería evitar casi todas las copias. Entonces, aquellos que prefieran la sintaxis de dplyr pueden usarla con data.tables.Pero aún carecerá de muchas funciones que proporciona data.table, incluida la (sub)asignación por referencia.
Agregar mientras se une:
Suponga que tiene dos tablas de datos 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 conseguir
sum(z) * mul
para cada fila enDT2
mientras se une por columnasx,y
. Podemos:-
agregar
DT1
para obtenersum(z)
, 2) realizar una unión y 3) multiplicar (o)forma de tabla de datos
DT1[, .(z = suma(z)), tecla por = .(x,y)][DT2][, z := z*mul][]
equivalente de dplyr
DF1 %>% group_by(x, y) %>% resumir(z = sum(z)) %>% right_join(DF2) %>% mutar(z = z * mul)
-
hazlo todo de una sola vez (usando
by = .EACHI
característica):DT1[DT2, lista(z=suma(z) * mul), por = .EACHI]
Cual es la ventaja?
No tenemos que asignar memoria para el resultado intermedio.
No tenemos que agrupar/hash dos veces (uno para agregar y otro para unir).
Y lo que es más importante, la operación que queríamos realizar está clara al observar
j
en (2).
Revise esta publicación para una explicación detallada de
by = .EACHI
. No se materializan resultados intermedios y la unión+agregación se realiza de una sola vez.Echa un vistazo a este, este y este publicaciones para escenarios de uso real.
In
dplyr
usted tendría que unir y agregar o agregar primero y luego unir, ninguno de los cuales es tan eficiente, en términos de memoria (que a su vez se traduce en velocidad).-
Actualiza y se une:
Considere el código data.table que se muestra a continuación:
DT1[DT2, col := i.mul]
agrega / actualiza
DT1
columna decol
amul
desdeDT2
en esas filas dondeDT2
la columna clave coincideDT1
. No creo que haya un equivalente exacto de esta operación endplyr
, es decir, sin evitar una*_join
operación, que tendría que copiar todo elDT1
solo para agregarle una nueva columna, lo cual es innecesario.Revise esta publicación para un escenario de uso real.
Para resumir, es importante darse cuenta de que cada parte de la optimización es importante. Como Grace Hopper diría, Cuida 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 este comentario inútil porque es muy subjetivo. Lo que tal vez podamos intentar es contrastar 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)
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 de 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 dentromutate()
. Sin embargo, en data.table, expresamos ambas operaciones con la misma lógica: operar en filas dondex > 2
, pero en el primer caso, obtenersum(y)
, mientras que en el segundo caso actualice esas filas paray
con su suma acumulada.Esto es lo que queremos decir cuando decimos el
DT[i, j, by]
formulario es consistente.De manera similar en el caso (c), cuando tenemos
if-else
condición, somos capaces de expresar la lógica "como es" tanto en data.table como en dplyr. Sin embargo, si quisiéramos devolver solo aquellas filas donde elif
la condición satisface y omitir de lo contrario, no podemos usarsummarise()
directamente (AFAICT). Tenemos quefilter()
primero y luego resumir porquesummarise()
siempre espera un valor único.Si bien devuelve el mismo resultado, usando
filter()
aquí hace que la operación real sea menos obvia.Podría muy bien ser posible utilizar
filter()
en el primer caso también (no me parece obvio), pero mi punto es que no deberíamos tener que hacerlo.
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 quedplyr
presenta*_each()
junto con un montón de funciones parafuns()
.data.table's
:=
requiere que se proporcionen los nombres de las columnas, mientras que dplyr lo genera automáticamente.En el caso (b), la sintaxis de dplyr es relativamente sencilla. La mejora de 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 como tantas columnas, en lugar de una sola vez. En data.table, todo lo que tenemos que hacer es devolver una lista enj
. Cada elemento de la lista se convertirá en una columna en el resultado. Entonces, podemos usar, una vez más, la función base familiarc()
concatenar.N
a unalist
que devuelve unlist
.
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 usarc()
,as.list()
,lapply()
,list()
etc... funciones base para lograr esto, sin tener que aprender ninguna función nueva.Deberá aprender solo las variables especiales:
.N
y.SD
al menos. El equivalente en dplyr sonn()
y.
Une
dplyr proporciona funciones separadas para cada tipo de combinación, mientras que data.table permite combinaciones usando la misma sintaxis
DT[i, j, by]
(y con razón). También proporciona un equivalentemerge.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 unión mucho mejor (izquierda, derecha, interna, anti, semi, etc.), mientras que a otros les puede gustar data.table's
DT[i, j, by]
omerge()
que es similar a la base R.Sin embargo, las uniones dplyr hacen precisamente eso. Nada mas. Nada menos.
data.tables puede seleccionar columnas mientras se une (2), y en dplyr deberá
select()
primero en ambos marcos de datos 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 función by = .EACHI. ¿Por qué materializar todo el resultado de la unión para agregar/actualizar solo algunas columnas?
data.table es capaz de uniones rodantes (5) - rollo adelante, LOCF, rodar hacia atrás, NOCB, más cercano.
data.table también tiene
mult =
argumento que selecciona la primera, pasado or todos partidos (6).data.table tiene
allow.cartesian = TRUE
argumento para proteger de uniones inválidas accidentales.
Una vez más, la sintaxis es consistente con
DT[i, j, by]
con argumentos adicionales que permiten controlar aún más la salida.
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()
. Debe conocer de antemano el valor de retorno de todas sus funciones.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
el equivalente es.
En data.table, puede incluir casi cualquier cosa en
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 se puede hacer eso. hay 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 agregando expresionesj
sin tener que preocuparte por estas cosas.
Echa un vistazo a esta pregunta SO y esta. Me pregunto si sería posible expresar la respuesta de forma sencilla utilizando la sintaxis de dplyr...
Para resumir, he destacado especialmente Varios instancias donde la sintaxis de dplyr es ineficiente, limitada o no logra que las operaciones sean sencillas. Esto se debe particularmente a que data.table recibe un poco de reacción negativa sobre la sintaxis "más difícil de leer/aprender" (como la pegada/vinculada arriba). La mayoría de las publicaciones que cubren dplyr hablan de las operaciones más sencillas. Y eso es genial. Pero también es importante darse cuenta de su sintaxis y limitaciones de funciones, y todavía 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 las uniones de data.table como he resaltado aquí.
Pero también se debe considerar la cantidad de características que le faltan a 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. Además:
miedo - El lector rápido de archivos está disponible desde hace mucho tiempo.
escribir - a paralelizado el escritor rápido de archivos ya está disponible. Ver esta publicación para obtener una explicación detallada sobre la implementación y #1664 para estar al tanto de los desarrollos futuros.
Indexación automática - otra característica útil para optimizar la sintaxis base R tal como está, internamente.
Agrupación ad-hoc:
dplyr
ordena automáticamente los resultados agrupando variables durantesummarise()
, que puede no ser siempre deseable.Numerosas ventajas en las uniones data.table (para velocidad/eficiencia de memoria y sintaxis) mencionadas anteriormente.
Uniones no equitativas: Permite uniones usando otros operadores
<=, <, >, >=
junto con todas las demás ventajas de las uniones data.table.Combinaciones de rangos superpuestos se implementó en data.table recientemente. Controlar 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 interfaz a bases de datos usando la misma sintaxis, que data.table no hace en este momento.
data.table
proporciona equivalentes más rápidos de establecer operaciones (escrito por Jan Gorecki) -fsetdiff
,fintersect
,funion
yfsetequal
con adicionalall
argumento (como en SQL).data.table se carga limpiamente sin advertencias de enmascaramiento y tiene un mecanismo descrito aquí en
[.data.frame
compatibilidad cuando se pasa a cualquier paquete R. dplyr cambia funciones basefilter
,lag
y[
que puede causar problemas; p.ej aquí y aquí.
Por último:
En las bases de datos: no hay ninguna razón por la cual data.table no pueda proporcionar una interfaz similar, pero esto no es una prioridad ahora. Podría mejorarse si a los usuarios les gustara mucho esa función... no estoy seguro.
Sobre el paralelismo - Todo es difícil, hasta que alguien se adelanta y lo hace. Por supuesto, requerirá esfuerzo (ser seguro para subprocesos).
- Actualmente se está progresando (en el desarrollo v1.9.7) hacia la paralelización de partes conocidas que consumen mucho tiempo para obtener ganancias de rendimiento incrementales usando
OpenMP
.
- Actualmente se está progresando (en el desarrollo v1.9.7) hacia la paralelización de partes conocidas que consumen mucho tiempo para obtener ganancias de rendimiento incrementales usando
Respondido el 29 de junio de 22 a las 08:06
@bluefeet: No creo que nos haya hecho ningún gran servicio al resto de nosotros al mover 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 información útil. - IRTFM
Creo que en todos los lugares donde usa la asignación por referencia (:=
), dplyr
equivalente también debería estar usando <-
como en DF <- DF %>% mutate...
en lugar de solo DF %>% mutate...
- David Aremburgo
En cuanto a la sintaxis. creo que el dplyr
puede ser más fácil para los usuarios que solían plyr
sintaxis, pero data.table
puede ser más fácil para los usuarios que solían consultar la sintaxis de idiomas como SQL
, y el álgebra relacional detrás de él, que tiene que ver con la transformación de datos tabulares. @Arun, debes tener en cuenta que establecer operadores son muy fáciles de hacer envolviendo data.table
función y, por supuesto, trae una aceleración significativa. - Jangorecki
He leído esta publicación muchas veces y me ayudó mucho a comprender data.table y poder usarla mejor. Yo, en 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 es no fácil, claro o detallado. De hecho, incluso después de haber usado mucho data.table, 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. en.wikipedia.org/wiki/Write-only_language Entonces, esperemos que algún día podamos usar dplyr en data.table. - Ovnis
En realidad, gran parte del código dplyr ya no se aplica (¿debido a las actualizaciones?) ... Esta respuesta podría usar una actualización, ya que es un gran recurso. - soy.yo.adam
438
Aquí está mi intento de una respuesta integral desde la perspectiva de dplyr, siguiendo el esquema general de la respuesta de Arun (pero algo reorganizado según las diferentes prioridades).
Sintaxis
Hay algo de subjetividad en 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 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 ti es que restricciones 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 estás aprendiendo a manipular datos, porque ayuda a ordenar tu forma de pensar 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 al canalizar 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 antes ha visto dplyr (¡o incluso R!), aún puede obtener 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 mejor autocompletado.
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 averiguar cómo sangrar el repetido [
de una manera que se vea bien a mis ojos). 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, aunque detallado.
Otros dos factores menores que creo que disminuyen ligeramente la legibilidad:
Dado que casi todas las operaciones de tablas de datos utilizan
[
necesita contexto adicional para averiguar lo que está sucediendo. por ejemplo, esx[y]
unir dos tablas de datos o extraer columnas de un marco de datos? 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 eso
group_by()
es una operación separada en dplyr. Cambia fundamentalmente el cómputo, por lo que creo que debería ser obvio al hojear el código, y es más fácil de detectar.group_by()
que laby
argumento para[.data.table
.
tambien me gusta que la el tubo
no se limita a un solo paquete. Puede comenzar ordenando sus datos con
ordenar, y terminar con una parcela 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 con %>%
no se limita solo a marcos de datos y se generaliza fácilmente a otros contextos: gráficos web interactivos, raspado web,
esencias, 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 medianos; siéntase libre de usar data.table para 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 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 funciones algo restringido). Si los datos con los que está trabajando no caben en la memoria, entonces puede usar un backend 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 ordenar radix y usar el mismo índice para uniones y filtros. También estamos trabajando en la paralelización para poder aprovechar los múltiples núcleos.
Caracteristicas
Algunas cosas en las que planeamos trabajar en 2015:
al
readr
paquete, para facilitar la extracción de archivos del disco y en la memoria, de forma análoga afread()
.Uniones más flexibles, incluida la compatibilidad con uniones no equitativas.
Agrupación más flexible como muestras de arranque, resúmenes y más
También estoy invirtiendo tiempo en mejorar las R conectores de base de datos, la capacidad de hablar con API web, y facilitando la raspar páginas html.
Respondido el 02 de enero de 18 a las 22:01
Solo una nota al margen, estoy de acuerdo con muchos de sus argumentos (aunque prefiero el data.table
sintaxis yo mismo), pero puede usar fácilmente %>%
para canalizar data.table
operaciones si no te gusta [
estilo. %>%
no es específico de dplyr
, más bien proviene de un paquete separado (del cual también es coautor), por lo que no estoy seguro de entender lo que está tratando de decir en la mayoría de sus Sintaxis párrafo. - David Aremburgo
@DavidArenburg buen punto. He reescrito la sintaxis para dejar más claro cuáles son mis puntos principales y para resaltar que puede usar %>%
con data.table - Hadley
Gracias Hadley, esta es una perspectiva útil. Volver a sangrar que suelo hacer DT[\n\texpression\n][\texpression\n]
(esencia) que en realidad funciona bastante bien. Mantendré la respuesta de Arun como la respuesta, ya que responde más directamente a mis preguntas específicas que no tienen tanto que ver con la accesibilidad de la sintaxis, pero creo que es una buena respuesta para las personas que intentan tener una idea general de las diferencias/puntos en común entre dplyr
y data.table
. - brodieg
¿Por qué trabajar en fastread cuando ya hay fread()
? ¿No se gastaría mejor el tiempo en mejorar fread() o trabajar en otras cosas (subdesarrolladas)? - EDi
La API de data.table
se basa en un abuso masivo de la []
notación. Esa es su mayor fortaleza y su mayor debilidad. - Paul
80
En respuesta directa a la Título de la pregunta...
dplyr
que probar definitivamente hace cosas que data.table
no puedo.
Tu punto #3
dplyr abstrae (o hará) posibles interacciones DB
es una respuesta directa a su propia pregunta, pero no se eleva a un nivel lo suficientemente alto. dplyr
es verdaderamente un front-end extensible a múltiples mecanismos de almacenamiento de datos donde como data.table
es una extensión de uno solo.
Revisa dplyr
como una interfaz agnóstica de back-end, con todos los objetivos usando la misma gramática, donde puede extender los objetivos y controladores a voluntad. data.table
es, de la dplyr
perspectiva, uno de esos objetivos.
Nunca verás (espero) un día que data.table
intenta traducir sus consultas para crear sentencias SQL que operan con almacenes de datos en disco o en red.
dplyr
posiblemente puede hacer cosas data.table
no lo hará o podría no hacerlo tan bien.
Basado en el diseño de trabajo en memoria, data.table
podría tener muchas más dificultades para extenderse al procesamiento paralelo de consultas que dplyr
.
En respuesta a las preguntas del cuerpo...
Uso
¿Hay tareas analíticas que son 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 frente al nivel requerido de esoterismo, donde menos de cada uno es algo bueno).
Esto puede parecer una broma, pero la verdadera respuesta es no. Gente familiar con las herramientas parecen usar la que les resulta más familiar o la que es realmente 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, es posible que solo necesite otra herramienta para acompañar lo que ya tiene para hacer abstracciones más claras. .
Rendimiento
¿Hay tareas analíticas que se realizan sustancialmente (es decir, más de 2 veces) más eficientemente en un paquete que en otro?
De nuevo, no. data.table
se destaca por ser eficiente en todo it hace donde dplyr
recibe 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 con data.table
puede estar bastante seguro de que está en su función de consulta y si is 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 back-end; tú puede ver algo sobrecarga de dplyr
pero lo más probable es que sea su consulta.
Cuándo dplyr
tiene problemas de rendimiento con los back-end, puede sortearlos 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 a ¿Cuándo es plyr mejor que data.table?
Respondido el 20 de junio de 20 a las 10:06
¿No puede dplyr envolver una tabla de datos con tbl_dt? ¿Por qué no obtener lo mejor de ambos mundos? - aaa90210
Olvidaste mencionar la declaración inversa "data.table definitivamente hace cosas que dplyr no puede" lo cual también es cierto. - Jangorecki
La respuesta de Arun lo explica bien. Lo más importante (en términos de rendimiento) sería fread, actualización por referencia, uniones continuas, uniones 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 este presentación. - Jangorecki
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
Me gusta data.table debido a su simplicidad y semejanza con la estructura de sintaxis SQL. Mi trabajo implica hacer 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 para hacer cosas complicadas. Ahora puedo reducir mi conjunto de herramientas a solo data.table para datos y lattice 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),]$, lo cual es muy bueno y lo considero una gran ventaja de SQL en entorno R clásico. - xappppp
19
Al leer las respuestas de Hadley y Arun, uno tiene la impresión de que aquellos que prefieren dplyr
La sintaxis de tendría que cambiar en algunos casos a data.table
o compromiso para largos tiempos de ejecución.
Pero como algunos ya han mencionado, dplyr
puede utilizar data.table
como back-end. Esto se logra utilizando el dtplyr
paquete que recientemente tuvo su versión 1.0.0 ,. Aprendiendo dtplyr
incurre en prácticamente cero esfuerzo adicional.
Cuando usas dtplyr
uno usa la función lazy_dt()
para declarar una tabla de datos perezosa, después de lo cual estándar dplyr
la sintaxis se utiliza para especificar operaciones en él. Esto se vería algo como lo siguiente:
new_table <- mtcars2 %>%
lazy_dt() %>%
filter(wt < 5) %>%
mutate(l100k = 235.21 / mpg) %>% # liters / 100 km
group_by(cyl) %>%
summarise(l100k = mean(l100k))
new_table
#> Source: local data table [?? x 2]
#> Call: `_DT1`[wt < 5][, `:=`(l100k = 235.21/mpg)][, .(l100k = mean(l100k)),
#> keyby = .(cyl)]
#>
#> cyl l100k
#> <dbl> <dbl>
#> 1 4 9.05
#> 2 6 12.0
#> 3 8 14.9
#>
#> # Use as.data.table()/as.data.frame()/as_tibble() to access results
La new_table
el objeto no se evalúa hasta que se llama a él as.data.table()
/as.data.frame()
/as_tibble()
momento en el que el subyacente data.table
se ejecuta la operación.
He recreado un análisis de referencia hecho por data.table
autor Matt Dowle en diciembre de 2018, que cubre el caso de operaciones sobre un gran número de grupos. he encontrado eso dtplyr
de hecho permite en su mayor parte a aquellos que prefieren el dplyr
sintaxis para seguir usándolo mientras disfrutas de la velocidad que ofrece data.table
.
Respondido el 25 de junio de 20 a las 03:06
probablemente no tendrá muchas funciones allí para las que no hay API en dplyr, como subasignación por referencia, uniones rotativas, uniones superpuestas, uniones no equitativas, actualización al unirse y probablemente muchas otras. - Jangorecki
Tengo que admitir que ninguna de esas características me suena. ¿Podría proporcionar ejemplos concretos en data.table? - Iyar Lin
?data.table
ejemplos, todo lo que mencioné, excepto las uniones superpuestas. hay - Jangorecki
La actualización de las uniones, las uniones rodantes y superpuestas se pueden construir directamente con varias partes de una tubería. - arturo yip
Ver fuzzyjoin para uniones no equitativas (parece tener aún más características y funcionalidades que las uniones no equitativas de data.table). - arturo yip
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas r data.table dplyr or haz tu propia pregunta.
La solución que es similar en lectura a la
dplyr
uno es:as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
- eddibueno, nuevamente imO, el conjunto de problemas que se expresan más limpiamente en
(d)plyr
tiene medida 0 - eddi@BrodieG lo único que realmente me molesta de ambos
dplyr
yplyr
con 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 aun no tiene sentido para mí), recuerda lo que hacen, qué argumentos toman, etc. Eso siempre ha sido un gran desvío para mí de la filosofía plyr. - eddi@eddi [lengua en la mejilla] lo único que realmente me molesta sobre la sintaxis de data.table es que tengo que aprender cómo interactúan demasiados argumentos de funciones y qué significan los atajos crípticos (p. ej.
.SD
). [en serio] Creo que estas son diferencias de diseño legítimas que atraerán a diferentes personas: hadley@hadley re
.SD
et al - eso es justo -.SD
Me tomó un poco de tiempo entenderlo, pero cuando llegué allí, ya podía hacer mucho, mientras que (d)plyr te presenta una gran barrera desde el principio. - eddi