首页 文章

R与ddply的总和和平均值

提问于
浏览
5

我的数据框有两列用作分组键,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))

总结一下:

  • 我不想显式地键入所有17列,就像第一个示例使用 xyz .

  • 理想情况下,我想通过单次调用 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

7 回答

  • 0

    使用 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)
    
  • 2

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

    只是投入另一个解决方案:

    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
    
  • 10

    它使用 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.tableplyrdplyr 慢了 . dplyrplyr 快 . 可以想象,基准测试结果可能会根据列数,分组因子中的级别数以及应用的特定函数而发生变化 . 例如,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)很挑剔,并且有几个相互掩盖的函数 .

  • 1

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

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

    要缩短代码(你尝试用colwise做什么),你可能必须编写一些函数来实现你想要的 .

  • 2

    为了完整起见,这里有一个基于 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)
    

    人们可能不会对之前命名的重复列规范感到满意,但这似乎是一个优雅的解决方案 .

    它具有优于MarkusN的优势解决方案,它不需要匹配新创建的列,也不会更改其名称 .

    Veerendra Gadekar的解决方案应以 select(cut, 8:10, price) %>% arrange(cut) 结束,以产生预期结果(列的子集,以及按分组键排序的行) . Hong Ooi的建议类似于第一个,但假设没有其他专栏 .

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

相关问题