【问题标题】:Why indexing .SD with a number is faster than when the number is stored in a variable为什么用数字索引 .SD 比将数字存储在变量中更快
【发布时间】:2023-01-29 21:27:35
【问题描述】:

我有一个 data.table,我想按组(客户 ID)汇总并查看他们的第一次支出与第二次支出、第三次支出等的比较。

选择例如第一行使用.SD[1]分组:

agg_dt <- dt[ , .SD[1], by = customer_id]

给出了一个非常快的结果(对于具有 500 万行以上的数据集,只需 1.94 秒)。

但是,如果我将索引号(此处为 1)放入一个变量(我想这样做,以便我可以将其转换为一个可以用不同数字重复调用的函数),它会突然变慢很多(5.23 分钟)。

number_var <- 1
agg_dt <- dt[ , .SD[number_var], by = customer_id]

就我而言,直接使用数字和通过变量使用数字应该没有区别,那么是什么原因造成的,我该如何解决呢?

为了完整展示代码,我在 kaggle 上做了一个玩具示例:

https://www.kaggle.com/code/keithcooper/data-table-weird-result/notebook

它要小得多,但即使在那里,差异也是 0.00995 秒到 2.71042 秒。

【问题讨论】:

  • 不完全是 this one 的副本,但很接近。 dt[dt[, .I[number_var], by = customer_id]$V1] 似乎是最快的选择。这比不必评估行号时慢 3 倍。
  • 那更快。但是将数字作为变量传递仍然会使时间加倍。知道为什么吗?
  • 我没有确定的答案。 [.data.table 做了很多。在您的示例中,它将首先尝试使用 data.table 中的变量,并且看到它不存在,它将测试下一个环境(这里是“全局”环境)。为什么 dt[dt[, .I 等更快,这让我很困惑。理论上它会假设做得更多,但可能优化得更好。
  • 所以它认为变量是一个 data.table 对象并且每次都试图查找它?
  • [.data.table中使用verbose = TRUE,你会发现d[ , .SD[1], by = g, verbose = TRUE]是GForce优化的,而d[ , .SD[ix], by = g, verbose = TRUE]不是。另见?datatable.optimize

标签: r performance data.table subset


【解决方案1】:

使用数字索引 .SD 是“GForce 优化”,而使用存储在变量中的数字索引未优化。

这可以通过设置verbose = TRUE(“打开控制台的状态和信息消息”)来验证

library(data.table)
d = data.table(g = rep(1:2, each = 2), v = 1:4)

对于每个组,使用数字索引 .SD,例如1:

d[ , .SD[1], by = g, verbose = TRUE]
#...snip... 
# lapply optimization changed j from '.SD[1]' to 'list(v[1])'
# GForce optimized j to 'list(`g[`(v, 1))'
# Making each group and running j (GForce TRUE)
#...snip...
#    g v
# 1: 1 1
# 2: 2 3 

索引.SD,带有存储在变量中的数字

ix = 1
d[ , .SD[ix], by = g, verbose = TRUE]
#...snip...
# lapply optimization is on, j unchanged as '.SD[ix]'
# GForce is on, left j unchanged
# Old mean optimization is on, left j unchanged.
# Making each group and running j (GForce FALSE) ... The result of j is a named list.
# It's very inefficient to create the same names over and over again for each group.

使用 .I[ix] 可以显着提高性能。但是,该操作都不是 GForce 优化的。

d[d[, .I[ix], by = g, verbose = TRUE]$V1]
#...snip...
# lapply optimization is on, j unchanged as '.I[ix]'
# GForce is on, left j unchanged
# Old mean optimization is on, left j unchanged.
# Making each group and running j (GForce FALSE)

正如@jangorecki 所指出的,在data.table (&gt;= v. 1.14.7) 的开发版本中,可以使用env 参数(此处为env = list(ix = ix))。然后,当索引存储在变量中时,GForce 也会被优化。

# data.table::update_dev_pkg()
library(data.table)
# data.table 1.14.7 IN DEVELOPMENT 

索引.SD

d[ , .SD[ix], by = g, env = list(ix = ix), verbose = TRUE]
#...snip...
# lapply optimization changed j from '.SD[1]' to 'list(v[1])'
# GForce optimized j to 'list(`g[`(v, 1))'
# Making each group and running j (GForce TRUE)  

索引.I

d[d[, .I[ix], by = g, env = list(ix = ix), verbose = TRUE]$V1]
#...snip...
# lapply optimization is on, j unchanged as '.I[1]'
# GForce optimized j to '`g[`(.I, 1)'
# Making each group and running j (GForce TRUE)

另见?datatable.optimize

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-20
    • 1970-01-01
    • 1970-01-01
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-30
    相关资源
    最近更新 更多