分组函数(tapply、by、aggregate)和 *apply 系列

Grouping functions (tapply, by, aggregate) and the *apply family

提问人:grautur 提问时间:8/18/2010 最后编辑:Waelgrautur 更新时间:7/2/2023 访问量:438336

问:

每当我想在 R 中做一些“map”py 时,我通常会尝试使用家族中的函数。apply

然而,我一直不太理解它们之间的区别——{、、等}如何将函数应用于输入/分组输入,输出会是什么样子,甚至输入可以是什么——所以我经常只是把它们都看一遍,直到我得到我想要的东西。sapplylapply

有人可以解释一下什么时候如何使用哪一个吗?

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

  1. sapply(vec, f):输入为向量。output 是一个向量/矩阵,其中 element 是 ,如果具有多元素输出,则为您提供一个矩阵if(vec[i])f

  2. lapply(vec, f):与 相同,但输出是列表?sapply

  3. apply(matrix, 1/2, f):输入为矩阵。输出是一个向量,其中元素是 f(矩阵的行/列 i)i
  4. tapply(vector, grouping, f):输出是一个矩阵/数组,其中矩阵/数组中的元素是向量分组的值,并被推送到行/列名称fgg
  5. by(dataframe, grouping, f):设为分组。应用于组/数据帧的每一列。漂亮地打印每列的分组和值。gff
  6. aggregate(matrix, grouping, f):类似于 ,但不是漂亮地打印输出,而是将所有内容粘贴到 DataFrame 中。by

附带问题:我还没有学会 plyr 或重塑——会或完全取代所有这些吗?plyrreshape

矢量化 应用 R-FAQ

评论

35赞 JD Long 8/18/2010
对于您的侧面问题:对于许多事情,PLYR 是 和 的直接替代品。plyr(至少对我而言)似乎更加一致,因为我总是确切地知道它期望什么数据格式以及它会吐出什么。这为我省去了很多麻烦。*apply()by
15赞 Iterator 10/10/2011
此外,我建议添加:以及 .doBydata.table
9赞 IRTFM 1/25/2013
sapply只是在输出上添加了 。 强制转换为原子向量,但输出可以是向量或列表。 将 DataFrame 拆分为子 DataFrame,但不会单独用于列。仅当存在 'data.frame'-class 的方法时,才能按列应用。 是泛型的,因此第一个参数的不同类存在不同的方法。lapplysimplify2arrayapplybyffbyaggregate
10赞 Lutz Prechelt 9/16/2014
助记符:l 代表“列表”,s 代表“简化”,t 代表“每个类型”(分组的每个级别都是一个类型)
0赞 Stefanos 11/17/2018
Rfast 包中也存在一些函数,例如:eachcol.apply、apply.condition 等,它们比 R 的等价物更快

答:

212赞 JoFrhwld 8/18/2010 #1

