R語言-數據整形之apply函數族


R語言中的apply函數族

前言

  最初學習R的時候,當成“又一門編程語言”來學習,但是怎么學都覺得別扭。現在我的看法傾向於,R不是一種通用型的編程語言,而是一種統計領域的軟件工具。因此,不能用通用型編程的思維來設計R代碼。R是一種面向數組(array-oriented)的語法,它更像數學,方便科學家將數學公式轉化為R代碼。在使用R時,要盡量用array的方式思考,避免for循環。

  這是為什么呢?原因在於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函數

 10. example匯總

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表示按列

    • FUN: 自定義的調用函數

    • …: 更多參數,可選

  例1:比如,對一個矩陣的每一行求和,下面就要用到apply做循環了。

	> x<-matrix(1:12,ncol=3)
	> apply(x,1,sum)
	[1] 15 18 21 24

  例2:下面計算一個稍微復雜點的例子,按行循環,讓數據框的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種操作上面性能上的消耗。

	# 清空環境變量
	> 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函數使用好。

10. example匯總

 Example_1

  split函數能夠分解類型更加復雜的對象,我們看下面的例子:我加載了一個叫datasets(數據集)的包,然后觀察里面的airquality(空氣質量)數據框,你可以看到數據的前6行,數據大概有100行。我們可以看到有Ozone、 Solar.R、 Wind、Tem等的測量值,比如我只想計算Ozone、Solar.R、 Wind和Tem在一個月內的平均值。那么我需要做的是,把這個數據框按月分組,然后利用apply函數或者是colMeans函數來計算不同變量的列均值。

	library(datasets)
	head(airquality)
	s <- split(airquality,airquality$Month)
	class(s)
	lapply(s, function(x) colMeans(x[,c("Ozone", "Solar.R", "Wind", "Temp")]))
	lapply(s, function(x) colMeans(x[,c("Ozone", "Solar.R", "Wind", "Temp")],na.rm=TRUE))
 Example_2

  在array上,沿margin方向,依次調用 FUN 。返回值為vector。margin表示數組引用的第幾維下標(即array[index1, index2, ...]中的第幾個index),1對應為1表示行,2表示列,c(1,2)表示行列。margin=1時, apply(a, 1, sum) 等效於下面的操作

	a <- array(c(1:24), dim=c(2,3,4))
	result=c()
	for (i in c(1:dim(a)[1])) {
	    result <- c(result, sum(a[i,,]))
	}

  經實測,只能用在二維及以上的array上,不能用在vector上(如果要應用於vector,請使用 lapply 或 sapply )。以matrix為例,如下

	> m <- matrix(c(1:10), nrow=2)
	> m
	  [,1] [,2] [,3] [,4] [,5]
	[1,]    1    3    5    7    9
	[2,]    2    4    6    8   10
	> apply(m, 1, sum)
	[1] 25 30
	> apply(m, 2, sum)
	[1]  3  7 11 15 19
 Example_3

  一維array的例子(即vector)

	> v <- c(1:5)
	> ind <- c('a','a','a','b','b')
	> tapply(v, ind)
	[1] 1 1 1 2 2
	> tapply(v, ind, sum)
	a b 
	6 9 
	> tapply(v, ind, fivenum)
	$a
	[1] 1.0 1.5 2.0 2.5 3.0
	
	$b
	[1] 4.0 4.0 4.5 5.0 5.0

  二維array的例子(即matrix)

	> m <- matrix(c(1:10), nrow=2)
	> m
	     [,1] [,2] [,3] [,4] [,5]
	[1,]    1    3    5    7    9
	[2,]    2    4    6    8   10
	> ind <- matrix(c(rep(1,5), rep(2,5)), nrow=2)
	> ind
	     [,1] [,2] [,3] [,4] [,5]
	[1,]    1    1    1    2    2
	[2,]    1    1    2    2    2
	> tapply(m, ind)
	 [1] 1 1 1 1 1 2 2 2 2 2
	> tapply(m, ind, mean)
	1 2 
	3 8 
	> tapply(m, ind, fivenum)
	$`1`
	[1] 1 2 3 4 5
	
	$`2`
	[1]  6  7  8  9 10
 Example_4
  • by

      by(dataframe, INDICES, FUN, ..., simplify=TRUE)
    

  by 可以當成dataframe上的 tapply 。 indices 應當和dataframe每列的長度相同。返回值是 by 類型的object。若simplify=FALSE,本質上是個list。

	> df <- data.frame(a=c(1:5), b=c(6:10))
	> ind <- c(1,1,1,2,2)
	> res <- by(df, ind, colMeans)
	 > res
	ind: 1
	a b 
	2 7 
	------------------------------------------------------------ 
	ind: 2
	  a   b 
	4.5 9.5 
	> class(res)
	[1] "by"
	> names(res)
	[1] "1" "2"
 Example_5

  在 list 上逐個元素調用 FUN 。可以用於dataframe上,因為dataframe是一種特殊形式的list。例

	> lst <- list(a=c(1:5), b=c(6:10))
	> lapply(lst, mean)
	$a
	[1] 3
	
	$b
	[1] 8
	
	> lapply(lst, fivenum)
	$a
	[1] 1 2 3 4 5
	
	$b
	[1]  6  7  8  9 10

  比 lapply 多了一個 simplify 參數。如果 simplify=FALSE ,則等價於 lapply 。否則,在上一種情況的基礎上,將 lapply 輸出的list簡化為vector或matrix。例

	> lst <- list(a=c(1:5), b=c(6:10))
	> sapply(lst, mean)
	a b 
	3 8 
	> sapply(lst, fivenum)
	  a  b
	[1,] 1  6
	[2,] 2  7
	[3,] 3  8
	[4,] 4  9
	[5,] 5 10
 Example_6
  • tapply實現crosstable功能

  以一個例子演示。原始數據為按年份year、地區loc和商品類別type進行統計的銷售量。我們要制作兩個銷售總量的crosstable,一個以年份為行、地區為列,一個以年份為行,類別為列。

	> df <- data.frame(year=kronecker(2001:2003, rep(1,4)), loc=c('beijing','beijing','shanghai','shanghai'), type=rep(c('A','B'),6), sale=rep(1:12))
	> df
	  year		loc type sale
	1  2001  beijing	 A	 1
	2  2001  beijing	 B	 2
	3  2001 shanghai	 A	 3
	4  2001 shanghai	 B	 4
	5  2002  beijing	 A	 5
	6  2002  beijing	 B	 6
	7  2002 shanghai	 A	 7
	8  2002 shanghai	 B	 8
	9  2003  beijing	 A	 9
	10 2003  beijing	 B	10
	11 2003 shanghai	 A	11
	12 2003 shanghai	 B	12
	> tapply(df$sale, df[,c('year','loc')], sum)
	    loc
	year	beijing shanghai
	  2001		 3		  7
	  2002		11		 15
	  2003		19		 23
	> tapply(df$sale, df[,c('year','type')], sum)
	    type
	year	 A  B
	  2001  4  6
	  2002 12 14
	  2003 20 22
 Example_7
	# code1
	x <- iris[,1:4]
	names(x) <- c("x1","x2","x3","x4")
	aggregate(x1+x2+x3+x4~x1,FUN=sum,data=x)
	#y <- subset(x, x1==4.4)
	#sum(y)
	with(x, sapply(split((x1 + x2 + x3 + x4), x1), sum)) #  s  stands for  simplify
	with(x, lapply(split((x1 + x2 + x3 + x4), x1), sum)) #  l  stands for list
	with(x, rapply(split((x1 + x2 + x3 + x4), x1), sum)) #  r  stands  for  recursive
	with(x, tapply((x1 + x2 + x3 + x4), INDEX=x1 , sum)) #  t  stands  for  table
	with(x, vapply(split((x1 + x2 + x3 + x4), x1), sum, FUN.VALUE=1))
	with(x, by((x1 + x2 + x3 + x4), x1, sum))
			
	# code1
	urls <- c("http://stat.ethz.ch/R-manual/R-devel/library/base/html/connections.html","http://en.wikipedia.org/wiki/Xz")
	x1=lapply(urls,readLines)
	x2=sapply(urls,readLines)
	x3=mapply(con=urls,readLines)
	x4=vapply(urls, function(i)  list(readLines(i)), list(1))
 Example_8
	theMatrix <- matrix(1:9, nrow = 3) 
	# 對行進行累加運算
	apply(theMatrix, 1, sum)   
	# 對列進行累加運算
	apply(theMatrix, 2, sum)   
	# same as
	rowSums(theMatrix)  
	colSums(theMatrix)  
	
	# apply
	theMatrix[2, 1] <- NA  
	apply(theMatrix, 1, sum)  
	apply(theMatrix, 1, sum, na.rm = TRUE)
	
	# lapply sapply
	theList <- list(A = matrix(1:9, 3), B = 1:8, c = matrix(1:4, 2))  
	lapply(theList, sum)
	sapply(theList, sum)
	
	# mapply
	firstList <- list(A = matrix(1:16, 4), B = matrix(1:9, 3))  
	secondList <- list(A = matrix(1:9, 3), B = matrix(1:16, 8))
	simpleFunc <- function(x, y) {  
	  # NROW函數返回對象的行數
	  NROW(x) + NROW(y) 
	}
	mapply(simpleFunc, firstList, secondList) 
 Example_9
