performance r plyr data.table do.call

performance - Problemas para convertir una larga lista de cuadros de datos(~ 1 millón) en un solo cuadro de datos usando do.call y ldply



plyr data.table (4)

Sé que hay muchas preguntas aquí en SO acerca de las formas de convertir una lista de cuadros de datos a un solo cuadro de datos usando do.call o ldply, pero esta pregunta trata sobre comprender el funcionamiento interno de ambos métodos y tratar de averiguar por qué Tampoco puedo trabajar para concatenar una lista de casi 1 millón de df de la misma estructura, los mismos nombres de campo, etc. en un solo data.frame. Cada data.frame es de una fila y 21 columnas.

Los datos comenzaron como un archivo JSON, que convertí a listas usando fromJSON, luego ejecuté otro lapply para extraer parte de la lista y convertí a data.frame y terminé con una lista de data.frames.

He intentado:

df <- do.call("rbind", list) df <- ldply(list)

pero tuve que terminar el proceso después de dejarlo correr hasta 3 horas y no recibir nada a cambio.

¿Hay un método más eficiente de hacer esto? ¿Cómo puedo solucionar lo que está sucediendo y por qué está tardando tanto?

FYI: estoy usando el servidor RStudio en un servidor de 72 GB de cuatro núcleos con RHEL, así que no creo que la memoria sea el problema. sessionInfo abajo:

> sessionInfo() R version 2.14.1 (2011-12-22) Platform: x86_64-redhat-linux-gnu (64-bit) locale: [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 [7] LC_PAPER=C LC_NAME=C [9] LC_ADDRESS=C LC_TELEPHONE=C [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C attached base packages: [1] stats graphics grDevices utils datasets methods base other attached packages: [1] multicore_0.1-7 plyr_1.7.1 rjson_0.2.6 loaded via a namespace (and not attached): [1] tools_2.14.1 >


Dado que está buscando rendimiento, parece que debería data.table una solución de data.table .

Hay una función rbindlist que es la same pero mucho más rápida que do.call(rbind, list)

library(data.table) X <- replicate(50000, data.table(a=rnorm(5), b=1:5), simplify=FALSE) system.time(rbindlist.data.table <- rbindlist(X)) ## user system elapsed ## 0.00 0.01 0.02

También es muy rápido para una lista de data.frame

Xdf <- replicate(50000, data.frame(a=rnorm(5), b=1:5), simplify=FALSE) system.time(rbindlist.data.frame <- rbindlist(Xdf)) ## user system elapsed ## 0.03 0.00 0.03

Para comparacion

system.time(docall <- do.call(rbind, Xdf)) ## user system elapsed ## 50.72 9.89 60.88

Y algunos puntos de referencia adecuados

library(rbenchmark) benchmark(rbindlist.data.table = rbindlist(X), rbindlist.data.frame = rbindlist(Xdf), docall = do.call(rbind, Xdf), replications = 5) ## test replications elapsed relative user.self sys.self ## 3 docall 5 276.61 3073.444445 264.08 11.4 ## 2 rbindlist.data.frame 5 0.11 1.222222 0.11 0.0 ## 1 rbindlist.data.table 5 0.09 1.000000 0.09 0.0

y contra las soluciones de @ JoshuaUlrich.

benchmark(use.rbl.dt = rbl.dt(X), use.rbl.ju = rbl.ju (Xdf), use.rbindlist =rbindlist(X) , replications = 5) ## test replications elapsed relative user.self ## 3 use.rbindlist 5 0.10 1.0 0.09 ## 1 use.rbl.dt 5 0.10 1.0 0.09 ## 2 use.rbl.ju 5 0.33 3.3 0.31

No estoy seguro de que realmente necesite usar as.data.frame , porque una data.table hereda la clase de data.frame


Su observación de que el tiempo tomado aumenta exponencialmente con el número de datos. Los cuadros sugieren que dividir el rbind en dos etapas podría acelerar las cosas.

Este simple experimento parece confirmar que ese es un camino muy fructífero a seguir:

## Make a list of 50,000 data.frames X <- replicate(50000, data.frame(a=rnorm(5), b=1:5), simplify=FALSE) ## First, rbind together all 50,000 data.frames in a single step system.time({ X1 <- do.call(rbind, X) }) # user system elapsed # 137.08 57.98 200.08 ## Doing it in two stages cuts the processing time by >95% ## - In Stage 1, 100 groups of 500 data.frames are rbind''ed together ## - In Stage 2, the resultant 100 data.frames are rbind''ed system.time({ X2 <- lapply(1:100, function(i) do.call(rbind, X[((i*500)-499):(i*500)])) X3 <- do.call(rbind, X2) }) # user system elapsed # 6.14 0.05 6.21 ## Checking that the results are the same identical(X1, X3) # [1] TRUE


Tiene una lista de cuadros de datos que cada uno tiene una sola fila. Si es posible convertir cada uno de ellos en un vector, creo que eso aceleraría mucho las cosas.

Sin embargo, suponiendo que deban ser datos. Marcos, crearé una función con el código tomado de la respuesta de Dominik en ¿Puede rbind estar paralelizado en R?

do.call.rbind <- function (lst) { while (length(lst) > 1) { idxlst <- seq(from = 1, to = length(lst), by = 2) lst <- lapply(idxlst, function(i) { if (i == length(lst)) { return(lst[[i]]) } return(rbind(lst[[i]], lst[[i + 1]])) }) } lst[[1]] }

He estado usando esta función durante varios meses, y he encontrado que es más rápido y usa menos memoria que do.call(rbind, ...) [el descargo de responsabilidad es que prácticamente solo lo he usado en objetos xts ]

Cuantas más filas tenga cada data.frame y más elementos tenga la lista, más beneficiosa será esta función.

Si tiene una lista de 100,000 vectores numéricos, do.call(rbind, ...) será mejor. Si tienes una lista de longitud mil millones, esta será mejor.

> df <- lapply(1:10000, function(x) data.frame(x = sample(21, 21))) > library(rbenchmark) > benchmark(a=do.call(rbind, df), b=do.call.rbind(df)) test replications elapsed relative user.self sys.self user.child sys.child 1 a 100 327.728 1.755965 248.620 79.099 0 0 2 b 100 186.637 1.000000 181.874 4.751 0 0

La velocidad relativa será exponencialmente mejor a medida que aumente la longitud de la lista.


rbind.data.frame realiza muchas comprobaciones que no necesita. Esto debería ser una transformación bastante rápida si solo haces exactamente lo que quieres.

# Use data from Josh O''Brien''s post. set.seed(21) X <- replicate(50000, data.frame(a=rnorm(5), b=1:5), simplify=FALSE) system.time({ Names <- names(X[[1]]) # Get data.frame names from first list element. # For each name, extract its values from each data.frame in the list. # This provides a list with an element for each name. Xb <- lapply(Names, function(x) unlist(lapply(X, `[[`, x))) names(Xb) <- Names # Give Xb the correct names. Xb.df <- as.data.frame(Xb) # Convert Xb to a data.frame. }) # user system elapsed # 3.356 0.024 3.388 system.time(X1 <- do.call(rbind, X)) # user system elapsed # 169.627 6.680 179.675 identical(X1,Xb.df) # [1] TRUE

Inspirado por la respuesta de data.table, decidí intentar hacer esto aún más rápido. Aquí está mi solución actualizada, para tratar de mantener la marca de verificación. ;-)

# My "rbind list" function rbl.ju <- function(x) { u <- unlist(x, recursive=FALSE) n <- names(u) un <- unique(n) l <- lapply(un, function(N) unlist(u[N==n], FALSE, FALSE)) names(l) <- un d <- as.data.frame(l) } # simple wrapper to rbindlist that returns a data.frame rbl.dt <- function(x) { as.data.frame(rbindlist(x)) } library(data.table) if(packageVersion("data.table") >= ''1.8.2'') { system.time(dt <- rbl.dt(X)) # rbindlist only exists in recent versions } # user system elapsed # 0.02 0.00 0.02 system.time(ju <- rbl.ju(X)) # user system elapsed # 0.05 0.00 0.05 identical(dt,ju) # [1] TRUE