在旁注中,以下是各种函数与基本函数的对应关系(从 plyr 网页的介绍到 plyr 文档 http://had.co.nz/plyr/plyr*apply)

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 

目标之一是为每个函数提供一致的命名约定,在函数名称中对输入和输出数据类型进行编码。它还提供了输出的一致性,因为输出很容易传递到产生有用的输出等。plyrdlply()ldply()

从概念上讲,学习并不比理解基本函数更难。plyr*apply

plyr在常使用中,功能几乎取代了所有这些功能。但是,同样来自 Plyr 简介文档:reshape

相关函数,在 中没有相应的函数,并且仍然有用。 可用于将摘要与原始数据相结合。tapplysweepplyrmerge

评论

14赞 JD Long 8/18/2010
当我开始从头开始学习 R 时,我发现 plyr 比函数系列更容易学习。对我来说,这是非常直观的,因为我熟悉 SQL 聚合函数。 成为我解决许多问题的锤子,其中一些问题本可以用其他命令更好地解决。*apply()ddply()ddply()
1437赞 joran 8/22/2011 #2

R 有许多 *apply 函数,这些函数在帮助文件中进行了描述(例如)。但是,它们的数量已经足够多了,以至于刚开始使用的人可能很难决定哪一个适合他们的情况,甚至很难记住它们。他们可能有一种普遍的感觉,即“我应该在这里使用 *apply 函数”,但一开始很难让他们都保持直截了当。?apply

尽管(在其他答案中指出)*apply 系列的大部分功能都包含在非常流行的软件包中,但基本功能仍然有用且值得了解。plyr

这个答案旨在作为新用户的路标,以帮助引导他们找到针对其特定问题的正确 *apply 函数。请注意,这并不是简单地反刍或替换 R 文档!希望这个答案能帮助您决定哪个 *apply 函数适合您的情况,然后由您进一步研究它。除一个例外情况外,性能差异将不会得到解决。

  • apply - 当您想将函数应用于行或列时 矩阵(和更高维的类似物);通常不建议用于数据帧,因为它会首先强制转换为矩阵。

     # Two dimensional matrix
     M <- matrix(seq(1,16), 4, 4)
    
     # apply min to rows
     apply(M, 1, min)
     [1] 1 2 3 4
    
     # apply max to columns
     apply(M, 2, max)
     [1]  4  8 12 16
    
     # 3 dimensional array
     M <- array( seq(32), dim = c(4,4,2))
    
     # Apply sum across each M[*, , ] - i.e Sum across 2nd and 3rd dimension
     apply(M, 1, sum)
     # Result is one-dimensional
     [1] 120 128 136 144
    
     # Apply sum across each M[*, *, ] - i.e Sum across 3rd dimension
     apply(M, c(1,2), sum)
     # Result is two-dimensional
          [,1] [,2] [,3] [,4]
     [1,]   18   26   34   42
     [2,]   20   28   36   44
     [3,]   22   30   38   46
     [4,]   24   32   40   48
    

    如果需要 2D 矩阵的行/列均值或总和,请确保 研究高度优化、闪电般快速、。colMeansrowMeanscolSumsrowSums

  • lapply - 当您想将函数应用于 依次列出并返回列表。

    这是许多其他 *apply 函数的主力。皮 支持他们的代码,你经常会在下面找到。lapply

     x <- list(a = 1, b = 1:3, c = 10:100) 
     lapply(x, FUN = length) 
     $a 
     [1] 1
     $b 
     [1] 3
     $c 
     [1] 91
     lapply(x, FUN = sum) 
     $a 
     [1] 1
     $b 
     [1] 6
     $c 
     [1] 5005
    
  • sapply - 当您想将函数应用于 依次列出,但您需要一个向量,而不是列表。

    如果您发现自己在打字,请停下来考虑一下。unlist(lapply(...))sapply

     x <- list(a = 1, b = 1:3, c = 10:100)
     # Compare with above; a named vector, not a list 
     sapply(x, FUN = length)  
     a  b  c   
     1  3 91
    
     sapply(x, FUN = sum)   
     a    b    c    
     1    6 5005 
    

    在更高级的用途中,它将试图胁迫 结果转换为多维数组(如果适用)。例如,如果我们的函数返回相同长度的向量,则将它们用作矩阵的列:sapplysapply

     sapply(1:5,function(x) rnorm(3,x))
    

    如果我们的函数返回一个二维矩阵,将执行基本相同的操作,将每个返回的矩阵视为单个长向量:sapply

     sapply(1:5,function(x) matrix(x,2,2))
    

    除非我们指定 ,在这种情况下,它将使用单个矩阵来构建一个多维数组:simplify = "array"

     sapply(1:5,function(x) matrix(x,2,2), simplify = "array")
    

    当然,这些行为中的每一个都取决于我们的函数返回相同长度或维度的向量或矩阵。

  • vapply - 当您想使用 sapply 但可能需要 从代码中榨取更多的速度,或者想要更多的类型安全性

    对于,你基本上给 R 举了一个例子,说明什么样的东西 您的函数将返回,这可以节省一些强制返回的时间 值以适合单个原子向量。vapply

     x <- list(a = 1, b = 1:3, c = 10:100)
     #Note that since the advantage here is mainly speed, this
     # example is only for illustration. We're telling R that
     # everything returned by length() should be an integer of 
     # length 1. 
     vapply(x, FUN = length, FUN.VALUE = 0L) 
     a  b  c  
     1  3 91
    
  • mapply - 当您有多个数据结构(例如 向量、列表),并且您想将函数应用于第 1 个元素 每个元素,然后是每个元素的第二个元素,依此类推,强制结果 添加到向量/数组,如 sapply

    从某种意义上说,这是多变量的,您的函数必须接受 多个参数。

     #Sums the 1st elements, the 2nd elements, etc. 
     mapply(sum, 1:5, 1:5, 1:5) 
     [1]  3  6  9 12 15
     #To do rep(1,4), rep(2,3), etc.
     mapply(rep, 1:4, 4:1)   
     [[1]]
     [1] 1 1 1 1
    
     [[2]]
     [1] 2 2 2
    
     [[3]]
     [1] 3 3
    
     [[4]]
     [1] 4
    
  • Map - 使用 SIMPLIFY = FALSE 进行 mapply 的包装器,因此可以保证返回列表。

     Map(sum, 1:5, 1:5, 1:5)
     [[1]]
     [1] 3
    
     [[2]]
     [1] 6
    
     [[3]]
     [1] 9
    
     [[4]]
     [1] 12
    
     [[5]]
     [1] 15
    
  • rapply - 当您想以递归方式将函数应用于嵌套列表结构的每个元素时。

    为了让您了解不常见,我在第一次发布此答案时忘记了它!显然,我相信很多人都在使用它,但是 YMMV。 最好用用户定义的函数来说明:rapplyrapply

     # Append ! to string, otherwise increment
     myFun <- function(x){
         if(is.character(x)){
           return(paste(x,"!",sep=""))
         }
         else{
           return(x + 1)
         }
     }
    
     #A nested list structure
     l <- list(a = list(a1 = "Boo", b1 = 2, c1 = "Eeek"), 
               b = 3, c = "Yikes", 
               d = list(a2 = 1, b2 = list(a3 = "Hey", b3 = 5)))
    
    
     # Result is named vector, coerced to character          
     rapply(l, myFun)
    
     # Result is a nested list like l, with values altered
     rapply(l, myFun, how="replace")
    
  • tapply - 当您想将函数应用于 向量和子集由其他向量定义,通常是 因素。

    *应用家族的害群之马,某种程度上。帮助文件的使用 “参差不齐的阵列”这个短语可能有点令人困惑,但实际上确实如此 很简单。

    向量:

     x <- 1:20
    

    定义组的因子(长度相同!

     y <- factor(rep(letters[1:5], each = 4))
    

    将 中定义的每个子组中的值相加,如下所示:xy

     tapply(x, y, sum)  
      a  b  c  d  e  
     10 26 42 58 74 
    

    在定义子组的地方可以处理更复杂的示例 通过几个因素列表的独特组合。 是 在精神上类似于 split-apply-combine 函数 常见于 R(、、、等)因此,它的 害群之马的地位。tapplyaggregatebyaveddply

评论

37赞 IRTFM 9/14/2011
相信你会发现这是纯粹的分裂,是他们的核心。我认为害群之马是极好的面料。byaggregatetapply
24赞 grautur 9/15/2011
很棒的回应!这应该是官方 R 文档:)的一部分。一个小建议:也许在使用和时添加一些要点?(经过您的描述,我终于理解了它们!,但它们很常见,因此将这两个函数分开并给出一些具体示例可能会很有用。aggregateby
1赞 isomorphismes 10/10/2011
是的。。。到底什么是参差不齐的数组?
148赞 isomorphismes 10/9/2011 #3

摘自第 21 张幻灯片(共 http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy 张):

apply, sapply, lapply, by, aggregate

(希望很清楚,这与@Hadley相对应,对应于@Hadley等,同一幻灯片的第 20 张幻灯片将澄清您是否没有从这张图片中得到它。applyaaplyaggregateddply

(左边是输入,顶部是输出)

117赞 Assad Ebrahim 4/25/2014 #4

首先,从乔兰的出色回答开始——毫无疑问,任何事情都能比这更好。

那么下面的助记符可能有助于记住两者之间的区别。虽然有些是显而易见的,但有些可能不那么---对于这些,你会在乔兰的讨论中找到理由。

记忆术

  • lapply是一个列表应用,它作用于列表或向量并返回一个列表
  • sapply一个简单的(函数默认在可能的情况下返回向量或矩阵)lapply
  • vapply已验证的应用(允许预先指定返回对象类型)
  • rapply是嵌套列表的归应用,即列表中的列表
  • tapply是一个标记的应用,其中标记标识子集
  • apply泛型的:将函数应用于矩阵的行或列(或者更一般地说,应用于数组的维度)

建立正确的背景

如果使用家庭对你来说仍然有点陌生,那么可能是你错过了一个关键的观点。apply

这两篇文章可以提供帮助。它们提供了必要的背景来激励函数系列提供的函数式编程技术apply

Lisp 的用户会立即认识到这种范式。如果你不熟悉 Lisp,一旦你掌握了 FP,你就会获得一个在 R 中使用的重要观点——并且会更有意义。apply

27赞 user3603486 11/6/2014 #5

也许值得一提的是. 是友善的表弟。它以一种形式返回结果,您可以直接将其插入到数据框中。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
##   ...

基础包中没有任何内容可以像整个数据帧那样工作(就像数据帧一样)。但你可以捏造它:avebytapply

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
## ...
63赞 SabDeM 8/28/2015 #6

自从我意识到这篇文章的(非常出色的)答案缺乏和解释以来。这是我的贡献。byaggregate

不过,如文档中所述,该函数可以作为 的“包装器”。当我们想要计算一个无法处理的任务时,就会产生强大的力量。一个示例是以下代码:bytapplybytapply

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 

如果我们打印这两个对象,并且 ,我们“基本上”会得到相同的结果,唯一的区别在于它们的显示方式和不同的属性,分别是 for 和 for 。ctcbclassbycbarrayct

正如我所说,当我们不能使用时,力量就会产生;以下代码是一个示例:bytapply

 tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) : 
  arguments must have same length

R 说参数必须具有相同的长度,比如“我们要计算沿因子的所有变量”:但 R 无法做到这一点,因为它不知道如何处理。summaryirisSpecies

使用函数 R 为类调度一个特定的方法,然后让函数工作,即使第一个参数(和类型)的长度不同。bydata framesummary

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     

它确实有效,结果非常令人惊讶。它是一个类对象,它(比如说,对于每个变量)计算每个变量。bySpeciessummary

请注意,如果第一个参数是 ,则调度函数必须具有该类对象的方法。例如,我们将此代码与函数一起使用,我们将拥有完全没有意义的代码:data framemean

 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

两个直接的区别是,的第二个参数必须是列表,而 can (不是强制性的) 是列表,并且 的输出是数据帧,而 的输出是 .aggregatetapplyaggregatetapplyarray

它的强大之处在于它可以通过参数轻松处理数据子集,并且它还具有对象方法。aggregatesubsettsformula

在某些情况下,这些元素使使用它变得更加容易。 以下是一些示例(可在文档中找到):aggregatetapply

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

还有其他时候我们不能使用或我们必须使用 .bytapplyaggregate

 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

我们不能在一次调用中获得之前的结果,但我们必须计算每个元素的平均值,然后将它们组合起来(另请注意,我们必须调用 ,因为该函数的方法默认具有 ):tapplyMonthna.rm = TRUEformulaaggregatena.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

虽然我们只是无法实现这一点,但实际上以下函数调用返回错误(但很可能与提供的函数有关):bymean

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)

前面的代码实现了相同的目标和结果,在某些时候使用什么工具只是个人品味和需求的问题;前两个对象在子集方面的需求非常不同。

41赞 jangorecki 12/9/2015 #7

有很多很好的答案,讨论了每个函数用例的差异。没有一个答案讨论性能差异。这是合理的,因为各种函数需要不同的输入并产生不同的输出,但它们中的大多数都有一个通用的共同目标,即按系列/组进行评估。我的答案是将重点放在性能上。由于上述原因,从向量创建的输入包含在计时中,因此函数也不被测量。apply

我一次测试了两个不同的功能。测试的音量为输入50M,输出50K。我还收录了两个目前流行的软件包,它们在提出问题时没有被广泛使用,并且.如果您的目标是获得良好的性能,那么两者都绝对值得一看。sumlengthdata.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
33赞 John Paul 5/16/2016 #8

尽管这里有很多很好的答案,但还有 2 个基本函数值得一提,有用的函数和晦涩的函数outereapply

outer是一个非常有用的功能,隐藏为一个更平凡的功能。如果您阅读其描述的帮助,则说:outer

The outer product of the arrays X and Y is the array A with dimension  
c(dim(X), dim(Y)) where element A[c(arrayindex.x, arrayindex.y)] =   
FUN(X[arrayindex.x], Y[arrayindex.y], ...).

这使得这似乎只对线性代数类型的东西有用。但是,它的使用方式与将函数应用于两个输入向量非常相似。不同之处在于,将函数应用于前两个元素,然后应用于后两个元素,以此类推,而将函数应用于第一个向量的一个元素和第二个向量的一个元素的每个组合。例如:mapplymapplyouter

 A<-c(1,3,5,7,9)
 B<-c(0,3,6,9,12)

mapply(FUN=pmax, A, B)

> mapply(FUN=pmax, A, B)
[1]  1  3  6  9 12

outer(A,B, pmax)

 > outer(A,B, pmax)
      [,1] [,2] [,3] [,4] [,5]
 [1,]    1    3    6    9   12
 [2,]    3    3    6    9   12
 [3,]    5    5    6    9   12
 [4,]    7    7    7    9   12
 [5,]    9    9    9    9   12

当我有一个值向量和一个条件向量并希望查看哪些值满足哪些条件时,我个人就使用了它。

电子申请

eapply就像,它不是将函数应用于列表中的每个元素,而是将函数应用于环境中的每个元素。例如,如果要在全局环境中查找用户定义函数的列表:lapply

A<-c(1,3,5,7,9)
B<-c(0,3,6,9,12)
C<-list(x=1, y=2)
D<-function(x){x+1}

> eapply(.GlobalEnv, is.function)
$A
[1] FALSE

$B
[1] FALSE

$C
[1] FALSE

$D
[1] TRUE 

坦率地说,我不太使用它,但是如果您正在构建大量软件包或创建大量环境,它可能会派上用场。

15赞 vonjd 6/17/2017 #9

我最近发现了一个相当有用的函数,为了完整起见,我在这里添加了它:sweep

基本思想是逐行或按列扫描数组并返回修改后的数组。一个例子可以清楚地说明这一点(来源:datacamp):

假设您有一个矩阵,并希望逐列对其进行标准化

dataPoints <- matrix(4:15, nrow = 4)

# Find means per column with `apply()`
dataPoints_means <- apply(dataPoints, 2, mean)

# Find standard deviation with `apply()`
dataPoints_sdev <- apply(dataPoints, 2, sd)

# Center the points 
dataPoints_Trans1 <- sweep(dataPoints, 2, dataPoints_means,"-")

# Return the result
dataPoints_Trans1
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5

# Normalize
dataPoints_Trans2 <- sweep(dataPoints_Trans1, 2, dataPoints_sdev, "/")

# Return the result
dataPoints_Trans2
##            [,1]       [,2]       [,3]
## [1,] -1.1618950 -1.1618950 -1.1618950
## [2,] -0.3872983 -0.3872983 -0.3872983
## [3,]  0.3872983  0.3872983  0.3872983
## [4,]  1.1618950  1.1618950  1.1618950

注意:对于这个简单的例子,当然可以通过以下方式更容易地获得相同的结果
apply(dataPoints, 2, scale)

评论

1赞 Frank 6/17/2017
这与分组有关吗?
3赞 vonjd 6/17/2017
@Frank:好吧,老实说,这篇文章的标题相当具有误导性:当你读到问题本身时,它是关于“应用家庭”的。 是一个高阶函数,就像这里提到的所有其他函数一样,例如,,因此可以对超过 1,000 个赞成票的接受答案以及其中给出的示例提出相同的问题。看看那里给出的例子。sweepapplysapplylapplyapply
3赞 moodymudskipper 3/24/2018
扫描具有误导性名称、误导性默认值和误导性参数名称:)。这样对我来说更容易理解:1) STATS 是向量或单个值,将重复形成与第一个输入相同大小的矩阵,2) FUN 将应用于第一个输入和这个新矩阵。也许可以更好地说明: 。它通常比 where 循环能够使用矢量化函数更有效。sweep(matrix(1:6,nrow=2),2,7:9,list)applyapplysweep
8赞 Sebastian 3/20/2020 #10

