R:如何像在 while 循环中一样使用 foreach 进行预先指定的重复次数

R: how to use foreach for a pre-specified number of replicates like in a while loop

library(foreach)
library(doMC)


myfun <- function(threshold){
  val <- rnorm(1, mean = 0, sd = 1)
  if(val > threshold){
    stop("bad")
  }else return(val)
}

results <- vector("list", length = 10)
parallel_fun <- function(reps, threshold){
  registerDoMC(cores = 48)
  results = foreach (j = 1:reps, .combine = rbind) %dopar% {
    myfun(threshold)
  }
}

> parallel_fun(reps = 10, threshold = 0)
 Error in { : task 1 failed - "bad" 

以上是一个简单的、可重现的例子。我想并行化 myfun 总共 reps = 10 次重复。如果生成的 val 大于某些 thresholdmyfun 可能会停止。既然如此,我想停止 运行 myfun 而不要 return val。最后,我希望我的 results 有 10 个 vals 比某些 threshold 大。因此,我想也许 while 循环在这里更合适,因为我想保留它 运行 直到我有 10 个值满足 threshold。是否可以重新利用 foreach 来并行化 while 循环?

控制流程

通常不鼓励对控制流使用异常。理想情况下,

使用已经完成你想要的功能

在这个具体示例中,您正在模拟 t运行cate 正态分布。所以你可以使用 truncnorm 包中的 truncnorm 函数。

重写函数

或者,将 myfun 重写为始终 return 正确的值:

myfun = function(threshold){
    repeat{
        val = rnorm(1, 0, 1)
        if(val <= threshold)
            break
        }
    val 
    }

这只是其中一种可能的变体。我在这里使用自定义 do-while 结构。

请注意,根据阈值,可能会发生大量或可能无限次的迭代,因此请谨慎行事,要么设置最大迭代次数,要么进行一些初步检查,如果 threshold 不是超出相关功能的最大范围,最好两者兼而有之。

有了这个,您应该能够像现在一样轻松 运行 foreach

写一个包装器

如果您无法控制 myfun,您需要构造包装器,该构造可能与上面的函数几乎相同:

wrap_myfun = function(threshold){
    repeat{
        val = try(myfun(threshold))
        if(is.numeric(val))
            break
        }
    val
    }

跟踪迭代:

如果您需要跟踪生成所述数字所花费的迭代次数,您可以将 repeat 重写为 for 循环或仅添加计数器和另一个选项:

wrap_myfun = function(threshold, .maxiter=10^9, .default=NA){
    iter = 1
    repeat{
        val = try(myfun(threshold))
        if(is.numeric(val))
            break

        if(iter >= .maxiter){
            val = .default 
            break
            }

        iter = iter + 1
        }
    list("value"=val, "iterations"=iter)
    }

或者,您可以使用 `stop("maximum iterations reached") 而不是分配默认值。这取决于问题的严重程度。

这样,您已将所有逻辑移至数据生成函数中,您不必管理 foreach 中实现的队列。负载应该在核心之间平均分配(超过某些迭代可能随机较长的计算时间,但这是您无法影响的事情)。