Как ускорить rbind?

Я должен загрузить таблицу с сервера MS-SQL.

Число строк больше 6 миллионов. Сервер не может сразу вернуть все данные.

Итак, я написал код, который загружает 10 000 строк за раз. и он связывает строки в цикле.

Предположим, что функция getData() возвращает фрейм данных, содержащий 10000 строк за раз. (Псевдокод)

for(i in 1:600)
{
    tempValue <- getData()
    wannagetValue <- rbind(wannagetValue,tempValue)
    print(i)
}

Проблема в том, что с течением времени она становится медленнее.

Я думаю, что использование rbind подобно этому - не очень хорошая идея.

Любые советы будут очень полезными. Заранее благодарю вас.

Ответ 1

Вот несколько вариантов, которые, я уверен, могут быть лучше:

library(data.table)
library(microbenchmark)

#function to generate your data
getData <- function(){
  data.frame(x=rnorm(10000),y=rnorm(10000),z=rnorm(10000))
}

#using data table rbindlist each iteration
fDT1 <- function(n){
  dat <- getData()
  for(i in 1:n){
    dat <- rbindlist(list(dat,getData()))
  }
  return(data.frame(dat))
}

#using data table rbindlist all at once
fDT2 <- function(n){
  return(data.frame(rbindlist(lapply(1:n,function(x) getData()))))
}

#pre-allocating a data frame
fPre <- function(n){
  dat <- data.frame(x=rep(0,n*10000),y=rep(0,n*10000),z=rep(0,n*10000))
  j <- 1
  for(i in 1:n){
    dat[j:(j+10000-1),] <- getData()
    j <- j + 10000
  }
  return(dat)
}

#standard do.call rbind
f2 <- function(n){
  return(do.call(rbind,lapply(1:n,function(x) getData())))
}

#current approach
f <- function(n){
  dat <- getData()
  for(i in 1:n){
    dat <- rbind(dat,getData())
  }
  return(dat)
}

Как вы можете видеть, использование data.table rbindlist() является большим улучшением по сравнению с базой R rbind(), и есть большая выгода при добавлении строк одновременно, а не в interations, однако это может быть невозможно, если есть памяти. Вы также можете заметить, что улучшения скорости нигде не близки к линейным по мере увеличения размера данных.

 > microbenchmark(fDT2(5),fDT1(5),fPre(5),f2(5),f(5),
+                fDT2(25),fDT1(25),fPre(25),f2(25),f(25),
+                fDT2(75),fDT1(75),fPre(75),f2(75),f(75),
+                times=10)
Unit: milliseconds
     expr        min         lq     median         uq         max neval
  fDT2(5)   18.31207   18.63969   24.09943   25.45590    72.01725    10
  fDT1(5)   27.65459   29.25147   36.34158   77.79446    88.82556    10
  fPre(5)   34.96257   39.39723   41.24445   43.30319    68.75897    10
    f2(5)   30.85883   33.00292   36.29100   43.53619    93.15869    10
     f(5)   87.40869   97.97500  134.50600  138.65354   147.67676    10
 fDT2(25)   89.42274   99.39819  103.90944  146.44160   156.01653    10
 fDT1(25)  224.65745  229.78129  261.52388  280.85499   300.93488    10
 fPre(25)  371.12569  412.79876  431.80571  485.37727  1046.96923    10
   f2(25)  221.03669  252.08998  265.17357  271.82414   281.47096    10
    f(25) 1446.32145 1481.01998 1491.59203 1634.99936  1849.00590    10
 fDT2(75)  326.66743  334.15669  367.83848  467.85480   520.27142    10
 fDT1(75) 1749.83842 1882.27091 2066.95241 2278.55589  2419.07205    10
 fPre(75) 3701.16220 3968.64643 4162.70585 4234.39716  4356.09462    10
   f2(75) 1174.47546 1183.98860 1314.64585 1421.09483  1537.42903    10
    f(75) 9139.36935 9349.24412 9510.90888 9977.24621 10861.51206    10

Ответ 2

Как было указано выше, R сохраняет все свои объекты в ОЗУ по умолчанию, поэтому с таким количеством данных вы столкнетесь с некоторыми проблемами.

Две вещи, которые я хотел бы добавить: 1) Как правило, если вы не хотите использовать data.table, вы можете использовать функцию rbind.fill в пакете Hadley plyr, что тоже довольно быстро. Никогда не используйте rbind так, как вы делали выше, в цикле "for", добавляя каждую строку отдельно. Он заставляет R делать копию объекта фрейма данных каждый раз, когда вы добавляете одну строку, и это медленно.

2) Чтобы работать с данными большего размера, чем с RAM, просмотрите раздел Большая память и данные из памяти на http://cran.r-project.org/web/views/HighPerformanceComputing.html, возможно, пакет bigmemory - это то, что вам нужно.

Ответ 3

Возможно, вы могли бы сделать SELECT COUNT(*) FROM table_name WHERE ..., а затем предварительно выделить место для вашего фрейма данных.

На самом деле, я не думаю, что запросить вашу базу данных на 10 тыс. строк - хорошая идея. Попытайтесь избежать этого, экспортировав данные на локальный диск и прочитав оттуда. Это также улучшит скорость. Хранение дешево, пропускная способность сети и память отсутствуют.