data.table vs dplyr: ¿puede uno hacer algo bien y el otro no puede o lo hace mal?
(3)
Visión general
Estoy relativamente familiarizado con data.table
, no tanto con dplyr
. He leído algunas viñetas y ejemplos de dplyr
que han aparecido en SO, y hasta ahora mis conclusiones son que:
-
data.table
ydplyr
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) -
dplyr
tiene una sintaxis más accesible -
dplyr
abstrae (o hará) posibles interacciones de DB - Hay algunas pequeñas diferencias de funcionalidad (ver "Ejemplos / Uso" más abajo)
En mi opinión 2. no tiene mucho peso porque estoy bastante familiarizado con su data.table
, aunque entiendo que para los usuarios nuevos para ambos será un factor importante. Me gustaría evitar un argumento sobre cuál es más intuitivo, ya que 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, una combinación de pulsaciones de teclas requeridas contra el nivel requerido de esoterismo, donde menos de cada una es una buena cosa).
- ¿Hay tareas analíticas que se realicen sustancialmente (es decir, más de 2x) 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 en esto, porque hasta ese momento no pensé que dplyr
ofrecería mucho más de lo que ya puedo hacer en la data.table
de 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))
Lo que fue mucho mejor que mi intento de hackear una solución data.table
. Dicho esto, las buenas soluciones de data.table
también son bastante buenas (gracias a Jean-Robert, Arun, y tenga en cuenta que estoy a favor de la declaración ú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 sintaxis de este último puede parecer muy esotérica, pero en realidad es bastante sencilla si estás acostumbrado a la data.table
de 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 tiene un rendimiento sustancialmente mejor.
Ejemplos
Uso-
dplyr
no 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 solución temporal potencial utilizandodo
en la respuesta a la pregunta de @eddi) . -
data.table
compatible con las combinaciones rodantes (gracias @dholstius), así como las combinaciones superpuestas -
data.table
optimiza internamente expresiones de la formaDT[col == value]
oDT[col %in% values]
para la velocidad a través de la indexación automática que utiliza la búsqueda binaria mientras usa la misma sintaxis de la base R. Vea aquí para más detalles y un pequeño punto de referencia. -
dplyr
ofrece versiones de evaluación estándar de funciones (por ejemplo,regroup
,summarize_each_
) que pueden simplificar el uso programático dedplyr
(tenga en cuenta que el uso programático dedata.table
es definitivamente posible, solo requiere una reflexión cuidadosa, sustitución / cotización, etc., al menos que yo sepa )
- 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 los que
data.table
vuelve sustancialmente más rápido. - @Arun ejecutó algunos puntos de referencia en las combinaciones , mostrando que la
data.table
escala mejor quedplyr
medida quedplyr
el número de grupos (actualizado con mejoras recientes en ambos paquetes y versión reciente de R). Además, un punto de referencia cuando se intentan obtener valores únicos tienedata.table
~ 6x más rápido. - (Sin verificar) tiene
data.table
75% más rápido en versiones más grandes de un grupo / solicitud / clasificación, mientras quedplyr
fue 40% más rápido en las versiones más pequeñas ( otra pregunta SO de comentarios , gracias danas). - Matt, el autor principal de
data.table
, hadata.table
operaciones de agrupación endata.table
,dplyr
y pythonpandas
en hasta 2 billones de filas (~ 100GB en RAM) . - Una referencia más antigua en grupos de 80K 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))
En respuesta directa al Título de la Pregunta ...
dplyr
definitivamente hace cosas que data.table
no puede.
Tu punto # 3
dplyr abstrae (o hará) posibles interacciones de DB
es una respuesta directa a su propia pregunta, pero no se eleva a un nivel suficientemente alto. dplyr
es verdaderamente un front-end extensible para múltiples mecanismos de almacenamiento de datos en el que, como data.table
es una extensión para uno solo.
Considere a dplyr
como una interfaz dplyr
back-end, con todos los objetivos utilizando la misma gramática, donde puede extender los objetivos y los manejadores a voluntad. data.table
es, desde la perspectiva de dplyr
, uno de esos objetivos.
Nunca (espero) verá un día en que data.table
intente traducir sus consultas para crear sentencias de SQL que funcionen con almacenes de datos en disco o en red.
dplyr
posiblemente puede hacer cosas de data.table
. data.table
no funcionará o no.
Basado en el diseño de trabajo en memoria, data.table
podría tener un tiempo mucho más difícil extenderse en el procesamiento paralelo de consultas que dplyr
.
En respuesta a las preguntas en el 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, una combinación de pulsaciones de teclas requeridas contra el nivel requerido de esoterismo, donde menos de cada una es una buena cosa).
Esto puede parecer un despeje, pero la respuesta real es no. Las personas familiarizadas con las herramientas parecen utilizar la más familiar para ellas o la que en realidad es la adecuada para el trabajo en cuestión. Con eso dicho, a veces desea presentar una legibilidad particular, a veces un nivel de rendimiento, y cuando necesita un nivel suficientemente alto de ambos, es posible que necesite otra herramienta para ir con lo que ya tiene para hacer abstracciones más claras .
Actuación
¿Hay tareas analíticas que se realicen sustancialmente (es decir, más de 2x) de manera más eficiente en un paquete en comparación con otro?
De nuevo, no. data.table
sobresale por ser eficiente en todo lo que hace, y dplyr
en algunos aspectos tiene la carga de estar limitado al almacén de datos subyacente y a los manejadores registrados.
Esto significa que cuando se encuentra con un problema de rendimiento data.table
puede estar bastante seguro de que está en la función de consulta y, si en realidad es un cuello de botella data.table
, se habrá ganado la alegría de presentar un informe. Esto también es cierto cuando dplyr
se usa data.table
como back-end; Es posible que veas algo de sobrecarga desde, dplyr
pero lo más probable es que sea tu consulta.
Cuando dplyr
tiene problemas de rendimiento con los back-ends, puede sortearlos al registrar una función para la evaluación híbrida o (en el caso de las bases de datos) manipular la consulta generada antes de la ejecución.
También vea la respuesta aceptada de cuándo es mejor plyr que data.table?
Este es mi intento de obtener una respuesta integral desde la perspectiva de dplyr, siguiendo el esquema general de la respuesta de Arun (pero algo reorganizado en función de las diferentes prioridades).
Sintaxis
Hay cierta 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 más difícil de leer. ¡Esto es en parte porque dplyr está resolviendo un problema mucho más fácil!
Una cosa realmente importante que dplyr hace por usted es que limita sus opciones. Afirmo que la mayoría de los problemas de una sola tabla se pueden resolver con solo cinco verbos clave que filtran, seleccionan, mutan, organizan 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 al canalizar estas operaciones simples junto con %>%
. Aquí hay un ejemplo de una de las publicaciones a las que Arun ha vinculado :
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 has visto dplyr (¡o incluso R!), Puedes entender lo que está sucediendo porque las funciones son verbos en inglés. La desventaja de los verbos en inglés es que requieren más mecanografía que [
, pero creo que eso se puede mitigar en gran medida con un autocompletado mejor.
Aquí está el código equivalente data.table:
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 lo 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 prefiero el código directo, aunque detallado.
Otros dos factores menores que creo que disminuyen ligeramente la legibilidad:
Dado que casi todas las operaciones de la tabla de datos utilizan
[
se necesita un contexto adicional para descubrir qué está sucediendo. Por ejemplo, ¿x[y]
une dos tablas de datos o extrae 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 hojea el código, y es más fácil detectargroup_by()
que el argumentoby
a[.data.table
.
También me gusta que la tubería no se limite a un solo paquete. Puede comenzar por ordenar sus datos con tidyr y terminar con un gráfico 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 anterior de data.table 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 , gists , contratos de tiempo de ejecución , ...)
Memoria y rendimiento
Los he agrupado juntos 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 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 funciones algo restringido). Si los datos con los que está trabajando no caben en la memoria, entonces puede usar una base de datos backend.
Dicho todo esto, el rendimiento de dplyr mejorará a largo plazo. Definitivamente implementaremos algunas de las grandes ideas de data.table como el ordenamiento de radix y el uso del 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:
el paquete de
readr
, para facilitar la obtención de archivos del disco y en la memoria, de forma análoga afread()
.Uniones más flexibles, incluido el soporte para uniones no equitativas.
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 .
Necesitamos cubrir al menos estos aspectos para proporcionar una respuesta / comparación completa (sin ningún orden particular de importancia): Speed
, Memory usage
, Syntax
y Features
.
Mi intención es cubrir cada uno de estos lo más claramente posible desde la perspectiva de data.table.
Nota: a menos que se mencione explícitamente lo contrario, refiriéndonos 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]
. Mantener i
, j
y juntos es por diseño. Al mantener las operaciones relacionadas juntas, permite optimizar fácilmente las operaciones para la velocidad y, lo que es más importante, el uso de la memoria , y también proporcionar algunas funciones 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ápida 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 compara pandas
.
En los puntos de referencia, sería genial cubrir también estos aspectos restantes:
Operaciones de agrupación que involucran un subconjunto de filas , es decir, operaciones de tipo
DT[x > val, sum(y), by = z]
.Hacer referencia a otras operaciones como la actualización y las combinaciones .
También comparará la huella de memoria para cada operación además del tiempo de ejecución.
2. Uso de la memoria
Las operaciones que involucran
filter()
oslice()
en dplyr pueden ser ineficientes en la memoria (tanto en data.frames como en data.tables). Ver este postTenga en cuenta que el comentario de Hadley habla de velocidad (que dplyr es bastante rápido para él), mientras que la principal preocupación aquí es la memoria .
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 de 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 por esto es la transparencia referencial . Actualizar 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 this y this post para casos interesantes. Y queremos mantenerlo.
Por lo tanto, estamos trabajando para exportar la función
shallow()
en data.table que proporcionará al usuario ambas posibilidades . Por ejemplo, si es deseable no modificar la tabla de datos de entrada dentro de una función, entonces 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()
, la funcionalidad anterior se conserva:bar <- function(DT) { DT[, newcol := 1L] ## old behaviour, original DT gets updated by reference DT[x > 2L, x := 3L] ## old behaviour, update column x in original DT. }
Al crear una copia superficial con
shallow()
, entendemos que no desea modificar el objeto original. Nos encargamos de todo internamente para asegurarnos de que al mismo tiempo nos aseguramos de copiar las columnas que modifique solo cuando sea absolutamente necesario . Cuando se implementa, esto debe resolver el problema de la transparencia referencial al mismo tiempo que proporciona al usuario ambas posibilidades.Además, una vez que se haya exportado a
shallow()
la interfaz data.table de dplyr debería evitar casi todas las copias. Así que aquellos que prefieren la sintaxis de dplyr pueden usarla con data.tables.Pero aún carecerá de muchas características que proporciona data.table, incluida la (sub) asignación por referencia.
Agregue al unirse:
Supongamos 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 le gustaría obtener
sum(z) * mul
para cada fila enDT2
mientras se une por las columnasx,y
. Podemos:1) agregue
DT1
para obtener lasum(z)
, 2) realice una unión y 3) multiplique (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) hacerlo 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 (uno para la agregación y otro para unirnos).
Y lo que es más importante, la operación que queríamos realizar está clara al observar
j
en (2).
Compruebe esta publicación para una explicación detallada de
by = .EACHI
. No se materializan resultados intermedios, y la combinación + agregado se realiza de una sola vez.Eche un vistazo a this , this y this post 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).Actualizar y se une:
Considere el código data.table que se muestra a continuación:
DT1[DT2, col := i.mul]
agrega / actualiza la columna de la columna
DT1
conmul
desde laDT2
en aquellas filas donde la columna clave de laDT2
coincide con laDT1
. No creo que exista un equivalente exacto de esta operación endplyr
, es decir, sin evitar una operación*_join
, que tendría que copiar todo elDT1
solo para agregarle una nueva columna, lo cual no es necesario.Compruebe esta publicación para un escenario de uso real.
Para resumir, es importante darse cuenta de que cada bit de optimización importa. Como diría Grace Hopper : ¡ Cuidado con los nanosegundos !
3. Sintaxis
Veamos ahora la sintaxis . Hadley comentó here :
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 las usa es más difícil de leer después de haberlas escrito ...
Considero que este comentario no tiene sentido 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 lado a lado.
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 al resumir . Pero al actualizar , tuvimos que mover la lógica dentro demutate()
. Sin embargo, en data.table, expresamos ambas operaciones con la misma lógica: operamos en filas dondex > 2
, pero en el primer caso, obtenemos lasum(y)
, mientras que en el segundo caso actualizamos esas filas paray
con su suma acumulada.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 expresar la lógica "tal como está" tanto en data.table como en dplyr. Sin embargo, si nos gustaría devolver solo aquellas filas donde la condiciónif
satisface y omitir de otra manera, no podemos usar resumizesummarise()
directamente (AFAICT). Primero tenemos quefilter()
y luego resumir porquesummarise()
siempre espera un valor único .Si bien devuelve el mismo resultado, el uso de
filter()
aquí hace que la operación real sea menos obvia.Es muy posible que también se pueda usar
filter()
en el primer caso (no me parece obvio), pero mi punto es que no deberíamos 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 la función base familiar
lapply()
, mientras quedplyr
introduce*_each()
junto con un montón de funciones parafuns()
.data.table''s
:=
requiere que se proporcionen nombres de columna, mientras que dplyr lo genera automáticamente.En el caso (b), la sintaxis de dplyr es relativamente 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 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()
para concatenar.N
a unalist
que devuelve unalist
.
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 funciones básicasc()
,as.list()
,lapply()
,list()
etc ... para lograr esto, sin tener que aprender ninguna función nueva.Tendrá que aprender solo las variables especiales -
.N
y.SD
al menos. Los equivalentes en dplyr sonn()
y.
Se une
dplyr proporciona funciones separadas para cada tipo de unión, donde como data.table permite uniones usando la misma sintaxis
DT[i, j, by]
(y con razón). También proporciona una funciónmerge.data.table()
equivalente como una alternativa.setkey(DT1, x, y) # 1. normal join DT1[DT2] ## data.table syntax left_join(DT2, DT1) ## dplyr syntax # 2. select columns while join DT1[DT2, .(z, i.mul)] left_join(select(DT2, x, y, mul), select(DT1, x, y, z)) # 3. aggregate while join DT1[DT2, .(sum(z) * i.mul), by = .EACHI] DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul) # 4. update while join DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI] ?? # 5. rolling join DT1[DT2, roll = -Inf] ?? # 6. other arguments to control output DT1[DT2, mult = "first"] ??
Algunos pueden encontrar una función separada para cada combinación mucho más agradable (izquierda, derecha, interior, anti, semi, etc.), mientras que a otros les puede gustar el
DT[i, j, by]
data.table, 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 cuadros 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 al unirse (3) y también actualizar al unirse (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 rodar uniones (5): avance, LOCF , retroceda, NOCB , el nearest .
data.table también tiene
mult =
argumento que selecciona primera , última o todas las coincidencias (6).data.table tiene
allow.cartesian = TRUE
argumentoallow.cartesian = TRUE
para proteger de uniones no válidas accidentales.
Una vez más, la sintaxis es consistente con
DT[i, j, by]
con argumentos adicionales que permiten controlar 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()
. Tienes que saber de antemano 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))))
El equivalente de
.SD
es.
En data.table, puedes lanzar casi cualquier cosa en
j
; lo único que debes 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. Debe recurrir a
do()
dependiendo de qué tan seguro esté de si su función siempre devolverá un solo valor. Y es bastante lento.
Una vez más, la sintaxis de data.table es consistente con
DT[i, j, by]
. Podemos seguir lanzando expresiones enj
sin tener que preocuparnos por estas cosas.
Echa un vistazo a esta pregunta de SO y este . Me pregunto si sería posible expresar la respuesta de forma sencilla usando la sintaxis de dplyr ...
Para resumir, he resaltado en particular varias instancias en las que la sintaxis de dplyr es ineficiente, limitada o no hace que las operaciones sean sencillas. Esto es particularmente porque data.table obtiene 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 los mensajes que cubren dplyr hablan de las operaciones más sencillas. Y eso es genial. Pero también es importante tener en cuenta su sintaxis y sus limitaciones de funciones, y todavía tengo que ver 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 lo he resaltado here .
Pero también se debe considerar la cantidad de características que carece 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 este post. Adicionalmente:
El lector de archivos fread - fast ha estado disponible durante mucho tiempo.
fwrite - NUEVO en el devel actual, v1.9.7, ahora está disponible un escritor rápido de archivos 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 útil para optimizar la sintaxis de la base R tal como es, internamente.
Agrupación ad-hoc :
dplyr
clasifica automáticamente los resultados agrupando las variables durante elsummarise()
, lo que puede no ser siempre deseable.Numerosas ventajas en las combinaciones data.table (para velocidad / eficiencia de memoria y sintaxis) mencionadas anteriormente.
Uniones no equi : es una nueva función disponible desde v1.9.7 +. Permite uniones usando otros operadores
<=, <, >, >=
junto con todas las otras ventajas de las combinaciones data.table.Superposición de combinaciones de rango se implementó en data.table recientemente. Compruebe esta publicación para una visión general con puntos de referencia.
setorder()
en data.table que permite una reordenación realmente rápida de data.tables por referencia.dplyr proporciona interfaz a las bases de datos con la misma sintaxis, que data.table no tiene en este momento.
data.table
proporciona equivalentes más rápidos de operaciones de conjuntos de v1.9.7 + (escrito por Jan Gorecki) -fsetdiff
,fintersect
,funion
yfsetequal
conall
argumentos adicionales (como en SQL).data.table se carga limpiamente sin advertencias de enmascaramiento y tiene un mecanismo descrito here para la compatibilidad de
[.data.frame
cuando se pasa a cualquier paquete R. Las funciones básicas de dplyr cambian elfilter
, ellag
y[
que puede causar problemas; por ejemplo here y here .
Finalmente:
En las bases de datos: no hay ninguna razón por la que data.table no pueda proporcionar una interfaz similar, pero ahora no es una prioridad. Podría ser superado si a los usuarios les gustara mucho 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 (siendo seguro para subprocesos).
- Actualmente se está avanzando (en el desarrollo de v1.9.7) hacia el paralelismo de partes conocidas que consumen mucho tiempo para obtener ganancias de rendimiento incrementales con
OpenMP
.
- Actualmente se está avanzando (en el desarrollo de v1.9.7) hacia el paralelismo de partes conocidas que consumen mucho tiempo para obtener ganancias de rendimiento incrementales con