R与ddply的总和和平均值

时间:2015-11-13 22:00:57

标签: r plyr

我的数据框有两列用作分组键,17列需要在每个组中求和,而另一列应该是平均值。让我在diamonds的{​​{1}}的不同数据框中说明这一点。

我知道我可以这样做:

ggplot2

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

在研究这个时,我找到了ddply(diamonds, ~cut, summarise, x=sum(x), y=sum(y), z=sum(z), price=mean(price)) 函数,但我想出的最好的是:

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))

总结一下:

  • 我不想明确键入所有17列,就像第一个示例使用ddply(diamonds, ~cut, colwise(sum, 7:9), colwise(mean, ~price)) xy一样。
  • 理想情况下,我想通过一次调用z来执行此操作,而无需使用ddply(或类似函数),如第二个示例所示。

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

cbind

7 个答案:

答案 0 :(得分:10)

我想为此建议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

答案 1 :(得分:5)

针对您的特定情况的另一种方法(在我看来更容易阅读)(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)))

答案 2 :(得分:5)

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

library(dplyr)
library(ggplot2)

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

答案 3 :(得分:2)

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

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

这个想法是函数makeListddply创建一个参数列表。通过这种方式,您可以非常轻松地将术语添加到列表中(如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

答案 4 :(得分:2)

它使用dplyr,但我相信这将完全以合理易读的语法完成指定的目标:

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

唯一的&#34;技巧&#34;是合并中有一个管道表达式,它与总和的计算分开处理平均价格的计算。

我将此解决方案与@David Arenburg(使用data.table)和@thothal(使用问题请求使用plyr)提供的解决方案进行基准测试,并进行了5000次重复。此处data.table的出现速度低于plyrdplyrdplyrplyr快。可以想象,基准测试结果可能会根据列数,分组因子中的级别数以及应用的特定函数而发生变化。例如,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对于它们的加载顺序(dplyr之前的plyr)是挑剔的,并且有几个相互掩盖的函数。

答案 5 :(得分:1)

不是100%你正在寻找的东西,但它可能会给你另一个想法如何做到这一点。使用data.table您可以执行以下操作:

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

要缩短代码(你尝试用colwise做的),你可能需要编写一些函数来实现你想要的。

答案 6 :(得分:0)

为了完整起见,我们提供了基于dplyr的解决方案以及Veerendra Gadekar in another questionhere 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) %>% arrange(cut)结束,以产生预期结果(列的子集,以及按分组键排序的行)。 Hong Ooi的建议与此处的第一个类似,但假设没有其他专栏。

最后,它似乎比data.table解决方案更易读,更易于理解,例如the one proposed by David Arenburg