# 使用tapply,sapply(lapply)函數可以快速實現功能和有效減少代碼量

tapply(x,f,g)  -- x為向量,f為因子列,g為操作函數,相對數據框進行類似操作可以用by函數
sapply(list,g) -- g為操作函數,返還結果為向量,而lapply返還結果為list形式。常與split結合使用

數據為1001路公交車不同站點上車人數統計

線路名稱 車牌號 到達站點 上車人數
1001 粵BM8475 14 11
1001 粵BM8475 13 3
1001 粵BM8475 12 10
1001 粵BM8475 10 5
1001 粵BM8475 8 1
1001 粵BM8475 14 11
1001 粵BM8475 13 3
1001 粵BM8475 12 10
1001 粵BM8475 10 5
1001 粵BM8475 8 1
1001 粵BM8476 14 11
1001 粵BM8476 13 3
1001 粵BM8476 12 10
1001 粵BM8476 10 5
1001 粵BM8476 8 1
1001 粵BM8476 14 11
1001 粵BM8476 13 3
1001 粵BM8476 12 10
1001 粵BM8476 10 5
1001 粵BM8476 8 1
# 統計不同站點的上車人數

	(Freq <- tapply(data[,4], data[,3], sum))
	  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16 
	120 257 325 164 174 186 205  80 118 267 259 130  77 541 704 133 

# 按照車牌號分類,分別統計不同站點上車人數

	data <- split(data, data$車牌號)   ## 對數據按照車牌號分組,拆成列表
	Freq <- sapply(data, function(data){
		## 按照車牌號統計不同站點上車人數
	    tapply(data$上車人數, data$到達站點, sum)
	})  
	## 查看結果
	Freq[1] 
	$粵BM8475
	 1   2   3   4   5   7   8   9  10  12  13  14  15 
	14  34  12   6  15   2   4  39   7  34   3  27   1

# R code run
	
	mydata <- read.table("clipboard",header=T)
	class(mydata)
	dim(mydata)
	head(mydata)
	data <- mydata
	
	(Freq <- tapply(data[,4], data[,3], sum))
	
	data <- split(data, data$車牌號)
	Freq <- sapply(data, function(data){
	  tapply(data$上車人數, data$到達站點, sum)
	})  
	Freq[1] 
	class(Freq)
	data.frame(Freq)

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM