December 16, 2014

R 執行效率

重點:

  • 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 的效能問題。