為什么用apply
因為我是一個程序員,所以在最初學習R的時候,當成“又一門編程語言”來學習,但是怎么學都覺得別扭。現在我的看法傾向於,R不是一種通用型的編程語言,而是一種統計領域的軟件工具。因此,不能用通用型編程的思維來設計R代碼。在Andrew Lim關於R和Python的對比回答中,R是一種面向數組(array-oriented)的語法,它更像數學,方便科學家將數學公式轉化為R代碼。而Python是一種通用編程語言,更工程化。在使用R時,要盡量用array的方式思考,避免for循環。不用循環怎么實現迭代呢?這就需要用到apply
函數族。它不是一個函數,而是一族功能類似的函數。
概述
apply系列函數的基本作用是對數組(array,可以是多維)或者列表(list)按照元素或元素構成的子集合進行迭代,並將當前元素或子集合作為參數調用某個指定函數。vector是一維的array,dataframe可以看作特殊的list。
這些函數間的關系
作用目標 | 在每個元素上應用 | 在子集合上應用 |
---|---|---|
array | apply |
tapply |
list | lapply (...) |
by |
其中lapply(...)
包括一族函數
lapply
|
|-> 簡化版: sapply
| | -> 可設置返回值模板: vapply
| |-> 多變量版: mapply
|
|-> 遞歸版: rapply
另外vector比較奇怪,vector是一維的array,但是卻不全是和array使用相同的函數。在按元素迭代的情況下,使用和list一樣的lapply
函數;而在按子集合迭代的情況下,tapply
和by
都能用,只是返回值形式不同。
功能與語法描述
apply
apply(array, margin, FUN, ...)
在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
tapply
tapply(array, indices, margin, FUN=NULL, ...)
按indices
中的值分組,把相同值對應下標的array中的元素形成一個集合,應用到FUN
。類似於group by indices的操作。如果FUN
返回的是一個值,tapply
返回vector;若FUN
返回多個值,tapply
返回list。vector或list的長度和indices
中不同值的個數相等。
當FUN
為NULL
的時候,返回一個長度和array中元素個數相等的vector,指示分組的結果,vector中相等的元素所對應的下標屬於同一組。例如,返回c(1, 2, 1, 3, 2), 表示根據傳入的indices
,第1、3個元素作為一組,第2、5個元素作為一組,第4個元素作為一組。
一維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
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"
lapply
lapply(list, FUN, ...)
在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
sapply
sapply(list, FUN, ..., simplify, USE.NAME=TRUE)
比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
vapply
vapply(list, FUN, FUN.VALUE, ..., USE.NAME=TRUE)
vapply
類似於sapply
,但是提供了第三個參數FUN.VALUE
用以指明返回值的形式,可以看作返回值的模板。例
> lst <- list(a=c(1:5), b=c(6:10))
> res <- vapply(lst, function(x) c(min(x), max(x)), c(min.=0, max.=0))
> res
a b
min. 1 6
max. 5 10
mapply
mapply(FUN, ..., MoreArgs=NULL, SIMPLIFY=TRUE, USE.NAMES=TRUE)
mapply
是多變量版的sapply
,參數(...)部分可以接收多個數據,mapply
將FUN
應用於這些數據的第一個元素組成的數組,然后是第二個元素組成的數組,以此類推。要求多個數據的長度相同,或者是整數倍關系。返回值是vector或matrix,取決於FUN
返回值是一個還是多個。
> mapply(sum, list(a=1,b=2,c=3), list(a=10,b=20,d=30))
a b c
11 22 33
> mapply(function(x,y) x^y, c(1:5), c(1:5))
[1] 1 4 27 256 3125
> mapply(function(x,y) c(x+y, x^y), c(1:5), c(1:5))
[,1] [,2] [,3] [,4] [,5]
[1,] 2 4 6 8 10
[2,] 1 4 27 256 3125
rapply
rapply(list, FUN, classes="ANY", deflt=NULL, how=c("unlist", "replace", "list"), ...)
rapply
是遞歸版的lappy
。基本原理是對list作遍歷,如果其中有的元素仍然是list,則繼續遍歷;對於每個非list類型的元素,如果其類型是classes
參數指定的類型之一,則調用FUN
。classes="ANY"表示匹配所有類型。
how參數用來指定操作方式,有三種:
- "replace" 直接用調用
FUN
后的結果替換原list中原來的元素 - "list" 新建一個list,元素類型在
classes
中的,調用FUN
;不在classes
中的類型,使用deflt
。會保留原始list的結構。 - "unlist" 相當於對"list"模式下的結果調用
unlist(recursive=TRUE)
> lst <- list(a=list(aa=c(1:5), ab=c(6:10)), b=list(ba=c(1:10)))
> lst
$a
$a$aa
[1] 1 2 3 4 5
$a$ab
[1] 6 7 8 9 10
$b
$b$ba
[1] 1 2 3 4 5 6 7 8 9 10
> rapply(lst, sum, how='list')
$a
$a$aa
[1] 15
$a$ab
[1] 40
$b
$b$ba
[1] 55
> rapply(lst, sum, how='unlist')
a.aa a.ab b.ba
15 40 55
第二個是關於classes
和deflt
參數使用的例子
> lst2
$a
$a$aa
[1] 1 2 3 4 5
$a$ab
[1] 6 7 8 9 10
$b
$b$ba
[1] "I am a string"
> rapply(lst2, sum, how='unlist')
Error in .Primitive("sum")("I am a string", ...) :
invalid 'type' (character) of argument
> rapply(lst2, sum, classes=c('integer'), deflt=-1, how='unlist')a.aa a.ab b.ba
15 40 -1
> rapply(lst2, nchar, classes=c('character'), deflt=as.integer(NA), how='unlist')
a.aa a.ab b.ba
NA NA 13
eapply
environment上的的apply。從沒用過environment,暫時不研究了。
應用
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
Reference
https://screamyao.wordpress.com/2011/05/03/various-apply-functions-in-r-explained/
https://nsaunders.wordpress.com/2010/08/20/a-brief-introduction-to-apply-in-r/
http://www.ats.ucla.edu/stat/r/library/advanced_function_r.htm#apply