標籤:儲存 變化 2.0 需求 als bbs lap 1.5 http
前言
剛開始接觸R語言時,會聽到各種的R語言提示,其中最重要的一條就是不要用迴圈,效率特別低,要用向量計算代替迴圈計算。
那麼,這是為什麼呢?原因在於R的迴圈操作for和while,都是基於R語言本身來實現的,而向量操作是基於底層的C語言函數實現的,從效能上來看,就會有比較明顯的差距了。那麼如何使用C的函數來實現向量計算呢,就是要用到apply的家族函數,包括apply, sapply, tapply, mapply, lapply, rapply, vapply, eapply等。
目錄
- apply的家族函數
- apply函數
- lapply函數
- sapply函數
- vapply函數
- mapply函數
- tapply函數
- rapply函數
- eapply函數
1. apply的家族函數
apply函數族是R語言中資料處理的一組核心函數,通過使用apply函數,我們可以實現對資料的迴圈、分組、過濾、類型控制等操作。但是,由於在R語言中apply函數與其他語言迴圈體的處理思路是完全不一樣的,所以apply函數族一直是使用者玩不轉一類核心函數。
很多R語言新手,寫了很多的for迴圈代碼,也不願意多花點時間把apply函數的使用方法瞭解清楚,最後把R代碼寫的跟C似得,我嚴重鄙視只會寫for的R程式員。
apply函數本身就是解決資料迴圈處理的問題,為了面向不同的資料類型,不同的傳回值,apply函數組成了一個函數族,包括了8個功能類似的函數。這其中有些函數很相似,有些也不是太一樣的。
我一般最常用的函數為apply和sapply,下面將分別介紹這8個函數的定義和使用方法。
2. apply函數
apply函數是最常用的代替for迴圈的函數。apply函數可以對矩陣、資料框、數組(二維、多維),按行或列進行迴圈計算,對子項目進行迭代,並把子項目以參數傳遞的形式給自訂的FUN函數中,並以返回計算結果。
函數定義:
apply(X, MARGIN, FUN, ...)
參數列表:
- X:數組、矩陣、資料框
- MARGIN: 按行計算或按按列計算,1表示按行,2表示按列,3表示每個維度和
- FUN: 自訂的調用函數
- …: 更多參數,可選
比如,對一個矩陣的每一行求和,下面就要用到apply做迴圈了。
> x<-matrix(1:12,ncol=3)> apply(x,1,sum)[1] 15 18 21 24
下面計算一個稍微複雜點的例子,按行迴圈,讓資料框的x1列加1,並計算出x1,x2列的均值。
# 產生data.frame> x <- cbind(x1 = 3, x2 = c(4:1, 2:5)); x x1 x2[1,] 3 4[2,] 3 3[3,] 3 2[4,] 3 1[5,] 3 2[6,] 3 3[7,] 3 4[8,] 3 5# 自訂函數myFUN,第一個參數x為資料# 第二、三個參數為自訂參數,可以通過apply的‘...‘進行傳入。> myFUN<- function(x, c1, c2) {+ c(sum(x[c1],1), mean(x[c2])) + }# 把資料框按行做迴圈,每行分別傳遞給myFUN函數,設定c1,c2對應myFUN的第二、三個參數> apply(x,1,myFUN,c1=‘x1‘,c2=c(‘x1‘,‘x2‘)) [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8][1,] 4.0 4 4.0 4 4.0 4 4.0 4[2,] 3.5 3 2.5 2 2.5 3 3.5 4
通過這個上面的自訂函數myFUN就實現了,一個常用的迴圈計算。
如果直接用for迴圈來實現,那麼代碼如下:
# 定義一個結果的資料框> df<-data.frame()# 定義for迴圈> for(i in 1:nrow(x)){+ row<-x[i,] # 每行的值+ df<-rbind(df,rbind(c(sum(row[1],1), mean(row)))) # 計算,並賦值到結果資料框+ }# 列印結果資料框> df V1 V21 4 3.52 4 3.03 4 2.54 4 2.05 4 2.56 4 3.07 4 3.58 4 4.0
通過for迴圈的方式,也可以很容易的實現上面計算過程,但是這裡還有一些額外的操作需要自己處理,比如構建迴圈體、定義結果資料集、併合每次迴圈的結果到結果資料集。
對於上面的需求,還有第三種實現方法,那就是完成利用了R的特性,通過向量化計算來完成的。
> data.frame(x1=x[,1]+1,x2=rowMeans(x)) x1 x21 4 3.52 4 3.03 4 2.54 4 2.05 4 2.56 4 3.07 4 3.58 4 4.0
那麼,一行就可以完成整個計算過程了。
接下來,我們需要再比較一下3種操作上面效能上的消耗。
# 清空環境變數> rm(list=ls())# 封裝fun1> fun1<-function(x){+ myFUN<- function(x, c1, c2) {+ c(sum(x[c1],1), mean(x[c2])) + }+ apply(x,1,myFUN,c1=‘x1‘,c2=c(‘x1‘,‘x2‘))+ }# 封裝fun2> fun2<-function(x){+ df<-data.frame()+ for(i in 1:nrow(x)){+ row<-x[i,]+ df<-rbind(df,rbind(c(sum(row[1],1), mean(row))))+ }+ }# 封裝fun3> fun3<-function(x){+ data.frame(x1=x[,1]+1,x2=rowMeans(x))+ }# 產生資料集> x <- cbind(x1=3, x2 = c(400:1, 2:500))# 分別統計3種方法的CPU耗時。> system.time(fun1(x))使用者 系統 流逝 0.01 0.00 0.02 > system.time(fun2(x))使用者 系統 流逝 0.19 0.00 0.18 > system.time(fun3(x))使用者 系統 流逝 0 0 0
從CPU的耗時來看,用for迴圈實現的計算是耗時最長的,apply實現的迴圈耗時很短,而直接使用R語言內建的向量計算的操作幾乎不耗時。通過上面的測試,對同一個計算來說,優先考慮R語言內建的向量計算,必須要用到迴圈時則使用apply函數,應該盡量避免顯示的使用for,while等操作方法。
3. lapply函數
lapply函數是一個最基礎迴圈操作函數之一,用來對list、data.frame資料集進行迴圈,並返回和X長度同樣的list結構作為結果集,通過lapply的開頭的第一個字母’l’就可以判斷返回結果集的類型。
函數定義:
lapply(X, FUN, ...)
參數列表:
- X:list、data.frame資料
- FUN: 自訂的調用函數
- …: 更多參數,可選
比如,計算list中的每個KEY對應該的資料的分位元。
# 構建一個list資料集x,分別包括a,b,c 三個KEY值。> x <- list(a = 1:10, b = rnorm(6,10,5), c = c(TRUE,FALSE,FALSE,TRUE));x$a [1] 1 2 3 4 5 6 7 8 9 10$b[1] 0.7585424 14.3662366 13.3772979 11.6658990 9.7011387 21.5321427$c[1] TRUE FALSE FALSE TRUE# 分別計算每個KEY對應該的資料的分位元。> lapply(x,fivenum)$a[1] 1.0 3.0 5.5 8.0 10.0$b[1] 0.7585424 9.7011387 12.5215985 14.3662366 21.5321427$c[1] 0.0 0.0 0.5 1.0 1.0
lapply就可以很方便地把list資料集進行迴圈操作了,還可以用data.frame資料集按列進行迴圈,但如果傳入的資料集是一個向量或矩陣對象,那麼直接使用lapply就不能達到想要的效果了。
比如,對矩陣的列求和。
# 產生一個矩陣> x <- cbind(x1=3, x2=c(2:1,4:5))> x; class(x) x1 x2[1,] 3 2[2,] 3 1[3,] 3 4[4,] 3 5[1] "matrix"# 求和> lapply(x, sum)[[1]][1] 3[[2]][1] 3[[3]][1] 3[[4]][1] 3[[5]][1] 2[[6]][1] 1[[7]][1] 4[[8]][1] 5
lapply會分別迴圈矩陣中的每個值,而不是按行或按列進行分組計算。
如果對資料框的列求和。
> lapply(data.frame(x), sum)$x1[1] 12$x2[1] 12
lapply會自動把資料框按列進行分組,再進行計算。
4. sapply函數
sapply函數是一個簡化版的lapply,sapply增加了2個參數simplify和USE.NAMES,主要就是讓輸出看起來更友好,傳回值為向量,而不是list對象。
函數定義:
sapply(X, FUN, ..., simplify=TRUE, USE.NAMES = TRUE)
參數列表:
- X:數組、矩陣、資料框
- FUN: 自訂的調用函數
- …: 更多參數,可選
- simplify: 是否數組化,當值array時,輸出結果按數組進行分組
- USE.NAMES: 如果X為字串,TRUE設定字串為資料名,FALSE不設定
我們還用上面lapply的計算需求進行說明。
> x <- cbind(x1=3, x2=c(2:1,4:5))# 對矩陣計算,計算過程同lapply函數> sapply(x, sum)[1] 3 3 3 3 2 1 4 5# 對資料框計算> sapply(data.frame(x), sum)x1 x2 12 12 # 檢查結果類型,sapply傳回型別為向量,而lapply的傳回型別為list> class(lapply(x, sum))[1] "list"> class(sapply(x, sum))[1] "numeric"
如果simplify=FALSE和USE.NAMES=FALSE,那麼完全sapply函數就等於lapply函數了。
> lapply(data.frame(x), sum)$x1[1] 12$x2[1] 12> sapply(data.frame(x), sum, simplify=FALSE, USE.NAMES=FALSE)$x1[1] 12$x2[1] 12
對於simplify為array時,我們可以參考下面的例子,構建一個三維數組,其中二個維度為方陣。
> a<-1:2# 按數組分組> sapply(a,function(x) matrix(x,2,2), simplify=‘array‘), , 1 [,1] [,2][1,] 1 1[2,] 1 1, , 2 [,1] [,2][1,] 2 2[2,] 2 2# 預設情況,則自動合并分組> sapply(a,function(x) matrix(x,2,2)) [,1] [,2][1,] 1 2[2,] 1 2[3,] 1 2[4,] 1 2
對於字串的向量,還可以自動產生資料名。
> val<-head(letters)# 預設設定資料名> sapply(val,paste,USE.NAMES=TRUE) a b c d e f "a" "b" "c" "d" "e" "f" # USE.NAMES=FALSE,則不設定資料名> sapply(val,paste,USE.NAMES=FALSE)[1] "a" "b" "c" "d" "e" "f"
5. vapply函數
vapply類似於sapply,提供了FUN.VALUE參數,用來控制傳回值的行名,這樣可以讓程式更健壯。
函數定義:
vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE)
參數列表:
- X:數組、矩陣、資料框
- FUN: 自訂的調用函數
- FUN.VALUE: 定義傳回值的行名row.names
- …: 更多參數,可選
- USE.NAMES: 如果X為字串,TRUE設定字串為資料名,FALSE不設定
比如,對資料框的資料進行累計求和,並對每一行設定行名row.names
# 產生資料集> x <- data.frame(cbind(x1=3, x2=c(2:1,4:5)))# 設定行名,4行分別為a,b,c,d> vapply(x,cumsum,FUN.VALUE=c(‘a‘=0,‘b‘=0,‘c‘=0,‘d‘=0)) x1 x2a 3 2b 6 3c 9 7d 12 12# 當不設定時,為預設的索引值> a<-sapply(x,cumsum);a x1 x2[1,] 3 2[2,] 6 3[3,] 9 7[4,] 12 12# 手動的方式設定行名> row.names(a)<-c(‘a‘,‘b‘,‘c‘,‘d‘)> a x1 x2a 3 2b 6 3c 9 7d 12 12
通過使用vapply可以直接設定傳回值的行名,這樣子做其實可以節省一行的代碼,讓代碼看起來更順暢,當然如果不願意多記一個函數,那麼也可以直接忽略它,只用sapply就夠了。
6. mapply函數
mapply也是sapply的變形函數,類似多變數的sapply,但是參數定義有些變化。第一參數為自訂的FUN函數,第二個參數’…’可以接收多個資料,作為FUN函數的參數調用。
函數定義:
mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE,USE.NAMES = TRUE)
參數列表:
- FUN: 自訂的調用函數
- …: 接收多個資料
- MoreArgs: 參數列表
- SIMPLIFY: 是否數組化,當值array時,輸出結果按數組進行分組
- USE.NAMES: 如果X為字串,TRUE設定字串為資料名,FALSE不設定
比如,比較3個向量大小,按索引順序取較大的值。
> set.seed(1)# 定義3個向量> x<-1:10> y<-5:-4> z<-round(runif(10,-5,5))# 按索引順序取較大的值。> mapply(max,x,y,z) [1] 5 4 3 4 5 6 7 8 9 10
再看一個例子,產生4個符合常態分佈的資料集,分別對應的均值和方差為c(1,10,100,1000)。
> set.seed(1)# 長度為4> n<-rep(4,4)# m為均值,v為方差> m<-v<-c(1,10,100,1000)# 產生4組資料,按列分組> mapply(rnorm,n,m,v) [,1] [,2] [,3] [,4][1,] 0.3735462 13.295078 157.57814 378.7594[2,] 1.1836433 1.795316 69.46116 -1214.6999[3,] 0.1643714 14.874291 251.17812 2124.9309[4,] 2.5952808 17.383247 138.98432 955.0664
由於mapply是可以接收多個參數的,所以我們在做資料操作的時候,就不需要把資料先合并為data.frame了,直接一次操作就能計算出結果了。
7. tapply函數
tapply用於分組的迴圈計算,通過INDEX參數可以把資料集X進行分組,相當於group by的操作。
函數定義:
tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)
參數列表:
- X: 向量
- INDEX: 用於分組的索引
- FUN: 自訂的調用函數
- …: 接收多個資料
- simplify : 是否數組化,當值array時,輸出結果按數組進行分組
比如,計算不同品種的鳶尾花的花瓣(iris)長度的均值。
# 通過iris$Species品種進行分組> tapply(iris$Petal.Length,iris$Species,mean) setosa versicolor virginica 1.462 4.260 5.552
對向量x和y進行計算,並以向量t為索引進行分組,求和。
> set.seed(1)# 定義x,y向量> x<-y<-1:10;x;y [1] 1 2 3 4 5 6 7 8 9 10 [1] 1 2 3 4 5 6 7 8 9 10# 設定分組索引t> t<-round(runif(10,1,100)%%2);t [1] 1 2 2 1 1 2 1 0 1 1# 對x進行分組求和> tapply(x,t,sum) 0 1 2 8 36 11
由於tapply只接收一個向量參考,通過’…’可以把再傳給你FUN其他的參數,那麼我們想去y向量也進行求和,把y作為tapply的第4個參數進行計算。
> tapply(x,t,sum,y) 0 1 2 63 91 66
得到的結果並不符合我們的預期,結果不是把x和y對應的t分組後求和,而是得到了其他的結果。第4個參數y傳入sum時,並不是按照迴圈一個一個傳進去的,而是每次傳了完整的向量資料,那麼再執行sum時sum(y)=55,所以對於t=0時,x=8 再加上y=55,最後計算結果為63。那麼,我們在使用’…’去傳入其他的參數的時候,一定要看清楚傳遞過程的描述,才不會出現的演算法上的錯誤。
8. rapply函數
rapply是一個遞迴版本的lapply,它只處理list類型資料,對list的每個元素進行遞迴遍曆,如果list包括子項目則繼續遍曆。
函數定義:
rapply(object, f, classes = "ANY", deflt = NULL, how = c("unlist", "replace", "list"), ...)
參數列表:
- object:list資料
- f: 自訂的調用函數
- classes : 匹配類型, ANY為所有類型
- deflt: 非匹配類型的預設值
- how: 3種操作方式,當為replace時,則用調用f後的結果替換原list中原來的元素;當為list時,建立一個list,類型匹配調用f函數,不匹配賦值為deflt;當為unlist時,會執行一次unlist(recursive = TRUE)的操作
- …: 更多參數,可選
比如,對一個list的資料進行過濾,把所有數字型numeric的資料進行從小到大的排序。
> x=list(a=12,b=1:4,c=c(‘b‘,‘a‘))> y=pi> z=data.frame(a=rnorm(10),b=1:10)> a <- list(x=x,y=y,z=z)# 進行排序,並替換原list的值> rapply(a,sort, classes=‘numeric‘,how=‘replace‘)$x$x$a[1] 12$x$b[1] 4 3 2 1$x$c[1] "b" "a"$y[1] 3.141593$z$z$a [1] -0.8356286 -0.8204684 -0.6264538 -0.3053884 0.1836433 0.3295078 [7] 0.4874291 0.5757814 0.7383247 1.5952808$z$b [1] 10 9 8 7 6 5 4 3 2 1> class(a$z$b)[1] "integer"
從結果發現,只有$z$a的資料進行了排序,檢查$z$b的類型,發現是integer,是不等於numeric的,所以沒有進行排序。
接下來,對字串類型的資料進行操作,把所有的字串型加一個字串’++++’,非字串類型資料設定為NA。
> rapply(a,function(x) paste(x,‘++++‘),classes="character",deflt=NA, how = "list")$x$x$a[1] NA$x$b[1] NA$x$c[1] "b ++++" "a ++++"$y[1] NA$z$z$a[1] NA$z$b[1] NA
只有$x$c為字串向量,都合并了一個新字串。那麼,有了rapply就可以對list類型的資料進行方便的資料過濾了。
9. eapply函數
對一個環境空間中的所有變數進行遍曆。如果我們有好的習慣,把自訂的變數都按一定的規則儲存到自訂的環境空間中,那麼這個函數將會讓你的操作變得非常方便。當然,可能很多人都不熟悉空間的操作,那麼請參考文章 揭開R語言中環境空間的神秘面紗,解密R語言函數的環境空間。
函數定義:
eapply(env, FUN, ..., all.names = FALSE, USE.NAMES = TRUE)
參數列表:
- env: 環境空間
- FUN: 自訂的調用函數
- …: 更多參數,可選
- all.names: 匹配類型, ANY為所有類型
- USE.NAMES: 如果X為字串,TRUE設定字串為資料名,FALSE不設定
下面我們定義一個環境空間,然後對環境空間的變數進行迴圈處理。
# 定義一個環境空間> env# 向這個環境空間中存入3個變數> env$a <- 1:10> env$beta <- exp(-3:3)> env$logic <- c(TRUE, FALSE, FALSE, TRUE)> env# 查看env空間中的變數> ls(env)[1] "a" "beta" "logic"# 查看env空間中的變數字串結構> ls.str(env)a : int [1:10] 1 2 3 4 5 6 7 8 9 10beta : num [1:7] 0.0498 0.1353 0.3679 1 2.7183 ...logic : logi [1:4] TRUE FALSE FALSE TRUE
計算env環境空間中所有變數的均值。
> eapply(env, mean)$logic[1] 0.5$beta[1] 4.535125$a[1] 5.5
再計算中當前環境空間中的所有變數的佔用記憶體大小。
# 查看當前環境空間中的變數> ls() [1] "a" "df" "env" "x" "y" "z" "X" # 查看所有變數的佔用記憶體大小> eapply(environment(), object.size)$a2056 bytes$df1576 bytes$x656 bytes$y48 bytes$z952 bytes$X1088 bytes$env56 bytes
eapply函數平時很難被用到,但對於R包開發來說,環境空間的使用是必須要掌握的。特別是當R要做為工業化的工具時,對變數的精確控制和管理是非常必要的。
本文全面地介紹了,R語言中的資料迴圈處理的apply函數族,基本已經可以應對所有的迴圈處理的情況了。同時,在apply一節中也比較了,3種資料處理方面的效能,R的內建向量計算,要優於apply迴圈,大幅優於for迴圈。那麼我們在以後的R的開發和使用過程中,應該更多地把apply函數使用好。
忘掉程式員的思維,換成資料的思維,也許你就一下子開朗了。
apply函數經常用來計算矩陣中行或列的均值、和值的函數,具體方法如下:
apply(x,計算行或列數字代碼,函數),詳見例子:
> b
first second
one 1 2
two 3 4
three 5 6
> apply(b,1,sum)#第一個參數表示要計算的矩陣,第二個參數1表示計算每一行,第三個參數是要計算每一行的函數,這裡是求每一行的和。
one two three
3 7 11
> apply(b,2,sum)#表示求每一列的和。
first second
9 12
> d<-array(1:24,dim=c(2,3,4))
> d
, , 1
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
, , 2
[,1] [,2] [,3]
[1,] 7 9 11
[2,] 8 10 12
, , 3
[,1] [,2] [,3]
[1,] 13 15 17
[2,] 14 16 18
, , 4
[,1] [,2] [,3]
[1,] 19 21 23
[2,] 20 22 24
> apply(d,3,sum)#表示求每一維的和,一個維度為一個矩陣,即這個維度每個元素相加之和。
[1] 21 57 93 129
本文來自: 人大經濟論壇 R語言論壇 版,詳細出處參考: http://bbs.pinggu.org/forum.php?mod=viewthread&tid=4200726&page=1
R語言中apply函數