每當我想在R中作「 map」 py任務時,我一般都會嘗試在apply
系列中使用一個函數。 html
可是,我從未徹底理解它們之間的區別-{ sapply
, lapply
等}如何將函數應用於輸入/分組輸入,輸出將是什麼樣,甚至輸入是什麼-因此我常常只是遍歷全部這些,直到獲得想要的東西。 編程
誰能解釋何時使用哪個? 數組
我目前(可能不正確/不完整)的理解是... app
sapply(vec, f)
:輸入是向量。 輸出是一個向量/矩陣,其中元素i
爲f(vec[i])
,若是f
具備多元素輸出,則爲您提供矩陣 函數式編程
lapply(vec, f)
:與sapply
相同,可是輸出是一個列表? 函數
apply(matrix, 1/2, f)
:輸入是一個矩陣。 輸出是一個向量,其中元素i
爲f(矩陣的行/列i) tapply(vector, grouping, f)
:輸出是一個矩陣/數組,其中矩陣/數組中的元素是向量分組g
處的f
值,而且g
被推到行/列名 by(dataframe, grouping, f)
:令g
爲一個分組。 將f
應用於組/數據框的每一列。 在每列漂亮地打印分組和f
的值。 aggregate(matrix, grouping, f)
:相似於by
,可是aggregate不會將輸出漂亮地打印by
,而是將全部內容粘貼到數據框中。 側問題:我尚未學會plyr或重塑-將plyr
或reshape
取代全部這些徹底? 工具
首先從喬蘭(Joran)的出色回答開始 -值得懷疑的事情會更好。 post
而後,如下助記符可能有助於記住它們之間的區別。 儘管有些顯而易見,但有些則不那麼明顯---對於這些,您會在Joran的討論中找到正當理由。 性能
助記符 學習
lapply
是一個列表應用,它做用於列表或向量並返回一個列表。 sapply
是一個簡單的 lapply
(函數默認狀況下會默認返回向量或矩陣) vapply
是已驗證的應用 (容許預先指定返回對象類型) rapply
是對嵌套列表(即列表中的列表)的遞歸應用 tapply
是帶標籤的應用,其中標籤標識子集 apply
是泛型的 :將函數應用於矩陣的行或列(或更通常地說,應用於數組的維) 創建正確的背景
若是使用apply
家庭仍然對您感到有些陌生,則多是您缺乏關鍵觀點。
這兩篇文章會有所幫助。 它們提供了必要的背景來激發由apply
功能家族提供的功能編程技術 。
Lisp的用戶將當即認識到該範例。 若是您不熟悉Lisp,一旦對FP有所瞭解,您將得到在R中使用的強大觀點-而且apply
會更有意義。
這也許是值得一提的ave
。 ave
是tapply
的友好表親。 它以能夠直接插入數據框的形式返回結果。
dfr <- data.frame(a=1:20, f=rep(LETTERS[1:5], each=4)) means <- tapply(dfr$a, dfr$f, mean) ## A B C D E ## 2.5 6.5 10.5 14.5 18.5 ## great, but putting it back in the data frame is another line: dfr$m <- means[dfr$f] dfr$m2 <- ave(dfr$a, dfr$f, FUN=mean) # NB argument name FUN is needed! dfr ## a f m m2 ## 1 A 2.5 2.5 ## 2 A 2.5 2.5 ## 3 A 2.5 2.5 ## 4 A 2.5 2.5 ## 5 B 6.5 6.5 ## 6 B 6.5 6.5 ## 7 B 6.5 6.5 ## ...
沒有什麼基本包,像工程ave
爲整個數據幀(如by
像tapply
數據幀)。 可是您能夠捏造它:
dfr$foo <- ave(1:nrow(dfr), dfr$f, FUN=function(x) { x <- dfr[x,] sum(x$m*x$m2) }) dfr ## a f m m2 foo ## 1 1 A 2.5 2.5 25 ## 2 2 A 2.5 2.5 25 ## 3 3 A 2.5 2.5 25 ## ...
由於我意識到這篇文章的(很是出色的)答案缺少by
和aggregate
解釋。 這是個人貢獻。
正如文檔中所述, by
函數能夠做爲tapply
的「包裝器」。 當咱們要計算tapply
沒法處理的任務時, by
的力量就會產生。 此代碼是一個示例:
ct <- tapply(iris$Sepal.Width , iris$Species , summary ) cb <- by(iris$Sepal.Width , iris$Species , summary ) cb iris$Species: setosa Min. 1st Qu. Median Mean 3rd Qu. Max. 2.300 3.200 3.400 3.428 3.675 4.400 -------------------------------------------------------------- iris$Species: versicolor Min. 1st Qu. Median Mean 3rd Qu. Max. 2.000 2.525 2.800 2.770 3.000 3.400 -------------------------------------------------------------- iris$Species: virginica Min. 1st Qu. Median Mean 3rd Qu. Max. 2.200 2.800 3.000 2.974 3.175 3.800 ct $setosa Min. 1st Qu. Median Mean 3rd Qu. Max. 2.300 3.200 3.400 3.428 3.675 4.400 $versicolor Min. 1st Qu. Median Mean 3rd Qu. Max. 2.000 2.525 2.800 2.770 3.000 3.400 $virginica Min. 1st Qu. Median Mean 3rd Qu. Max. 2.200 2.800 3.000 2.974 3.175 3.800
若是咱們打印這兩個對象ct
和cb
,則「本質上」具備相同的結果,惟一的區別在於它們的顯示方式和不一樣的class
屬性,分別by
cb
和ct
array
。
正如我已經說過的,當咱們不能使用tapply
時, by
的力量就會產生。 如下代碼是一個示例:
tapply(iris, iris$Species, summary ) Error in tapply(iris, iris$Species, summary) : arguments must have same length
R說參數必須具備相同的長度,好比說「咱們要計算沿Species
因子的iris
中全部變量的summary
」:可是R不能這樣作,由於它不知道如何處理。
使用by
函數R調度data frame
類的特定方法,而後使summary
函數起做用,即便第一個參數(以及類型)的長度不一樣也是如此。
bywork <- by(iris, iris$Species, summary ) bywork iris$Species: setosa Sepal.Length Sepal.Width Petal.Length Petal.Width Species Min. :4.300 Min. :2.300 Min. :1.000 Min. :0.100 setosa :50 1st Qu.:4.800 1st Qu.:3.200 1st Qu.:1.400 1st Qu.:0.200 versicolor: 0 Median :5.000 Median :3.400 Median :1.500 Median :0.200 virginica : 0 Mean :5.006 Mean :3.428 Mean :1.462 Mean :0.246 3rd Qu.:5.200 3rd Qu.:3.675 3rd Qu.:1.575 3rd Qu.:0.300 Max. :5.800 Max. :4.400 Max. :1.900 Max. :0.600 -------------------------------------------------------------- iris$Species: versicolor Sepal.Length Sepal.Width Petal.Length Petal.Width Species Min. :4.900 Min. :2.000 Min. :3.00 Min. :1.000 setosa : 0 1st Qu.:5.600 1st Qu.:2.525 1st Qu.:4.00 1st Qu.:1.200 versicolor:50 Median :5.900 Median :2.800 Median :4.35 Median :1.300 virginica : 0 Mean :5.936 Mean :2.770 Mean :4.26 Mean :1.326 3rd Qu.:6.300 3rd Qu.:3.000 3rd Qu.:4.60 3rd Qu.:1.500 Max. :7.000 Max. :3.400 Max. :5.10 Max. :1.800 -------------------------------------------------------------- iris$Species: virginica Sepal.Length Sepal.Width Petal.Length Petal.Width Species Min. :4.900 Min. :2.200 Min. :4.500 Min. :1.400 setosa : 0 1st Qu.:6.225 1st Qu.:2.800 1st Qu.:5.100 1st Qu.:1.800 versicolor: 0 Median :6.500 Median :3.000 Median :5.550 Median :2.000 virginica :50 Mean :6.588 Mean :2.974 Mean :5.552 Mean :2.026 3rd Qu.:6.900 3rd Qu.:3.175 3rd Qu.:5.875 3rd Qu.:2.300 Max. :7.900 Max. :3.800 Max. :6.900 Max. :2.500
它確實有效,而且結果使人驚訝。 by
沿Species
(例如,針對它們中的每個)計算每一個變量的summary
是一個類的對象。
請注意,若是第一個參數是data frame
,則分派的函數必須具備用於該類對象的方法。 例如,咱們將此代碼與mean
函數一塊兒使用時,將獲得徹底沒有意義的代碼:
by(iris, iris$Species, mean) iris$Species: setosa [1] NA ------------------------------------------- iris$Species: versicolor [1] NA ------------------------------------------- iris$Species: virginica [1] NA Warning messages: 1: In mean.default(data[x, , drop = FALSE], ...) : argument is not numeric or logical: returning NA 2: In mean.default(data[x, , drop = FALSE], ...) : argument is not numeric or logical: returning NA 3: In mean.default(data[x, , drop = FALSE], ...) : argument is not numeric or logical: returning NA
aggregate
能夠看做是使用另外一種不一樣的方式tapply
若是咱們使用它以這樣一種方式。
at <- tapply(iris$Sepal.Length , iris$Species , mean) ag <- aggregate(iris$Sepal.Length , list(iris$Species), mean) at setosa versicolor virginica 5.006 5.936 6.588 ag Group.1 x 1 setosa 5.006 2 versicolor 5.936 3 virginica 6.588
兩個直接的區別是, aggregate
的第二個參數必須是一個列表,而tapply
能夠 (不是強制性)是一個列表,而且aggregate
的輸出是一個數據幀,而tapply
的一個是一個array
。
aggregate
的強大之處在於它可使用subset
參數輕鬆處理數據的subset
,而且還具備用於ts
對象和formula
方法。
這些元素使aggregate
更容易的工做與tapply
在某些狀況下。 如下是一些示例(可在文檔中找到):
ag <- aggregate(len ~ ., data = ToothGrowth, mean) ag supp dose len 1 OJ 0.5 13.23 2 VC 0.5 7.98 3 OJ 1.0 22.70 4 VC 1.0 16.77 5 OJ 2.0 26.06 6 VC 2.0 26.14
咱們能夠用tapply
實現相同的功能,可是語法稍微難一些,而且輸出(在某些狀況下)可讀性較低:
att <- tapply(ToothGrowth$len, list(ToothGrowth$dose, ToothGrowth$supp), mean) att OJ VC 0.5 13.23 7.98 1 22.70 16.77 2 26.06 26.14
在其餘狀況下,咱們不能使用by
或tapply
,而必須使用aggregate
。
ag1 <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, mean) ag1 Month Ozone Temp 1 5 23.61538 66.73077 2 6 29.44444 78.22222 3 7 59.11538 83.88462 4 8 59.96154 83.96154 5 9 31.44828 76.89655
咱們沒法在一次調用中用tapply
得到先前的結果,可是咱們必須計算每一個元素沿Month
的平均值,而後將它們組合起來(還要注意,咱們必須調用na.rm = TRUE
,由於aggregate
函數的formula
方法默認狀況下具備na.action = na.omit
):
ta1 <- tapply(airquality$Ozone, airquality$Month, mean, na.rm = TRUE) ta2 <- tapply(airquality$Temp, airquality$Month, mean, na.rm = TRUE) cbind(ta1, ta2) ta1 ta2 5 23.61538 65.54839 6 29.44444 79.10000 7 59.11538 83.90323 8 59.96154 83.96774 9 31.44828 76.90000
而使用by
沒法實現,實際上如下函數調用返回錯誤(但極可能與所提供的函數mean
):
by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)
其餘時候,結果是相同的,不一樣之處僅在於類(而後是如何顯示/打印,而不只是-例如,如何對其進行子集化)對象:
byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary) aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)
先前的代碼實現了相同的目標和結果,在某些時候,使用哪一種工具只是我的喜愛和需求的問題。 就子集而言,前兩個對象有很是不一樣的需求。
有不少很棒的答案討論了每一個功能用例的差別。 沒有答案討論性能差別。 這是合理的,由於各類功能指望各類輸入併產生各類輸出,可是大多數功能具備按系列/組進行評估的通用目標。 個人答案將集中在性能上。 因爲上述緣由,向量中的輸入建立已包含在時序中,所以未測量apply
功能。
我同時測試了兩個不一樣的函數sum
和length
。 測試的音量在輸入時爲50M,在輸出時爲50K。 我還包括了兩個當前很流行的軟件包,它們在被問到當時並無被普遍使用,它們是data.table
和dplyr
。 若是您但願得到良好的性能,那麼絕對值得一看。
library(dplyr) library(data.table) set.seed(123) n = 5e7 k = 5e5 x = runif(n) grp = sample(k, n, TRUE) timing = list() # sapply timing[["sapply"]] = system.time({ lt = split(x, grp) r.sapply = sapply(lt, function(x) list(sum(x), length(x)), simplify = FALSE) }) # lapply timing[["lapply"]] = system.time({ lt = split(x, grp) r.lapply = lapply(lt, function(x) list(sum(x), length(x))) }) # tapply timing[["tapply"]] = system.time( r.tapply <- tapply(x, list(grp), function(x) list(sum(x), length(x))) ) # by timing[["by"]] = system.time( r.by <- by(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE) ) # aggregate timing[["aggregate"]] = system.time( r.aggregate <- aggregate(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE) ) # dplyr timing[["dplyr"]] = system.time({ df = data_frame(x, grp) r.dplyr = summarise(group_by(df, grp), sum(x), n()) }) # data.table timing[["data.table"]] = system.time({ dt = setnames(setDT(list(x, grp)), c("x","grp")) r.data.table = dt[, .(sum(x), .N), grp] }) # all output size match to group count sapply(list(sapply=r.sapply, lapply=r.lapply, tapply=r.tapply, by=r.by, aggregate=r.aggregate, dplyr=r.dplyr, data.table=r.data.table), function(x) (if(is.data.frame(x)) nrow else length)(x)==k) # sapply lapply tapply by aggregate dplyr data.table # TRUE TRUE TRUE TRUE TRUE TRUE TRUE
# print timings as.data.table(sapply(timing, `[[`, "elapsed"), keep.rownames = TRUE )[,.(fun = V1, elapsed = V2) ][order(-elapsed)] # fun elapsed #1: aggregate 109.139 #2: by 25.738 #3: dplyr 18.978 #4: tapply 17.006 #5: lapply 11.524 #6: sapply 11.326 #7: data.table 2.686
在旁註中,這是各類plyr
函數如何與基本*apply
函數相對應(從plyr網站http://had.co.nz/plyr/的介紹到plyr文檔)
Base function Input Output plyr function --------------------------------------- aggregate d d ddply + colwise apply a a/l aaply / alply by d l dlply lapply l l llply mapply a a/l maply / mlply replicate r a/l raply / rlply sapply l a laply
plyr
的目標之一是爲每一個函數提供一致的命名約定,在函數名稱中編碼輸入和輸出數據類型。 它還提供了輸出的一致性,由於dlply()
輸出很容易傳遞給ldply()
以產生有用的輸出,等等。
從概念上講,學習plyr
並不比理解基本的*apply
函數困難。
在個人平常使用中, plyr
和reshape
功能已取代了幾乎全部這些功能。 可是,從Intro到Plyr文件中:
相關職能
tapply
和sweep
在沒有相應的功能plyr
,並保持有效。merge
對於將摘要與原始數據結合起來頗有用。