バナナでもわかる話

計量経済学・統計学が専門の大学院生です。統計学・経済学・投資理論・マーケティング等々に関する勉強・解説ブログ。ときどき趣味も。極力数式は使わずイメージで説明出来るよう心掛けていますが、時々暴走します。

Rのapplyとforの計算時間比較~forだって遅いわけじゃない~

Rユーザーの間でよく言われる話として

「forを使うのは初心者。こなれたRユーザーはapplyを活用する」

なんて話があります。

理由は「forを使うと計算が遅くなるから」「applyの方がコードが見やすいから」


ということで、基本的にはその通りなんですけど
ここでR初心者の人は次のような勘違いしてしまうんです。

「そうか~!applyの方が計算が速くてforだと遅いのか!」


なーんて誤解が発生します。


別にforだって、うまく使いこなせれば何ならapplyより早いし、forやapplyを使うより、別の関数を使った方が早いときもあるんですね。

というわけで、今回は計算時間の比較を通してそれを実感してみます。


Rの計算時間の測定方法

まず、計算時間についてですが、proc.time関数を使用します。

> t=proc.time()
> sqrt(5)
[1] 2.236068
> proc.time()-t
   ユーザ   システム       経過  
      0.03       0.00       0.03 

こんな感じで経過時間がわかります。


行列の列や行の平均を求める

まず、今回いじる100×10000の行列を適当に作っておきましょう。

X=matrix(rpois(100*10000,lambda=5),100,10000)


各列について平均を取るという処理をしてみます。この処理を行うド定番の関数としてcolMeans関数があるので、この関数とapplyで処理した場合と、for関数を使った場合について比較してみました。

>#apply関数のケース
> t=proc.time()
> d1=apply(X,MARGIN=2,FUN=mean)
> proc.time()-t
   ユーザ   システム       経過  
      0.11       0.00       0.11 
> 
>#colMeansのケース
> t=proc.time()
> d2=colMeans(X)
> proc.time()-t
   ユーザ   システム       経過  
      0.02       0.00       0.01 
> 
> 
> #forのケース
> t=proc.time()
> d3=vector()
> for(i in 1:10000){
+ d3[i]=mean(X[,i])
+ }
> proc.time()-t
   ユーザ   システム       経過  
      0.10       0.04       0.16 
> 

今回だと

colMeans<apply<for

ということで
colMeans関数の圧勝でした。まあそれ用の関数だから当たり前と言えば当たり前ですね。

>#一応値が一致しているか確認
> sum(d1==d3)
[1] 10000
> sum(d1==d2)
[1] 10000

次は各行について平均を取ってみます。
今度はrowMeans関数を使ってみます。

> t=proc.time()
> d1=apply(X,MARGIN=1,FUN=mean)
> proc.time()-t
   ユーザ   システム       経過  
      0.05       0.01       0.06 
> 
> t=proc.time()
> d2=rowMeans(X)
> proc.time()-t
   ユーザ   システム       経過  
      0.00       0.02       0.01 
> 
> 
> 
> t=proc.time()
> d3=vector()
> for(i in 1:100){
+ d3[i]=mean(X[i,])
+ }
> proc.time()-t
   ユーザ   システム       経過  
      0.07       0.02       0.08 
> sum(d1==d2)
[1] 100
> sum(d1==d3)
[1] 100


またもや
rowMeans<apply<for

という結果になりました。
やはり専用の関数がある場合はそれを使った方が早いですね。

3次元配列に対して平均を取る

3次元配列のデータXXを下のように用意してみました。

> XX=array(rpois(2*3*1000000,lambda=5),c(2,3,1000000))


先ほどの要領でやってみます。

> t=proc.time()
> dd1=apply(XX,MARGIN=3,FUN=mean)
> proc.time()-t
   ユーザ   システム       経過  
     10.50       0.06      10.63 
> 
> 
> t=proc.time()
> dd2=vector()
> for(i in 1:1000000){
+ dd2[i]=mean(XX[,,i])
+ }
> proc.time()-t
   ユーザ   システム       経過  
      7.26       0.04       7.33 
> 
> sum(dd1==dd2)
[1] 1000000


んん。
for<apply
になりましたね。



今度は次元を変えてみます。

> XX=array(rpois(100*100*100,lambda=5),c(100,100,100))
> t=proc.time()
> dd1=apply(XX,MARGIN=3,FUN=mean)
> proc.time()-t
   ユーザ   システム       経過  
      0.04       0.01       0.06 
> 
> 
> t=proc.time()
> dd2=vector()
> for(i in 1:100){
+ dd2[i]=mean(XX[,,i])
+ }
> proc.time()-t
   ユーザ   システム       経過  
      0.05       0.07       0.11 
> 
> sum(dd1==dd2)
[1] 100

この場合はapply<forのようです。


わかったこと

applyの中身が分からないので、あくまで推測ですが、今回の実験から
MERGIN部分の読み込み回数が多い場合はfor関数を使った方が良いのではないかという推測が立てられそうです。

あとまあこちらは当然ですが、既存の関数で何とかできるんならapplyよりもそちらを利用した方が良いということですね。