【问题标题】:Aggregate sum and mean in R with ddply使用 ddply 汇总 R 中的总和和均值
【发布时间】:2015-11-13 22:00:56
【问题描述】:

我的数据框有两列用作分组键,每组有 17 列需要求和,还有一列应该取平均值。让我在不同的数据帧上说明这一点,diamonds 和 ggplot2。

我知道我可以这样做:

ddply(diamonds, ~cut, summarise, x=sum(x), y=sum(y), z=sum(z), price=mean(price))

但是,虽然 3 列是合理的,但其中 17 列是不可接受的。

在研究这个时,我发现了colwise 函数,但我想出的最好的是:

cbind(ddply(diamonds, ~cut, colwise(sum, 7:9)), price=ddply(diamonds, ~cut, summarise, mean(price))[,2])

是否有可能进一步改进这一点?我想以更直接的方式来做,比如(想象的命令):

ddply(diamonds, ~cut, colwise(sum, 7:9), price=mean(price))

或:

ddply(diamonds, ~cut, colwise(sum, 7:9), colwise(mean, ~price))

总结一下:

  • 我不想像第一个示例中的 x、y 和 z 一样,明确键入所有 17 列。
  • 理想情况下,我希望通过一次调用 ddply 来完成此操作,而不像第二个示例中那样诉诸 cbind(或类似函数)。

作为参考,我期望的结果是 5 行 5 列:

        cut         x         y        z    price
1      Fair  10057.50   9954.07  6412.26 4358.758
2      Good  28645.08  28703.75 17855.42 3928.864
3 Very Good  69359.09  69713.45 43009.52 3981.760
4   Premium  82385.88  81985.82 50297.49 4584.258
5     Ideal 118691.07 118963.24 73304.61 3457.542

【问题讨论】:

  • 如果你使用的是dplyr,也许是diamonds %>% group_by(cut) %>% mutate(MeanPrice=mean(price)) %>% mutate_each(funs(sum), 7:9) %>% select(c(2,7:11)) %>% unique()

标签: r plyr


【解决方案1】:

我想为此建议data.table 解决方案。您可以轻松地按位置或按名称预定义要操作的列,然后无论您要操作多少列,都可以重用相同的代码。

预定义列名

Sums <- 7:9
Means <- "price"

运行代码

library(data.table)
data.table(diamonds)[, c(lapply(.SD[, Sums, with = FALSE], sum),
                         lapply(.SD[, Means, with = FALSE], mean))
                     , by = cut]

#          cut         x         y        z    price
# 1:     Ideal 118691.07 118963.24 73304.61 3457.542
# 2:   Premium  82385.88  81985.82 50297.49 4584.258
# 3:      Good  28645.08  28703.75 17855.42 3928.864
# 4: Very Good  69359.09  69713.45 43009.52 3981.760
# 5:      Fair  10057.50   9954.07  6412.26 4358.758

对于您的具体示例,这可以简化为

data.table(diamonds)[, c(lapply(.SD[, 7:9, with = FALSE], sum), pe = mean(price)), by = cut]
#          cut         x         y        z       pe
# 1:     Ideal 118691.07 118963.24 73304.61 3457.542
# 2:   Premium  82385.88  81985.82 50297.49 4584.258
# 3:      Good  28645.08  28703.75 17855.42 3928.864
# 4: Very Good  69359.09  69713.45 43009.52 3981.760
# 5:      Fair  10057.50   9954.07  6412.26 4358.758