在最近在 CRAN 上发布的 collapse 包中,我尝试将大多数常见的应用功能压缩为仅 2 个函数:

  1. dapply(Data-Apply) 将函数应用于矩阵和 data.frames 的行或(默认)列,并且(默认)返回具有相同类型和相同属性的对象(除非每次计算的结果是原子和 )。性能与 data.frame 列相当,比矩阵行或列快约 2 倍。并行性可通过以下方式获得(仅适用于 MAC)。drop = TRUElapplyapplymclapply

语法:

dapply(X, FUN, ..., MARGIN = 2, parallel = FALSE, mc.cores = 1L, 
       return = c("same", "matrix", "data.frame"), drop = TRUE)

例子:

# Apply to columns:
dapply(mtcars, log)
dapply(mtcars, sum)
dapply(mtcars, quantile)
# Apply to rows:
dapply(mtcars, sum, MARGIN = 1)
dapply(mtcars, quantile, MARGIN = 1)
# Return as matrix:
dapply(mtcars, quantile, return = "matrix")
dapply(mtcars, quantile, MARGIN = 1, return = "matrix")
# Same for matrices ...
  1. BY是 S3 泛型,用于使用向量、矩阵和 data.frame 方法进行拆分-应用-组合计算。它比 和 快得多(在大数据上也比 快)。tapplybyaggregateplyrdplyr

