¿Por qué es rbindlist "mejor" que rbind?

135

Estoy revisando la documentación data.tabley también noté de algunas de las conversaciones aquí sobre SO que rbindlistse supone que es mejor que rbind.

Me gustaría saber por qué es rbindlistmejor rbindy en qué escenarios rbindlistrealmente sobresale rbind.

¿Hay alguna ventaja en términos de utilización de memoria?

Chinmay Patil
fuente

Respuestas:

155

rbindlistes una versión optimizada de do.call(rbind, list(...)), que es conocida por ser lenta cuando se usarbind.data.frame


¿Dónde realmente sobresale?

Algunas preguntas que muestran dónde rbindlistestán los brillos

Rápida fusión vectorizada de la lista de datos. Marcos por fila

Problemas para convertir una larga lista de data.frames (~ 1 millón) en un solo data.frame usando do.call y ldply

Estos tienen puntos de referencia que muestran lo rápido que puede ser.


rbind.data.frame es lento, por una razón

rbind.data.framehace muchas comprobaciones y coincidirá por nombre. (es decir, rbind.data.frame tendrá en cuenta el hecho de que las columnas pueden estar en diferentes órdenes y coincidir por nombre), rbindlistno realiza este tipo de comprobación y se unirá por posición

p.ej

do.call(rbind, list(data.frame(a = 1:2, b = 2:3), data.frame(b = 1:2, a = 2:3)))
##    a b
## 1  1 2
## 2  2 3
## 3  2 1
## 4  3 2

rbindlist(list(data.frame(a = 1:5, b = 2:6), data.frame(b = 1:5, a = 2:6)))
##     a b
##  1: 1 2
##  2: 2 3
##  3: 1 2
##  4: 2 3

Algunas otras limitaciones de rbindlist

Se utiliza para luchar para hacer frente factors, debido a un error que desde entonces ha sido fijada:

rbindlist dos data.tables donde uno tiene factor y otro tiene tipo de carácter para una columna ( Bug # 2650 )

Tiene problemas con nombres de columna duplicados.

vea Mensaje de advertencia: en rbindlist (allargs): NAs introducidos por coerción: ¿posible error en data.table? ( Error # 2384 )


Los nombres de fila de rbind.data.frame pueden ser frustrantes

rbindlistpuede manejar lists data.framesy data.tables, y devolverá un data.table sin nombres de fila

puedes meterte en una confusión de nombres de fila usando do.call(rbind, list(...)) ver

¿Cómo evitar el cambio de nombre de las filas cuando se usa rbind dentro de do.call?


Eficiencia de la memoria

En términos de memoria rbindlistse implementa en C, por lo que es eficiente en memoria, se utiliza setattrpara establecer atributos por referencia

rbind.data.framese implementa en R, realiza muchas asignaciones y utiliza attr<-( class<-y rownames<-todo lo cual (internamente) creará copias de la data.frame creada.

mnel
fuente
1
Para su información attr<-, class<-y (creo) rownames<-todos modifican en su lugar.
Hadley
55
@hadley ¿Estás seguro? Tratar DF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF)).
Matt Dowle
44
rbind.data.frametiene una lógica especial de "secuestro": cuando su primer argumento es a data.table, llama en su .rbind.data.tablelugar, lo que hace una pequeña comprobación y luego llama rbindlistinternamente. Entonces, si ya tiene data.tableobjetos para vincular, probablemente haya poca diferencia de rendimiento entre rbindy rbindlist.
Ken Williams
66
Mnel, esta publicación tal vez necesite edición, ahora que rbindlistes capaz de coincidir por nombres ( use.names=TRUE) y también llenar las columnas que faltan ( fill=TRUE). He actualizado esto , esto y esta publicación. ¿Te importa editar este o está bien si lo hago? De cualquier manera está bien para mí.
Arun
1
dplyr::rbind_listtambién es bastante similar
hadley
48

Por v1.9.2, rbindlisthabía evolucionado bastante, implementando muchas características que incluyen:

  • Elección de la SEXPTYPEcolumna más alta mientras se vincula: implementado al v1.9.2cerrar FR # 2456 y Bug # 4981 .
  • Manejo de factorcolumnas correctamente: primero se implementó al v1.8.10cerrar el Bug # 2650 y se extendió también a los factores ordenados vinculantes v1.9.2, cerrando FR # 4856 y Bug # 5019 .

Además, en v1.9.2, rbind.data.tabletambién ganó un fillargumento, que permite enlazar rellenando columnas faltantes, implementado en R.

Ahora v1.9.3, hay aún más mejoras en estas características existentes:

  • rbindlistgana un argumento use.names, que por defecto es FALSEpara compatibilidad con versiones anteriores.
  • rbindlisttambién gana un argumento fill, que por defecto también es FALSEpara compatibilidad con versiones anteriores.
  • Todas estas características se implementan en C y se escriben cuidadosamente para no comprometer la velocidad al agregar funcionalidades.
  • Ya rbindlistque ahora puede coincidir por nombres y llenar columnas faltantes, rbind.data.tablesolo llama rbindlistahora. La única diferencia es que, use.names=TRUEpor defecto rbind.data.table, para compatibilidad con versiones anteriores.

rbind.data.framese ralentiza bastante debido principalmente a las copias (que @mnel también señala) que podrían evitarse (moviéndose a C). Creo que esa no es la única razón. La implementación para verificar / hacer coincidir los nombres de columna rbind.data.frametambién podría ser más lenta cuando hay muchas columnas por data.frame y hay muchos data.frames para vincular (como se muestra en el punto de referencia a continuación).

Sin embargo, esa rbindlistfalta (ed) de ciertas características (como verificar niveles de factores o nombres coincidentes) tiene un peso muy pequeño (o nulo) para que sea más rápido rbind.data.frame. Se debe a que se implementaron cuidadosamente en C, optimizados para la velocidad y la memoria.

Aquí hay un punto de referencia que resalta el enlace eficiente al tiempo que coincide con los nombres de columna, así como con rbindlistla use.namesfunción de v1.9.3. El conjunto de datos consta de 10000 data.frames cada uno de tamaño 10 * 500.

Nota: este punto de referencia ha sido actualizado para incluir una comparación con dplyr'sbind_rows

library(data.table) # 1.11.5, 2018-06-02 00:09:06 UTC
library(dplyr) # 0.7.5.9000, 2018-06-12 01:41:40 UTC
set.seed(1L)
names = paste0("V", 1:500)
cols = 500L
foo <- function() {
    data = as.data.frame(setDT(lapply(1:cols, function(x) sample(10))))
    setnames(data, sample(names))
}
n = 10e3L
ll = vector("list", n)
for (i in 1:n) {
    .Call("Csetlistelt", ll, i, foo())
}

system.time(ans1 <- rbindlist(ll))
#  user  system elapsed 
# 1.226   0.070   1.296 

system.time(ans2 <- rbindlist(ll, use.names=TRUE))
#  user  system elapsed 
# 2.635   0.129   2.772 

system.time(ans3 <- do.call("rbind", ll))
#   user  system elapsed 
# 36.932   1.628  38.594 

system.time(ans4 <- bind_rows(ll))
#   user  system elapsed 
# 48.754   0.384  49.224 

identical(ans2, setDT(ans3)) 
# [1] TRUE
identical(ans2, setDT(ans4))
# [1] TRUE

La vinculación de columnas como tal sin verificar los nombres tomó solo 1.3, mientras que la verificación de los nombres de columnas y la vinculación de manera adecuada solo tomó 1.5 segundos más. En comparación con la solución base, es 14 veces más rápido y 18 veces más rápido que dplyrla versión de.

Arun
fuente