6

私はコードを持っていて、合計経過時間は約30秒で、次のコードは約27秒です。問題のあるコードを次のように絞り込みました。

d$dis300[i] <- h

だから私はこの他の作品に変更し、今は本当に速く働いています(予想通り)。

私の質問は、なぜこれが秒に対して遅すぎるのかということです。datosDFは約7500x18変数です

最初:(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
}

2番目:(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への割り当てによって変更されていないにもかかわらず、列ベクトルの 16 進アドレスも毎回変更されていることがわかりますb

私の知る限り、自分でCコードにドロップすることなく、Rで(現在)data.frameメモリのコピーをまったく使用せずに a のアイテムを変更する唯一の方法は、両方のパッケージ内の:=演算子と関数です。Stack Overflow で here を使用した参照による割り当てに関する17 の質問があります。set()data.table:=

しかし、この場合、ループをまったく必要としないので、Tommy のワンライナーが間違いなく最適です。

于 2012-04-25T09:27:01.360 に答える