语法:

BY(X, g, FUN, ..., use.g.names = TRUE, sort = TRUE,
   expand.wide = FALSE, parallel = FALSE, mc.cores = 1L,
   return = c("same", "matrix", "data.frame", "list"))

例子:

# Vectors:
BY(iris$Sepal.Length, iris$Species, sum)
BY(iris$Sepal.Length, iris$Species, quantile)
BY(iris$Sepal.Length, iris$Species, quantile, expand.wide = TRUE) # This returns a matrix 
# Data.frames
BY(iris[-5], iris$Species, sum)
BY(iris[-5], iris$Species, quantile)
BY(iris[-5], iris$Species, quantile, expand.wide = TRUE) # This returns a wider data.frame
BY(iris[-5], iris$Species, quantile, return = "matrix") # This returns a matrix
# Same for matrices ...

还可以向 提供分组变量列表。g

谈论性能:collapse 的一个主要目标是在 R 中培养高性能编程,并超越 split-apply-combine alltogether。为此,该软件包具有一整套基于 C++ 的快速泛型函数:、、、它们在单次传递数据时执行分组计算(即没有拆分和重组)。fmeanfmedianfmodefsumfprodfsdfvarfminfmaxffirstflastfNobsfNdistinctfscalefbetweenfwithinfHDbetweenfHDwithinflagfdifffgrowth

