以高效的内存方式生成dataframe

根据逐行创buildR数据 data.frame使用rbind附加到data.frame并不理想,因为它每次都会创build整个data.frame的一个副本。 我如何在R累加数据而不会产生这种惩罚? 中间格式不需要是data.frame

第一种方法

我试图访问预分配data.frame的每个元素:

 res <- data.frame(x=rep(NA,1000), y=rep(NA,1000)) tracemem(res) for(i in 1:1000) { res[i,"x"] <- runif(1) res[i,"y"] <- rnorm(1) } 

但是tracemem变得疯狂(例如,data.frame每次都被复制到一个新的地址)。

替代方法(也不工作)

一种方法(不确定它是否更快,因为我还没有进行基准testing)是创build一个data.frames列表,然后将它们stack在一起:

 makeRow <- function() data.frame(x=runif(1),y=rnorm(1)) res <- replicate(1000, makeRow(), simplify=FALSE ) # returns a list of data.frames library(taRifx) res.df <- stack(res) 

不幸的是在创build列表中,我认为你将很难预先分配。 例如:

 > tracemem(res) [1] "<0x79b98b0>" > res[[2]] <- data.frame() tracemem[0x79b98b0 -> 0x71da500]: 

换句话说,replace列表的元素将导致列表被复制。 我假设整个列表,但它可能只是列表中的元素。 我对R的内存pipe理细节并不熟悉。

可能是最好的方法

就像现在许多速度或内存受限的进程一样,最好的方法可能是使用data.table而不是data.frame 。 由于data.table具有:=由参考运算符分配,因此可以在不重新复制的情况下进行更新:

 library(data.table) dt <- data.table(x=rep(0,1000), y=rep(0,1000)) tracemem(dt) for(i in 1:1000) { dt[i,x := runif(1)] dt[i,y := rnorm(1)] } # note no message from tracemem 

但正如@MatthewDowle指出的那样, set()是在循环内完成这个操作的恰当方法。 这样做使得它仍然更快:

 library(data.table) n <- 10^6 dt <- data.table(x=rep(0,n), y=rep(0,n)) dt.colon <- function(dt) { for(i in 1:n) { dt[i,x := runif(1)] dt[i,y := rnorm(1)] } } dt.set <- function(dt) { for(i in 1:n) { set(dt,i,1L, runif(1) ) set(dt,i,2L, rnorm(1) ) } } library(microbenchmark) m <- microbenchmark(dt.colon(dt), dt.set(dt),times=2) 

(结果如下)

标杆

循环运行10000次后,数据表几乎快了整整一个数量级:

 Unit: seconds expr min lq median uq max 1 test.df() 523.49057 523.49057 524.52408 525.55759 525.55759 2 test.dt() 62.06398 62.06398 62.98622 63.90845 63.90845 3 test.stack() 1196.30135 1196.30135 1258.79879 1321.29622 1321.29622 

基准

和比较:= set()

 > m Unit: milliseconds expr min lq median uq max 1 dt.colon(dt) 654.54996 654.54996 656.43429 658.3186 658.3186 2 dt.set(dt) 13.29612 13.29612 15.02891 16.7617 16.7617 

请注意,这里的n是10 ^ 6而不是10 ^ 5,如上面绘制的基准。 所以有更多的工作量级,其结果是毫秒而不是秒。 确实令人印象深刻

你也可以有一个空的列表对象,其中元素用数据框填充; 然后用sapply或类似的方法收集结果。 一个例子可以在这里find。 这不会招致成长对象的惩罚。

那么,我很惊讶,没有人提到转换为matrix呢…

与Ari B. Friedman定义的dt.colondt.set函数相比,matrix的转换具有最好的运行时间(稍快于dt.colon )。 matrix内的所有情绪都是通过引用完成的,所以在这段代码中没有执行不必要的内存拷贝。

码:

 library(data.table) n <- 10^4 dt <- data.table(x=rep(0,n), y=rep(0,n)) use.matrix <- function(dt) { mat = as.matrix(dt) # converting to matrix for(i in 1:n) { mat[i,1] = runif(1) mat[i,2] = rnorm(1) } return(as.data.frame(mat)) # converting back to a data.frame } dt.colon <- function(dt) { # same as Ari's function for(i in 1:n) { dt[i,x := runif(1)] dt[i,y := rnorm(1)] } } dt.set <- function(dt) { # same as Ari's function for(i in 1:n) { set(dt,i,1L, runif(1) ) set(dt,i,2L, rnorm(1) ) } } library(microbenchmark) microbenchmark(dt.colon(dt), dt.set(dt), use.matrix(dt),times=10) 

结果:

 Unit: milliseconds expr min lq median uq max neval dt.colon(dt) 7107.68494 7193.54792 7262.76720 7277.24841 7472.41726 10 dt.set(dt) 93.25954 94.10291 95.07181 97.09725 99.18583 10 use.matrix(dt) 48.15595 51.71100 52.39375 54.59252 55.04192 10 

使用matrix的优点:

  • 这是迄今为止最快的方法
  • 你不必学习/使用data.table对象

使用matrix的Con:

  • 你只能处理matrix中的一种数据types(特别是,如果你的data.frame的列中有混合types的话,那么它们将全部被转换为字符mat = as.matrix(dt)#conversion到matrix

我喜欢RSQLitedbWriteTable(...,append=TRUE)语句,结束时使用dbReadTable语句。

如果数据足够小,可以使用“:memory:”文件,如果大的话,就是硬盘。

当然,它无法在速度方面进行竞争:

 makeRow <- function() data.frame(x=runif(1),y=rnorm(1)) library(RSQLite) con <- dbConnect(RSQLite::SQLite(), ":memory:") collect1 <- function(n) { for (i in 1:n) dbWriteTable(con, "test", makeRow(), append=TRUE) dbReadTable(con, "test", row.names=NULL) } collect2 <- function(n) { res <- data.frame(x=rep(NA, n), y=rep(NA, n)) for(i in 1:n) res[i,] <- makeRow()[1,] res } > system.time(collect1(1000)) User System verstrichen 7.01 0.00 7.05 > system.time(collect2(1000)) User System verstrichen 0.80 0.01 0.81 

但是,如果data.frame行,它可能会更好看。 而且你不需要事先知道行数。