R語言中apply函數

來源:互聯網
上載者:User

標籤:儲存   變化   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等。

目錄

  1. apply的家族函數
  2. apply函數
  3. lapply函數
  4. sapply函數
  5. vapply函數
  6. mapply函數
  7. tapply函數
  8. rapply函數
  9. 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函數

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.