语法:

fFUN(x, g = NULL, [w = NULL,] TRA = NULL, [na.rm = TRUE,] use.g.names = TRUE, drop = TRUE)

例子:

v <- iris$Sepal.Length
f <- iris$Species

# Vectors
fmean(v)             # mean
fmean(v, f)          # grouped mean
fsd(v, f)            # grouped standard deviation
fsd(v, f, TRA = "/") # grouped scaling
fscale(v, f)         # grouped standardizing (scaling and centering)
fwithin(v, f)        # grouped demeaning

w <- abs(rnorm(nrow(iris)))
fmean(v, w = w)      # Weighted mean
fmean(v, f, w)       # Weighted grouped mean
fsd(v, f, w)         # Weighted grouped standard-deviation
fsd(v, f, w, "/")    # Weighted grouped scaling
fscale(v, f, w)      # Weighted grouped standardizing
fwithin(v, f, w)     # Weighted grouped demeaning

# Same using data.frames...
fmean(iris[-5], f)                # grouped mean
fscale(iris[-5], f)               # grouped standardizing
fwithin(iris[-5], f)              # grouped demeaning

# Same with matrices ...

在软件包小插曲中,我提供了基准测试。使用 fast 函数进行编程比使用 dplyrdata.table 进行编程要快得多,尤其是在较小的数据上,但也在大数据上。

3赞 Mikael Jagan 4/8/2023 #11

从 R 4.3.0 开始,将支持数据帧和/或两者,并支持使用公式对数据帧行进行分组。tapplytapplyby

> R.version.string
[1] "R version 4.3.0 beta (2023-04-07 r84200)"
> dd <- data.frame(x = 1:10, f = gl(5L, 2L), g = gl(2L, 5L))
    x f g
1   1 1 1
2   2 1 1
3   3 2 1
4   4 2 1
5   5 3 1
6   6 3 2
7   7 4 2
8   8 4 2
9   9 5 2
10 10 5 2
> tapply(dd, ~f + g, nrow)
   g
f   1 2
  1 2 0
  2 2 0
  3 1 1
  4 0 2
  5 0 2
> by(dd, ~g, identity)
g: 1
  x f g
1 1 1 1
2 2 1 1
3 3 2 1
4 4 2 1
5 5 3 1
------------------------------------------------------------ 
g: 2
    x f g
6   6 3 2
7   7 4 2
8   8 4 2
9   9 5 2
10 10 5 2
0赞 شاه نواز 5/4/2023 #12

某些软件包中也有一些替代方案,上面没有讨论。

包中的函数提供了 apply 系列函数的替代方法,用于在群集上执行并行计算。R 中并行计算的其他替代方法包括包和包,它们允许并行执行循环和函数。该软件包提供了一个简单且一致的 API,用于使用 futures,这是一种异步计算表达式的方法,无论是并行还是顺序。此外,该包还提供了一种用于迭代和映射的函数式编程方法,并支持通过该包进行并行化。parApply()parallelsforeachdoParallelfuturepurrrfuture

