R語言在哪些情況下for循環可以避免?

比如說

r&<-1

for(i in 1:n){

dr=rnorm(1,0,1)*r[i]+rnorm(1,0,0.01)

r[i+1]=r[i]+dr;

}

這種情況是否有辦法避免for?


類似的問題太多啦,知乎有,其他網站也有,很多答案都沒涉及到問題的本質,我來展開說一下吧。

用過一段時間R的朋友估計對R語言的for loop循環和apply函數孰優孰劣問題都不會陌生,網路上可以找到很多討論,知乎上類似的問題也不少,可以看到大多數的意見是不要用for loop循環,能不用就不用,盡量用apply,或者向量化。

但是for loop循環真的那麼不堪么?for loop和apply的爭論,本質上涉及到兩個問題:

  • 代碼可讀性,從這一點來說,apply勝,因為for loop的代碼機械化的讓你看到每一步是怎樣做的,然而並不能給你整段程序在做什麼的直感,apply則是在描述做什麼,而不用去管怎麼做。
  • 執行效率,我想這個問題才是大家真正關心的,下面就展開說說。

這裡說來話有點長,首先要搞清楚小的基本問題,最後我們再綜合起來討論。

1)apply系列函數是什麼?R裡面有好多apply函數,比如apply,lapply,tapply至於他們到用法,我已經在別的文章里討論過所以這裡就不討論來,如果你還沒有看到,可以關注微信公眾號:機器會學習(id:jiqihuixuexi)。

為什麼問apply系列函數是什麼呢?難道他們不顯然是函數么?他們當然是函數,但是和別的函數又有區別 。對於通常的函數,比如sum函數:

&> sum(c(1,2,3))
[1] 6

輸入是數值,輸出也是一個數值。再看lapply函數:

&> lapply(c(1,-2,3),abs)
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

輸入參數既有數值,也有abs這個函數。在R語言裡面,這種函數是高階函數,也就是函數的函數(這可以看出R語言是支持函數式編程的),英文叫functional,他們可以把函數作為輸入參數,輸出一個vector,和functional對應的另一種函數叫做closure,這裡就不展開說了。R裡面很多functional都是基於lapply實現,那lapply呢?lapply是在c裡面用for loop實現的。所以其實是殊途同歸,最後都是for loop循環。

雖然lapply是c實現的for loop,應該比R裡面自己寫循環快,但是這並不是很多情況下用R寫for loop比調用apply系列函數慢的本質原因,因為loop本身並不會有太多時間消耗。那麼為什麼很多人會覺得R裡面的for loop循環要慢呢?

真正的原因是很多時候我們是在對data.frame進行操作,導致了R對被操作的data.frame類型對象進行copy,這極大的降低了效率。下面舉例說明:

x=data.frame(array(rnorm(10000),c(100,100)))
y=as.list(x)
z=as.list(x)

fun.a=function(){for (i in 1:10){y[[i]]=y[[i]]-1}}
fun.b=function(){for (i in 1:10){x[,i]=x[,i]-1}}

myfun=function(x){x-1}
fun.d=function(){lapply(z,myfun)}

library(microbenchmark)

microbenchmark(fun.a,fun.b,fun.d)

在上面的代碼里,首先我們生成一個data.frame類型對象,叫做x,x有10列。y是一個list,內容很x一樣,z也是一個list,內容和x一樣,我們想做的操作是把x每一列減去1。為了實現這個操作,我們寫了三個函數,分布叫做fun.a, fun.b 和fun.d。三個函數分別用了不同的方式實現:

  • fun.a是通過一個for loop 循環直接對list類型的對象y進行操作
  • fun.b也有一個for loop循環,但是這裡我們是對data.frame類型的對象x進行操作
  • fun.d就不用for loop循環而用上面提到的functional,函數的函數,也就是lappy進行操作

通過micobenchmark,我們對三個函數進行了速度的比較,那麼結果如何呢?fun.d會比前兩個好么?

