如何避免为大型数据集编写嵌套的 for 循环?

J. *_*ini 11 r nested-loops

对于二变量问题,outer最有可能是最好的解决方案,如果循环空间足够小,那么我们可以expand.grid做我们的跑腿工作。然而,如果我们有两个以上的变量和一个大的空间来循环,这些就被排除了。outer不能处理两个以上的变量,并且expand.grid占用的内存比我见过的机器能够占用的内存多。

我最近发现自己在写这样的代码:

n<-1000
for(c in 1:n){
    for(b in 1:c){
        for(a in 1:b){
            if(foo(a,b,c))
            {
                bar(a,b,c)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这些情况下,嵌套循环似乎是自然的解决方案(例如,mapply不会这样做并且没有好的因素tapply可供使用),但是有更好的方法吗?这似乎是通往错误代码的道路。

我怀疑这combn可能会以某种方式做到这一点,但根据我的经验,它很快就会陷入与expand.grid. 如果没记错的话,我也知道它采取了不明智的步骤,告诉我更改递归限制的全局设置。

Col*_*ole 6

这是与重复的组合。可能是您即用的最佳选择,但在 . 中n = 1000L,需要处理的组合超过 5 亿个,将占用约 2GB 的内存。

library(RcppAlgos)
n = 1000L
mat <- comboGeneral(n, 3L, repetition = TRUE)
Run Code Online (Sandbox Code Playgroud)

现在有两条路要走。如果您有 RAM 并且您的函数能够被矢量化,那么您可以非常快速地完成上述操作。假设组合的总和大于 1000,您需要组合的均值,否则您需要组合的总和。

res <- if (rowSums(mat) > 1000L) 
  rowMeans(mat)
else
  rowSums(mat)

## Error: cannot allocate vector of size 1.2 Gb
Run Code Online (Sandbox Code Playgroud)

不好了!我得到了可怕的分配向量错误。允许您返回函数的结果。但请注意,它返回一个列表并且速度要慢得多,因为它将不得不评估您的 R 函数,而不是停留在 C++ 中。正因为如此,我改成了n = 100L因为我没有整天...

comboGeneral(100L, 3L, repetition = TRUE,
                        FUN = function(x) { 
                          if (sum(x) > 100L)
                            mean(x)
                          else
                            sum(x)
                        }
)
Run Code Online (Sandbox Code Playgroud)

如果我有一组静态,我总是选择3个组合出来的n,我可能会使用Rcpp直接取决于什么码foo(a,b,c)bar(a,b,c)是第一个,但我想知道更多有关的功能。


r2e*_*ans 5

我之前的函数lazyExpandGrid不是完美匹配,但我认为它解决了您对内存耗尽的担忧。其他语言有惰性迭代器的前景;R 有它在iterators包中,由于我不精通它,前段时间我写了这个要点来解决一个问题。

一个问题lazyExpandGrid是它期望因子是预先定义的。这可以通过快速条件处理,因此它会节省内存,但不可否认的是不节省空间。我认为在方法中实现条件不是一个快速的解决方案,因为它懒惰地处理扩展的机制是从数学上知道哪个索引附加到哪个因素组合......而条件会破坏它。

以下是该功能在此处的工作方式:

n <- 3
it <- lazyExpandGrid(aa = 1:n, bb = 1:n, cc = 1:n)
while (length(thistask <- it$nextItem())) {
  if (with(thistask, bb > aa || cc > bb)) next
  print(jsonlite::toJSON(thistask))
}
# [{"aa":1,"bb":1,"cc":1}] 
# [{"aa":2,"bb":1,"cc":1}] 
# [{"aa":3,"bb":1,"cc":1}] 
# [{"aa":2,"bb":2,"cc":1}] 
# [{"aa":3,"bb":2,"cc":1}] 
# [{"aa":3,"bb":3,"cc":1}] 
# [{"aa":2,"bb":2,"cc":2}] 
# [{"aa":3,"bb":2,"cc":2}] 
# [{"aa":3,"bb":3,"cc":2}] 
# [{"aa":3,"bb":3,"cc":3}] 

### to demonstrate what an exhausted lazy-expansion looks like
it$nextItem()
# NULL
it$nextItem()
# NULL
Run Code Online (Sandbox Code Playgroud)

(注意条件 with 如何next跳过这些组合。)

这将转化为您的流程:

n <- 1000
it <- lazyExpandGrid(aa = 1:n, bb = 1:n, cc = 1:n)
it
# lazyExpandGrid: 4 factors, 1e+09 rows
#   $ index : 0

while (length(thistask <- it$nextItem())) {
  if (with(thistask, bb > aa || cc > bb)) next
  with(thistask, {
    if (foo(aa, bb, cc)) bar(aa, bb, cc)
  })
}
Run Code Online (Sandbox Code Playgroud)

(或不使用with、使用thistask$aa等)

注意:我不会撒谎,不过,这简化了流程,但不会使其变快。在这种情况下,做一些事情1e+09需要时间,除了并行操作和友好的 R 主机集群之外,我不知道有什么可以帮助解决这个问题。(我开始while像上面那样运行一个空的无操作循环,花了 268 秒才能通过其中的 822K。我希望你有很多处理能力。)