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


1040

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

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

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

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

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

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

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

附带的问题:我还没有学会摆弄或重塑-是否会全部取代plyrreshape全部取代?


33
对您的另一个问题是:plyr在许多方面都可以直接替代*apply()by。plyr(至少对我而言)似乎更加一致,因为我始终确切知道它期望的数据格式以及它要吐出的数据。那省了我很多麻烦。
JD

12
另外,我建议添加:doBy和的选择和应用功能data.table
Iterator

7
sapply只是lapply在添加simplify2array上输出。apply确实强制转换为原子向量,但输出可以是向量或列表。by将数据帧拆分为子数据帧,但不f单独用于列。仅当有data.frame类的方法时,才可能按f列应用byaggregate是通用的,因此针对第一个参数的不同类存在不同的方法。
IRTFM

8
助记符:l表示“列表”,s表示“简化”,t表示“每种类型”(分组的每个级别都是一种类型)
Lutz Prechelt

Rfast软件包中还存在一些函数,例如:eachcol.apply,apply.condition等,比R的等效函数更快
Stefanos

Answers:


1330

R有许多* apply函数,这些函数在帮助文件(例如?apply)中进行了详细描述。但是,尽管有足够多的资源,但初次使用的用户可能很难决定哪一个适合他们的情况,甚至难以记住它们。它们可能具有一般意义,即“我应该在这里使用* apply函数”,但是一开始很难保持它们的整齐。

尽管事实(在其他答案中已指出)* apply系列的许多功能已由非常流行的plyr软件包涵盖,但基本功能仍然有用且值得了解。

此答案旨在充当新用户的路标,以帮助将其定向到针对其特定问题的正确* apply函数。注意,这并不是要简单地反省或替换R文档!希望这个答案可以帮助您确定哪个* 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

    如果你想行/列手段或资金用于二维矩阵,一定要调查高度优化,闪电般快速的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 

    在更高级的用法中sapply,如果合适,它将尝试将结果强制为多维数组。例如,如果我们的函数返回相同长度的向量,sapply则将它们用作矩阵的列:

    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,但也许需要挤出一些更多的速度你的代码。

    对于vapply,您基本上给R给出了函数将返回哪种类型的示例,这可以节省一些时间来强制将返回值适合单个原子向量。

    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 - 当你有几个数据结构(例如载体,列表)和要一个函数应用到每个的第一元件,并且然后每个第二元件等,结果强迫到向量/阵列中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 - with 的包装,因此可以确保返回列表。mapplySIMPLIFY = FALSE

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
  • rapply - 当你想给一个函数应用到的每个元素嵌套列表结构,递归。

    为了让您了解不常见的rapply情况,我在首次发布此答案时就忘记了!显然,我敢肯定会有很多人使用它,但是YMMV。rapply最好用用户定义的函数来说明:

    # 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 - 当你想给一个函数应用到子集的载体和子集是由一些其它载体,通常是一个因素确定。

    * apply家族的败类。帮助文件中使用短语“参差不齐的数组”可能会有些混乱,但实际上非常简单。

    一个向量:

    x <- 1:20

    定义组的因素(长度相同!):

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

    x每个子组中的值相加y

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

    可以处理更复杂的示例,其中子组由几个因素列表的唯一组合定义。tapply在本质上分裂应用-结合是常见的功能在R(类似aggregatebyaveddply,等等)因此,它的黑色羊状态。


32
相信您会发现这by纯粹是分裂的,并且aggregatetapply它们的核心。我认为败类是最好的面料。
IRTFM 2011年

21
很棒的回应!这应该是官方R文档的一部分:)。一个微小的建议:也许还要添加关于使用aggregate和的项目符号by?(我在您的描述之后终于了解了它们!但是它们很常见,因此分离出来并为这两个功能提供一些特定的示例可能会很有用。)
grautur 2011年

3
@grautur我正在积极地从此答案中删除内容,以避免它(a)太长和(b)重新编写文档。我决定,同时aggregateby等基于*应用功能,你的方法使用它们的方法是从用户的角度不同,以至于他们应该在一个单独的回答进行总结。我可能会尝试,如果我有时间,或者别人可能会击败我,赢得我的支持。
joran 2011年

4
另外,?Map作为mapply
baptiste

3
@jsanders-我完全不同意。data.frames是R的绝对中心部分,并且list经常使用来操纵对象lapply。它们还充当将传统矩形数据集中许多类型的向量/因子组合在一起的容器。尽管data.table并且plyr可能会添加某些类型的语法,有些可能会觉得更舒适,但它们分别在data.frames 上扩展和作用。
thelatemail 2014年

191

在旁注中,这是各种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文件中:

相关的功能tapply,并sweep在没有相应的功能plyr,并保持有效。merge对于将摘要与原始数据结合起来很有用。


13
当我从头开始学习R时,我发现plyr MUCH比*apply()函数系列更容易学习。对我来说,ddply()非常直观,因为我熟悉SQL聚合函数。ddply()成了解决许多问题的锤子,其中一些问题本可以用其他命令更好地解决。
JD

1
我想我认为plyr功能背后的概念类似于*apply功能,因此,如果您可以做一个,就可以做另一个,但是plyr功能更容易记住。但是我完全同意这把ddply()锤子!
2010年