【讨论】:

    【解决方案2】:

    另一个使用dplyr 的解决方案。首先,您将两个聚合函数应用于要聚合的每个变量。在结果变量中,您只选择所需的函数/变量组合。

    library(dplyr)
    library(ggplot2)
    
    diamonds %>%
        group_by(cut) %>%
        summarise_each(funs(sum, mean), x:z, price) %>%
        select(cut, matches("[xyz]_sum"), price_mean)
    

    【讨论】:

    • 然后您是否需要构建一个匹配表达式,该表达式要求用户明确键入所有 17 列...我想您可以使用范围内的 colnames 上的文本解析表达式来快捷方式.. .?
    • dplyr 的 @rpierce 选择功能为选择变量提供了很多可能性。 OP 必须找到一种方法来指定需要聚合的变量。但这对于此处讨论的所有解决方案都是必要的。
    • 技术上我想,任何使用范围选择器的东西在满足他们关于不需要直接指定每一列的要求方面似乎更干净一些。无论如何,您的解决方案比其他解决方案快很多!
    【解决方案3】:

    另一种方法(我认为更容易阅读)针对您的特定情况 (mean = sum/n!)

    nCut <- ddply(diamonds, ~cut, nrow)
    res <- ddply(diamonds, ~cut, colwise(sum, 6:9))
    res$price <- res$price/nCut$V1
    

    或更通用的,

    do.call(merge, 
        lapply(c(colwise(sum, 7:9), colwise(mean, 6)), 
               function(cw) ddply(diamonds, ~cut, cw)))
    

    【讨论】:

      【解决方案4】:

      只是提出另一个解决方案:

      library(plyr)
      library(ggplot2)
      trans <- list(mean = 8:10, sum = 7)
      
      makeList <- function(inL, mdat = diamonds, by = ~cut) {
         colN <- names(mdat)
         args <- unlist(llply(names(inL), function(n) {
            llply(inL[[n]], function(x) {
               ret <- list(call(n, as.symbol(colN[[x]])))
               names(ret) <- paste(n, colN[[x]], sep = ".")
               ret
            })
         }))
         args$.data <- as.symbol(deparse(substitute(mdat)))
         args$.variables <- by
         args$.fun <- as.symbol("summarise")
         args
      }
      
      do.call(ddply, makeList(trans))
      #         cut   mean.x   mean.y   mean.z sum.price
      # 1      Fair 6.246894 6.182652 3.982770   7017600
      # 2      Good 5.838785 5.850744 3.639507  19275009
      # 3 Very Good 5.740696 5.770026 3.559801  48107623
      # 4   Premium 5.973887 5.944879 3.647124  63221498
      # 5     Ideal 5.507451 5.520080 3.401448  74513487
      

      这个想法是函数makeList 为ddply 创建一个参数列表。通过这种方式,您可以很容易地将术语添加到列表中(如function.name = column.indices),ddply 将按预期工作:

      trans <- c(trans, sd = list(9:10))
      do.call(ddply, makeList(trans))
      #         cut   mean.x   mean.y   mean.z sum.price      sd.y      sd.z
      # 1      Fair 6.246894 6.182652 3.982770   7017600 0.9563804 0.6516384
      # 2      Good 5.838785 5.850744 3.639507  19275009 1.0515353 0.6548925
      # 3 Very Good 5.740696 5.770026 3.559801  48107623 1.1029236 0.7302281
      # 4   Premium 5.973887 5.944879 3.647124  63221498 1.2597511 0.7311610
      # 5     Ideal 5.507451 5.520080 3.401448  74513487 1.0744953 0.6576481
      

      【讨论】:

        【解决方案5】:

        它使用dplyr,但我相信这将以相当容易阅读的语法完全实现指定目标:

        diamonds %>%
          group_by(cut) %>%
          select(x:z) %>%
          summarize_each(funs(sum)) %>%
          merge(diamonds %>%
                  group_by(cut) %>%
                  summarize(price = mean(price))
                ,by = "cut")
        

        唯一的“诀窍”是合并内部有一个管道表达式,它可以将平均价格的计算与总和的计算分开处理。

        我将此解决方案与@David Arenburg(使用data.table)和@thothal(根据问题要求使用plyr)提供的解决方案进行了5000次复制的基准测试。这里data.table 的输出速度比plyr 和dplyr 慢。 dplyr 比 plyr 快。可以想象,基准测试结果可能会随着列数、分组因子中的级别数以及所应用的特定函数而变化。例如,MarkusN 在我完成初始基准测试后提交了一个答案,该答案比之前提交的样本数据答案要快得多。他通过计算许多不需要的汇总统计数据然后丢弃它们来实现这一点……当然,在某个点上,这种方法的成本一定会超过好处。

               test replications elapsed relative user.self sys.self user.child sys.child
        2 dataTable         5000 119.686    2.008   119.611    0.127          0         0
        1     dplyr         5000  59.614    1.000    59.676    0.004          0         0
        3      plyr         5000  68.505    1.149    68.493    0.064          0         0
        ?      MarkusN      5000  23.172    ?????    23.926        0          0         0
        

        当然,速度不是唯一的考虑因素。特别是,dplyr 和 plyr 对它们的加载顺序很挑剔(plyr 在 dplyr 之前)并且有几个相互屏蔽的函数。

        【讨论】:

        • 我想这不是关于速度,而是关于方便。如果您现在需要添加另一个汇总函数,则需要添加另一个 merge 语句。
        • 相对于 data.table 解决方案,这肯定更冗长。但是,它没有 plyr 解决方案那么冗长。对于这三种解决方案中的哪一种是“最好的”,我没有任何利益关系。我怀疑那里的答案将完全取决于某人对什么感到满意以及他们看重什么。我唯一认为值得提供符合问题标准的 dplyr 解决方案。
        • ... 据我所知,如果您想使用另一组不相交的变量进行汇总,则只需要添加另一个合并语句。在dplyr 中很可能有更好的方式来表达这个过程,这只是我的尝试。
        • 与哪个data.table 解决方案进行比较?第一个还是第二个?您在这里只使用了一次summarize_each,因为您假设只有一列可以运行mean,因此您应该与第二种解决方案进行比较。此外,数据应该足够大。您应该在此处发布整个基准测试代码,而不是只说哪个更快。
        • 遗憾的是,基准代码是短暂的。下次我会发布它;在我写作的那一刻,它似乎有点冗长。我基准测试的 data.table 版本是你的。
        【解决方案6】:

        不是 100% 你在寻找什么,但它可能会给你另一个关于如何去做的想法。使用data.table 你可以这样做:

        diamonds2[, .(c = sum(c), p = sum(p), ce = sum(ce), pe = mean(pe)), by = cut]
        

        为了缩短代码(你试图用 colwise 做的事情),你可能必须编写一些函数来实现你想要的。

        【讨论】:

        • 我想使用来自ggplot2 的diamonds 数据集使其可重现,但被我工作区中的旧数据框愚弄了,抱歉。我已经更新了我的问题,希望这次能说明我想要实现的目标。
        【解决方案7】:

        为了完整起见,这里有一个基于dplyr 的解决方案以及Veerendra Gadekar in another question 和here by MarkusN 发布的答案。

        在这种特殊情况下,可以先将sum 应用于某些列,然后将mean 应用于所有感兴趣的列:

        diamonds %>%
          group_by(cut) %>%
          mutate_each('sum', 8:10) %>%
          summarise_each('mean', 8:10, price)
        

        这是可能的,因为mean 不会更改8:10 列的计算总和,而是会计算所需的平均价格。但是如果我们想计算价格的标准差而不是平均值,这种方法就行不通了,因为8:10 的列都为 0。

        更通用的方法可能是:

        diamonds %>%
           group_by(cut) %>%
           mutate_each('sum', 8:10) %>%
           mutate_each('mean', price) %>%
           summarise_each('first', 8:10, price)
        

        summarise_each 重复前面提到的列规范可能会让您不满意,但这似乎是一个优雅的解决方案。

        与 MarkusN 的解决方案相比,它的优势在于它不需要匹配新创建的列,也不会更改它们的名称。

        Veerendra Gadekar 的解决方案应以 select(cut, 8:10, price) %&gt;% arrange(cut) 结尾,以产生预期的结果(列的子集,加上按分组键排序的行)。 Hong Ooi 的建议与此处的第一个类似,但假设没有其他列。

        最后,它似乎比data.table 解决方案(如the one proposed by David Arenburg)更清晰易懂。

        【讨论】:

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