forとapplyの速度比較―data frame,matrix編

気の利いたタイトルが浮かばない : for と apply どっちが速い?の真似をして,forとapplyの速度比較をやってみた。apply族の関数は僕は主にdata frameに対して使うので,そのあたりを中心に。テストに使用したスクリプトは以下。

v1 <- 1:10
col_len <- c(1:9*1000, 1:10*10000) 
names(col_len) <- col_len
dlist  <- lapply(col_len, function(x)matrix(rep(v1, x), ncol=x))
dlist2 <- lapply(dlist, as.data.frame)

fun_lapply <- function(x)system.time(lapply(x, sd))[[3]]
fun_sapply <- function(x)system.time(sapply(x, sd))[[3]]
fun_for    <- function(x)system.time(for(i in 1:ncol(x))sd(x[,i]))[[3]]
fun_apply  <- function(x)system.time(apply(x, 2, sd))[[3]]

n1 <- 1:3
result <- list()
result$lapply <- sapply(n1, function(...)sapply(dlist2, fun_lapply))
result$sapply <- sapply(n1, function(...)sapply(dlist2, fun_sapply))
result$apply1 <- sapply(n1, function(...)sapply(dlist2, fun_apply ))
result$apply2 <- sapply(n1, function(...)sapply(dlist , fun_apply ))
result$for1   <- sapply(n1, function(...)sapply(dlist2, fun_for   ))
result$for2   <- sapply(n1, function(...)sapply(dlist , fun_for   ))

(d1 <- as.data.frame(sapply(result, apply, 1, mean)))

少しだけ解説を。col_lenの各要素の数値を元に,v1を数値分だけ横に並べたmatrixおよびdata frameを4,5行目で作っている。dlist,dlist2の中には,length(col_len)個のmatrixおよびdata frameが格納されている。7-10行目はテストの本体。12-19行目でテストを3回繰り返して,21行目で平均を取っている。テスト自体でapply族を活用しまくっているので,applyに慣れてないと暗号かもしれません。

結果は下の表。

lapply sapply apply1 apply2 for1 for2
1000 0.08 0.09 0.13 0.09 0.15 0.08
2000 0.22 0.20 0.27 0.20 0.34 0.19
3000 0.25 0.31 0.41 0.28 0.53 0.26
4000 0.33 0.40 0.59 0.37 0.68 0.34
5000 0.39 0.47 0.69 0.47 0.96 0.41
6000 0.46 0.56 0.79 0.56 1.14 0.51
7000 0.67 0.59 1.03 0.66 1.35 0.57
8000 0.62 0.74 1.18 0.77 1.64 0.68
9000 0.73 0.72 1.24 0.80 1.89 0.73
10000 0.84 0.89 1.33 0.95 2.15 0.89
20000 1.70 1.75 2.82 2.03 5.69 1.67
30000 2.63 2.53 4.08 2.80 11.12 2.55
40000 3.42 3.45 5.53 3.90 17.72 3.28
50000 4.19 4.24 6.85 4.73 22.54 4.16
60000 5.01 5.33 8.40 5.81 31.22 4.94
70000 5.85 6.14 9.57 6.92 41.37 5.79
80000 6.48 7.09 10.90 7.70 51.39 6.79
90000 7.39 8.31 12.36 8.66 61.94 7.57
10000 8.10 9.18 13.79 9.55 74.12 8.29

まず,apply族の中ではlapply<sapply<applyの順に処理時間が長くなる。これはsapplyは内部的にlapplyを利用していて,さらにapplyは内部的にsapplyを利用しているので,当たり前といえば当たり前。

次に,apply1とapply2,for1とfor2を比較すると,apply2とfor2が速い。apply1,for1はdata frameを処理していて,apply2,for2はmatrixを処理している。よく知られたことではあるが,data frameは処理に時間がかかる。それを考えるとlapply,sapplyは非常にがんばっている。

以上を踏まえてapplyとforを比較すると,lapplyとmatrixを使ったfor(for2)はほぼ同等。data frameをforで触ろうとすると非常に遅いので要注意。

結論としては,data frameをいじるときはlapply,sapplyでほぼ決定,matrixのときは好みでforも使用可,といったところだろうか。基本的にはlapply,sapplyを使っていれば損はしないようだ。

apply族のイメージは,集合に対して処理を行う感じ。対してforのイメージは,要素に対して処理を行う感じ。一言で言えば,添え字を意識する必要があるかどうか。細かいことのように思えるが,applyに慣れると意外とこの差は大きく感じる。例えばapplyは集合に対する処理なので,返値も当然集合で返ってくる。forはあくまで要素を処理するだけなので,返値も要素。別の処理で集合にまとめ直す必要がある。やはりRの哲学的にもapply族が一押し。

余談。一時環境(env<-new.env())に変数を放り込んでeapplyというのはどうだろう,と思ってやってみたが,まず変数を放り込むのに非常に時間がかかるうえに,eapplyも特に速くはなかった。

余談2。applyは思考の流れには乗りやすいけど,可読性は高くない気がする。つまり,書いてるときには適当に書いたら思った通りに処理して結果を返してくれるけど,時間をおいて読み返すと何をやってるのかよくわからない。forだとあまりそういうことはない。慣れの問題なんだろうか。

最後に。誰かggplot2できれいにプロットしてください。そこまでする余力がありませんでした…。