使用apply族函數進行向量化運算替代for循環

2021-02-13 R語言學堂

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)

相關焦點

  • R語言向量化運算:apply函數族用法心得
    個人公眾號:數據科學家養成記 (微信ID:louwill12)當初入坑R語言的時候,就在各種場合看到老司機的忠告,「儘量避免使用循環!」一開始並不明白這其中的奧義,直到後來對R語言有深入接觸後,才領會R語言在向量化運算方面的強大功能。本篇內容就總結小編在使用R語言向量化運算apply函數族的一些心得體會。
  • R語言中的apply函數族
    前言apply函數族是R語言中數據處理的一組核心函數,通過使用apply函數,我們可以實現對數據的循環、分組、過濾、類型控制等操作
  • R語言——使用apply function替代for loop循環語句
    當想到循環的時候,我們一般都是指for或者while等等。雖然我個人非常喜歡使用for loop,但是不得不承認,for loop的運算速度相對來說並沒有那麼快。熟悉Python的朋友想必對vectorized operations並不陌生,而apply function就相當於R的vectorized operations,雖然嚴格意義上來說使用apply並不能算是。
  • 乾貨 掌握R語言中的apply函數族
    ,通過使用apply函數,我們可以實現對數據的循環、分組、過濾、類型控制等操作。但是,由於在R語言中apply函數與其他語言循環體的處理思路是完全不一樣的,所以apply函數族一直是使用者玩不轉一類核心函數。很多R語言新手,寫了很多的for循環代碼,也不願意多花點時間把apply函數的使用方法了解清楚,最後把R代碼寫的跟C似得,我嚴重鄙視只會寫for的R程式設計師。
  • R 語言 apply 族函數基本用法(一)
    R 語言有很多強大的循環函數,與其他程式語言(比如 Java、C/C++、Python、Julia 等)手寫循環不同,R 使用內置的循環函數會讓你的代碼更具有可讀性
  • 【R知識】R語言apply函數族筆記
    在使用R時,要儘量用array的方式思考,避免for循環。不用循環怎麼實現迭代呢?這就需要用到    apply 函數族。它不是一個函數,而是一族功能類似的函數。概述apply系列函數的基本作用是對數組(array,可以是多維)或者列表(list)按照元素或元素構成的子集合進行迭代,並將當前元素或子集合作為參數調用某個指定函數。
  • 【R】提升R代碼運算效率的11個實用方法
    循環運算前,記得預先設置好數據結構和輸出變量的長度和類型,千萬別在循環過程中漸進性地增加數據長度。接下來,我們將探究向量化處理是如何提高處理數據的運算速度。本部分的測試將和 case(2) 部分進行比較,和預想的結果一致,該方法確實提升了運算效率。
  • 一文弄懂apply、map和applymap三種函數的區別
    CDA數據分析師 出品在日常處理數據的過程中,會經常遇到這樣的情況,對一個DataFrame進行逐行、逐列或者逐元素的操作,很多小夥伴也知道需要用到apply、map或者applymap,但是具體什麼情況下運用哪種方法或者說對這些方法了解不夠,用起來暈暈乎乎始終沒有很明白
  • 成為R語言高手:再談apply和for loop循環
    for loop循環,能不用就不用,儘量用apply,或者向量化。但是for loop循環真的那麼不堪麼?for loop和apply的爭論,本質上涉及到兩個問題:這裡說來話有點長,首先要搞清楚小的基本問題,最後我們再綜合起來討論。1)apply系列函數是什麼?
  • 一行代碼讓 pandas 的 apply 速度飆到極致!
    向量化向量化是最優的方法,具體用法參考文章:還在抱怨pandas運行速度慢?這幾個方法會顛覆你的看法。舉個例子,我們將向量化定義為使用Numpy表示整個數組而不是元素的計算。for循環將這些數組求和,但這非常慢。
  • R語言apply家族函數的用法及其比較
    與matlab一樣,其循環結構的效率也無法讓人滿意。在平常的編程過程中我們應該儘量避免使用循環,而採用向量化的編程語法可以幫助我們有效提高數據處理效率。本文我們主要介紹如何使用R語言中的apply家族函數來實現向量化運算。案例中需要用到的數據集為了更好的理解apply家族函數的用法,本文需要用到mtcars數據集和beavers數據集。
  • apply家族
    ()函數是一個很R語言的函數,可以起到很好的替代冗餘的for循環的作用,R語言的循環操作for和while,都是基於R語言本身來實現的,而向量操作是基於底層的C語言函數實現的,所以使用apply()家族進行向量計算是高性價比的。
  • Pandas數據處理|apply()函數的常規用法
    本文介紹一下關於 Pandas 中 apply() 函數的幾個常見用法,apply() 函數的自由度較高,可以直接對 Series 或者 DataFrame 中元素進行逐元素遍歷操作,方便且高效,具有類似於 Numpy 的特性。
  • pandas | 詳解DataFrame中的apply與applymap方法
    在上一篇文章當中,我們介紹了panads的一些計算方法,比如兩個dataframe的四則運算,以及dataframe填充Null的方法。今天這篇文章我們來聊聊dataframe中的廣播機制,以及apply函數的使用方法。
  • 【R函數學習】R語言 apply函數家族詳解
    lapplyApply a Function over a List or Vector對列表或者向量使用函數lapply(X, FUN, ...) apply {base}通過對數組或者矩陣的一個維度使用函數生成值得列表或者數組、向量。apply(X, MARGIN, FUN, ...)
  • 不再糾結,一文詳解pandas中的map、apply、applymap、groupby、agg...
    2.2 apply()apply()堪稱pandas中最好用的方法,其使用方式跟map()很像,主要傳入的主要參數都是接受輸入返回輸出。但相較於map()針對單列Series進行處理,一條apply()語句可以對單列或多列進行運算,覆蓋非常多的使用場景。
  • R語言 apply函數家族詳解
    USE.NAMES 邏輯值,如果為TRUE,且x沒有被命名,則對x進行命名。例:> sapply(k, paste,USE.NAMES=FALSE,1:5,sep="..."),但是它的返回值有預定義類型,所以它使用起來會更加安全,有的時候會更快在vapply函數中總是會進行簡化,vapply會檢測FUN的所有值是否與FUN.VALUE兼容,以使他們具有相同的長度和類型。
  • 數據分析工具篇——for循環運算優化(一)
    以及整體(集合)運算方法,特別是for循環,可以說百分之九十九的函數會出現for循環;常見的包主要有:pandas、pyspark、numpy,這三個包可謂是人盡皆知,特別是前兩個,一個是小數據使用的包,一個是大數據使用的包,隨著python的不斷豐富,這兩個包越來越完善,今天我們先了解一下for循環的優化方法:
  • Python數據分析—apply函數
    在對海量數據進行分析的過程中,我們可能要把文本型的數據處理成數值型的數據,方便放到模型中進行使用。
  • Pandas三大利器-map、apply、applymap
    、apply、applymap實際工作中,我們在利用 pandas進行數據處理的時候,經常會對數據框中的單行、多行(列也適用)甚至是整個數據進行某種相同方式的處理,比如將數據中的 sex欄位將 男替換成1,女替換成0。