掌握R語言中的apply函數族


R的極客理想系列文章,涵蓋了R的思想,使用,工具,創新等的一系列要點,以我我的的學習和體驗去詮釋R的強大。程序員

R語言做爲統計學一門語言,一直在小衆領域閃耀着光芒。直到大數據的爆發,R語言變成了一門煊赫一時的數據分析的利器。隨着愈來愈多的工程背景的人的加入,R語言的社區在迅速擴大成長。如今已不只僅是統計領域,教育,銀行,電商,互聯網….都在使用R語言。算法

要成爲有理想的極客,咱們不能停留在語法上,要掌握牢固的數學,機率,統計知識,同時還要有創新精神,把R語言發揮到各個領域。讓咱們一塊兒動起來吧,開始R的極客理想。數組

關於做者:app

  • 張丹(Conan), 程序員Java,R,PHP,Javascriptide

  • weibo:@Conan_Z函數

  • blog: http://blog.fens.me工具

  • email: bsspirit@gmail.com性能

轉載請註明出處:
http://blog.fens.me/r-apply/學習

apply-title

前言測試

剛開始接觸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

我通常最經常使用的函數爲apply和sapply,下面將分別介紹這8個函數的定義和使用方法。

2. apply函數

apply函數是最經常使用的代替for循環的函數。apply函數能夠對矩陣、數據框、數組(二維、多維),按行或列進行循環計算,對子元素進行迭代,並把子元素以參數傳遞的形式給自定義的FUN函數中,並以返回計算結果。

函數定義:

apply(X, MARGIN, FUN, ...)

參數列表:

  • X:數組、矩陣、數據框

  • MARGIN: 按行計算或按按列計算,1表示按行,2表示按列

  • 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爲數據
# 第2、三個參數爲自定義參數,能夠經過apply的'...'進行傳入。
> myFUN<- function(x, c1, c2) {
+   c(sum(x[c1],1), mean(x[c2])) 
+ }

# 把數據框按行作循環,每行分別傳遞給myFUN函數,設置c1,c2對應myFUN的第2、三個參數
> 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  V2
1  4 3.5
2  4 3.0
3  4 2.5
4  4 2.0
5  4 2.5
6  4 3.0
7  4 3.5
8  4 4.0

經過for循環的方式,也能夠很容易的實現上面計算過程,可是這裏還有一些額外的操做須要本身處理,好比構建循環體、定義結果數據集、併合每次循環的結果到結果數據集。

對於上面的需求,還有第三種實現方法,那就是完成利用了R的特性,經過向量化計算來完成的。

> data.frame(x1=x[,1]+1,x2=rowMeans(x))
  x1  x2
1  4 3.5
2  4 3.0
3  4 2.5
4  4 2.0
5  4 2.5
6  4 3.0
7  4 3.5
8  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 x2
a  3  2
b  6  3
c  9  7
d 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 x2
a  3  2
b  6  3
c  9  7
d 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 10
beta :  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)
$a
2056 bytes

$df
1576 bytes

$x
656 bytes

$y
48 bytes

$z
952 bytes

$X
1088 bytes

$env
56 bytes

eapply函數平時很難被用到,但對於R包開發來講,環境空間的使用是必需要掌握的。特別是當R要作爲工業化的工具時,對變量的精確控制和管理是很是必要的。

本文全面地介紹了,R語言中的數據循環處理的apply函數族,基本已經能夠應對全部的循環處理的狀況了。同時,在apply一節中也比較了,3種數據處理方面的性能,R的內置向量計算,要優於apply循環,大幅優於for循環。那麼咱們在之後的R的開發和使用過程當中,應該更多地把apply函數使用好。

忘掉程序員的思惟,換成數據的思惟,也許你就一會兒開朗了。

轉載請註明出處:
http://blog.fens.me/r-apply/

相關文章
相關標籤/搜索