我正在尝试在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]))
}
}
答案 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)并且慢于outer
。 fn1()
非常慢(由于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。