【问题标题】:Add new columns to a data.table containing many variables将新列添加到包含许多变量的 data.table
【发布时间】:2014-10-16 02:37:44
【问题描述】:

我想根据分组计算同时向data.table 添加许多新列。我的数据的一个工作示例如下所示:

     Time     Stock x1 x2 x3
1: 2014-08-22     A 15 27 34
2: 2014-08-23     A 39 44 29
3: 2014-08-24     A 20 50  5
4: 2014-08-22     B 42 22 43
5: 2014-08-23     B 44 45 12
6: 2014-08-24     B  3 21  2

现在我想scalesum 的许多变量得到如下输出:

         Time Stock x1 x2 x3   x2_scale   x3_scale x2_sum x3_sum
1: 2014-08-22     A 15 27 34 -1.1175975  0.7310560    121     68
2: 2014-08-23     A 39 44 29  0.3073393  0.4085313    121     68
3: 2014-08-24     A 20 50  5  0.8102582 -1.1395873    121     68
4: 2014-08-22     B 42 22 43 -0.5401315  1.1226726     88     57
5: 2014-08-23     B 44 45 12  1.1539172 -0.3274462     88     57
6: 2014-08-24     B  3 21  2 -0.6137858 -0.7952265     88     57

我的问题的蛮力实现是:

library(data.table)

set.seed(123)
d <- data.table(Time = rep(seq.Date( Sys.Date(), length=3, by="day" )),
                Stock = rep(LETTERS[1:2], each=3 ),
                x1 = sample(1:50, 6),
                x2 = sample(1:50, 6),
                x3 = sample(1:50, 6))

d[,x2_scale:=scale(x2),by=Stock]
d[,x3_scale:=scale(x3),by=Stock]
d[,x2_sum:=sum(x2),by=Stock]
d[,x3_sum:=sum(x3),by=Stock]

描述类似问题的其他帖子(Add multiple columns to R data.table in one function call?Assign multiple columns using := in data.table, by group)建议以下解决方案:

  d[, c("x2_scale","x3_scale"):=list(scale(x2),scale(x3)), by=Stock]
  d[, c("x2_sum","x3_sum"):=list(sum(x2),sum(x3)), by=Stock]

但同样,如果有很多变量,这会变得非常混乱,而且这会在 scale 中显示错误消息(但不会在 sum 中出现,因为它不返回向量)。

