R 學習筆記: plyr 包還得學習一個
plyr 包中有若干的具有不同特性的 apply 類的函數. 大神 Hadley Wickham 在其關於 plyr 包的文章中抽象出了數據分析中的範式: 分割-應用-整合 (split-apply-combine). 這個範式正是 plyr 包的基礎. 同時也被 Python 的數據分析工具包 Pandas 非常好地採用了.
R 的 for 循環的效率是知名的低, plyr 中提供的 apply 類型函數可以很大程度上去替代在數據分析中出現的一些 for 循環. 然而, apply 函數的意義還不僅僅是提高了速度, 而是把數據處理的邏輯給抽象出來, 我們可以更關注數據整體是採用怎樣的邏輯被處理的, 而不必把過多的精力放在每一個數據是怎麼被計算出來的.
栗子
我們不妨以著名的 iris 數據為例子. 我們可以使用 Hadley Wickham 的另一個包 tidyr 來處理 iris 數據, 不過這裡我們先使用 utils 中的 stack 函數, tidyr 的使用會在相應的筆記中說明.
long.iris <- stack(iris, select=-Species)nlong.iris <- data.frame(n long.iris,n Species=rep(n iris[[Speices]], 4n )n)ncolnames(long.iris)n# [1] "values" "ind" "Species"n
如果我們需要去看一下對於不同物種的不同的屬性的最大和最小值. 我們自然是可以使用 summary 這樣的函數 summary(iris). 也可以使用 tapply 函數來根據 index 去計算.
tapply(n long.iris$values,n INDEX=paste(n long.iris$Species,n long.iris$ind,n sep=.n ),n function(x) {c(max(x), min(x))}n)n
這樣可以計算出來結果, 不過結果是 list 的數據結構, 不方便進一步的計算, 我們希望結果能夠成為一個 data.frame. 如果使用 plyr 包中的 ddply 則可以很好解決問題.
ddply(n long.iris,n .(Species, ind),n function(x) {c(max(x$values), min(x$values))}n)nn# Species ind V1 V2n# 1 setosa Petal.Length 1.9 1.0n# 2 setosa Petal.Width 0.6 0.1n# 3 setosa Sepal.Length 5.8 4.3n# 4 setosa Sepal.Width 4.4 2.3n# 5 versicolor Petal.Length 5.1 3.0n# 6 versicolor Petal.Width 1.8 1.0n# 7 versicolor Sepal.Length 7.0 4.9n# 8 versicolor Sepal.Width 3.4 2.0n# 9 virginica Petal.Length 6.9 4.5n# 10 virginica Petal.Width 2.5 1.4n# 11 virginica Sepal.Length 7.9 4.9n# 12 virginica Sepal.Width 3.8 2.2n
plyr 包中的 apply 函數
首先, 我們先要回顧一下 R 中最基本的數據結構, 向量 (vector), 列表 (list), 和數組 (array).
另外, 矩陣 (matrix) 是特殊的僅僅有兩維的數組, 而數據表 (data.frame) 是特殊的具有矩陣特性的列表. 我們用 a 代替數組, 用 l 代替列表, 用 d 代替數據表, 用 - 代替無. 那麼我們就可以做一下排列組合, apply 函數的輸入的數據結構可以有 a, l, d 三種, 輸出的數據結構可以有 a, l, d, - 四種, - 代表不輸出. 這樣我們就有了 12 種組合, 這也是 plyr 中的 12 種 apply 函數.| | Array | Data Frame | List | Discard |
|------------------|----------|---------------------|----------|--------------|
| Array | aaply | adply | alply | a_ply |
| Data Frame | daply | ddply | dlply | d_ply |
| List | laply | ldply | llply | l_ply |
如 aaply 代表輸入的是數組, 輸出的是數組; adply 代表輸入的是數組, 輸出的是數據表; a_ply 輸入的是數組, 而不輸出.
a*ply
對於 a*ply 函數, 即 aaply, adply, alply 和 a_ply 函數, 有:
a*ply(.data, .margins, .fun, ..., .progress=none)n
- .data 接受的是輸出的多維數組
- .margins 是一個向量, 包含需要加以考慮的數組的維度,
例如對於三維數組, 要對前兩維進行計算則 .margins=c(1, 2)
- .fun 接受對分組進行操作的函數.
a <- array(rnorm(27), c(3, 3, 3))nnadply(a, c(1, 2), mean)nn# X1 X2 V1n# 1 1 1 -0.48106276n# 2 2 1 0.69799472n# 3 3 1 -0.21132999n# 4 1 2 0.54177418n# 5 2 2 0.64463422n# 6 3 2 0.93857373n# 7 1 3 -0.86448199n# 8 2 3 -0.93706920n# 9 3 3 -0.08792167n
對於對分組進行操作的 .fun 則需要注意根據所輸入的數組和選用的 margin 進行匹配.
如果輸入的是三維數組, 選擇其中一個維度進行分組, 那麼 .fun 則應該是對二維數組, 即矩陣的操作.aaply(a, c(1), class)nn# 1 2 3n# "matrix" "matrix" "matrix"nnaaply(a, c(1, 2), class)nn# X2n# X1 1 2 3n# 1 "numeric" "numeric" "numeric"n# 2 "numeric" "numeric" "numeric"n# 3 "numeric" "numeric" "numeric"n
d*ply
對於 d*ply 函數, 即 daply, ddply, dlply 和 d_ply 函數, 有:
d*ply(.data, .variables, .fun, ..., .progress=none)n
- .data 接受的是輸出的數據表
- .variables 是一個向量, 包含需要加以考慮的列名, 可以用 c(Col1, Col2) 這樣的形式或者 .(Col1, Col2). 其中 .() 這個函數是用來把沒有引號標記的字元轉為標記字元串.
- .fun 接受對分組進行操作的函數.
偷個懶, 還是上面用過的例子咯.
daply(n long.iris,n c(Species, ind),n function(x) {c(max(x$values), min(x$values))}n)nn# , , = 1n#n# indn# Species Petal.Length Petal.Width Sepal.Length Sepal.Widthn# setosa 1.9 0.6 5.8 4.4n# versicolor 5.1 1.8 7.0 3.4n# virginica 6.9 2.5 7.9 3.8n#n# , , = 2n#n# indn# Species Petal.Length Petal.Width Sepal.Length Sepal.Widthn# setosa 1.0 0.1 4.3 2.3n# versicolor 3.0 1.0 4.9 2.0n# virginica 4.5 1.4 4.9 2.2n#n
對於 d*ply 函數,
其 .fun 所接受的函數應該是對於和輸入數據表同樣的列數而不同行數的數據表進行操作的函數.dim(long.iris)nn# 600 3nnddply(n long.iris,n .(Species, ind),n function(x) {c(class(x), dim(x))}n)nn# Species ind V1 V2 V3n# 1 setosa Petal.Length data.frame 50 3n# 2 setosa Petal.Width data.frame 50 3n# 3 setosa Sepal.Length data.frame 50 3n# 4 setosa Sepal.Width data.frame 50 3n# 5 versicolor Petal.Length data.frame 50 3n# 6 versicolor Petal.Width data.frame 50 3n# 7 versicolor Sepal.Length data.frame 50 3n# 8 versicolor Sepal.Width data.frame 50 3n# 9 virginica Petal.Length data.frame 50 3n# 10 virginica Petal.Width data.frame 50 3n# 11 virginica Sepal.Length data.frame 50 3n# 12 virginica Sepal.Width data.frame 50 3nn50 * 12nn# [1] 600n
l*ply
對於 l*ply 函數, 即 laply, ldply, llply 和 l_ply 函數, 有:
l*ply(.data, .fun, ..., .progress=none)n
- .data 接受的是輸出的數據表
- .fun 接受對分組進行操作的函數.
舉個簡單的例子:
llply(long.iris, unique)nn# $valuesn# [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.4 4.8 4.3 5.8 5.7 5.2 5.5 4.5 5.3 7.0 6.4 6.9 6.5n# [20] 6.3 6.6 5.9 6.0 6.1 5.6 6.7 6.2 6.8 7.1 7.6 7.3 7.2 7.7 7.4 7.9 3.5 3.0 3.2n# [39] 3.1 3.6 3.9 3.4 2.9 3.7 4.0 3.8 3.3 4.1 4.2 2.3 2.8 2.4 2.7 2.0 2.2 2.5 2.6n# [58] 1.4 1.3 1.5 1.7 1.6 1.1 1.2 1.0 1.9 0.2 0.4 0.3 0.1 0.5 0.6 1.8 2.1n#n# $indn# [1] Sepal.Length Sepal.Width Petal.Length Petal.Widthn# Levels: Petal.Length Petal.Width Sepal.Length Sepal.Widthn#n# $Speciesn# [1] setosa versicolor virginican# Levels: setosa versicolor virginican
m*ply
在 plyr 包中還有一系列對應於 mapply 的函數,
包括 maply, mdply, mlply, m_ply 等.m*ply(.data, .fun, ..., .progress=none)n
- .data 接受一個矩陣或者數據表
- .fun 是輸入的函數
m*ply 函數接受一個矩陣, 或者數據表,
按照行的方式去處理數據,每一行的一個列對應於函數的一個參數.所以可以對應一個函數有不同的輸入參數從而產生不同的結果.例如生成若干隨機數:
b <- data.frame(n n=c(5, 5, 5),n mean=c(1, 20, 300),n sd=c(1, 20, 300)n)nnmdply(b, rnorm)nn# n mean sd V1 V2 V3 V4 V5n# 1 5 1 1 1.13 1.23 0.39 3.34 0.19n# 2 5 20 20 17.37 -23.71 33.75 27.07 0.37n# 3 5 300 300 507.72 634.58 -70.71 643.29 555.39n
r*ply
對應於 replicate 函數, plyr 中的函數是 r*ply.
其也包括 raply, rdply, rlaply, r_ply 等.r*ply(.n, .expr, ..., .progress=none)n
- .n 是要重複的個數
- .expr 是需要重複的表達式
例如生成隨機數:
raply(5, rnorm(5))nn# 1 2 3 4 5n# [1,] 0.74 0.05 -2.08 -0.57 0.12n# [2,] -1.23 0.32 -0.58 0.19 -0.31n# [3,] -1.10 0.32 0.07 -0.97 -0.05n# [4,] 0.82 -0.07 -0.12 1.53 -0.32n# [5,] 0.83 -0.20 -0.61 -0.42 -0.56n
總結
於是這主要的十二個 apply 函數介紹完了.
我們可以發現, 對於 a*ply, d*ply, 和 l*ply 函數,他們重要的區別在於如何確定分組.a*ply 函數是根據 .margins 指定的輸入數組的維度的值進行分組計算,d*ply 函數是根據 .variables 指定的列名進行分組計算,而 l*ply 函數類似於 lapply 對列表的每一個元素進行計算.ActionScript 3輔助函數
Hadley 大神把能想到的都做到了.
顯然很多函數並不能直接用在 plyr 中, plyr 包中有很多輔助函數.- splat: 把函數轉換為可以接受一個列表進行輸入, 配合 m*ply 有奇效
splat(rnorm)(list(n=5, mean=2))nn# [1] 2.505678 2.944919 1.485397 1.536358 2.216418n
- each: 同一個值傳入不同的函數, 輸出向量等, 可以配合 ddply 使用.
each(max, min)(seq(5))nn# max minn# 5 1n
- colwise: 把一個標量函數轉為向量化的函數
class(iris)nn# [1] "data.frame"nncolwise(class)(iris)nn# Sepal.Length Sepal.Width Petal.Length Petal.Width Speciesn# 1 numeric numeric numeric numeric factorn
- failwith: 如果一個函數報錯了, 整個計算就錯了, 算的豈不可惜?
可以用 failwith 對於報錯的函數返回特定的值, 而不中斷計算.
sum(c(1, 2, 3))nn# Error in sum(c(1, 2, "3")) : invalid type (character) of argumentnnfailwith(錯啦, sum)(c(1, 2, 3))nn# Error in f(...) : invalid type (character) of argumentn# [1] "錯啦"nnfailwith(又錯啦, sum, quiet=T)(c(1, 2, 3))n# [1] "又錯啦"n
推薦閱讀:
※機器學習注釋剪切分支點
※使用API訪問ClinVar數據
※碼農轉臨床,未來想做癌症數據分析,解決癌症問題,可行嗎?
※生信進階第2課-你應該知道的人類基因組信息
※P值與基因組學(1):從fastq文件的分析的分析談起