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

Я знаю, что в SO много вопросов о способах преобразования списка data.frames в один data.frame с использованием do.call или ldply, но этот вопрос касается понимания внутренней работы обоих методов и попыткивыяснить, почему я не могу заставить ни одного из них объединить список из почти 1 миллиона df с одинаковой структурой, одинаковыми именами полей и т. д. в один data.frame.Каждый data.frame состоит из одной строки и 21 столбца.

Данные начинались как файл JSON, который я преобразовал в списки с использованием fromJSON, затем запустил еще один пакет для извлечения части списка и преобразовал в данные.фрейм и закончился списком 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
> 

Ответы [ 4 ]

17 голосов
/ 06 сентября 2012

Учитывая, что вы ищете производительность, похоже, стоит предложить решение 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

17 голосов
/ 16 марта 2012

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

# 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

Вдохновленный ответом 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
8 голосов
/ 16 марта 2012

Ваше наблюдение о том, что время увеличивается экспоненциально с количеством 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
5 голосов
/ 16 марта 2012

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

Однако, предполагая, что они должны быть 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

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

...