【问题标题】:Optimizing subsetting with data.table in a loop在循环中使用 data.table 优化子集
【发布时间】:2018-04-27 22:28:27
【问题描述】:

我有一个关于如何优化以下代码的基本问题。这是我的代码的一个非常非常简略的版本。基本上,我有一个很大的 data.table(> 50M 行),我想经常对数据进行子集化(比如 10000 次)并在子集上运行一些函数(显然比下面示例中显示的更复杂,即我需要子集的所有列,并且该函数返回一个新的 data.table)。我只是选择了使示例简单的平均值。

dt <- data.table(a=sample(letters, 1000000,replace=T),b=sample(1:100000))

mm <- list()

foo <- function(x) mean(x$b)

for(i in 1:1000)
{
  mm[[i]] <-  foo(dt[a %in% sample(letters,5)])
}

很明显,即使是这个最小的例子(设置键等),这也不是最快的编程方式。

不过,我对如何优化 for 循环很感兴趣。我想为子集创建索引,然后使用 data.table dt[,foo(.SD),by=subset_ID] ,但我不确定如何执行此操作,因为我正在使用替换进行采样(多个组 ID)。任何基于 data.table 的想法都将不胜感激(例如如何删除循环?)。

【问题讨论】:

  • 如果您的 foo 实际上只需要一列,则在将其传递给 foo 之前从 dt 中选择该列会更快一些 (~15-20%)。
  • 顺便说一句,如果您想编辑问题以制作一个更好地说明的示例,我可以添加一个“赏金”(stackoverflow.com/help/bounty),也许会吸引更多/更好的答案。

标签: r loops data.table


【解决方案1】:

我打算为子集创建索引,然后使用 data.table dt[,foo(.SD),by=subset_ID],但我不确定如何执行此操作,因为我正在使用替换进行采样(多个组 ID)。

通过加入,您可以拥有重叠的组:

# convert to numeric
dt[, b := as.numeric(b)]

# make samples
set.seed(1)
mDT = setDT(melt(replicate(1000, sample(letters,5))))
setnames(mDT, c("seqi", "g", "a"))

# compute function on each sample
dt[mDT, on=.(a), allow.cartesian=TRUE, .(g, b)][, .(res = mean(b)), by=g]

给了

         g      res
   1:    1 50017.85
   2:    2 49980.03
   3:    3 50093.80
   4:    4 50087.67
   5:    5 49990.83
  ---              
 996:  996 50013.11
 997:  997 50095.43
 998:  998 49913.61
 999:  999 50058.44
1000: 1000 49909.36

要确认它在做正确的事情,您可以检查例如,

dt[a %in% mDT[g == 1, a], mean(b)]
# [1] 50017.85

这种方法的一个缺点是它涉及创建一个非常大的表(包含所有样本的数据),这可能会给您带来麻烦,RAM 方面。

这种方法利用了您的函数mean,因为显式传递它允许某些优化;请参阅?GForce,这也是我将b 转换为数字的原因。

我同意 Rob Jensen 的建议,将列传递给函数而不是传递表(函数会假设表中出现哪些列),这既是为了提高效率又是为了清晰。

在取平均值的特定情况下,您可以通过先将每个字母相加来进一步加快速度,我认为:

dtagg = dt[, .(.N, sumb = sum(b)), by=a]

dtagg[mDT, on=.(a), .(g, sumb, N)][, lapply(.SD, sum), by=g][, .(g, res = sumb/N)]

         g      res
   1:    1 50017.85
   2:    2 49980.03
   3:    3 50093.80
   4:    4 50087.67
   5:    5 49990.83
  ---              
 996:  996 50013.11
 997:  997 50095.43
 998:  998 49913.61
 999:  999 50058.44
1000: 1000 49909.36

在这种情况下不需要allow.cartesian,因为mDT 的每一行只在dtagg 中找到一行。在我的计算机上,示例数据的加速效果相当大,但我猜大部分好处来自利用示例函数的形式:

  • 13.7 秒 OP 的方法
  • 11.4 秒简单连接
  • 0.02 秒聚合优先

【讨论】:

  • 谢谢你,弗兰克。我理解你的解决方案。但是,由于 RAM 限制(正如您所提到的),我担心创建 1000 x 50M 行的数据集。我明天会试试这个。没有“allow.cartesian”的任何其他建议都会很好。请注意,函数 foo 不是平均值(这只是一个示例)。
  • @PukiLuki 是的,我唯一能想到的就是熟悉聚合优先策略,看看它是否可以应用于您的实际功能。如果没有,我认为循环是一个好方法。如果您需要加速,您还可以查看并行化包(因为每次运行都独立于其他运行)。
猜你喜欢
  • 1970-01-01
  • 2019-01-11
  • 1970-01-01
  • 2013-08-28
  • 2017-01-09
  • 2013-01-03
  • 1970-01-01
  • 1970-01-01
  • 2014-02-20
相关资源
最近更新 更多