【问题标题】:Replace values in data.table by an aggregated value用聚合值替换 data.table 中的值
【发布时间】:2020-04-24 11:15:37
【问题描述】:

我想从data.table 计算一个聚合值并用它来替换原始值。我尝试了以下方法

library(data.table)
(DT <- data.table(grp  = rep(LETTERS[1:3], 3), 
                  val  = 1:9, 
                  val2 = rep(letters[24:26], each = 3)))
#    grp val val2
# 1:   A   1    x
# 2:   B   2    x
# 3:   C   3    x
# 4:   A   4    y
# 5:   B   5    y
# 6:   C   6    y
# 7:   A   7    z
# 8:   B   8    z
# 9:   C   9    z

(agg <- DT[, .SD[which.min(val)], grp])
#    grp val val2
# 1:   A   1    x
# 2:   B   2    x
# 3:   C   3    x

DT[, val3 := "New Value"]
agg[DT, on = "grp"][, .SD, .SDcols = !patterns("^i\\.")]
#    grp val val2      val3
# 1:   A   1    x New Value
# 2:   B   2    x New Value
# 3:   C   3    x New Value
# 4:   A   1    x New Value
# 5:   B   2    x New Value
# 6:   C   3    x New Value
# 7:   A   1    x New Value
# 8:   B   2    x New Value
# 9:   C   3    x New Value

虽然这种方法在这个玩具示例中有效,但我有几个问题:

  1. 我想首先避免链接以避免大量复制未使用的列(我的真实数据集相当大2e5 x 200)引用data.table FAQ 1.10

    因此,我们强烈建议使用 X[Y, j] 而不是 X[Y]

    .SD 部分放入第一个子集中的j 插槽,不起作用

    agg[DT, .SD, on = "grp", .SDcols = !patterns("^i\\.")]
    # Error in do_patterns(colsub, names_x) : Pattern not found: [^i\.]
    
  2. patterns 方法感觉有点老套,它依赖于假设data.table 将始终在Y 的列前面加上i.。如果包维护者出于某种原因更改它,我的代码就会中断。
  3. 我还在学习data.table,我想了解data.table 中解决此问题的最“惯用”方法是什么

如何解决此问题,同时避免制作不必要的副本以节省资源?

注意。重要的是:我不想通过引用更改DT 中的值。

【问题讨论】:

标签: r data.table


【解决方案1】:

根据您的数据,这可能更有效并明确命名列:

library(data.table)
DT <- data.table(grp  = rep(LETTERS[1:3], 3), 
                  val  = 1:9, 
                  val2 = rep(letters[24:26], each = 3))
agg <- DT[, .SD[which.min(val)], grp]
DT[, val3 := "New Value"]

repcols <- setdiff(colnames(agg), "grp")
DT[, (repcols) := agg[DT, .SD, on = .(grp), .SDcols=repcols]][]
#>    grp val val2      val3
#> 1:   A   1    x New Value
#> 2:   B   2    x New Value
#> 3:   C   3    x New Value
#> 4:   A   1    x New Value
#> 5:   B   2    x New Value
#> 6:   C   3    x New Value
#> 7:   A   1    x New Value
#> 8:   B   2    x New Value
#> 9:   C   3    x New Value

编辑: 根据后续问题,通过引用进行更改可以例如可以这样直接实现:

library(data.table)
DT <- data.table(grp  = rep(LETTERS[1:3], 3), 
                 val  = 1:9, 
                 val2 = rep(letters[24:26], each = 3))

keepcols <- setdiff(colnames(DT), "grp") 
DT[, val3 := letters[1:9]]
# if you want to keep all of val3; otherwise switch the previous two lines

DT[, (keepcols) := .SD[which.min(val)], by=.(grp), .SDcols=keepcols][]
#>    grp val val2 val3
#> 1:   A   1    x    a
#> 2:   B   2    x    b
#> 3:   C   3    x    c
#> 4:   A   1    x    d
#> 5:   B   2    x    e
#> 6:   C   3    x    f
#> 7:   A   1    x    g
#> 8:   B   2    x    h
#> 9:   C   3    x    i

reprex package (v0.3.0) 于 2020-04-24 创建

【讨论】:

  • 很好,但正如我所说,我想避免通过引用进行更改。
  • 对不起 - 我认为这适用于显式加入先前创建的 agg data.table 而不是立即替换值(否则首先不创建它会更容易)。跨度>
  • 您能否详细说明“否则首先不创建它会更容易)”?
  • 现在完全跳过agg 是有意义的。我越想,也许我确实应该通过参考来改变。我想我应该开始习惯这个想法(很难改变习惯)。
【解决方案2】:

通过引用分配将是非常惯用的。为什么您表示您对这种方法不感兴趣?

DT[agg, on = "grp", val3 := val3]

或者,如果 agg 确实是 DT 的集合,那么它可能是:

DT[, val4 := min(val), by = grp]

      grp   val   val2      val3  val4
   <char> <int> <char>    <char> <int>
1:      A     1      x New Value     1
2:      B     2      x New Value     2
3:      C     3      x New Value     3
4:      A     4      y New Value     1
5:      B     5      y New Value     2
6:      C     6      y New Value     3
7:      A     7      z New Value     1
8:      B     8      z New Value     2
9:      C     9      z New Value     3

【讨论】:

  • 感谢您的回答。但是,如果我的代码正确,我们将通过引用将新列添加到原始数据表中。我想要一个数据表,其中原始值被相应的聚合值替换,如下面的答案所示,但不使用 by ref 语义。
  • 如果不想修改原文,可以copy(DT)[, newval := 1]。这是为了说明,您可以在DT[...]中更改为您需要的任何内容
猜你喜欢
  • 2016-02-20
  • 2021-08-21
  • 2014-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-26
  • 2015-07-16
相关资源
最近更新 更多