分組功能(tapply,by,aggregate)和* apply系列

每當我想在R中作「 map」 py任務時,我一般都會嘗試在apply系列中使用一個函數。 html

可是,我從未徹底理解它們之間的區別-{ sapplylapply等}如何將函數應用於輸入/分組輸入,輸出將是什麼樣,甚至輸入是什麼-因此我常常只是遍歷全部這些,直到獲得想要的東西。 編程

誰能解釋何時使用哪個? 數組

我目前(可能不正確/不完整)的理解是... app

  1. sapply(vec, f) :輸入是向量。 輸出是一個向量/矩陣,其中元素if(vec[i]) ,若是f具備多元素輸出,則爲您提供矩陣 函數式編程

  2. lapply(vec, f) :與sapply相同,可是輸出是一個列表? 函數

  3. apply(matrix, 1/2, f) :輸入是一個矩陣。 輸出是一個向量,其中元素i爲f(矩陣的行/列i)
  4. tapply(vector, grouping, f) :輸出是一個矩陣/數組,其中矩陣/數組中的元素是向量分組g處的f值,而且g被推到行/列名
  5. by(dataframe, grouping, f) :令g爲一個分組。 將f應用於組/數據框的每一列。 在每列漂亮地打印分組和f的值。
  6. aggregate(matrix, grouping, f) :相似於by ,可是aggregate不會將輸出漂亮地打印by ,而是將全部內容粘貼到數據框中。

側問題:我尚未學會plyr或重塑-將plyrreshape取代全部這些徹底? 工具


#1樓

首先從喬蘭(Joran)的出色回答開始 -值得懷疑的事情會更好。 post

而後,如下助記符可能有助於記住它們之間的區別。 儘管有些顯而易見,但有些則不那麼明顯---對於這些,您會在Joran的討論中找到正當理由。 性能

助記符 學習

  • lapply是一個列表應用,它做用於列表或向量並返回一個列表。
  • sapply是一個簡單的 lapply (函數默認狀況下會默認返回向量或矩陣)
  • vapply是已驗證的應用 (容許預先指定返回對象類型)
  • rapply是對嵌套列表(即列表中的列表)的遞歸應用
  • tapply是帶標籤的應用,其中標籤標識子集
  • apply泛型的 :將函數應用於矩陣的行或列(或更通常地說,應用於數組的維)

創建正確的背景

若是使用apply家庭仍然對您感到有些陌生,則多是您缺乏關鍵觀點。

這兩篇文章會有所幫助。 它們提供了必要的背景來激發由apply功能家族提供的功能編程技術

Lisp的用戶將當即認識到該範例。 若是您不熟悉Lisp,一旦對FP有所瞭解,您將得到在R中使用的強大觀點-而且apply會更有意義。


#2樓

這也許是值得一提的aveavetapply的友好表親。 它以能夠直接插入數據框的形式返回結果。

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爲整個數據幀(如bytapply數據幀)。 可是您能夠捏造它:

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
## ...

#3樓

由於我意識到這篇文章的(很是出色的)答案缺少byaggregate解釋。 這是個人貢獻。

經過

正如文檔中所述, 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

若是咱們打印這兩個對象ctcb ,則「本質上」具備相同的結果,惟一的區別在於它們的顯示方式和不一樣的class屬性,分別by cbct 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

在其餘狀況下,咱們不能使用bytapply ,而必須使用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)

先前的代碼實現了相同的目標和結果,在某些時候,使用哪一種工具只是我的喜愛和需求的問題。 就子集而言,前兩個對象有很是不一樣的需求。


#4樓

有不少很棒的答案討論了每一個功能用例的差別。 沒有答案討論性能差別。 這是合理的,由於各類功能指望各類輸入併產生各類輸出,可是大多數功能具備按系列/組進行評估的通用目標。 個人答案將集中在性能上。 因爲上述緣由,向量中的輸入建立已包含在時序中,所以未測量apply功能。

我同時測試了兩個不一樣的函數sumlength 。 測試的音量在輸入時爲50M,在輸出時爲50K。 我還包括了兩個當前很流行的軟件包,它們在被問到當時並無被普遍使用,它們是data.tabledplyr 。 若是您但願得到良好的性能,那麼絕對值得一看。

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

#5樓

在旁註中,這是各類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函數困難。

在個人平常使用中, plyrreshape功能已取代了幾乎全部這些功能。 可是,從Intro到Plyr文件中:

相關職能tapplysweep在沒有相應的功能plyr ,並保持有效。 merge對於將摘要與原始數據結合起來頗有用。

相關文章
相關標籤/搜索