Ошибка преобразования длинного списка 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

Относительная скорость будет экспоненциально лучше, поскольку вы увеличиваете длину списка.