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)