Ari*_*man 43
第一种方法
我尝试访问预先分配的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)
}
Run Code Online (Sandbox Code Playgroud)
但tracemem变得疯狂(例如每次都将data.frame复制到一个新地址).
替代方法(也不起作用)
一种方法(不确定它更快,因为我尚未进行基准测试)是创建一个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)
Run Code Online (Sandbox Code Playgroud)
不幸的是,在创建列表时,我认为你很难预先分配.例如:
> tracemem(res)
[1] "<0x79b98b0>"
> res[[2]] <- data.frame()
tracemem[0x79b98b0 -> 0x71da500]:
Run Code Online (Sandbox Code Playgroud)
换句话说,替换列表的元素会导致列表被复制.我假设整个列表,但它可能只是列表中的那个元素.我对R的内存管理细节并不十分熟悉.
可能是最好的方法
与目前许多速度或内存限制的流程一样,最好的方法可能是使用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
Run Code Online (Sandbox Code Playgroud)
但是正如@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)
Run Code Online (Sandbox Code Playgroud)
(结果如下所示)
标杆
循环运行10,000次,数据表几乎快了整整一个数量级:
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
Run Code Online (Sandbox Code Playgroud)

并:=与之比较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
Run Code Online (Sandbox Code Playgroud)
注意,n这里是10 ^ 6而不是10 ^ 5,如上面绘制的基准.因此,工作量会增加一个数量级,结果以毫秒而非秒为单位进行测量.确实令人印象深刻
好吧,我很惊讶没人提到转换到矩阵了......
与Ari B. Friedman定义的dt.colon和dt.set函数相比,转换为矩阵的运行时间最短(比dt.colon略快).矩阵内的所有影响都是通过引用完成的,因此在此代码中不会执行不必要的内存复制.
码:
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)
Run Code Online (Sandbox Code Playgroud)
结果:
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
Run Code Online (Sandbox Code Playgroud)
使用矩阵的优点:
使用矩阵的Con:
我喜欢RSQLite这件事:dbWriteTable(...,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
Run Code Online (Sandbox Code Playgroud)
但如果data.frames有多行,它看起来可能会更好.而且您不需要事先知道行数.