R基礎包base的apply族函數採用向量化運算實現批量計算,相較於for循環語句其代碼更加簡潔、運行速度也更快,恰當地運用這些函數能夠極大提高代碼質量。本篇主要介紹apply族函數的apply(),lapply(),sapply(),mapply()和tapply()函數。
1 apply()函數官方文檔給出的apply()函數的語法結構如下:
apply(X, MARGIN, FUN, ...)apply()函數採用的是函數式編程(functional programming, FP),雖然它表面上只有三個參數,但由於它第三個參數FUN本身也是個函數,使得它能夠自動繼承函數FUN所有的參數,這些參數在語法結構中以三個點...表示。
X以數組(array)或矩陣(matrix)的形式儲存可作為函數FUN第一個參數的多組數據;X也可以為數據框(data.frame),但其各列數據的格式必須為同一性質(如均為數值型或均為文本型等),apply()函數會將其強制轉化為數組再進行運算,否則會報錯;MARGIN表示維度。對於矩陣而言,MARGIN = 1時,表示每行為一組函數FUN的第一個輸入參數;MARGIN = 2時表示每列為一組函數FUN的第一個輸入參數,MARGIN = c(1,2)表示每行每列的元素為函數FUN的第一個輸入參數;對於數組而言,其可能存在更高的維度,MARGIN = n,表示以它的第n維為分組依據;MARGIN為向量時,表示以多個維度作為分組依據,如MARGIN = c(2 : 4),表示以第2,3,4三個維度為分組依據。...為函數FUN除第一個參數外的其他參數,在調用時一般要指明參數名稱。比如我們想計算mtcars每列變量的平均值(mtcars雖然是數據框,但是其每列變量均是數值類型),可以使用如下for循環語句:
n <- length(mtcars) # 計算變量個數,用於確定循環次數
cmean <- c(1:n) # 定義儲存結果的變量
for(i in (1 : n)) {
cmean[i] <- mean(mtcars[,i])
}使用apply()語句如下:
cmean2 <- apply(mtcars, 2, mean)相比於for循環,apply()不用事先確定循環次數,也不需要引入過多的變量,運算速度也更快。
apply()也可以在自身參數位置上調用其他函數的參數
比如我們將mtcars的部分數據改為缺失值,在計算每列平均值時需要先移除平均值:
dta <- mtcars
dta[1,3] <- NA
# 使用for循環
n = length(dta)
cmean <- c(1:n)
for(i in (1 : n)) {
cmean[i] <- mean(dta[,i], na.rm = TRUE) # 在mean函數內調用na.rm參數
}
# 使用apply函數
cmean2 <- apply(dta, 2, mean, na.rm = TRUE) # 在apply函數內調用mean函數的na.rm參數需要注意的是,apply()只能實現單參數(且為第一個參數)的向量化運算,而其他參數均是全局參數
比較下面代碼中a、b、c和d的區別:
X <- matrix(c(1 : 9), ncol = 3, byrow = TRUE)
Y <- matrix(c(1 : 9), ncol = 3)
a <- X * Y
b <- Y * X
c <- apply(X, 2, "*", Y)
d <- apply(Y, 2, "*", X)「*」用於兩個同型矩陣相乘表示的是Hadamard乘積,即X和Y對應位置的數值相乘,這種矩陣乘法滿足交換律,因此a和b的結果一致。
c中X為分組參數且按列分組,Y為全局參數,相當於X每列構成的行向量(R中只有行向量)分別與Y相乘;d則恰好相反,是由Y每列構成的行向量與X相乘。需要注意的是,在apply()輸出結果中各組按是按列排序的(即輸出矩陣的列數與分組組數相同),因此各組相乘後的矩陣會被強制轉成一列,但這與預期目的是不一致的。
由上面的例子也可以發現,apply()的輸出結果的類型不具備一致性(可以為向量、矩陣,還可以為列表,但是不能人為控制),這也是很多情況下使用apply()函數不能達到預期目的的原因,因此當每組輸出結果不是單個數值或向量時並不推薦使用它。
2 lapply()函數lapply()函數的語法結構如下:
lapply(X, FUN, ...)
lapply()函數要求X必須為list類型,或者可以轉化為list的矩陣、數據框;lapply()函數沒有MARGIN參數,因為list的每個元素相當於一組;當X為數據框時,數據框的每個變量(即每列)會被轉化為list的一個元素;當X為矩陣時,矩陣的每個元素會被轉為list的一個元素;lapply()函數的輸出結果的類型具有一致性,是與輸入參數X同樣長度的list,每組結果對應輸出結果的一個元素。X <- matrix(c(1 : 9), ncol = 3, byrow = TRUE)
Y <- matrix(c(1 : 9), ncol = 3)
# 將X按列轉為list
Z <- as.list(as.data.frame(X))
c2 <- lapply(Z, "*", Y)c2是一個含3個元素的列表,每個元素均為一個3*3的矩陣,分別記錄每組的計算結果。相對於apply()函數,lapply()在計算結果較複雜時更有優勢。
3 sapply()函數sapply()函數的語法結構如下:
sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
sapply()函數對輸入參數的要求與lapply()完全一致;sapply()函數是lapply()的簡化版,參數simplify控制是否對輸出結果進行簡化,默認為TRUE,即將輸出結果轉化為矩陣;而當simplify = FASLE時,sapply()和lapply()功能完全一致。X <- matrix(c(1 : 9), ncol = 3, byrow = TRUE)
Y <- matrix(c(1 : 9), ncol = 3)
# 將X按列轉為list
Z <- as.list(as.data.frame(X))
# 結果轉化為矩陣,與apply()結果一樣
c3 <- sapply(Z, "*", Y)
# 結果不轉化為矩陣,與lapply()結果一樣
c4 <- sapply(Z, "*", Y, simplify = FALSE)apply()、lapply()和sapply()在語法和功能上都具有很大的相似性,在實際選用時可以根據輸入參數類型和想要的輸出參數類型進行選擇。
另外,這三個函數均只能針對單參數進行向量化運算,要想實現多參數的向量化運算需要使用mapply()函數。
4 mapply()函數mapply()函數的語法結構如下:
mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE,
USE.NAMES = TRUE)mapply()與前三個函數用法基本相似,主要區別有以下幾點:
...表示的是參與向量化運算的參數,而前三個函數表示的都是全局參數;全局參數以list的形式放置在參數MoreArgs中。X <- matrix(c(1 : 9), ncol = 3, byrow = TRUE)
fun <- function(x, y, z) (y + z)^x
mapply(fun, X[,1], X[,2], X[,3])
mapply(fun, X[,1], X[,2], MoreArgs = list(z = 2)) # z作為全局參數
mapply(fun, X[,1], MoreArgs = list(y = 2, z = 2)) # y和z作為全局參數
5 tapply()函數tapply()函數主要用於分組計算,語法結構如下:
tapply(X, INDEX, FUN = NULL, ..., default = NA, simplify = TRUE)
X為參與計算的指標,數據格式為向量或者數據框的某一列;INDEX為分組指標,數據格式為向量、數據框某一列或列表。tapply()可以實現常規的分組計算
比如我們按mtcars的cyl分組對mpg求平均值:
tapply(mtcars$mpg, mtcars$cyl, mean)當分組指標為多個時,要寫成list的形式
比如我們按mtcars的cyl和gear兩列分組對mpg求平均值:
tapply(mtcars$mpg, list(mtcars$cyl, mtcars$gear), mean)