Saltar al contenido

Manera eficiente de rbind data.frames con diferentes columnas

Luego de de esta larga selección de información solucionamos este disgusto que tienen algunos lectores. Te compartimos la solución y deseamos serte de mucha apoyo.

Solución:

ACTUALIZAR: En su lugar, consulte esta respuesta actualizada.

ACTUALIZAR (eddi): Esto ahora se ha implementado en la versión 1.8.11 como un fill argumento para rbind. Por ejemplo:

DT1 = data.table(a = 1:2, b = 1:2)
DT2 = data.table(a = 3:4, c = 1:2)

rbind(DT1, DT2, fill = TRUE)
#   a  b  c
#1: 1  1 NA
#2: 2  2 NA
#3: 3 NA  1
#4: 4 NA  2

FR # 4790 agregado ahora – rbind.fill (de plyr) como funcionalidad para fusionar la lista de data.frames / data.tables

Nota 1:

Esta solución utiliza data.table‘s rbindlist función para “rbind” lista de data.tables y para esto, asegúrese de usar la versión 1.8.9 debido a este error en las versiones <1.8.9.

Nota 2:

rbindlist al vincular listas de data.frames / data.tables, a partir de ahora, conservará el tipo de datos de la primera columna. Es decir, si una columna en el primer data.frame es de carácter y la misma columna en el segundo data.frame es “factor”, entonces, rbindlist dará como resultado que esta columna sea un personaje. Entonces, si su data.frame consistió en todas las columnas de caracteres, entonces, su solución con este método será idéntica al método plyr. De lo contrario, los valores seguirán siendo los mismos, pero algunas columnas serán caracteres en lugar de factores. Tendrá que convertir a “factorizar” usted mismo después. Con suerte, este comportamiento cambiará en el futuro.

Y ahora aquí está usando data.table (y comparación de evaluación comparativa con rbind.fill de plyr):

require(data.table)
rbind.fill.DT <- function(ll) 
    # changed sapply to lapply to return a list always
    all.names <- lapply(ll, names)
    unq.names <- unique(unlist(all.names))
    ll.m <- rbindlist(lapply(seq_along(ll), function(x) 
        tt <- ll[[x]]
        setattr(tt, 'class', c('data.table', 'data.frame'))
        data.table:::settruelength(tt, 0L)
        invisible(alloc.col(tt))
        tt[, c(unq.names[!unq.names %chin% all.names[[x]]]) := NA_character_]
        setcolorder(tt, unq.names)
    ))


rbind.fill.PLYR <- function(ll) 
    rbind.fill(ll)


require(microbenchmark)
microbenchmark(t1 <- rbind.fill.DT(ll), t2 <- rbind.fill.PLYR(ll), times=10)
# Unit: seconds
#                      expr      min        lq    median        uq       max neval
#   t1 <- rbind.fill.DT(ll)  10.8943  11.02312  11.26374  11.34757  11.51488    10
# t2 <- rbind.fill.PLYR(ll) 121.9868 134.52107 136.41375 184.18071 347.74724    10


# for comparison change t2 to data.table
setattr(t2, 'class', c('data.table', 'data.frame'))
data.table:::settruelength(t2, 0L)
invisible(alloc.col(t2))
setcolorder(t2, unique(unlist(sapply(ll, names))))

identical(t1, t2) # [1] TRUE

se debe notar que plyr's rbind.fill bordes más allá de este particular data.table solución hasta un tamaño de lista de aproximadamente 500.

Parcela de evaluación comparativa:

Aquí está el gráfico de las ejecuciones con la longitud de la lista de datos. seq(1000, 10000, by=1000). he usado microbenchmark con 10 repeticiones en cada una de estas diferentes longitudes de lista.

ingrese la descripción de la imagen aquí

Lo esencial de la evaluación comparativa:

Aquí está la esencia de la evaluación comparativa, en caso de que alguien quiera replicar los resultados.

