重點:
- Growing objects are generally slow in R.
- Always pre-allocate spaces if possible.
- Avoid using loops and complex objects(i.e. data.frame) if possible.
- Use microbenchmark package to measure the efficiency.
程式的執行效率會大大影響其執行速度,這邊講的不是演算法中的那些 Big(O) 概念,而是 R 語言本身的特色,這些特色也應能套用在大部份的程式語言。
(一) 範例一
請參考這個討論:
Append value to empty vector in R ?
此範例顯示一個 pre-allocated space 的 vector 的速度比沒有事先 allocated 的要快得多。
set.seed(21)
values <- sample(letters, 1e4, TRUE)
vector <- character(0)
# slow
system.time( for (i in 1:length(values)) vector[i] <- values[i] )
# user system elapsed
# 0.340 0.000 0.343
vector <- character(length(values))
# fast(er)
system.time( for (i in 1:length(values)) vector[i] <- values[i] )
# user system elapsed
# 0.024 0.000 0.023
(二) 範例二
請參考這個討論:
How to append rows to an R data frame
這個討論就更有趣了,回答者提出了 f1(), f2(), f3(), f4() 四個方式 來比較其效率
我擷取其中的 f1(), f3(), f4()
f1() 先建立好 data.frame,然後每次都用 rbind() 綁定新 row 到 data.frame。
f3() 先建立好 data.frame,pre-allocate 了大量空間,每次填入新資料進 data.frame 的剩餘空間。
f4() 先建立好 pre-alocated vector,每次填入 新資料進 vector 的剩餘空間,最後將 vector 建立成 data.frame。
f1():
# use rbind
f1 <- function(n){
df <- data.frame(x = numeric(), y = character())
for(i in 1:n){
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
df
}
f3():
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(1000), y = character(1000), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
f4():
# Use preallocated vectors
f4 <- function(n) {
x <- numeric(n)
y <- character(n)
for (i in 1:n) {
x[i] <- i
y[i] <- i
}
data.frame(x, y, stringsAsFactors=FALSE)
}
最後的結果:
library(microbenchmark)
microbenchmark(f1(1000), f3(1000), f4(1000), times = 5)
# Unit: milliseconds
# expr min lq median uq max neval
# f1(1000) 1024.539618 1029.693877 1045.972666 1055.25931 1112.769176 5
# f3(1000) 149.417636 150.529011 150.827393 151.02230 160.637845 5
# f4(1000) 7.872647 7.892395 7.901151 7.95077 8.049581 5
f3() 用 pre-allocate 的空間擺脫 f1() rbind() 的效能問題;
f4() 又用 vector 做事先處理,擺脫 data.frame 的效能問題。