【问题标题】:data.table operations by column name按列名的 data.table 操作
【发布时间】:2014-01-09 15:21:59
【问题描述】:

假设我有一个data.table

a <- data.table(id=c(1,1,2,2,3),a=21:25,b=11:15,key="id")

我可以像这样添加新列:

a[, sa := sum(a), by="id"]
a[, sb := sum(b), by="id"]
> a
   id  a  b sa sb
1:  1 21 11 43 23
2:  1 22 12 43 23
3:  2 23 13 47 27
4:  2 24 14 47 27
5:  3 25 15 25 15

但是,假设我有 列名

for (n in c("a","b")) {
  s <- paste0("s",n)
  a[, s := sum(n), by="id", with=FALSE] # ERROR: invalid 'type' (character) of argument
}

我该怎么办?

【问题讨论】:

  • ...和 ​​data.table 常见问题解答中的 1.6。
  • @joran:我不明白我怎么可以在这里使用eval/quote;我试过s &lt;- quote(paste0("s..",n) := sum(n)); a[, eval(s), by="id"] 失败了。
  • @sds 我只是在集思广益。不过我很同情。 data.table 对这类问题造成的混乱程度是我不经常使用它的最大原因。

标签: r data.table


【解决方案1】:

您也可以这样做:

a <- data.table(id=c(1,1,2,2,3),a=21:25,b=11:15,key="id")

a[, c("sa", "sb") := lapply(.SD, sum), by = id]

或者更笼统地说:

cols.to.sum = c("a", "b")
a[, paste0("s", cols.to.sum) := lapply(.SD, sum), by = id, .SDcols = cols.to.sum]

【讨论】:

  • +1 甚至更好,因为它在单个分组中,而不是为每一列添加一个分组。在这种情况下使用.SD 不应该,因为.SD 中的所有数据都在使用中。
  • @MattDowle 作为旁白——使用.SD 的主要问题发生在涉及[.data.table 时,因为该函数的开销很大,所以像.SD[1:.N] 这样的东西会慢几个数量级在 .SD 的“by”循环中
  • 哦,是的,谢谢提醒。它在优化列表中。
  • @eddi,也提醒this讨论。不仅仅是[.data.table。它肯定会让事情变慢。但仅仅评估.SD 似乎也会让事情变得缓慢。我将使用 data.frame 和 data.table 进行适当的基准测试并回发。
【解决方案2】:

这类似于:

How to generate a linear combination of variables and update table using data.table in a loop call?

但您也想将其与by= 结合使用,因此set() 不够灵活。这是一个深思熟虑的设计设计,set() 在这方面不太可能改变。

我有时会在该答案的末尾使用 EVAL 助手。
https://stackoverflow.com/a/20808573/403310
这种方法有些畏缩,但我只是认为它就像构建动态 SQL 语句,这是很常见的做法. EVAL 方法提供了极大的灵活性,而无需担心eval()quote()。要查看已构建的动态查询(检查),您可以在 EVAL 辅助函数中添加 print

但是,在这个简单的示例中,您可以将 := 的 LHS 用括号括起来,以告诉 data.table 查找值(比 with=FALSE 更清晰),而 RHS 需要 get()

for (n in c("a","b")) {
  s <- paste0("s",n)
  a[, (s) := sum(get(n)), by="id"]
}

【讨论】:

  • 我不需要 s 周围的括号,是吗?
  • 是的,否则它将在循环的两次迭代中创建一个名为 "s" 的列。
  • 哦,对不起-我错过了with=FALSE。我们已经转向(LHS):=,因为它更易于阅读。 with=FALSE不清楚是指:=的LHS还是RHS。
  • 这提醒了我,当使用:= 时,我们应该正式弃用with=FALSE
  • 正确,所以问题很小。只需用get(n) 包裹n 即可。错误来自sum("a")
【解决方案3】:

编辑 2020-02-15 关于..

data.table 还支持.. 语法来“查找一个级别”,在大多数情况下不需要with=FALSE,例如dt[ , ..n1]dt[ , ..n2] 在下面


看看with中的? data.table

dt <- data.table(id=1:5,a=21:25,b=11:15,key="id")
dt[, n3 := dt[ , n1, with = FALSE ] * dt[ , n2, with = FALSE ], with = FALSE ]

编辑:

或者你只是来回更改列名:

dt <- data.table(id=1:5,a=21:25,b=11:15,key="id")
dt[ , dt.names["n3"] := 1L, with = FALSE ]

dt.names <- c( n1 = "a", n2 = "b", n3 = "c" )
setnames( dt, dt.names, names(dt.names) )

dt[ , n3 := n1 * n2, by = "id" ]
setnames( dt, names(dt.names), dt.names )

与 by 一起使用。

【讨论】:

    【解决方案4】:

    这是一种使用.SD 处理调用并避免任何开销的方法

    # a helper function
    makeCall <- function(x,fun) bquote(.(fun)(.(x)))
    # the columns you wish to sum (apply function to)
    cols <- c('a','b')
    new.cols <- paste0('s',cols)
    # create named list of names
    name.cols <- setNames(sapply(cols,as.name), new.cols)
    # create the call
    my_call <-  as.call(c(as.name(':='), lapply(name.cols, makeCall, fun = as.name('sum'))))
    (a[, eval(my_call), by = 'id'])
    
    #    id  a  b sa sb
    # 1:  1 21 11 43 23
    # 2:  1 22 12 43 23
    # 3:  2 23 13 47 27
    # 4:  2 24 14 47 27
    # 5:  3 25 15 25 15
    

    【讨论】:

      猜你喜欢
      • 2015-08-25
      • 2020-07-08
      • 1970-01-01
      • 2016-03-30
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 2012-11-11
      相关资源
      最近更新 更多