Ahora eso rbindlist (y rbind) por data.table ha mejorado la funcionalidad y la velocidad con los cambios / confirmaciones recientes en v1.9.3 (versión de desarrollo), y dplyr tiene una versión más rápida de plyr's rbind.fill, llamado rbind_all, esta respuesta mía parece un poco anticuada.

Aquí está la entrada de NOTICIAS relevante para rbindlist:

o  'rbindlist' gains 'use.names' and 'fill' arguments and is now implemented entirely in C. Closes #5249    
  -> use.names by default is FALSE for backwards compatibility (doesn't bind by 
     names by default)
  -> rbind(...) now just calls rbindlist() internally, except that 'use.names' 
     is TRUE by default, for compatibility with base (and backwards compatibility).
  -> fill by default is FALSE. If fill is TRUE, use.names has to be TRUE.
  -> At least one item of the input list has to have non-null column names.
  -> Duplicate columns are bound in the order of occurrence, like base.
  -> Attributes that might exist in individual items would be lost in the bound result.
  -> Columns are coerced to the highest SEXPTYPE, if they are different, if/when possible.
  -> And incredibly fast ;).
  -> Documentation updated in much detail. Closes DR #5158.

Por lo tanto, he comparado las versiones más nuevas (y más rápidas) con datos relativamente más grandes a continuación.


Nuevo punto de referencia:

Crearemos un total de 10,000 tablas de datos con columnas que van de 200 a 300 con el número total de columnas después de la vinculación de 500.

Funciones para crear datos:

require(data.table) ## 1.9.3 commit 1267
require(dplyr)      ## commit 1504 devel
set.seed(1L)
names = paste0("V", 1:500)
foo <- function() 
    cols = sample(200:300, 1)
    data = setDT(lapply(1:cols, function(x) sample(10)))
    setnames(data, sample(names)[1:cols])

n = 10e3L
ll = vector("list", n)
for (i in 1:n) 
    .Call("Csetlistelt", ll, i, foo())

Y aquí están los horarios:

## Updated timings on data.table v1.9.5 - three consecutive runs:
system.time(ans1 <- rbindlist(ll, fill=TRUE))
#   user  system elapsed 
#  1.993   0.106   2.107 
system.time(ans1 <- rbindlist(ll, fill=TRUE))
#   user  system elapsed 
#  1.644   0.092   1.744 
system.time(ans1 <- rbindlist(ll, fill=TRUE))
#   user  system elapsed 
#  1.297   0.088   1.389 


## dplyr's rbind_all - Timings for three consecutive runs
system.time(ans2 <- rbind_all(ll))
#   user  system elapsed  
#  9.525   0.121   9.761 

#   user  system elapsed  
#  9.194   0.112   9.370 

#   user  system elapsed  
#  8.665   0.081   8.780 

identical(ans1, setDT(ans2)) # [1] TRUE

Todavía hay algo que ganar si paraleliza ambos rbind.fill y rbindlist. Los resultados se hacen con data.table la versión 1.8.8 como la versión 1.8.9 se bloqueó cuando la probé con la función paralelizada. Entonces los resultados no son idénticos entre data.table y plyr, pero son idénticos dentro data.table o plyr solución. Significado paralelo plyr coincide con incomparable plyr, y viceversa.

Aquí está el punto de referencia / scripts. los parallel.rbind.fill.DT se ve horrible, pero ese es el más rápido que pude tirar.

require(plyr)
require(data.table)
require(ggplot2)
require(rbenchmark)
require(parallel) 

