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

陌路散爱 提交于 2019-12-13 18:48:21

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

每当我想在R中做“ map” py任务时,我通常都会尝试在apply系列中使用一个函数。

但是,我从未完全理解它们之间的区别-{ sapplylapply等}如何将函数应用于输入/分组输入,输出将是什么样,甚至输入是什么-所以我经常只是遍历所有这些,直到得到想要的东西。

谁能解释什么时候使用哪一个?

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

  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)的出色回答开始 -值得怀疑的事情会更好。

然后,以下助记符可能有助于记住它们之间的区别。 尽管有些显而易见,但有些则不那么明显---对于这些,您会在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对于将摘要与原始数据结合起来很有用。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!