【问题标题】:Aggregating sub totals and grand totals with data.table使用 data.table 聚合小计和总计
【发布时间】:2012-03-08 02:03:44
【问题描述】:

我在 R 中有一个 data.table

library(data.table)
set.seed(1)
DT = data.table(
  group=sample(letters[1:2],100,replace=TRUE), 
  year=sample(2010:2012,100,replace=TRUE),
  v=runif(100))

按组和年份将这些数据汇总到汇总表中既简单又优雅:

table <- DT[,mean(v),by='group, year']

但是,将这些数据汇总到一个汇总表中(包括小计和总计)有点困难,而且也不那么优雅:

library(plyr)
yearTot <- DT[,list(mean(v),year='Total'),by='group']
groupTot <- DT[,list(mean(v),group='Total'),by='year']
Tot <- DT[,list(mean(v), year='Total', group='Total')]
table <- rbind.fill(table,yearTot,groupTot,Tot)
table$group[table$group==1] <- 'Total'
table$year[table$year==1] <- 'Total'

这会产生:

table[order(table$group, table$year), ]

有没有一种简单的方法可以用 data.table 指定小计和总计,例如 plyr 的 margins=TRUE 命令?我更喜欢在我的数据集上使用 data.table 而不是 plyr,因为它是一个非常大的数据集,我已经拥有 data.table 格式。