1
plyr软件包具有join()执行类似于合并的任务的功能。在plyr的背景下提及它也许更重要。
2014年

让我们不要忘记贫穷,被遗忘eapply
JDL

总的来说,这是一个很好的答案,但是我认为它轻描淡写了的效用vapply和缺点sapply。的主要优点vapply是它可以强制输出类型和长度,因此最终将得到确切的预期输出或信息错误。另一方面,sapply将尝试按照并非总是显而易见的规则来简化输出,否则将返回列表。例如,尝试预测将产生的输出类型:sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1))。那sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)
阿列克谢·希克洛马诺夫


100

首先从乔兰(Joran)的出色回答开始 -值得怀疑的事情会更好。

然后,以下助记符可能有助于记住它们之间的区别。虽然有些显而易见,但其他的可能不那么明显---对于这些,您会在Joran的讨论中找到正当理由。

助记符

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

建立正确的背景

如果使用apply家庭仍然对您有点陌生,则可能是您缺少关键观点。

这两篇文章会有所帮助。它们提供了必要的背景来激发函数apply系列提供的函数编程技术

Lisp的用户将立即认识到该范例。如果您不熟悉Lisp,一旦对FP有所了解,您将获得在R中使用的强大观点-并且apply会更加有意义。


51

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

通过

by但是,如文档中所述,该功能可以用作的“包装器” tapplyby当我们要计算tapply无法处理的任务时,就会产生力量。此代码是一个示例:

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对于cbarrayct

就像我说过的那样,by当我们不能使用时,力量就会产生tapply。以下代码是一个示例:

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

R说参数必须具有相同的长度,比如说“我们要计算沿因子summary的所有变量的irisSpecies”:但是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     

它确实有效,并且结果令人惊讶。这是一个类的对象,bySpecies(针对summary每个变量)计算每个变量的。

请注意,如果第一个参数是a 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

骨料

aggregatetapply如果我们以这种方式使用它,可以将其视为另一种不同的使用方法。

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参数的数据子集,并且具有用于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,因为formulaaggregate函数的方法默认具有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)

先前的代码实现了相同的目标和结果,在某些时候,使用哪种工具只是个人喜好和需求的问题。就子集而言,前两个对象有非常不同的需求。


正如我已经说过的,当我们不能使用tapply时,by的力量就会产生。以下代码是一个示例:这是您上面使用过的词。您已给出了计算摘要的示例。好吧,可以说只有在需要清洗的情况下才可以计算汇总统计数据:例如, data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))这是使用tapply . With the right splitting there is nothing you cant do with tapply . The only thing is it returns a matrix. Please be careful by saying we cant use tapply`
Onyambu

35

有很多很棒的答案讨论了每个功能用例的差异。没有答案讨论性能差异。这是合理的,因为各种功能需要各种输入并产生各种输出,但是大多数功能具有按系列/组进行评估的通用目标。我的答案将集中在性能上。由于上述原因,向量中的输入创建已包含在时序中,因此也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

dplyr低于applt函数是否正常?
Mostafa

1
@DimitriPetrenko我不这么认为,不确定为什么会在这里。最好根据自己的数据进行测试,因为有许多因素在起作用。
jangorecki

28

尽管这里有很多很棒的答案,但还有两个基本功能值得一提:有用的outer功能和晦涩的eapply功能

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], ...).

这似乎只对线性代数类型的东西有用。但是,它可以像mapply将函数应用于两个输入向量一样使用。不同之处在于,mapply将函数应用于前两个元素,然后outer应用于第二个,以此类推,而将函数应用于第一向量中的一个元素和第二向量中的一个的每种组合。例如:

 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 

坦白地说,我使用的不是很多,但是如果您要构建很多软件包或创建很多环境,它可能会派上用场。


25

也许值得一提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整个数据帧一样工作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
## ...

12

我最近发现了相当有用的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,"-")
print(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
# 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)

2
@弗兰克:好吧,老实说,这篇文章的标题是令人误解的:当您阅读问题本身时,它就是关于“申请家庭”的。sweep是一个高阶函数,如此处提到的所有其他函数,例如apply,因此可能会问相同的问题,即接受的答案超过1000个投票以及其中给出的示例。看看那里给出的例子。sapplylapplyapply
vonjd

2
扫描具有误导的名称,误导的默认值和误导的参数名称:)。这样我更容易理解:1)STATS是向量或单个值,将被重复以形成与第一个输入大小相同的矩阵,2)FUN将应用于第一个输入和这个新矩阵。也许更好地说明:sweep(matrix(1:6,nrow=2),2,7:9,list)。它通常比apply在where apply循环sweep中能够使用矢量化函数更有效。
Moody_Mudskipper

2

在最近在CRAN上发布的崩溃包中,我尝试将大多数常见的Apply功能压缩为仅两个功能:

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

句法:

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. BYS3泛型,用于使用向量,矩阵和data.frame方法进行拆分应用组合计算。它显着快于tapplyby并且aggregate(但plyr在大数据上也快于dplyr)。

句法:

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

谈论性能:崩溃的主要目标是在R中促进高性能编程,并超越所有组合在一起的组合。为此目的,该包装具有全套的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 ...

在小插图包中,我提供了基准。使用快速功能进行编程比使用dplyrdata.table进行编程快得多,尤其是在较小的数据上,在大型数据上也是如此。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.