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

问题描述 投票:3回答:1

对于二变量问题,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)
            }
        }
    }
}

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

[我怀疑combn可能能够以某种方式做到这一点,但是根据我的经验,很快它就可以落入与expand.grid相同的内存陷阱中。如果有内存可用,我也知道它采取了不明智的步骤,告诉我更改递归限制的全局设置。

r nested-loops
1个回答
2
投票

我以前的功能lazyExpandGrid并不是一个完美的选择,但我认为它可以解决您对内存耗尽的担忧。其他语言可能会出现延迟迭代器。 R在iterators程序包中拥有它,并且由于我不熟练,所以前一段时间我写了this gist来解决一个问题。

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

这里是该功能的工作方式:

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

((注意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)
  })
}

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

:我不会撒谎,不过,这简化了流程,但并没有使流程变快。在这种情况下,进行1e+09次操作将花费time,除了并行操作和友好的R主机群集之外,我不知道有什么方法可以帮助实现这一点。 (如上所述,我开始运行一个空的无操作while循环,并花了268秒的时间才能完成其中的822K。希望您有很多处理能力。)

© www.soinside.com 2019 - 2024. All rights reserved.