【问题标题】:Apply multiple functions to multiple columns in data.table将多个函数应用于 data.table 中的多个列
【发布时间】:2015-06-19 16:13:21
【问题描述】:

我正在尝试将多个函数应用于data.table 的多个列。示例:

DT <- data.table("a"=1:5,
                 "b"=2:6,
                 "c"=3:7)

假设我想获得ab 列的平均值和中位数。 这有效:

stats <- DT[,.(mean_a=mean(a),
               median_a=median(a),
               mean_b=mean(b),
               median_b=median(b))]

但它太重复了。有没有使用.SDcolslapply 实现类似结果的好方法?

【问题讨论】:

  • 为什么不把函数放到自定义函数中调用呢?
  • 或者,也许看看“data.table”的开发版本,dcast 可以同时处理多个列聚合。
  • 这可能更容易使用dplyr summarise_each(DT,funs(mean, median), 1:2)
  • 实现colwise()会更好。

标签: r data.table


【解决方案1】:

其他答案显示了如何做到这一点,但没有人费心解释基本原理。基本规则是j 表达式返回的列表元素形成结果data.table 的列。任何产生列表的j 表达式都可以工作,列表中的每个元素都对应于结果中的所需列。考虑到这一点,我们可以使用

DT[, c(mean = lapply(.SD, mean),
       median = lapply(.SD, median)),
  .SDcols = c('a', 'b')]
##    mean.a mean.b median.a median.b
## 1:      3      4        3        4

DT[, unlist(lapply(.SD,
                   function(x) list(mean = mean(x),
                                    median = median(x))),
            recursive = FALSE),
   .SDcols = c('a', 'b')]
##    a.mean a.median b.mean b.median
## 1:      3        3      4        4

取决于所需的顺序。

重要的是,我们可以使用任何我们想要产生所需结果的方法,只要我们将结果排列到如上所述的列表中即可。例如,

library(matrixStats)
DT[, c(mean = as.list(colMeans(.SD)),
       median = setNames(as.list(colMedians(as.matrix(.SD))), names(.SD))),
   .SDcols = c('a', 'b')]
##    mean.a mean.b median.a median.b
## 1:      3      4        3        4

也可以。

【讨论】:

  • 我认为第一个示例不会重命名列,否则这是一个非常有用的答案。谢谢!
  • 不偏离原则的最佳答案。我一直使用.() 来获得多个输出,但是将它与.(lapply(.SD), other = function(col) ) 之类的lapply 结合起来效果不佳。我意识到使用c(lapply(.SD,func), other = function(col)) 是正确的方法。
【解决方案2】:

这可能有点过度设计,但如果您来自 dplyr 的 summarize_at(),您可能希望获得类似的结构化结果。

首先定义一个函数lapply_at(),它接受.SD 和函数名称的字符向量作为输入。然后,您可以轻松计算所需的统计数据并获得可读的结果。

library(data.table)
iris_dt <- as.data.table(iris)

lapply_at <- function(var, funs, ...) {
  results <- sapply(var, function(var) {
    lapply(funs, do.call, list(var, ...))
  })
  names(results) <- vapply(names(var), paste, funs, sep = "_", 
                           FUN.VALUE = character(length(funs)),
                           USE.NAMES = FALSE)
  results
}

iris_dt[, lapply_at(.SD, c("mean", "sd"), na.rm = TRUE), 
        .SDcols = patterns("^Sepal"),
        by = Species]

#>       Species Sepal.Length_mean Sepal.Length_sd Sepal.Width_mean
#> 1:     setosa             5.006       0.3524897            3.428
#> 2: versicolor             5.936       0.5161711            2.770
#> 3:  virginica             6.588       0.6358796            2.974
#>    Sepal.Width_sd
#> 1:      0.3790644
#> 2:      0.3137983
#> 3:      0.3224966

reprex package (v0.2.0) 于 2019 年 7 月 3 日创建。

【讨论】:

    【解决方案3】:

    使用 dcast

    DT$dday <- 1 # add a constant column
    dt <- dcast(DT, dday~dday, fun=list(sum, mean), value.var = c('a', 'b'))
    # dday a_sum_1 b_sum_1 a_mean_1 b_mean_1
    # 1      15      20        3        4
    

    其实我们可以用dcast来实现onehot和特征工程师。

    【讨论】:

      【解决方案4】:

      我通常会这样做:

      my.summary = function(x) list(mean = mean(x), median = median(x))
      
      DT[, unlist(lapply(.SD, my.summary)), .SDcols = c('a', 'b')]
      #a.mean a.median   b.mean b.median 
      #     3        3        4        4 
      

      【讨论】:

      • 我有类似的想法,但认为 OP 想要一个 data.table 输出而不是一个向量 DT[, as.list(unlist(lapply(.SD, my.summary))), .SDcols = c('a', 'b')]
      • 你也可以简化为my.summary = function(x) c(mean = mean(x), median = median(x)) ; DT[, sapply(.SD, my.summary), .SDcols = a:b]
      • 但是如果我按类别添加一个组DT[, as.list(unlist(lapply(.SD, my.summary))), by=category, .SDcols=c('a', 'b') ] 这似乎非常慢,这比单独进行每个摘要然后加入要花费更长的时间。有什么更快的方法来做到这一点?我在@akrun 类别列中有大约 150 万个组
      • 值得一提的是,如果添加 by 分组,输出会完全不同(长)!那你会怎么做呢?
      • 我认为by 的代码是:as.list(unlist(lapply(...?
      【解决方案5】:

      这有点笨拙,但可以使用data.table

      funcs = c('median', 'mean', 'sum')
      
      m = DT[, lapply(.SD, function(u){
              sapply(funcs, function(f) do.call(f,list(u)))
           })][, t(.SD)]
      colnames(m) = funcs
      
      #  median mean sum
      #a      3    3  15
      #b      4    4  20
      #c      5    5  25
      

      【讨论】:

      • 为一个t() 调用添加一个新的依赖似乎有点开销,为什么不使用链接呢? m = DT[...][, t(.SD)]。我认为它也更具可读性。
      • 好建议,我加了!
      • 如何包含一个有附加参数的函数?示例:quantile(., 0.25).
      • 使用来自functional 包的Curry 并定义funcs = c(median, sum, Curry(quantile, probs=0.25))。但是在这个阶段你必须自己定义 colnames。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-09
      • 2021-08-09
      • 2022-10-14
      相关资源
      最近更新 更多