6

我有一段代码,总运行时间约为 30 秒,以下代码约为 27 秒。我将有问题的代码缩小到:

d$dis300[i] <- h

所以我改用另一部分,现在工作得非常快(正如预期的那样)。

我的问题是为什么这对第二个来说太慢了。datos DF 约为 7500x18 vars

第一个:(经过 27 秒)

d$dis300 <- 0
for (i in 1:netot) {
  h <- aaa[d$ent[i], d$dis[i]]
  if (h == 0) writeLines(sprintf("ERROR. ent:%i dis:%i", d$ent[i], d$dis[i]))
  d$dis300[i] <- h
}

秒:(经过 0.2 秒)

d$dis300 <- 0
for (i in 1:netot) {
  h <- aaa[d$ent[i], d$dis[i]]
  if (h == 0) writeLines(sprintf("ERROR. ent:%i dis:%i", d$ent[i], d$dis[i]))
  foo[i] <- h
}
d$foo <- foo

您可以看到两者都是“相同的”,但有问题的有这个 DF 而不是单个向量。

任何评论都非常感谢。我来自另一种语言,这让我发疯了一段时间。至少我有解决方案,但我希望将来防止此类问题。

谢谢你的时间,

4

2 回答 2

10

原因是d$dis300[i] <- h调用$<-.data.frame.

如您所见,这是一个相当复杂的功能:

`$<-.data.frame`

你不说是什么foo,但如果是原子向量,$<-为了速度,函数是用C实现的。

不过,我希望你声明 foo 如下:

foo <- numeric(netot)

这将确保您不需要为循环中的每个分配重新分配向量:

foo <- 0 # BAD!
system.time( for(i in 1:5e4) foo[i] <- 0 ) # 4.40 secs
foo <- numeric(5e4) # Pre-allocate
system.time( for(i in 1:5e4) foo[i] <- 0 ) # 0.09 secs

使用*apply家庭而不用担心:

d$foo <- vapply(1:netot, function(i, aaa, ent, dis) {
  h <- aaa[ent[i], dis[i]]
  if (h == 0) writeLines(sprintf("ERROR. ent:%i dis:%i", ent[i], dis[i]))
  h
}, numeric(1), aaa=aaa, ent=d$ent, dis=d$dis)

...在这里,我还提取d$entd$dis循环之外的内容,这也应该有所改善。由于您没有提供可重复的数据,因此无法自己运行它。但这里有一个类似的例子:

d <- data.frame(x=1)
system.time( vapply(1:1e6, function(i) d$x, numeric(1)) )         # 3.20 secs
system.time( vapply(1:1e6, function(i, x) x, numeric(1), x=d$x) ) # 0.56 secs

...但最后它似乎都可以简化为(除非您的错误检测代码):

d$foo <- aaa[cbind(d$ent, d$dis)]
于 2012-04-25T00:41:29.353 回答
2

汤米是最好的答案。这太大了,无法发表评论,因此将其添加为答案...

这就是您自己查看副本的方式(DF正如 joran 评论的那样,整个副本):

> DF = data.frame(a=1:3,b=4:6)
> tracemem(DF)
[1] "<0x0000000003104800"
> for (i in 1:3) {DF$b[i] <- i; .Internal(inspect(DF))}
tracemem[0000000003104800 -> 000000000396EAD8]: 
tracemem[000000000396EAD8 -> 000000000396E4F0]: $<-.data.frame $<- 
tracemem[000000000396E4F0 -> 000000000399CDC8]: $<-.data.frame $<- 
@000000000399CDC8 19 VECSXP g0c2 [OBJ,NAM(2),TR,ATT] (len=2, tl=0)
  @000000000399CD90 13 INTSXP g0c2 [] (len=3, tl=0) 1,2,3
  @000000000399CCE8 13 INTSXP g0c2 [] (len=3, tl=0) 1,5,6
ATTRIB: # .. snip ..

tracemem[000000000399CDC8 -> 000000000399CC40]: 
tracemem[000000000399CC40 -> 000000000399CAB8]: $<-.data.frame $<- 
tracemem[000000000399CAB8 -> 000000000399C9A0]: $<-.data.frame $<- 
@000000000399C9A0 19 VECSXP g0c2 [OBJ,NAM(2),TR,ATT] (len=2, tl=0)
  @000000000399C968 13 INTSXP g0c2 [] (len=3, tl=0) 1,2,3
  @000000000399C888 13 INTSXP g0c2 [] (len=3, tl=0) 1,2,6
ATTRIB: # .. snip ..

tracemem[000000000399C9A0 -> 000000000399C7E0]: 
tracemem[000000000399C7E0 -> 000000000399C700]: $<-.data.frame $<- 
tracemem[000000000399C700 -> 00000000039C78D8]: $<-.data.frame $<- 
@00000000039C78D8 19 VECSXP g0c2 [OBJ,NAM(2),TR,ATT] (len=2, tl=0)
  @00000000039C78A0 13 INTSXP g0c2 [] (len=3, tl=0) 1,2,3
  @0000000003E07890 13 INTSXP g0c2 [] (len=3, tl=0) 1,2,3
ATTRIB: # .. snip ..
> DF
  a b
1 1 1
2 2 2
3 3 3

这些行中的每一tracemem[]行都对应于整个对象的副本。您也可以看到a列向量的十六进制地址每次都在变化,尽管它没有被分配修改b

AFAIK,在不自己放入 C 代码的情况下,在 R 中(当前)修改 a 项而data.frame根本没有任何内存副本的唯一方法是:=操作符和set()函数,两者都在 package 中data.table。在 Stack Overflow 上有17 个关于通过引用分配的问题。:=

但在这种情况下,Tommy 的一个班轮绝对是最好的,因为您甚至根本不需要循环。

于 2012-04-25T09:27:01.360 回答