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

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

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

do.call("rbind", LIST)

Ответ 1

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

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

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

Ответ 2

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

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). На каждой итерации этот код сокращает вдвое количество кадров данных.

Ответ 3

Поскольку вы сказали, что хотите привязать объекты data.frame, вы должны использовать пакет data.table. Он имеет функцию, называемую rbindlist, которая резко увеличивает rbind. Я не уверен на 100%, но я бы поставил на то, что любое использование rbind приведет к копированию, когда rbindlist не будет. В любом случае a 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

Молния быстро...

Ответ 4

Здесь решение, естественно, распространяется на функции 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

Ответ 5

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

Мы можем использовать mclapply из параллельного пакета для дальнейшего увеличения скорости. Также rbind.fill делает лучшую работу, чем rbind, так что здесь улучшенный код. ПРИМЕЧАНИЕ. Это будет работать только с mac/linux. mclapply не поддерживается в Windows. EDIT: если вы хотите увидеть прогресс, раскомментируйте строку print (i) и убедитесь, что вы работаете с терминалом, а не с RStudio. Печать в RStudio из параллельного процесса, типа беспорядков RStudio up.

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]]))
    })
  }
}

Ответ 6

Похоже, что на это уже хорошо ответили многие люди, но если это подходит кому-то, вот версия параллельного 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 и его варианты не будут работать для объединения всех функций.