Проблемы с преобразованием длинного списка data.frames (~ 1 млн.) В один data.frame с использованием do.call и ldply

Я знаю, что в SO много вопросов о способах преобразования списка 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 часов и ничего не получил обратно.

Есть ли более эффективный способ сделать это? Как я могу устранить неполадки, что происходит и почему это занимает так много времени?

К вашему сведению - я использую сервер RStudio на четырехъядерном сервере 72 ГБ с RHEL, поэтому не думаю, что проблема с памятью. сессия ниже:

> 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
> 
 wahalulu15 мар. 2012 г., 22:47
@joran Я профилировал ранее, но вот последние показатели для system.time для ldply (список). 1k: 0,994 0,001 0,995, 10k: 19,896 2,126 22,025, 100k: все еще идет ...
 joran15 мар. 2012 г., 22:34
Я не могу обещать, что это приведет к хорошему решению, но, возможно, стоит выяснить, сколько из ваших df вы можете найти, прежде чем столкнетесь с неприятностями. 1k? 10k? 100k?
 Matt Parker15 мар. 2012 г., 22:51
Возможно, вы захотите проверить ответ Шейна и комментарии ХэдлиВот, если вы еще этого не сделали - rbind.fill может быть быстрее.
 mnel06 сент. 2012 г., 00:50
data.table а такжеrbindlist путь сюда!
 Matt Dowle06 сент. 2012 г., 14:53
@wahalulu. Когда у вас есть возможность посмотреть на новый ответ, используя новую функцию вdata.tableНе могли бы вы рассмотреть вопрос о переносе ответа на ответ mnel? Тем не менее, я не знаю, как С.О. Этикет работает, когда лучший ответ приходит спустя долгое время, особенно когда этот новый ответ использует новые функции, которые изначально не были доступны.rbindlist это убедительное решение, которое во много раз быстрее, чемdo.call("bind",...)и этот вопрос и ответы все о скорости для больших данных.

Ответы на вопрос(4)

каждый из которых имеет одну строку. Если возможно преобразовать каждый из них в вектор, я думаю, это сильно ускорит процесс.

Однако, предполагая, что они должны быть data.frames, я создам функцию с кодом, заимствованным из ответа Доминика вМожно ли распараллелить 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

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

что время увеличивается экспоненциально с увеличением количества data.frames, свидетельствует о том, что нарушениеrbindразделение на два этапа может ускорить процесс.

Этот простой эксперимент, кажется, подтверждает, что это очень плодотворный путь:

## 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
 Eduardo Leoni16 мар. 2012 г., 03:54
rbind.fill дает мне почти то же время (разница не более 10%) от вашего решения X2 / X3.

rbind.data.frame делает много проверок вам не нужно. Это должно быть довольно быстрое преобразование, если вы делаете только то, что хотите.

# Use data from Josh O'Brien's post..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
 wahalulu16 мар. 2012 г., 16:01
Принятый ответ за лучшую производительность. Спасибо!
 wahalulu16 мар. 2012 г., 03:32
Спасибо, ребята, это выглядит великолепно. Я сделаю это завтра утром и сообщу вам результаты.
 Josh O'Brien16 мар. 2012 г., 03:06
+1 - это отличная идея. Мое предложение близкодовольно в его скорости, что мне интересно, сможет ли он побить этот подход, если будет более тщательно настроен. Но на самом деле, я бы пошел по этому пути.
 wahalulu16 мар. 2012 г., 15:33
Красивый! ~ 1 миль в секунду в течение примерно 600 секунд. Я могу жить с этой производительностью, и с этого момента она станет методом объединения df в списки, если они имеют одинаковую структуру, как это обычно и бывает.
 flodel16 мар. 2012 г., 03:21
+1 - если бы все столбцы имели одинаковый класс, я бы тоже учелas.data.frame(matrix(unlist(X), nrow = length(X), byrow=TRUE)).
Решение Вопроса

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

 mnel07 сент. 2012 г., 01:06
Возможно - моя машина 3.00 Gh Core 2 Duo под управлением Windows XP - рабочий компьютер. Я добавил бенчмаркинг, чтобы получить более достоверную оценку.
 wahalulu15 нояб. 2012 г., 20:21
Я обновляю принятый ответ на @mnel, используя rbindlist в пакете data.table. Это, безусловно, самый быстрый и эффективный ответ, но я хотел бы сказать, что все предыдущие ответы также очень креативны!
 wahalulu22 янв. 2013 г., 20:41
@JoshuaUlrich Эй, мне нужно проверить ваш обновленный ответ ... Я хотел бы поставить две галочки на вопрос;)
 Joshua Ulrich15 нояб. 2012 г., 20:51
Прощай, галочка. Я всегда буду помнить наше время вместе ...: '(
 Joshua Ulrich06 сент. 2012 г., 18:28
Как ты понялdo.call(rbind,Xdf) бежать в 60-х? На моем двухъядерном 3,33 ГГц процессоре требуется 90 секунд. Может быть, ваша оперативная память значительно быстрее моей?

Ваш ответ на вопрос