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/學習
前言測試
剛開始接觸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函數
apply函數族是R語言中數據處理的一組核心函數,經過使用apply函數,咱們能夠實現對數據的循環、分組、過濾、類型控制等操做。可是,因爲在R語言中apply函數與其餘語言循環體的處理思路是徹底不同的,因此apply函數族一直是使用者玩不轉一類核心函數。
不少R語言新手,寫了不少的for循環代碼,也不肯意多花點時間把apply函數的使用方法瞭解清楚,最後把R代碼寫的跟C似得,我嚴重鄙視只會寫for的R程序員。
apply函數自己就是解決數據循環處理的問題,爲了面向不一樣的數據類型,不一樣的返回值,apply函數組成了一個函數族,包括了8個功能相似的函數。這其中有些函數很類似,有些也不是太同樣的。
我通常最經常使用的函數爲apply和sapply,下面將分別介紹這8個函數的定義和使用方法。
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等操做方法。
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會自動把數據框按列進行分組,再進行計算。
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"
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就夠了。
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了,直接一次操做就能計算出結果了。
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。那麼,咱們在使用’…’去傳入其餘的參數的時候,必定要看清楚傳遞過程的描述,纔不會出現的算法上的錯誤。
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類型的數據進行方便的數據過濾了。
對一個環境空間中的全部變量進行遍歷。若是咱們有好的習慣,把自定義的變量都按必定的規則存儲到自定義的環境空間中,那麼這個函數將會讓你的操做變得很是方便。固然,可能不少人都不熟悉空間的操做,那麼請參考文章 揭開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/