使用data.table在循环中优化子集

时间:2017-11-14 20:43:49

标签: r loops data.table

我有一个关于如何优化以下代码的基本问题。这是我的代码的一个非常简短的版本。基本上,我有一个大的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的想法都会受到高度赞赏(例如如何删除循环?)。

1 个答案:

答案 0 :(得分:3)

  

我考虑过为子集创建索引然后使用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秒聚合第一