apply函數族介紹-來源網絡

1 、 apply函數

數據量比較大的時候,R 語言for循環非常的慢,
apply函數是最常用的代替for循環的函數。apply函數可以對矩陣、數據框、數組(二維、多維),按行或列進行循環計算,對子元素進行迭代,并把子元素以參數傳遞的形式給自定義的FUN函數中,并以返回計算結果。

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

X:數組、矩陣、數據框
MARGIN:按行計算或按按列計算,1表示按行,2表示按列
FUN:自定義的調用函數
…:更多參數,可選

示例:對一個矩陣的每一行求和

> 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

計算一個稍微復雜點的例子,按行循環,讓數據框的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  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種操作上面性能上的消耗。
```{r}
# 清空環境變量
> 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等操作方法。

2、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會自動把數據框按列進行分組,再進行計算。

3、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不設置

示例:計算list中的每個KEY對應該的數據的分位數。

> 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"

4、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就夠了。

5、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了,直接一次操作就能計算出結果了。

6、tapply函數

tapply用于分組的循環計算,通過INDEX參數可以把數據集X進行分組,相當于group by的操作。

tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)

X:向量
FUN:自定義的調用函數
…:更多參數,可選
INDEX:用于分組的索引
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。那么,我們在使用’…’去傳入其他的參數的時候,一定要看清楚傳遞過程的描述,才不會出現的算法上的錯誤。

7、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類型的數據進行方便的數據過濾了

8、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函數使用好。

忘掉程序員的思維,換成數據的思維,也許你就一下子開朗了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • 循環對于代碼運行來說是非常消耗時間和資源的,在R中,要盡量少使用for while循環,用apply函數族的話對于...
    willnight閱讀 3,488評論 0 2
  • 第一部分Common Lisp介紹第1章 介紹一下Lisp你在學的時候覺得已經明白了,寫的時候更加確信了解了,教別...
    geoeee閱讀 2,991評論 5 8
  • http://python.jobbole.com/85231/ 關于專業技能寫完項目接著寫寫一名3年工作經驗的J...
    燕京博士閱讀 7,603評論 1 118
  • 我怎么還沒睡 我需要一點味 我得承認異地戀的確比別人累 抱又抱不著我親又親不到 睡醒后發現又是沒有你的大清早 最怕...
    莞毓閱讀 247評論 0 0
  • 雖已入秋,但夏天的威力卻一點沒減少,太陽仍是360°無死角的曝曬著。作為見“光”死的妹子,簡直快要曬到融化啦。是時...
    梅花傘閱讀 1,730評論 0 0