【问题标题】:data.table column order when using lapply and get使用 lapply 和 get 时的 data.table 列顺序
【发布时间】:2018-11-25 11:02:13
【问题描述】:

谁能帮我理解为什么下面两个版本的 lapply 操作使用和不使用 get() 不会产生相同的结果?使用 get() 时,结果列会混淆。

dt <- data.table(v1 = c(1,2), v2 = c(3,4), type = c('A', 'B'))

   v1 v2 type
1:  1  3    A
2:  2  4    B

col_in <- c('v2', 'v1')
col_out <- paste0(col_in, '.new')

以硬编码方式访问“类型”

dt[, (col_out) := lapply(.SD, function(x){x * min(x[type == 'A'])}), .SDcols = col_in]

产生预期的结果:

   v1 v2 type v2.new v1.new
1:  1  3    A      9      1
2:  2  4    B     12      2

但是,当通过 get() 访问“类型”时

dt[, (col_out) := lapply(.SD, function(x){x * min(x[get('type') == 'A'])}), .SDcols = col_in]

v1.new 的预期值在 v2.new 中,反之亦然:

   v1 v2 type v2.new v1.new
1:  1  3    A      1      9
2:  2  4    B      2     12

注意:这是我从尝试实现的更复杂操作中提炼出来的最小玩具示例。 'type' 变量的名称作为输入参数给出。

【问题讨论】:

  • 很奇怪,添加get("type") 改变了 x 的顺序。如果在函数调用中添加print(x),您可以看到更改。如果你这样做 col_in &lt;- sort(col_in) 这不再是问题。
  • 如果您想要替代方案,请将 get("type") 更改为 dt[["type"]]works
  • 如果names(dt) 不等于sort(names(dt)),则使用sort(col_in) 选项不起作用,如果您按组进行这些计算,则使用dt[['type']] 不起作用。对于第三个选项,您可以使用:col_in &lt;- col_in[match(col_in, names(dt))]
  • 你可能知道,这可行,尽管它增加了两个对[.data.table的调用:dt[, (col_out) := lapply(.SD[, col_in, with = F], function(x){x * min(x[.SD[, 'type', with = F] == 'A'])})]
  • 我还注意到它与 data.table 中列的顺序和/或“col_in”向量有关。也许有人仍然可以评论执行这些类型的操作的“正确”(+高效+可读)方式是为了避免糟糕的意外。在我的实际代码中,我确实也按其他变量分组。作为一种解决方法,我遍历输入列以一次计算一个结果列,但这是以一遍又一遍地计算分组为代价的。

标签: r data.table lapply


【解决方案1】:

有趣!感谢分享!似乎使用 get 需要一些内部排序(错误?)。

避免这种情况的两种方法:

  1. 将 type == 'A' 部分移到 dt[,lapply(...)] 之外

    referenceRows <- which(dt[,type == 'A'])
    referenceRows <- which(dt[,get('type') == 'A'])
    dt[, lapply(.SD, function(x){x * min(x[referenceRows])}), .SDcols = col_in]
    
       v1 v2 type v2.new v1.new
    1:  1  3    A      9      1
    2:  2  4    B     12      2
    
  2. 首先创建新列,然后使用 setnames 确保为新列分配正确的列名。最后用cbind将两部分绑定在一起:

    dtNew <- dt[, lapply(.SD, function(x){x * min(x[type == 'A'])}), .SDcols = col_in]
    setnames(dtNew, col_in, col_out)
    cbind(dt, dtNew)
    
    
       v1 v2 type v2.new v1.new
    1:  1  3    A      9      1
    2:  2  4    B     12      2
    

相同的结果(虽然排序不同):

    dtNew <- dt[, lapply(.SD, function(x){x * min(x[get('type') == 'A'])}), .SDcols = col_in]
    setnames(dtNew, col_in, col_out)
    cbind(dt, dtNew)


       v1 v2 type v1.new v2.new
    1:  1  3    A      1      9
    2:  2  4    B      2     12

【讨论】:

  • 这两个选项仅在不分组时有效。在选项 2 中,如果某些 'col_out' 也在 'col_in' 中,则需要在 cbind 步骤之前删除此类列(以及任何分组列)。
【解决方案2】:

另一种方法是使用名为计算语言的酷R功能而不是get,并使用substitute生成所需的j参数作为语言对象功能。
这也适用于分组。

library(data.table)
dt <- data.table(v1 = c(1,2), v2 = c(3,4), type = c('A', 'B'))
col_in <- c('v2', 'v1')
col_out <- paste0(col_in, '.new')

col_where <- 'type'
qj <- substitute(.col_out := lapply(.SD, function(x){x * min(x[.col_where == 'A'])}),
                 list(.col_out=col_out, .col_where=as.name(col_where)))
print(qj)
#`:=`(c("v2.new", "v1.new"), lapply(.SD, function(x) {
#    x * min(x[type == "A"])
#}))

dt[, eval(qj), .SDcols = col_in][]
#      v1    v2   type v2.new v1.new
#   <num> <num> <char>  <num>  <num>
#1:     1     3      A      9      1
#2:     2     4      B     12      2

R language definition: Computing-on-the-language chapter 中有关此功能的更多信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-01
    • 2018-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-12
    • 2017-01-30
    相关资源
    最近更新 更多