【问题标题】:Grouping functions (tapply, by, aggregate) and the *apply family分组函数(tapply、by、aggregate)和 *apply 系列
【发布时间】:2021-04-16 16:48:04
【问题描述】:

每当我想在 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,但不是漂亮地打印输出,而是聚合将所有内容粘贴到数据框中。

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

【问题讨论】:

  • 你的小问题:对于很多事情,plyr 是*apply()by 的直接替代品。 plyr(至少对我来说)似乎更加一致,因为我总是确切地知道它期望什么数据格式以及它会吐出什么。这为我省去了很多麻烦。
  • 另外,我建议添加:doBy 以及data.table 的选择和应用功能。
  • sapply 只是lapply,在输出中添加了simplify2arrayapply 会强制转换为原子向量,但输出可以是向量或列表。 by 将数据帧拆分为子数据帧,但不会在列上单独使用f。仅当存在 'data.frame' 类的方法时,f 才会由by 逐列应用。 aggregate 是通用的,因此对于第一个参数的不同类存在不同的方法。
  • 助记符:l 表示'list',s 表示'simplifying',t 表示'per type'(分组的每一级都是一个类型)
  • Rfast 包中也存在一些函数,例如:eachcol.apply、apply.condition 等,比 R 的等价物更快

标签: r lapply sapply tapply r-faq


【解决方案1】:

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
    

    如果您想要二维矩阵的行/列均值或总和,请务必 调查高度优化的闪电般快速的colMeansrowMeans, colSums, rowSums.

  • 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 - 当你想将一个函数应用到一个 依次列出,但您想要一个 vector 返回,而不是列表。

    如果您发现自己在输入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 但可能需要 从您的代码或want more type safety 中挤出更多速度。

    对于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
    
  • ma​​pply - 当您有多个数据结构时(例如 向量,列表),并且您想将函数应用于第一个元素 每个的,然后是每个的第二个元素,等等,强制结果 到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 - 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 - 当你想将一个函数应用到一个 subsets 向量和子集由其他向量定义,通常是 因素。

    *apply 家族的害群之马。帮助文件的使用 短语“参差不齐的数组”可能有点confusing,但实际上是 很简单。

    一个向量:

     x <- 1:20
    

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

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

    y 定义的每个子组内将x 中的值相加:

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

    可以处理定义子组的更复杂的示例 由一系列因素的独特组合。 tapply 是 在精神上类似于拆分应用组合功能 在 R 中很常见(aggregatebyaveddply 等)因此它的 害群之马状态。

【讨论】:

  • 相信你会发现by是纯粹的split-lapply,aggregatetapply的核心。我认为害群之马是出色的面料。
  • 很棒的反应!这应该是官方 R 文档的一部分:)。一个小小的建议:也许在使用aggregateby 时也添加一些要点? (经过您的描述,我终于明白了!但是它们很常见,因此将这两个功能分开并提供一些具体示例可能会很有用。)
  • 是的……到底什么是参差不齐的数组?
【解决方案2】:

附带说明,这里是各种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 to Plyr 文档:

相关函数tapplysweepplyr中没有对应的函数,仍然有用。 merge 可用于将摘要与原始数据相结合。

【讨论】:

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

来自http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy的第21张幻灯片:

(希望很明显apply 对应于@Hadley 的aaplyaggregate 对应于@Hadley 的ddply 等。如果您没有从这张图片中得到它,同一张幻灯片的幻灯片20 会澄清。 )

(左边是输入,上面是输出)

【讨论】:

    【解决方案4】:

    首先从Joran's excellent answer 开始——怀疑有什么能比这更好。

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

    助记符

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

    构建正确的背景

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

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

    Lisp 的用户会立即认出这个范例。如果您不熟悉 Lisp,一旦您了解了 FP,您将获得在 R 中使用的强大观点——apply 会更有意义。

    【讨论】:

      【解决方案5】:

      因为我意识到这篇文章的(非常优秀的)答案缺乏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 987654332@ 和 arrayct

      正如我所说,当我们不能使用tapply 时,by 的力量就会出现;下面的代码就是一个例子:

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

      R 说参数必须具有相同的长度,例如“我们想计算 iris 中所有变量的 summary 沿因子 Species”:但 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 参数的数据子集,并且它还具有用于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 函数的 987654375@ 方法默认具有 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`的使用`
      【解决方案6】:

      有很多很好的答案讨论了每个功能的用例差异。没有一个答案讨论性能差异。这是合理的,因为各种功能期望各种输入并产生各种输出,但它们中的大多数都有一个通用的共同目标来按系列/组进行评估。我的答案将集中在性能上。由于上述来自向量的输入创建包含在计时中,因此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
      

      【讨论】:

        【解决方案7】:

        尽管这里有所有很好的答案,但还有 2 个基本函数值得一提,有用的 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
        

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

        申请

        eapplylapply 类似,只是它不是将函数应用于列表中的每个元素,而是将函数应用于环境中的每个元素。例如,如果您想在全局环境中查找用户定义函数的列表:

        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 
        

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

        【讨论】:

          【解决方案8】:

          也许值得一提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
          ## ...
          

          【讨论】:

            【解决方案9】:

            我最近发现了相当有用的sweep 函数,为了完整起见将其添加到这里:

            扫一扫

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

            假设你有一个矩阵并且想standardize它的列:

            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)

            更轻松地实现

            【讨论】:

            • 这和分组有关吗?
            • @Frank:好吧,老实说,这篇文章的标题颇具误导性:当您阅读问题本身时,它是关于“申请家庭”的。 sweep 是一个高阶函数,就像这里提到的所有其他函数一样,例如apply, sapply, lapply 所以同样的问题可以被问到超过 1,000 票赞成的答案和其中给出的例子。看看那里为apply 给出的例子。
            • sweep 具有误导性名称、误导性默认值和误导性参数名称 :)。这样我更容易理解:1) STATS 是向量或单个值,将重复形成与第一个输入相同大小的矩阵,2) FUN 将应用于第一个输入和这个新矩阵。也许更好地说明:sweep(matrix(1:6,nrow=2),2,7:9,list)。它通常比 apply 更有效,因为在 apply 循环的地方,sweep 能够使用矢量化函数。
            【解决方案10】:

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

            1. dapply(Data-Apply)将函数应用于矩阵和 data.frames 的行或(默认)列,并且(默认)返​​回相同类型和具有相同属性的对象(除非每次计算的结果都是原子的并且@ 987654322@)。对于 data.frame 列,性能与 lapply 相当,对于矩阵行或列,性能比 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. BY 是一个 S3 泛型,用于使用向量、矩阵和 data.frame 方法进行拆分-应用-组合计算。它比tapplybyaggregate 快得多(也比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

            谈论性能:collapse 的主要目标是促进 R 中的高性能编程,并超越拆分-应用-组合。为此,该软件包具有全套基于 C++ 的快速泛型函数:fmeanfmedianfmodefsumfprodfsdfvarfmin、@987654345 @, ffirst, flast, fNobs, fNdistinct, fscale, fbetween, fwithin, fHDbetween, fHDwithin, flag, flag, @987654355.它们在一次遍历数据中执行分组计算(即没有拆分和重组)。

            语法:

            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 编程要快得多,尤其是在较小的数据上,而且在大数据上也是如此。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              相关资源
              最近更新 更多