有没有更有效的方法来达到所需的结果(记住我的实际数据集非常大)?

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    我认为只要对你最后的代码做一个小的修改,你就可以轻松地为你想要的尽可能多的变量做这两个

    vars <- c("x2", "x3") # <- Choose the variable you want to operate on
    
    d[, paste0(vars, "_", "scale") := lapply(.SD, function(x) scale(x)[, 1]), .SDcols = vars, by = Stock]
    d[, paste0(vars, "_", "sum") := lapply(.SD, sum), .SDcols = vars, by = Stock]
    
    ##          Time Stock x1 x2 x3   x2_scale   x3_scale x2_sum x3_sum
    ## 1: 2014-08-22     A 13 14 32 -1.1338934  1.1323092     87     44
    ## 2: 2014-08-23     A 25 39  9  0.7559289 -0.3701780     87     44
    ## 3: 2014-08-24     A 18 34  3  0.3779645 -0.7621312     87     44
    ## 4: 2014-08-22     B 44  8  6 -0.4730162 -0.7258662     59     32
    ## 5: 2014-08-23     B 49  3 18 -0.6757374  1.1406469     59     32
    ## 6: 2014-08-24     B 15 48  8  1.1487535 -0.4147807     59     32
    

    对于简单的功能(不需要像 scale 这样的特殊处理),您可以轻松地执行类似的操作

    vars <- c("x2", "x3") # <- Define the variable you want to operate on
    funs <- c("min", "max", "mean", "sum") # <- define your function
    for(i in funs){
      d[, paste0(vars, "_", i) := lapply(.SD, eval(i)), .SDcols = vars, by = Stock] 
    }
    

    【讨论】:

    • 感谢大家的聪明解决方案!我接受了@DavidArenburg 的回答,因为它只使用data.table,因为它与我的原始代码非常相似。
    【解决方案2】:

    另一个使用data.table的变体

      vars <- c("x2", "x3")
      d[,  paste0(rep(vars, each=2), "_", c("scale", "sum")) := do.call(`cbind`,
                   lapply(.SD, function(x) list(scale(x)[,1], sum(x)))), .SDcols=vars, by=Stock]
       d
       #        Time Stock x1 x2 x3   x2_scale x2_sum   x3_scale x3_sum
      #1: 2014-08-22     A 15 27 34 -1.1175975    121  0.7310560     68
      #2: 2014-08-23     A 39 44 29  0.3073393    121  0.4085313     68
      #3: 2014-08-24     A 20 50  5  0.8102582    121 -1.1395873     68
      #4: 2014-08-22     B 42 22 43 -0.5401315     88  1.1226726     57
      #5: 2014-08-23     B 44 45 12  1.1539172     88 -0.3274462     57
      #6: 2014-08-24     B  3 21  2 -0.6137858     88 -0.7952265     57
    

    基于@Arun 的 cmets,您还可以这样做:

       cols <- paste0(rep(vars, each=2), "_", c("scale", "sum"))
        d[,(cols):= unlist(lapply(.SD, function(x) list(scale(x)[,1L], sum(x))), 
                                  rec=F), by=Stock, .SDcols=vars]
    

    【讨论】:

    • d[, (cols) := unlist(lapply(.SD, function(x) list(scale(x)[,1L], sum(x))), rec=FALSE), by=Stock, .SDcols=vars] - 我们只需要在j 中获取一个列表,列表中的每个元素都将成为一列。
    【解决方案3】:

    您可能正在寻找纯粹的data.table 解决方案,但您也可以考虑在此处使用dplyr,因为它也适用于data.tables(无需转换)。然后,从dplyr,您可以使用函数mutate_all,就像我在此示例中所做的那样(使用您在问题中显示的第一个数据集):

    library(dplyr)
    dt %>%
      group_by(Stock) %>%
      mutate_all(funs(sum, scale), x2, x3)
    #Source: local data table [6 x 9]
    #Groups: Stock
    #
    #        Time Stock x1 x2 x3 x2_sum x3_sum   x2_scale   x3_scale
    #1 2014-08-22     A 15 27 34    121     68 -1.1175975  0.7310560
    #2 2014-08-23     A 39 44 29    121     68  0.3073393  0.4085313
    #3 2014-08-24     A 20 50  5    121     68  0.8102582 -1.1395873
    #4 2014-08-22     B 42 22 43     88     57 -0.5401315  1.1226726
    #5 2014-08-23     B 44 45 12     88     57  1.1539172 -0.3274462
    #6 2014-08-24     B  3 21  2     88     57 -0.6137858 -0.7952265
    

    您可以轻松添加更多要计算的函数,这将为您创建更多列。请注意,mutate_all 默认情况下将函数应用于除分组变量 (Stock) 之外的每一列。但是您可以指定您只想将函数应用到的列(我在本例中这样做),或者您可以指定不想要将函数应用到的列(即, 例如-c(x2,x3) 而不是我写的x2, x3)。

    编辑:将上面的mutate_each 替换为mutate_all,因为mutate_each 将在不久的将来被弃用。

    【讨论】:

    • 我想知道dplyr 怎么知道在没有任何修改的情况下处理scale
    • @Jaap,感谢您的更新,但如果我们这样做,我们还应该注意 data.table 的 dplyr 现在是 dtplyr,afaik。
    • dplyr 内部不调用dtplyr 吗?无论如何,添加关于 dtplyr 的注释会进一步改善答案
    • @Jaap,对此不确定。我刚刚在加载包时看到了消息
    【解决方案4】:

    编辑:使用functional 的更清洁版本。我认为这是最接近dplyr 的答案。

    library(functional)
    funs <- list(scale=Compose(scale, c), sum=sum)    # See data.table issue #783 on github for the need for this
    cols <- paste0("x", 2:3)
    cols.all <- outer(cols, names(funs), paste, sep="_")
    
    d[, 
      c(cols.all) := unlist(lapply(funs, Curry(lapply, X=.SD)), rec=F),
      .SDcols=cols,
      by=Stock
    ]
    

    生产:

             Time Stock x1 x2 x3   x2_scale   x3_scale x2_sum x3_sum
    1: 2014-08-22     A 15 27 34 -1.1175975  0.7310560    121     68
    2: 2014-08-23     A 39 44 29  0.3073393  0.4085313    121     68
    3: 2014-08-24     A 20 50  5  0.8102582 -1.1395873    121     68
    4: 2014-08-22     B 42 22 43 -0.5401315  1.1226726     88     57
    5: 2014-08-23     B 44 45 12  1.1539172 -0.3274462     88     57
    6: 2014-08-24     B  3 21  2 -0.6137858 -0.7952265     88     57
    

    【讨论】:

    • 你在说什么变量规格?显然很容易让 4 个函数完成 4 个常见任务,并将其他所有内容移至 do
    • @Arun,像这样的事情,你想分配给提前不知道的列,对提前不知道的其他列进行操作(即规范进入变量) .
    • @Arun,但如果你有更好的方法来做类似上面的事情,那是完全通用的,我很乐意看到它。这是我在短时间内能想到的最好的方法,虽然它工作起来有点复杂(更不用说它使用data.frame...)。
    • data.table 已经为聚合自动生成名称。它需要:= 的名称。这就是你所说的“弱区”吗?如果是这样,为什么不向 FR 提交问题?但这不是这里的问题。问题是在lapply(.) 中使用了许多聚合函数。
    • 我已经在 Akrun 的回答下提供了一个解决方案。不过谢谢,我们会看看这是否可以改进。
    猜你喜欢
    • 1970-01-01
    • 2022-01-04
    • 1970-01-01
    • 2012-05-17
    • 1970-01-01
    • 2012-08-16
    • 1970-01-01
    • 2011-05-17
    相关资源
    最近更新 更多