有效构建大型(200 MM线)数据帧

时间:2014-06-28 16:54:58

标签: r

我正在尝试在R中构建一个大的(~200 MM行)数据帧。数据帧中的每个条目将包含大约10个数字(例如1234.12345)。代码设计用于遍历列表,从[i]之后的每个项目中减去位置[i]中的项目,而不是[i]之前的项目(如果我将输出放入矩阵中,它将是三角矩阵) )。代码很简单,在较小的列表上工作正常,但我想知道是否有更快或更有效的方法来做到这一点?我假设答案的第一部分将要求使用嵌套的for循环,"但我不确定替代品是什么。

这个想法是,这将是一个"边缘列表"用于社交网络分析图。一旦我有' outlist'我将根据一些标准(<,>,==)减少边数,因此最终列表(和图表)不会那么笨重。

#Fake data of same approximate dimensions as real data
dlist<-sample(1:20,20, replace=FALSE) 
#purge the output list before running the loop
rm(outlist)
outlist<-data.frame()

for(i in 1:(length(dlist)-1)){
         for(j in (i+1):length(dlist)){

             outlist<-rbind(outlist, c(dlist[i],dlist[j], dlist[j]-dlist[i]))

     }
    }

4 个答案:

答案 0 :(得分:7)

IIUC你的最终数据集将是2亿行3列,全部类型为numeric,总占用空间为:

200e6 (rows) * 3 (cols) * 8 (bytes) / (1024 ^ 3)
# ~ 4.5GB

这是一个非常大的数据,必须尽可能避免复制。

这是一个方法,它使用data.table包的未导出(内部)vecseq函数(用C编写并且快速+内存有效)并使用它的引用运算符赋值{{ 1}},以避免复制。

:=

基准:

我会根据您数据维度的其他答案对功能进行基准测试。请注意,我的基准测试是在R v3.0.2上,但fn1 <- function(x) { require(data.table) ## 1.9.2 lx = length(x) vx = as.integer(lx * (lx-1)/2) # R v3.1.0 doesn't copy on doing list(.) - so should be even more faster there ans = setDT(list(v1 = rep.int(head(x,-1L), (lx-1L):1L), v2=x[data.table:::vecseq(2:lx, (lx-1L):1, vx)])) ans[, v3 := v2-v1] } 应该在R v3.1.0上提供更好的性能(包括速度和内存),因为fn1()不再导致复制。

list(.)

请注意,由于使用fn2 <- function(x) { diffmat <- outer(x, x, "-") ss <- which(upper.tri(diffmat), arr.ind = TRUE) data.frame(v1 = x[ss[,1]], v2 = x[ss[,2]], v3 = diffmat[ss]) } fn3 <- function(x) { idx <- combn(seq_along(x), 2) out2 <- data.frame(v1=x[idx[1, ]], v2=x[idx[2, ]]) out2$v3 <- out2$v2-out2$v1 out2 } set.seed(45L) x = runif(20e3L) system.time(ans1 <- fn1(x)) ## 18 seconds + ~8GB (peak) memory usage system.time(ans2 <- fn2(x)) ## 158 seconds + ~19GB (peak) memory usage system.time(ans3 <- fn3(x)) ## 809 seconds + ~12GB (peak) memory usage 导致fn2()需要相当多的内存(峰值内存使用率> = 19GB)并且慢于outerfn1()非常慢(由于fn3()和不必要的副本)。

答案 1 :(得分:2)

创建该数据的另一种方法是

#Sample Data
N <- 20
set.seed(15) #for reproducibility
dlist <- sample(1:N,N, replace=FALSE) 

我们可以做到

idx <- combn(1:N,2)
out2 <- data.frame(i=dlist[idx[1, ]], j=dlist[idx[2, ]])
out2$dist <- out2$j-out2$i

这使用combn在data.set中创建所有索引的paris而不是循环。这允许我们一次构建data.frame,而不是一次添加一行。

我们将其与

进行比较
out1 <- data.frame()
for(i in 1:(length(dlist)-1)){
    for(j in (i+1):length(dlist)){
        out1<-rbind(out1, c(dlist[i],dlist[j], dlist[j]-dlist[i]))
    }
}

我们看到了

all(out1==out2)
# [1] TRUE

另外,如果我们与microbenchmark进行比较,我们会看到

microbenchmark(loops(), combdata())
# Unit: microseconds
#        expr       min        lq     median         uq       max neval
#     loops() 30888.403 32230.107 33764.7170 34821.2850 82891.166   100
#  combdata()   684.316   800.384   873.5015   940.9215  4285.627   100

不使用循环的方法要快得多。

答案 2 :(得分:1)

您始终可以从三角矩阵开始,然后直接从中创建数据框:

vec <- 1:10

diffmat <- outer(vec,vec,"-")
ss <- which(upper.tri(diffmat),arr.ind = TRUE)

data.frame(one = vec[ss[,1]], 
           two = vec[ss[,2]], 
           diff = diffmat[ss])

答案 3 :(得分:0)

您需要预先分配列表,这将显着提高代码的速度。通过预分配,我的意思是创建一个已经具有所需大小的输出结构,但填充了例如NA&#39; s。