【问题标题】:How can I avoid my very big nested for-loops?如何避免我非常大的嵌套 for 循环?
【发布时间】:2021-06-25 11:55:18
【问题描述】:

我必须使用物理公式进行一些模拟。在一个公式中,有许多变量。我想用 100 个样本改变这些变量。对于每个样本,我必须使用所有组合进行计算。简化的 for 循环更好地解释了我想要做什么:

set.seed(3)
a = rnorm(100)
b = rnorm(100)
c = rnorm(100)
d = rnorm(100)
f = rnorm(100)

for (a in 1:length(a)) {
        
        for (b in 1:length(b)) {
                
                for (c in 1:length(c)) {
                        
                        for (d in 1:length(d)) {
                                
                                for (f in 1:length(f)) {
                                        
                                        value = a + b / c * d - f # for illustrative purposes only
                                        # .... 
                                        # ... then I append the value to a vector etc.
                                        
                                }
                                
                        }
                        
                }
                
        }
        
}

如您所见,当要改变的参数数量(每个参数 100 个样本)增加时,模拟次数会呈指数增加,计算时间也会增加。我必须改变 10-15 个参数并计算所有参数组合的“值”。有什么办法(完全)可以避免这些循环吗?在处理这样的大型计算时,有什么好的编程习惯?

【问题讨论】:

  • 您的代码将生成一个包含 100 ^ 5 = 10e5 个值的向量。你确定那是你想要的吗?这是一个非常大的数字,并且该向量将占用超过 37 GiB 的 RAM。
  • @KonradRudolph 我正在生成合成数据,对于我的任务,我可以生成的数据越多,它就越能帮助我解决我正在处理的问题。我可以访问异常出色的计算机,因此计算机功率不是什么大问题。但是,我目前的 for-loop 方法并不吸引人,我觉得应该有更好的方法来解决这个问题。
  • 在一个问题上投入更多的数据只能让你到目前为止:详尽的搜索的用处非常有限,这就是为什么它们很少这样做,即使使用强大的硬件也是如此。 ——也就是说,你应该看看 R 的矩阵运算;特别是outer。使用此功能,您可以摆脱所有循环。

标签: r loops for-loop nested-loops


【解决方案1】:

您所做的实际上是使用一些给定的操作创建一个大型的多维排列数组,然后将结果展平。

可以使用outer 创建排列数组:例如,outer(a, b, `+`)a[i] + b[j] 的所有成对组合的数组。整个数组由(注意运算符优先级!)给出:

array = outer(outer(a, outer(outer(b, c, `/`), d), `+`), f, `-`)

(或者outer(a, b),和outer(a, b, `*`)一样,也可以写成a %o% b。)

要展平数组,请使用as.vector

value = as.vector(array)

结果与使用expand.grid 的结果相同。不同的是使用expand.grid 更具可读性:

value = with(expand.grid(a = a, b = b, c = c, d = d, f = f), a + b / c * d - f)

…但是速度明显变慢,并且使用了很多更多的内存。

我们可以通过创建自定义运算符来提高数组排列的可读性:

make_outer = function (f) function (a, b) outer(a, b, f)
`%o+%` = make_outer(`+`)
`%o-%` = make_outer(`-`)
`%o/%` = make_outer(`/`)

value = as.vector(a %o+% ((b %o/% c) %o% d) %o-% f)

【讨论】:

  • 我用我的答案中的小集合对它进行了基准测试,expand_grid 方法需要大约 6-7 倍的时间并使用两倍的内存。
【解决方案2】:

我会在计算前使用expand.grid 定义所有组合:

set.seed(3)
a = rnorm(10)
b = rnorm(10)
c = rnorm(10)
d = rnorm(10)
f = rnorm(10)

# find all possible combinations
sets <- expand.grid(
  a = a,
  b = b,
  c = c,
  d = d,
  f = f
)

# calculations is quick and vectorised
value <-  sets$a + sets$b / sets$c * sets$d - sets$f
head(value)
#> [1] -0.58891116  0.08049653  0.63181047 -0.77910963  0.56880508  0.40314620

没有必要,但在tidyverse 中看起来更好看

library(tidyverse)
sets %>% 
  mutate(value = a + b / c * d - f) %>% 
  as_tibble() # just for nicer printing
#> # A tibble: 100,000 x 6
#>          a      b      c     d     f   value
#>      <dbl>  <dbl>  <dbl> <dbl> <dbl>   <dbl>
#>  1 -0.962  -0.745 -0.578 0.901 0.787 -0.589 
#>  2 -0.293  -0.745 -0.578 0.901 0.787  0.0805
#>  3  0.259  -0.745 -0.578 0.901 0.787  0.632 
#>  4 -1.15   -0.745 -0.578 0.901 0.787 -0.779 
#>  5  0.196  -0.745 -0.578 0.901 0.787  0.569 
#>  6  0.0301 -0.745 -0.578 0.901 0.787  0.403 
#>  7  0.0854 -0.745 -0.578 0.901 0.787  0.458 
#>  8  1.12   -0.745 -0.578 0.901 0.787  1.49  
#>  9 -1.22   -0.745 -0.578 0.901 0.787 -0.846 
#> 10  1.27   -0.745 -0.578 0.901 0.787  1.64  
#> # … with 99,990 more rows

reprex package (v1.0.0) 于 2021-03-29 创建

【讨论】:

    猜你喜欢
    • 2012-06-25
    • 2017-08-27
    • 2017-11-11
    • 1970-01-01
    • 2017-09-20
    • 1970-01-01
    • 1970-01-01
    • 2020-05-21
    • 2019-12-26
    相关资源
    最近更新 更多