Unit: nanoseconds
expr min lq mean median uq max neval
fun.a 41 45 52.98 49 51 509 100
fun.b 38 41 141.44 45 47 9337 100
fun.d 42 46 54.09 49 51 614 100

哈哈,上面的輸出有沒有意外?

結果使用for loop的fun.a和使用lapply的fun.d戰平,最慢的是使用for loop對data.frame操作的fun.b。

所以結論是使用for apply未必就要比lappy慢。

那麼為什麼fun.b對data.frame操作會慢呢?是因為在for loop中的每一步,R都對data.frame x進行了copy,這是很費時間的。我們怎麼知道R對x在每一步循環都進行copy了?可以通過查看x的內存地址來驗證。為了看到R裡面對象的內存地址,我們需要使用一個包——pryr:

&> library(pryr)
&> fun.a.print=function(){for (i in 1:10){y[[i]]=y[[i]]-1;print(address(y))}}
&> fun.b.print=function(){for (i in 1:10){x[,i]=x[,i]-1;print(address(x))}}
&> fun.a.print()
[1] "0x10c2af410"
[1] "0x10c2af410"
[1] "0x10c2af410"
[1] "0x10c2af410"
[1] "0x10c2af410"
[1] "0x10c2af410"
[1] "0x10c2af410"
[1] "0x10c2af410"
[1] "0x10c2af410"
[1] "0x10c2af410"
&> fun.b.print()
[1] "0x10c2b7c80"
[1] "0x10c2b89c0"
[1] "0x10c2b9700"
[1] "0x10c2ba440"
[1] "0x10c2bb180"
[1] "0x10c2bbec0"
[1] "0x10c2bcc00"
[1] "0x10c2bd940"
[1] "0x10c2be680"
[1] "0x10c2bf3c0"

上面我們修改了fun.a和fun.b兩個函數,使得每一步都print出y和x的地址。結果很顯然,每次data.frame類型的x的內存地址都發生了變化,但是list類型的y卻沒有。所以fun.a雖然也使用了for loop循環,但是並不比lapply慢。

那麼為什麼data.frame類型的x每次修改都被從新copy到了內存的新地方,而list類型定y就沒有?這是因為list在R裡面屬於primitive函數(是用c實現,但是可以被R直接調用,而不必通過.internal介面,可以通過is.primitive()判斷一個函數是不是primitive函數),而data.frame不是primitive函數。在R裡面primitive函數每次讀取一個對象的時候,就會copy這個對象。

比較完for loop循環和apply,另一個問題就是向量化(vectorize),這個其實很簡單,就是直接對vector/matrix進行處理,遠比for loop或者apply速度,比如下面例子:

&> fun.plain=function(){for(i in 1:1e6){sqrt(i)}}
&> fun.vector=function(){sqrt(1:1e6)}
&> microbenchmark(fun.plain,fun.vector)
Unit: nanoseconds
expr min lq mean median uq max neval
fun.plain 40 47 122.00 51 115.5 2135 100
fun.vector 41 44 75.57 48 51.0 847 100

這裡面我們計算1到1百萬的平方根,使用向量化,sqrt函數直接對整個vector求根,速度提高很多。

總之,能使用向量/矩陣就用。

#############################################################################

終於寫完了,歡迎大家關注 微信公眾號:機器會學習,搜索id:jiqihuixuexi,更多精彩原創文章每日更新!


for循環可以避免?


R具有函數式編程的特徵,理論上所有的for都可以用lapply替代。


vector自帶循環,為什麼要用for呢


R語言能實現批處理,只要入門後就不會用for循環了


只是想說一下apply function family 其實就是用for loop寫的。速度自然不會有差別。R最有效率的方法無疑是vectorization。


推薦閱讀:

如何理解R中因子(factor)的概念?
R語言分層抽樣的函數是什麼?
r語言中句號(點號)「.」的含義是什麼?
R中的列表(list)和數據框(data.frame)有什麼區別?
用R可以做數據清洗嗎?或者有更好的數據清洗工具?

TAG:統計軟體 | R編程語言 |