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