以下是一些示例

parApply() 示例:

library(parallel)

# Create a matrix
m <- matrix(1:20, nrow = 5)

# Define a function to apply to each column of the matrix
my_fun <- function(x) {
  x^2
}

# Apply the function to each column of the matrix in parallel
result <- parApply(cl = makeCluster(2), X = m, MARGIN = 2, FUN = my_fun)

# View the result
result

foreach 示例:

library(foreach)
library(doParallel)

# Register a parallel backend
registerDoParallel(cores = 2)

# Create a list of numbers
my_list <- list(1, 2, 3, 4, 5)

# Define a function to apply to each element of the list
my_fun <- function(x) {
  x^2
}

# Apply the function to each element of the list in parallel
result <- foreach(i = my_list) %dopar% my_fun(i)

# View the result
result

未来示例:

library(future)

# Plan to use a parallel backend
plan(multisession, workers = 2)

# Create a list of numbers
my_list <- list(1, 2, 3, 4, 5)

# Define a function to apply to each element of the list
my_fun <- function(x) {
  x^2
}

# Apply the function to each element of the list in parallel using futures
result <- future_map(my_list, my_fun)

# View the result
result

咕噜咕噜的例子:

library(purrr)
library(future)

# Plan to use a parallel backend
plan(multisession, workers = 2)

# Create a list of numbers
my_list <- list(1, 2, 3, 4, 5)

# Define a function to apply to each element of the list
my_fun <- function(x) {
  x^2
}

# Apply the function to each element of the list in parallel using purrr
result <- future_map(my_list, my_fun)

# View the result
result

编辑 2023-07-02(由未来的作者):将已弃用且不再存在的未来后端替换为 .multiprocessmultisession

评论

0赞 HenrikB 7/2/2023
您希望在调用之前创建一次,然后在示例末尾使用以确保它们已关闭。正如现在所写的那样,该示例每次调用时都会启动两个新的并行工作线程,最终阻塞计算机。cl <- makeCluster(2)parLapply()stopCluster(cl)