我有一个关于如何优化以下代码的基本问题。这是我的代码的一个非常简短的版本。基本上,我有一个大的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的想法都会受到高度赞赏(例如如何删除循环?)。
答案 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
中找到一行。在我的计算机上,示例数据的加速非常大,但大部分好处来自于利用示例函数的形式,我想: