Ошибка преобразования длинного списка data.frames(~ 1 миллион) в единый файл data.frame с использованием do.call и ldply
Я знаю, что здесь есть много вопросов о способах преобразования списка data.frames в один файл data.frame с использованием do.call или ldply, но эти вопросы касаются понимания внутренней работы обоих методов и попыток выяснить, почему я не могу заставить работать для объединения списка почти 1 миллион df одной и той же структуры, одинаковых имен полей и т.д. в один файл data.frame. Каждый data.frame имеет одну строку и 21 столбец.
Данные начинались как JSON файл, который я преобразовал в списки, используя fromJSON, а затем запускал еще один лап, чтобы извлечь часть списка и преобразовать в data.frame, и в итоге появился список data.frames.
Я пробовал:
df <- do.call("rbind", list)
df <- ldply(list)
но мне пришлось убить процесс после того, как он запустил до 3 часов и ничего не получил.
Есть ли более эффективный способ сделать это? Как я могу устранить то, что происходит, и почему это так долго?
FYI. Я использую RStudio-сервер на 72-Гбайт четырехъядерном сервере с RHEL, поэтому я не думаю, что проблема памяти. sessionInfo ниже:
> 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
>
Ответы
Ответ 1
Учитывая, что вы ищете производительность, кажется, что нужно предложить решение data.table
.
Существует функция rbindlist
, которая является same
, но намного быстрее, чем 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
Он также очень быстро для списка 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
Для сравнения
system.time(docall <- do.call(rbind, Xdf))
## user system elapsed
## 50.72 9.89 60.88
И некоторый правильный бенчмаркинг
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
и против решений @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
Я не уверен, что вам действительно нужно использовать as.data.frame
, потому что data.table
наследует класс data.frame
Ответ 2
rbind.data.frame
делает много проверок, которые вам не нужны. Это должно быть довольно быстрой трансформацией, если вы делаете именно то, что хотите.
# Use data from Josh O'Brien 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
Вдохновленный ответом data.table, я решил попробовать и сделать это еще быстрее. Здесь мое обновленное решение, чтобы попытаться сохранить галочку.; -)
# 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
Ответ 3
Ваше наблюдение за тем, что время, затраченное на экспоненциальное увеличение числа кадров данных, предполагает, что нарушение rbind
ing на два этапа может ускорить процесс.
Этот простой эксперимент, похоже, подтверждает, что это очень плодотворный путь:
## 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
Ответ 4
У вас есть список data.frames, каждый из которых имеет одну строку. Если можно преобразовать каждый из них в вектор, я думаю, что это ускорит процесс.
Однако, если предположить, что они должны быть data.frames, я создам функцию с кодом, заимствованным из ответа Dominik на Может ли rbind быть распараллелен в 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]]
}
Я использую эту функцию в течение нескольких месяцев, и нашел, что она быстрее и использует меньше памяти, чем do.call(rbind, ...)
[отказ в том, что я в значительной степени использовал его только на объектах xts
]
Чем больше строк, которые имеют каждый файл data.frame, тем больше элементов будет иметь этот список.
Если у вас есть список из 100 000 числовых векторов, do.call(rbind, ...)
будет лучше. Если у вас есть список длиной один миллиард, это будет лучше.
> 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
Относительная скорость будет экспоненциально лучше, поскольку вы увеличиваете длину списка.