# data.table::rbindlist solutions
rbind.fill.DT <- function(ll) 
  all.names <- lapply(ll, names)
  unq.names <- unique(unlist(all.names))
  rbindlist(lapply(seq_along(ll), function(x) 
    tt <- ll[[x]]
    setattr(tt, 'class', c('data.table', 'data.frame'))
    data.table:::settruelength(tt, 0L)
    invisible(alloc.col(tt))
    tt[, c(unq.names[!unq.names %chin% all.names[[x]]]) := NA_character_]
    setcolorder(tt, unq.names)
  ))


 parallel.rbind.fill.DT <- function(ll, cluster=NULL)
   all.names <- lapply(ll, names)
   unq.names <- unique(unlist(all.names)) 
   if(is.null(cluster))
     ll.m <- rbindlist(lapply(seq_along(ll), function(x) 
       tt <- ll[[x]]
       setattr(tt, 'class', c('data.table', 'data.frame'))
       data.table:::settruelength(tt, 0L)
       invisible(alloc.col(tt))
       tt[, c(unq.names[!unq.names %chin% all.names[[x]]]) := NA_character_]
       setcolorder(tt, unq.names)
     ))
   else
     cores <- length(cluster)
     sequ <- as.integer(seq(1, length(ll), length.out = cores+1))
     Call <- paste(paste("list", seq(cores), sep=""), " = ll[", c(1, sequ[2:cores]+1), ":", sequ[2:(cores+1)], "]", sep="", collapse=", ") 
     ll <- eval(parse(text=paste("list(", Call, ")")))
     rbindlist(clusterApply(cluster, ll, function(ll, unq.names)
        rbindlist(lapply(seq_along(ll), function(x, ll, unq.names) 
          tt <- ll[[x]]
          setattr(tt, 'class', c('data.table', 'data.frame'))
          data.table:::settruelength(tt, 0L)
          invisible(alloc.col(tt))
          tt[, c(unq.names[!unq.names %chin% colnames(tt)]) := NA_character_]
          setcolorder(tt, unq.names)
        , ll=ll, unq.names=unq.names))
      , unq.names=unq.names))
    
             


# plyr::rbind.fill solutions
rbind.fill.PLYR <- function(ll) 
  rbind.fill(ll)


parallel.rbind.fill.PLYR <- function(ll, cluster=NULL, magicConst=400)
  if(is.null(cluster)  

# Function to generate sample data of varying list length
set.seed(45)
sample.fun <- function() 
  nam <- sample(LETTERS, sample(5:15))
  val <- data.frame(matrix(sample(letters, length(nam)*10,replace=TRUE),nrow=10))
  setNames(val, nam)


ll <- replicate(10000, sample.fun())
cl <- makeCluster(4, type="SOCK")
clusterEvalQ(cl, library(data.table))
clusterEvalQ(cl, library(plyr))
benchmark(t1 <- rbind.fill.PLYR(ll),
  t2 <- rbind.fill.DT(ll),
  t3 <- parallel.rbind.fill.PLYR(ll, cluster=cl, 400),
  t4 <- parallel.rbind.fill.DT(ll, cluster=cl),
  replications=5)
stopCluster(cl)

# Results for rbinding 10000 dataframes
# done with 4 cores, i5 3570k and 16gb memory
# test                          reps elapsed relative 
# rbind.fill.PLYR                 5  321.80    16.682   
# rbind.fill.DT                   5   26.10    1.353    
# parallel.rbind.fill.PLYR        5   28.00    1.452     
# parallel.rbind.fill.DT          5   19.29    1.000    

# checking are results equal
t1 <- as.matrix(t1)
t2 <- as.matrix(t2)
t3 <- as.matrix(t3)
t4 <- as.matrix(t4)

t1 <- t1[order(t1[, 1], t1[, 2]), ]
t2 <- t2[order(t2[, 1], t2[, 2]), ]
t3 <- t3[order(t3[, 1], t3[, 2]), ]
t4 <- t4[order(t4[, 1], t4[, 2]), ]

identical(t2, t4) # TRUE
identical(t1, t3) # TRUE
identical(t1, t2) # FALSE, mismatch between plyr and data.table

Como puede ver, paralizar rbind.fill lo hizo comparable a data.table, y podría obtener un aumento marginal de velocidad al paralizar data.table incluso con este recuento de tramas de datos tan bajo.

Sección de Reseñas y Valoraciones

Puedes añadir valor a nuestro contenido informacional aportando tu experiencia en las ilustraciones.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *