¿Por qué rbindlist es “mejor” que rbind?

Estoy revisando la documentación de data.table y también me di cuenta de algunas de las conversaciones aquí en SO que se supone que rbindlist es mejor que rbind .

Me gustaría saber por qué rbindlist es mejor que rbind y en qué escenarios rbindlist realmente sobresale sobre rbind .

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

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


¿Dónde realmente sobresale?

Algunas preguntas que muestran dónde brilla rbindlist son

Fusión vectorizada rápida de la lista de data.frames por fila

Problema al convertir una larga lista de data.frames (~ 1 millón) a single data.frame usando do.call y ldply

Estos tienen puntos de referencia que muestran qué tan rápido puede ser.


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

rbind.data.frame realiza muchas comprobaciones e rbind.data.frame 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), rbindlist no hace 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))) ## ab ## 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))) ## ab ## 1: 1 2 ## 2: 2 3 ## 3: 1 2 ## 4: 2 3 

Algunas otras limitaciones de rbindlist

Solía ​​tener problemas para lidiar con los factors , debido a un error que desde entonces se ha solucionado:

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

Tiene problemas con los nombres de columna duplicados

ver Mensaje de advertencia: en rbindlist (allargs): NA introducida por coerción: posible error en data.table? ( Bug # 2384 )


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

rbindlist puede manejar las lists data.frames y data.tables , y devolverá una data.table sin rownames

puede obtener un embrollo de nombres de filas usando do.call(rbind, list(...)) ver

¿Cómo evitar el cambio de nombre de filas al usar rbind dentro de do.call?


Eficiencia de memoria

En términos de memoria rbindlist se implementa en C , por lo que es eficiente desde el punto de vista de la memoria, usa setattr para establecer atributos por referencia

rbind.data.frame se implementa en R , realiza muchas asignaciones y usa attr<- (y class<- y rownames<- todos los cuales (internamente) crearán copias del data.frame creado.

Por v1.9.2 , rbindlist había evolucionado bastante, implementando muchas características, incluyendo:

  • Elegir el SEXPTYPE más SEXPTYPE de columnas durante el enlace: implementado en v1.9.2 cerrando FR # 2456 y Bug # 4981 .
  • Manejo de las columnas de factor correctamente: primero implementado en v1.8.10 cerrando el Bug # 2650 y ampliado a los factores ordenados de enlace cuidadosamente también en v1.9.2 , cerrando FR # 4856 y Bug # 5019 .

Además, en v1.9.2 , rbind.data.table también ganó un argumento de fill , que permite enlazar completando columnas faltantes, implementado en R.

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

  • rbindlist gana un argumento use.names , que por defecto es FALSE para compatibilidad con versiones anteriores.
  • rbindlist también gana un fill argumento, que por defecto también es FALSE para compatibilidad con versiones anteriores.
  • Todas estas características se implementan en C y se escriben cuidadosamente para no comprometer la velocidad al agregar funcionalidades.
  • Como rbindlist ahora puede coincidir por nombres y completar las columnas que faltan, rbind.data.table simplemente llama a rbindlist ahora. La única diferencia es que use.names=TRUE por defecto para rbind.data.table , por compatibilidad con versiones anteriores.

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

Sin embargo, esa lista de rbindlist (ed) ciertas características (como verificar los niveles de factores o los nombres que coinciden) tiene un peso muy pequeño (o nulo) para que sea más rápido que rbind.data.frame . Es porque fueron implementados cuidadosamente en C, optimizados para velocidad y memoria.

Aquí hay un punto de referencia que resalta el enlace eficiente al mismo tiempo que coincide con los nombres de las columnas utilizando la función v1.9.3 de v1.9.3 de v1.9.3 . El conjunto de datos consta de 10000 marcos de datos de tamaño 10 * 500 cada uno.

NB: este punto de referencia se ha actualizado para incluir una comparación con los dplyr de bind_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 las columnas como tal sin verificar los nombres tomó solo 1.3, ya que la verificación de los nombres de las columnas y la vinculación apropiada demoraron solo 1,5 segundos más. Comparado con la solución base, esto es dplyr más rápido y dplyr más rápido que la versión de dplyr .