【问题讨论】:

    标签: r aggregate plyr data.table


    【解决方案1】:

    在最近开发的 data.table 中,您可以使用称为“分组集”的新功能来生成小计:

    library(data.table)
    set.seed(1)
    DT = data.table(
        group=sample(letters[1:2],100,replace=TRUE), 
        year=sample(2010:2012,100,replace=TRUE),
        v=runif(100))
    
    cube(DT, mean(v), by=c("group","year"))
    #    group year        V1
    # 1:     a 2011 0.4176346
    # 2:     b 2010 0.5231845
    # 3:     b 2012 0.4306871
    # 4:     b 2011 0.4997119
    # 5:     a 2012 0.4227796
    # 6:     a 2010 0.2926945
    # 7:    NA 2011 0.4463616
    # 8:    NA 2010 0.4278093
    # 9:    NA 2012 0.4271160
    #10:     a   NA 0.3901875
    #11:     b   NA 0.4835788
    #12:    NA   NA 0.4350153
    cube(DT, mean(v), by=c("group","year"), id=TRUE)
    #    grouping group year        V1
    # 1:        0     a 2011 0.4176346
    # 2:        0     b 2010 0.5231845
    # 3:        0     b 2012 0.4306871
    # 4:        0     b 2011 0.4997119
    # 5:        0     a 2012 0.4227796
    # 6:        0     a 2010 0.2926945
    # 7:        2    NA 2011 0.4463616
    # 8:        2    NA 2010 0.4278093
    # 9:        2    NA 2012 0.4271160
    #10:        1     a   NA 0.3901875
    #11:        1     b   NA 0.4835788
    #12:        3    NA   NA 0.4350153
    

    【讨论】:

    • 你能添加更多关于立方体函数的细节吗?我似乎无法从 ??cube 中找到任何信息,而且我的 R 也找不到该功能。
    • @DVL 所有分组集功能都在同一手册页下可用,?cube 应该会出现在此页面。你会在那里找到例子。在线版:rdatatable.gitlab.io/data.table/library/data.table/html/…
    • @jangorecki:难道这个版本的 data.table 还没有在 CRAN 上?您提供的链接说明 data.table 版本 1.10.5;我刚刚从 CRAN 更新了 data.table,但只更新到版本 1.10.4-3
    • @Julian 1.10.5 尚未在 CRAN 上,data.table 的开发非常活跃。您可以使用 install.packages("data.table", type = "source", repos = "http://Rdatatable.github.io/data.table") 从软件包源安装。如果您想要二进制包,请参阅Installation wiki 了解详细信息。
    • 对于没有密切关注评论日期的任何人,这是自 2018 年 5 月 1 日以来在 CRAN 上的。
    【解决方案2】:

    我不知道一个简单的方法。这是实现的第一次尝试。我不知道 plyr 中的 margins=TRUE,这是做什么的?

    crossby = function(DT, j, by) {
        j = substitute(j)
        ans = rbind(
            DT[,eval(j),by],
            DT[,list("Total",eval(j)),by=by[1]],
            cbind("Total",DT[,eval(j),by=by[2]]),
            list("Total","Total",DT[,eval(j)]),
            use.names=FALSE
            # 'use.names' argument added in data.table v1.8.0
        )
        setkeyv(ans,by)
        ans
    }
    
    crossby(DT, mean(v), c("group","year"))
    
          group  year        V1
     [1,]     a  2010 0.2926945
     [2,]     a  2011 0.4176346
     [3,]     a  2012 0.4227796
     [4,]     a Total 0.3901875
     [5,]     b  2010 0.5231845
     [6,]     b  2011 0.4997119
     [7,]     b  2012 0.4306871
     [8,]     b Total 0.4835788
     [9,] Total  2010 0.4278093
    [10,] Total  2011 0.4463616
    [11,] Total  2012 0.4271160
    [12,] Total Total 0.4350153
    

    【讨论】:

    • tables 包似乎可以与DT 一起使用,尽管不清楚它是否真的在下面使用data.table 操作 -- library(tables); tabular(group + 1 ~ (factor(year) + 1) * v * mean, data = DT)
    • Matthew,请您直接在 data.table 中实现类似的东西吗?
    • @Michael 你的意思是希望我将crossby(如上所示)作为新功能添加到data.table 包中吗?或者您想将margin 参数添加到[.data.table?我不是很清楚。
    • @迈克尔。好的。我现在提交了#2695 以添加(或类似的)。谢谢。
    • @MatthewDowle 我不知道这是否是因为上面的代码适用于旧版本的data.table,但我必须进行一些更改才能使上面的代码工作 - 我必须将by[1]by[2] 包装在c() 中,我必须将.SD 添加到第二个eval,并且我必须确保yearDT 中的一个字符列(否则rbind会将Total 转换为NA)
    【解决方案3】:

    请参阅下面的解决方案 - 类似于上面的 @MattDowle - 需要任意数量的组。

    crossby2 <- function(data, j, by, grand.total = T, total.label = "(all)", value.label = "value") {
      j = substitute(j)
    
      # Calculate by each group
      lst <- lapply(1:length(by), function(i) {
        x <- data[, list(..VALUE.. = eval(j)), by = eval(by[1:i])]
        if (i != length(by)) x[, (by[-(1:i)]) := total.label]
        return(x)
      })
    
      # Grand total
      if (grand.total) lst <- c(lst, list(data[, list(..VALUE.. = eval(j))][, (by) := total.label]))
    
      # Combine all tables
      res <- rbindlist(lst, use.names = T, fill = F)
    
      # Change value column name
      setnames(res, "..VALUE..", value.label)
    
      # Set proper column order
      setcolorder(res, c(by, value.label))
    
      # Sort values
      setkeyv(res, by)
    
      return(res)
    }
    

    【讨论】:

      【解决方案4】:

      使用当前答案,我添加了对多个度量和聚合函数的支持,并且可以添加聚合级别指示器。

      #' @title SQL's ROLLUP function
      #' @description Returns data.table of aggregates value for each level of hierarchy provided in `by`.
      #' @param x data.table input data.
      #' @param j expression to evaluate in `j`, support multiple measures.
      #' @param by character a hierarchy level for aggregations.
      #' @param level logical, use `TRUE` to add `level` column of sub-aggregation.
      #' @seealso [postgres: GROUPING SETS, CUBE, and ROLLUP](http://www.postgresql.org/docs/9.5/static/queries-table-expressions.html#QUERIES-GROUPING-SETS), [SO: Aggregating sub totals and grand totals with data.table](http://stackoverflow.com/a/24828162/2490497)
      #' @return data.table
      #' @examples 
      #' set.seed(1)
      #' x = data.table(group=sample(letters[1:2],100,replace=TRUE),
      #'                year=sample(2010:2012,100,replace=TRUE),
      #'                v=runif(100))
      #' rollup(x, .(vmean=mean(v), vsum=sum(v)), by = c("group","year"))
      library(data.table)
      rollup = function(x, j, by, level=FALSE){
          stopifnot(is.data.table(x), is.character(by), length(by) >= 2L, is.logical(level))
          j = substitute(j)
          aggrs = rbindlist(c(
              lapply(1:(length(by)-1L), function(i) x[, eval(j), c(by[1:i])][, (by[-(1:i)]) := NA]), # subtotals
              list(x[, eval(j), c(by)]), # leafs aggregations
              list(x[, eval(j)][, c(by) := NA]) # grand total
          ), use.names = TRUE, fill = FALSE)
          if(level) aggrs[, c("level") := sum(sapply(.SD, is.na)), 1:nrow(aggrs), .SDcols = by]
          setcolorder(aggrs, neworder = c(by, names(aggrs)[!names(aggrs) %in% by]))
          setorderv(aggrs, cols = by, order=1L, na.last=TRUE)
          return(aggrs[])
      }
      set.seed(1)
      x = data.table(group=sample(letters[1:2],100,replace=TRUE),
                     year=sample(2010:2012,100,replace=TRUE),
                     month=sample(1:12,100,replace=TRUE),
                     v=runif(100))
      rollup(x, .(vmean=mean(v), vsum=sum(v)), by = c("group","year","month"), level=TRUE)
      

      【讨论】:

      • 这是您的一个包裹的一部分吗?
      • 不,在 data.table 中有这样的东西会很好,我已经添加了 FR,看看是否有机会在 C 中加速此类操作,如果不可能,那么也许这样的包装器可以在 PR 中提出。
      • @DavidArenburg 从现在开始就是这样。它更加精确,因为它允许仅计算选择的聚合级别而跳过其余级别。 levels 参数采用整数向量表示不同级别的聚合。你可以在这里找到函数rollup.R#L11
      【解决方案5】:

      借用这个答案 (https://stackoverflow.com/a/39536828/4241780),下面提供了所有子集的摘要(与 crossby2rollup 不同,它们似乎错过了 OP 所需输出的第 9 到 11 行)。此函数可扩展到任意数量的 by 或聚合变量,尽管在其当前状态下仅允许一种类型的聚合函数。非常适合按组交互计算行小计(我用它来做什么)。

      add_col_sums.data.table <- function(data, aggvars, byvars, FUN = sum, level = "level") {
      
        # Find all possible subsets of your data
        subsets <- lapply(0:length(byvars), combn, x = byvars, simplify = FALSE)
        subsets <- do.call(c, subsets)
      
        # Calculate summary value by each subset
        agg_values <- lapply(subsets, function(x) 
          data[,lapply(.SD, FUN), by = x, .SDcols = aggvars])
      
        # Pull them all into one dataframe
        dat_out <- rbindlist(agg_values, fill = TRUE)
      
        # Order columns and rows
        setorderv(dat_out, byvars, na.last = TRUE)
        setcolorder(dat_out, c(byvars, aggvars))
      
        # Add level indication
        dat_out[, c(level) := Reduce("+", lapply(.SD, is.na))]
      
        # Return data.table
        dat_out[]
      
      }
      
      add_col_sums.data.table(DT, "v", c("group", "year"), FUN = mean)
      

      【讨论】:

      • 非常好的解决方案。第一次工作,不像其他人在多列集上工作。
      猜你喜欢
      • 2015-12-25
      • 2014-07-27
      • 2013-07-04
      • 2018-09-01
      • 2020-03-26
      • 2013-06-10
      • 2019-10-03
      • 1970-01-01
      • 2019-06-14
      相关资源
      最近更新 更多