1 、 apply函數(shù)
數(shù)據(jù)量比較大的時候硝拧,R 語言for循環(huán)非常的慢葛假,
apply函數(shù)是最常用的代替for循環(huán)的函數(shù)聊训。apply函數(shù)可以對矩陣、數(shù)據(jù)框鼓寺、數(shù)組(二維遏暴、多維)朋凉,按行或列進(jìn)行循環(huán)計算,對子元素進(jìn)行迭代墓毒,并把子元素以參數(shù)傳遞的形式給自定義的FUN函數(shù)中亲怠,并以返回計算結(jié)果团秽。
apply(X, MARGIN, FUN, ...)
X:數(shù)組、矩陣习勤、數(shù)據(jù)框
MARGIN:按行計算或按按列計算,1表示按行夷都,2表示按列
FUN:自定義的調(diào)用函數(shù)
…:更多參數(shù)予颤,可選
示例:對一個矩陣的每一行求和
> x<-matrix(1:12,ncol=4,nrow=3)
> x
[,1] [,2] [,3] [,4]
[1,] 1 4 7 10
[2,] 2 5 8 11
[3,] 3 6 9 12
> apply(x,1,sum)
[1] 22 26 30
計算一個稍微復(fù)雜點的例子冬阳,按行循環(huán)党饮,讓數(shù)據(jù)框的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
# 自定義函數(shù)myFUN劫谅,第一個參數(shù)x為數(shù)據(jù)
# 第二嚷掠、三個參數(shù)為自定義參數(shù),可以通過apply的'...'進(jìn)行傳入不皆。
> myFUN<- function(x, c1, c2) {
+ c(sum(x[c1],1), mean(x[c2]))
+ }
# 把數(shù)據(jù)框按行做循環(huán),每行分別傳遞給myFUN函數(shù)霹娄,設(shè)置c1,c2對應(yīng)myFUN的第二、三個參數(shù)
> 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
通過這個上面的自定義函數(shù)myFUN就實現(xiàn)了犬耻,一個常用的循環(huán)計算
如果直接用for循環(huán)來實現(xiàn),那么代碼如下:
# 定義一個結(jié)果的數(shù)據(jù)框
> df<-data.frame()
# 定義for循環(huán)
> for(i in 1:nrow(x)){
+ row<-x[i,] # 每行的值
+ df<-rbind(df,rbind(c(sum(row[1],1), mean(row)))) # 計算渡蜻,并賦值到結(jié)果數(shù)據(jù)框
+ }
# 打印結(jié)果數(shù)據(jù)框
> 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循環(huán)的方式计济,也可以很容易的實現(xiàn)上面計算過程茸苇,但是這里還有一些額外的操作需要自己處理沦寂,比如構(gòu)建循環(huán)體、定義結(jié)果數(shù)據(jù)集传藏、并合每次循環(huán)的結(jié)果到結(jié)果數(shù)據(jù)集。
對于上面的需求哭靖,還有第三種實現(xiàn)方法,那就是完成利用了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種操作上面性能上的消耗燎含。
```{r}
# 清空環(huán)境變量
> 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))
+ }
# 生成數(shù)據(jù)集
> x <- cbind(x1=3, x2 = c(400:1, 2:500))
# 分別統(tǒng)計3種方法的CPU耗時腿短。
> system.time(fun1(x))
用戶 系統(tǒng) 流逝
0.01 0.00 0.02
> system.time(fun2(x))
用戶 系統(tǒng) 流逝
0.19 0.00 0.18
> system.time(fun3(x))
用戶 系統(tǒng) 流逝
0 0 0
從CPU的耗時來看,用for循環(huán)實現(xiàn)的計算是耗時最長的橘忱,apply實現(xiàn)的循環(huán)耗時很短,而直接使用R語言內(nèi)置的向量計算的操作幾乎不耗時钝诚。
通過上面的測試,對同一個計算來說凝颇,優(yōu)先考慮R語言內(nèi)置的向量計算,必須要用到循環(huán)時則使用apply函數(shù)芦岂,應(yīng)該盡量避免顯示的使用for,while等操作方法。
2禽最、lapply函數(shù)
lapply函數(shù)是一個最基礎(chǔ)循環(huán)操作函數(shù)之一辜伟,
用來對list、data.frame數(shù)據(jù)集進(jìn)行循環(huán)狱庇,并返回和X長度同樣的list結(jié)構(gòu)作為結(jié)果集舀透,通過lapply的開頭的第一個字母’l’就可以判斷返回結(jié)果集的類型。
lapply(X, FUN, ...)
X:list愕够、data.frame數(shù)據(jù)
FUN:自定義的調(diào)用函數(shù)
…:更多參數(shù)佛猛,可選
示例:計算list中的每個KEY對應(yīng)該的數(shù)據(jù)的分位數(shù)。
# 構(gòu)建一個list數(shù)據(jù)集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對應(yīng)該的數(shù)據(jù)的分位數(shù)。
> 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數(shù)據(jù)集進(jìn)行循環(huán)操作了凯亮,還可以用data.frame數(shù)據(jù)集按列進(jìn)行循環(huán),但如果傳入的數(shù)據(jù)集是一個向量或矩陣對象假消,那么直接使用lapply就不能達(dá)到想要的效果了岭接。
比如富拗,對矩陣的列求和鸣戴。
# 生成一個矩陣
> 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會分別循環(huán)矩陣中的每個值,而不是按行或按列進(jìn)行分組計算谅阿。
如果對數(shù)據(jù)框的列求和。
> lapply(data.frame(x), sum)
$x1
[1] 12
$x2
[1] 12
lapply會自動把數(shù)據(jù)框按列進(jìn)行分組,再進(jìn)行計算寓涨。
3、sapply函數(shù)
sapply函數(shù)是一個簡化版的lapply戒良,sapply增加了2個參數(shù)simplify和USE.NAMES,主要就是讓輸出看起來更友好几缭,返回值為向量,而不是list對象
sapply(X, FUN, ..., simplify=TRUE, USE.NAMES = TRUE)
X:數(shù)組年栓、矩陣薄霜、數(shù)據(jù)框
FUN:自定義的調(diào)用函數(shù)
…:更多參數(shù)某抓,可選
simplify:是否數(shù)組化惰瓜,當(dāng)值array時,輸出結(jié)果按數(shù)組進(jìn)行分組
USE.NAMES:如果X為字符串备禀,TRUE設(shè)置字符串為數(shù)據(jù)名,F(xiàn)ALSE不設(shè)置
示例:計算list中的每個KEY對應(yīng)該的數(shù)據(jù)的分位數(shù)曲尸。
> x <- cbind(x1=3, x2=c(2:1,4:5))
# 對矩陣計算,計算過程同lapply函數(shù)
> sapply(x, sum)
[1] 3 3 3 3 2 1 4 5
# 對數(shù)據(jù)框計算
> sapply(data.frame(x), sum)
x1 x2
12 12
# 檢查結(jié)果類型队腐,sapply返回類型為向量,而lapply的返回類型為list
> class(lapply(x, sum))
[1] "list"
> class(sapply(x, sum))
[1] "numeric"
如果simplify=FALSE和USE.NAMES=FALSE柴淘,那么完全sapply函數(shù)就等于lapply函數(shù)了。
> 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時敛熬,我們可以參考下面的例子,構(gòu)建一個三維數(shù)組应民,其中二個維度為方陣。
> a<-1:2
# 按數(shù)組分組
> 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
# 默認(rèn)情況诲锹,則自動合并分組
> sapply(a,function(x) matrix(x,2,2))
[,1] [,2]
[1,] 1 2
[2,] 1 2
[3,] 1 2
[4,] 1 2
對于字符串的向量涉馅,還可以自動生成數(shù)據(jù)名。
> val<-head(letters)
# 默認(rèn)設(shè)置數(shù)據(jù)名
> sapply(val,paste,USE.NAMES=TRUE)
a b c d e f
"a" "b" "c" "d" "e" "f"
# USE.NAMES=FALSE稚矿,則不設(shè)置數(shù)據(jù)名
> sapply(val,paste,USE.NAMES=FALSE)
[1] "a" "b" "c" "d" "e" "f"
4、vapply函數(shù)
vapply類似于sapply晤揣,提供了FUN.VALUE參數(shù),用來控制返回值的行名昧识,這樣可以讓程序更健壯。
vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE)
X:數(shù)組形导、矩陣、數(shù)據(jù)框
FUN:自定義的調(diào)用函數(shù)
…:更多參數(shù)朵耕,可選
FUN.VALUE:定義返回值的行名row.names
USE.NAMES: 如果X為字符串淋叶,TRUE設(shè)置字符串為數(shù)據(jù)名,F(xiàn)ALSE不設(shè)置
示例:對數(shù)據(jù)框的數(shù)據(jù)進(jìn)行累計求和,并對每一行設(shè)置行名row.names
# 生成數(shù)據(jù)集
> x <- data.frame(cbind(x1=3, x2=c(2:1,4:5)))
# 設(shè)置行名处嫌,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
# 當(dāng)不設(shè)置時,為默認(rèn)的索引值
> a<-sapply(x,cumsum);a
x1 x2
[1,] 3 2
[2,] 6 3
[3,] 9 7
[4,] 12 12
# 手動的方式設(shè)置行名
> row.names(a)<-c('a','b','c','d')
> a
x1 x2
a 3 2
b 6 3
c 9 7
d 12 12
通過使用vapply可以直接設(shè)置返回值的行名檐薯,
這樣子做其實可以節(jié)省一行的代碼注暗,讓代碼看起來更順暢,當(dāng)然如果不愿意多記一個函數(shù)捆昏,那么也可以直接忽略它,只用sapply就夠了骗卜。
5、mapply函數(shù)
mapply也是sapply的變形函數(shù)寇仓,類似多變量的sapply,但是參數(shù)定義有些變化焚刺。
第一參數(shù)為自定義的FUN函數(shù),第二個參數(shù)’…’可以接收多個數(shù)據(jù)乳愉,作為FUN函數(shù)的參數(shù)調(diào)用屯远。
mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE,USE.NAMES = TRUE)
FUN:自定義的調(diào)用函數(shù)
…:更多參數(shù),可選
MoreArgs:參數(shù)列表
SIMPLIFY:是否數(shù)組化坡脐,當(dāng)值array時,輸出結(jié)果按數(shù)組進(jìn)行分組
USE.NAMES:如果X為字符串备闲,TRUE設(shè)置字符串為數(shù)據(jù)名捅暴,F(xiàn)ALSE不設(shè)置
示例:比較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個符合正態(tài)分布的數(shù)據(jù)集演痒,分別對應(yīng)的均值和方差為c(1,10,100,1000)。
> set.seed(1)
# 長度為4
> n<-rep(4,4)
# m為均值鸟顺,v為方差
> m<-v<-c(1,10,100,1000)
# 生成4組數(shù)據(jù)器虾,按列分組
> 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是可以接收多個參數(shù)的,所以我們在做數(shù)據(jù)操作的時候曾撤,就不需要把數(shù)據(jù)先合并為data.frame了,直接一次操作就能計算出結(jié)果了挤悉。
6、tapply函數(shù)
tapply用于分組的循環(huán)計算装悲,通過INDEX參數(shù)可以把數(shù)據(jù)集X進(jìn)行分組,相當(dāng)于group by的操作洞渤。
tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)
X:向量
FUN:自定義的調(diào)用函數(shù)
…:更多參數(shù),可選
INDEX:用于分組的索引
simplify:是否數(shù)組化载迄,當(dāng)值array時,輸出結(jié)果按數(shù)組進(jìn)行分組
示例:計算不同品種的鳶尾花的花瓣(iris)長度的均值护昧。
# 通過iris$Species品種進(jìn)行分組
> tapply(iris$Petal.Length,iris$Species,mean)
setosa versicolor virginica
1.462 4.260 5.552
對向量x和y進(jìn)行計算粗截,并以向量t為索引進(jìn)行分組,求和熊昌。
> 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
# 設(shè)置分組索引t
> t<-round(runif(10,1,100)%%2);t
[1] 1 2 2 1 1 2 1 0 1 1
# 對x進(jìn)行分組求和
> tapply(x,t,sum)
0 1 2
8 36 11
由于tapply只接收一個向量參考,通過’…’可以把再傳給你FUN其他的參數(shù)婿屹,那么我們想去y向量也進(jìn)行求和,把y作為tapply的第4個參數(shù)進(jìn)行計算选泻。
> tapply(x,t,sum,y)
0 1 2
63 91 66
得到的結(jié)果并不符合我們的預(yù)期美莫,結(jié)果不是把x和y對應(yīng)的t分組后求和梯捕,而是得到了其他的結(jié)果。
第4個參數(shù)y傳入sum時傀顾,并不是按照循環(huán)一個一個傳進(jìn)去的,而是每次傳了完整的向量數(shù)據(jù)短曾,那么再執(zhí)行sum時sum(y)=55,所以對于t=0時哩都,x=8 再加上y=55,
最后計算結(jié)果為63漠嵌。那么盖呼,我們在使用’…’去傳入其他的參數(shù)的時候儒鹿,一定要看清楚傳遞過程的描述几晤,才不會出現(xiàn)的算法上的錯誤。
7蟹瘾、rapply函數(shù)
rapply是一個遞歸版本的lapply,它只處理list類型數(shù)據(jù)贱傀,對list的每個元素進(jìn)行遞歸遍歷,如果list包括子元素則繼續(xù)遍歷。
rapply(object, f, classes = "ANY", deflt = NULL, how = c("unlist", "replace", "list"), ...)
object:list數(shù)據(jù)
f:自定義的調(diào)用函數(shù)
…:更多參數(shù)魁衙,可選
classes:匹配類型, ANY為所有類型
deflt:非匹配類型的默認(rèn)值
how:3種操作方式,當(dāng)為replace時剖淀,則用調(diào)用f后的結(jié)果替換原list中原來的元素;當(dāng)為list時翻诉,新建一個list,類型匹配調(diào)用f函數(shù)碰煌,不匹配賦值為deflt;當(dāng)為unlist時芦圾,會執(zhí)行一次unlist(recursive = TRUE)的操作
…: 更多參數(shù),可選
示例:對一個list的數(shù)據(jù)進(jìn)行過濾洪乍,把所有數(shù)字型numeric的數(shù)據(jù)進(jìn)行從小到大的排序
> 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)
# 進(jìn)行排序,并替換原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"
從結(jié)果發(fā)現(xiàn)壳澳,只有$z$a的數(shù)據(jù)進(jìn)行了排序茫经,檢查$z$b的類型巷波,發(fā)現(xiàn)是integer科平,是不等于numeric的,所以沒有進(jìn)行排序瞪慧。
接下來,對字符串類型的數(shù)據(jù)進(jìn)行操作弃酌,把所有的字符串型加一個字符串’++++’,非字符串類型數(shù)據(jù)設(shè)置為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類型的數(shù)據(jù)進(jìn)行方便的數(shù)據(jù)過濾了
8、eapply函數(shù)
對一個環(huán)境空間中的所有變量進(jìn)行遍歷驶拱。如果我們有好的習(xí)慣,把自定義的變量都按一定的規(guī)則存儲到自定義的環(huán)境空間中阴孟,
那么這個函數(shù)將會讓你的操作變得非常方便晌纫。當(dāng)然永丝,可能很多人都不熟悉空間的操作,那么請參考文章 揭開R語言中環(huán)境空間的神秘面紗类溢,
解密R語言函數(shù)的環(huán)境空間。
eapply(env, FUN, ..., all.names = FALSE, USE.NAMES = TRUE)
env:環(huán)境空間
FUN:自定義的調(diào)用函數(shù)
…:更多參數(shù)砂心,可選
all.names: 匹配類型, ANY為所有類型
USE.NAMES: 如果X為字符串蛇耀,TRUE設(shè)置字符串為數(shù)據(jù)名辩诞,F(xiàn)ALSE不設(shè)置
示例:下面我們定義一個環(huán)境空間纺涤,然后對環(huán)境空間的變量進(jìn)行循環(huán)處理。
# 定義一個環(huán)境空間
> env# 向這個環(huán)境空間中存入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空間中的變量字符串結(jié)構(gòu)
> 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環(huán)境空間中所有變量的均值外永。
> eapply(env, mean)
$logic
[1] 0.5
$beta
[1] 4.535125
$a
[1] 5.5
再計算中當(dāng)前環(huán)境空間中的所有變量的占用內(nèi)存大小拧咳。
# 查看當(dāng)前環(huán)境空間中的變量
> ls()
[1] "a" "df" "env" "x" "y" "z" "X"
# 查看所有變量的占用內(nèi)存大小
> 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函數(shù)平時很難被用到,但對于R包開發(fā)來說骆膝,環(huán)境空間的使用是必須要掌握的。特別是當(dāng)R要做為工業(yè)化的工具時阅签,對變量的精確控制和管理是非常必要的。
本文全面地介紹了路克,R語言中的數(shù)據(jù)循環(huán)處理的apply函數(shù)族,基本已經(jīng)可以應(yīng)對所有的循環(huán)處理的情況了衷戈。同時层坠,在apply一節(jié)中也比較了刁笙,3種數(shù)據(jù)處理方面的性能破花,R的內(nèi)置向量計算,要優(yōu)于apply循環(huán)前鹅,大幅優(yōu)于for循環(huán)峭梳。那么我們在以后的R的開發(fā)和使用過程中舰绘,應(yīng)該更多地把a(bǔ)pply函數(shù)使用好葱椭。
忘掉程序員的思維,換成數(shù)據(jù)的思維孵运,也許你就一下子開朗了。