陣列的迭代
lapply
、vapply
以及 sapply
這幾個函數也可以用在矩陣與陣列的迭代上,不過這樣使用的結果通常不如使用者所預期,它會將矩陣或陣列直接視為一般的向量,將每個元素逐一交給指定的函數來處理。
x.mat <- matrix(1:9, 3, 3) lapply(x.mat, sum)
[[1]] [1] 1 [[2]] [1] 2 [[3]] [1] 3 [[4]] [1] 4 [[5]] [1] 5 [[6]] [1] 6 [[7]] [1] 7 [[8]] [1] 8 [[9]] [1] 9
若要對矩陣的一整個行(column)或列(row)來處理,可以使用 matlab
這個套件:
install.packages("matlab") library(matlab)
matlab
套件中包含一些運作方式類似 Matlab 的函數,而該套件在載入之後,會將 base
、stats
與 utils
內建套件中的一些函數覆蓋掉,在使用完畢之後,若要讓這些函數恢復,可以執行 detach("package:matlab")
將 matlab
卸載即可。matlab
套件所提供的 apply
函數其功能類似 lapply
,不過它可以允許對矩陣的行或列進行指定的運算,例如計算矩陣每個列的總和:
apply(x.mat, 1, sum)
[1] 12 15 18
apply
的第一個參數是輸入的矩陣或陣列,第二個參數是指定如何迭代矩陣或陣列中的資料,若指定為 1
則代表以列(row)的方式迭代,而若指定為 2
則代表以行(column)的方式迭代。而上面這行效果就等同於 rowSums
:
rowSums(x.mat)
[1] 12 15 18
matlab
的 apply
函數可以跟任意的函數結合,產生各式各樣的變化:
apply(x.mat, 2, summary)
[,1] [,2] [,3] Min. 1.0 4.0 7.0 1st Qu. 1.5 4.5 7.5 Median 2.0 5.0 8.0 Mean 2.0 5.0 8.0 3rd Qu. 2.5 5.5 8.5 Max. 3.0 6.0 9.0
apply
也可以套用在 data frame 上:
(x.df <- data.frame( name = c("foo", "bar", "foo.bar"), value = c(1.2, 6.9, 2.4) ))
name value 1 foo 1.2 2 bar 6.9 3 foo.bar 2.4
apply(x.df, 1, toString)
[1] "foo, 1.2" "bar, 6.9" "foo.bar, 2.4"
apply(x.df, 2, toString)
name value "foo, bar, foo.bar" "1.2, 6.9, 2.4"
當 apply
以行的方式迭代時,其作用會跟 sapply
相同(data frame 可以視為一種巢狀的列表,而其每個列表元素的長度都相等):
sapply(x.df, toString)
name value "foo, bar, foo.bar" "1.2, 6.9, 2.4"
mapply
與 Vectorize
函數
lapply
有一個缺點就是它一次只能對一個列表變數的元素做迭代處理,另外在指定的處理函數當中,也無法獲取每個列表元素的名稱。
mapply
是一個可以同時處理多個列表變數迭代的函數,而若需要取得列表元素的名稱,也可以透過第二個參數把名稱傳入。
x.list <- list( foo = 12, bar = 34 ) my.fun <- function(name, value) { paste(name, "is", value) } mapply(my.fun, names(x.list), x.list)
foo bar "foo is 12" "bar is 34"
mapply
預設會跟 sapply
函數一樣將傳回值簡化,若要改變這個行為,可以加上 SIMPLIFY = FALSE
參數。
Vectorize
是一個可以將非向量化函數轉換為向量化寫法的函數(但效能不會特別的改善),假設我們有一個無法以向量化表示的函數:
scalar.fun <- function(x) { switch(x, foo = "FOO", bar = "BAR", "Other") }
這個函數若直接傳入一個向量參數,會產生錯誤:
input <- c("foo", "bar", "foo.bar") scalar.fun(input)
Error in switch(x, foo = "FOO", bar = "BAR", "Other") : EXPR 必須是長度為 1 的向量
這種狀況就可以使用 Vectorize
將其包裝成可以處理向量的形式:
vectorize.fun <- Vectorize(scalar.fun) vectorize.fun(input)
foo bar foo.bar "FOO" "BAR" "Other"
資料分組與迭代
在審視資料時,時常會需要將資料先分組後再計算各組的某些統計量,以 iris
資料為例:
head(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species 1 5.1 3.5 1.4 0.2 setosa 2 4.9 3.0 1.4 0.2 setosa 3 4.7 3.2 1.3 0.2 setosa 4 4.6 3.1 1.5 0.2 setosa 5 5.0 3.6 1.4 0.2 setosa 6 5.4 3.9 1.7 0.4 setosa
假設我們要依據 Species
來分組,計算 Sepal.Length
的平均,首先將資料分組:
(group.data <- with(iris, split(Sepal.Length, Species)))
$setosa [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.6 5.0 4.4 4.9 5.4 4.8 [13] 4.8 4.3 5.8 5.7 5.4 5.1 5.7 5.1 5.4 5.1 4.6 5.1 [25] 4.8 5.0 5.0 5.2 5.2 4.7 4.8 5.4 5.2 5.5 4.9 5.0 [37] 5.5 4.9 4.4 5.1 5.0 4.5 4.4 5.0 5.1 4.8 5.1 4.6 [49] 5.3 5.0 $versicolor [1] 7.0 6.4 6.9 5.5 6.5 5.7 6.3 4.9 6.6 5.2 5.0 5.9 [13] 6.0 6.1 5.6 6.7 5.6 5.8 6.2 5.6 5.9 6.1 6.3 6.1 [25] 6.4 6.6 6.8 6.7 6.0 5.7 5.5 5.5 5.8 6.0 5.4 6.0 [37] 6.7 6.3 5.6 5.5 5.5 6.1 5.8 5.0 5.6 5.7 5.7 6.2 [49] 5.1 5.7 $virginica [1] 6.3 5.8 7.1 6.3 6.5 7.6 4.9 7.3 6.7 7.2 6.5 6.4 [13] 6.8 5.7 5.8 6.4 6.5 7.7 7.7 6.0 6.9 5.6 7.7 6.3 [25] 6.7 7.2 6.2 6.1 6.4 7.2 7.4 7.9 6.4 6.3 6.1 7.7 [37] 6.3 6.4 6.0 6.9 6.7 6.9 5.8 6.8 6.7 6.7 6.3 6.5 [49] 6.2 5.9
接著再以 mean
配合 lapply
來處理:
lapply(group.data, mean)
$setosa [1] 5.006 $versicolor [1] 5.936 $virginica [1] 6.588
或是使用 sapply
等函數亦可:
sapply(group.data, mean)
setosa versicolor virginica 5.006 5.936 6.588
像這樣典型的資料分組與迭代的動作,可以使用 tapply
函數來處理:
with(iris, tapply(Sepal.Length, Species, mean))
setosa versicolor virginica 5.006 5.936 6.588
plyr 套件
R 內建的 *apply
函數對於許多的迭代問題而言是一個很好用的工具,不過它們還是有一些小缺點,像函數的名稱並沒有清楚表明其作用(例如 tapply
的 t
到底代表什麼意思我們也不是很清楚),而不同函數的參數順序也沒有一致性,另外其傳回值的變數型態並沒有很彈性,造成有時候在使用上會不太方便。
plyr
套件提供了一系列完整的 **ply
函數,不僅參數格式統一,輸入與輸出的變數型態也很齊全,它的函數名稱有一定的規則,第一個字母代表輸入的變數型態,而第二個字幕則代表輸出的變數型態,例如 llply
函數就是輸入與輸出都是列表變數(list),所以它可以直接用來替代 lapply
函數:
x.list <- list( a = rgeom(6, prob = 0.1), b = rgeom(6, prob = 0.4), c = rgeom(6, prob = 0.7) ) llply(x.list, unique)
$a [1] 2 13 1 3 8 $b [1] 1 2 0 $c [1] 0 1
laply
則代表輸入變數為列表,而輸出為向量:
laply(x.list, length)
[1] 6 6 6
raply
是 replicate
的一個替代函數,傳回值是一般的向量,而 rlply
與 rdply
函數則可以將重複的結果以列表或 data frame 的形式傳回,另外 r_ply
會直接將結果丟棄,不傳回任何東西(適用於繪圖等情況)。
raply(3, rnorm(1))
[1] 0.08807394 -1.17962037 2.76096399
rlply(3, rnorm(1))
[[1]] [1] -1.242925 [[2]] [1] 0.3688085 [[3]] [1] 0.6099664
rdply(3, rnorm(1))
.n V1 1 1 0.6272091 2 2 -0.3050538 3 3 -0.6814648
r_ply(3, rnorm(1))
ddply
的輸入與輸出都是 data frame 變數,而且可以同時處理多個欄位的資料,可以用來替代 tapply
函數。假設我們要將 iris
這個 data frame 的所有資料都依照 Species
分組後計算平均值,可以這樣做:
ddply( iris, # 輸入的 data frame .(Species), # 依據 Species 分組 colwise(mean) # 對每個行(column)執行 mean 計算平均值 )
Species Sepal.Length Sepal.Width Petal.Length Petal.Width 1 setosa 5.006 3.428 1.462 0.246 2 versicolor 5.936 2.770 4.260 1.326 3 virginica 6.588 2.974 5.552 2.026
這裡使用 colwise
會自動對每一個欄位(除了第二個參數有指定的欄位之外)做指定的運算,不過它的限制就是每個欄位的計算方式都相同。
第二個參數中所使用的句點函數(.
)是 plyr
套件中所定義的一個特殊函數,其作用類似 ~
,是為了取得變數名稱而設計的,沒有特別的含義,以下幾種寫法的作用都是相同的:
ddply(iris, .(Species), colwise(mean)) ddply(iris, "Species", colwise(mean)) ddply(iris, ~ Species, colwise(mean))
我們也可以使用 summarize
的方式,自行指定要計算的欄位以及各欄位的計算方式:
ddply( iris, # 輸入的 data frame .(Species), # 依據 Species 分組 summarize, # 自行指定每個欄位的計算方式 SL.Mean = mean(Sepal.Length), # Sepal.Length 的平均值 PL.Max = max(Petal.Length) # Petal.Length 的最大值 )
Species SL.Mean PL.Max 1 setosa 5.006 1.9 2 versicolor 5.936 5.1 3 virginica 6.588 6.9
繼續閱讀: 12