Можно ли распараллелить rbind в R?

Пока я сижу здесь, ожидая запуска некоторых R-скриптов... Мне было интересно... Есть ли способ распараллелить rbind в R?

Я сижу в ожидании этого звонка, чтобы часто завершать, поскольку я имею дело с большими объемами данных.

do.call("rbind", LIST)

5 ответов

Решение

Я сомневаюсь, что вы можете заставить это работать быстрее, распараллелив его: кроме того факта, что вам, вероятно, придется написать это самостоятельно (нить 1 сначала связывает элементы 1 и 2, а нить 2 rbinding пункты 3 и 4 и т. Д., И когда все готово, результаты "восстановлены", что-то в этом роде - я не вижу способа улучшить это без использования C), это будет связано с копированием больших объемов данных между потоками, что обычно является это идет медленно во-первых.

В C вы можете обмениваться объектами между потоками, чтобы все ваши потоки записывались в одну и ту же память. Я желаю вам удачи с этим:-)

Наконец, в качестве отступления: rbinding data.frames просто медленный. Если вы заранее знаете, что структура всех ваших data.frames точно такая же, и она не содержит столбцы с чистыми символами, вы, вероятно, можете использовать трюк из этого ответа на один из моих вопросов. Если ваш data.frame содержит символьные столбцы, я подозреваю, что лучше всего обрабатывать их отдельно (do.call(c, lapply(LIST, "[[", "myCharColName"))) и затем выполним трюк с остальными, после чего вы сможете их воссоединить.

Я до сих пор не нашел способа сделать это параллельно. Однако для моего набора данных (это список из примерно 1500 фреймов данных общей длиной в 4,5 млн. Строк), мне показался следующий фрагмент кода:

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 это список. Казалось бы, примерно в 4 раза быстрее, чем при использовании do.call(rbind, lst) или даже do.call(rbind.fill, lst) (с помощью rbind.fill из пакета plyr). На каждой итерации этот код вдвое сокращает количество фреймов данных.

Потому что вы сказали, что хотите rbind data.frame объекты, которые вы должны использовать data.table пакет. Имеет функцию под названием rbindlist что резко увеличить rbind, Я не уверен на 100%, но я бы поспорил на любое использование rbind вызовет копию, когда rbindlist не. Во всяком случае data.table это data.frame так что вы ничего не потеряете, чтобы попробовать.

РЕДАКТИРОВАТЬ:

library(data.table)
system.time(dt <- rbindlist(pieces))
utilisateur     système      écoulé 
       0.12        0.00        0.13 
tables()
     NAME  NROW MB COLS                        KEY
[1,] dt   1,000 8  X1,X2,X3,X4,X5,X6,X7,X8,...    
Total: 8MB

Молниеносно...

Вот решение, оно естественным образом распространяется на функции rbind.fill, merge и другие списки данных:

Но вроде со всеми моими ответами / вопросами проверяю:)

require(snowfall)
require(rbenchmark)

rbinder <- function(..., cores=NULL){
  if(is.null(cores)){
    do.call("rbind", ...)
  }else{
    sequ <- as.integer(seq(1, length(...), length.out=cores+1))
    listOLists <- paste(paste("list", seq(cores), sep=""), " = ...[",  c(1, sequ[2:cores]+1), ":", sequ[2:(cores+1)], "]", sep="", collapse=", ") 
    dfs <- eval(parse(text=paste("list(", listOLists, ")")))
    suppressMessages(sfInit(parallel=TRUE, cores))
    dfs <- sfLapply(dfs, function(x) do.call("rbind", x))
    suppressMessages(sfStop())
    do.call("rbind", dfs)   
  }
}

pieces <- lapply(seq(1000), function(.) data.frame(matrix(runif(1000), ncol=1000)))

benchmark(do.call("rbind", pieces), rbinder(pieces), rbinder(pieces, cores=4), replications = 10)

#test replications elapsed relative user.self sys.self user.child sys.child
#With intel i5 3570k    
#1     do.call("rbind", pieces)           10  116.70    6.505    115.79     0.10         NA        NA
#3 rbinder(pieces, cores = 4)           10   17.94    1.000      1.67     2.12         NA        NA
#2              rbinder(pieces)           10  116.03    6.468    115.50     0.05         NA        NA

Это расширяет ответ @Dominik.

Мы можем использовать mclapply из параллельного пакета для дальнейшего увеличения скорости. Также rbind.fill работает лучше, чем rbind, так что вот улучшенный код. ПРИМЕЧАНИЕ: это будет работать только на Mac / Linux. mclapply не поддерживается в Windows. РЕДАКТИРОВАТЬ: если вы хотите увидеть прогресс, раскомментируйте строку print (i) и убедитесь, что вы работаете с терминала, а не с RStudio. Печать в RStudio из параллельного процесса, вроде как портит RStudio.

library(parallel)
rbind.fill.parallel <- function(list){
  while(length(list) > 1) {
    idxlst <- seq(from=1, to=length(list), by=2)

    list <- mclapply(idxlst, function(i) {
      #print(i) #uncomment this if you want to see progress
      if(i==length(list)) { return(list[[i]]) }
      return(rbind.fill(list[[i]], list[[i+1]]))
    })
  }
}

Похоже, что на это уже хорошо ответили многие люди, но если это подходит кому-то, вот версия параллельного rbind для объектов не-data.table / data.frame-esque:

rbind.parallel <- function(list,ncore)
  {
  library(parallel)
  do.call.rbind<-function(x){do.call(rbind,x)}
  cl<-makeCluster(ncore)
  list.split<-split(list,rep(1:ncore,length(list)+1)[1:length(list)])
  list.join<-parLapply(cl,list.split,do.call.rbind)
  stopCluster(cl)
  list.out<-do.call(rbind,list.join)
  return(list.out)
  }

Это эффективно работает с объектами типа sf. Например, если вы читаете список шейп-файлов из каталога, используя lapply(.,st_read)Очевидно, что rbind.fill и его варианты не будут работать, чтобы объединить все функции.

Другие вопросы по тегам