首页 > 解决方案 > 如何在 R 的“foreach”循环中转换“for 循环”?

问题描述

我正在处理一个需要并行计算才能获得比经典“for 循环”更快的结果的问题。

这是问题所在:

我需要为列表对象内的数据框中包含的 198135 个结果变量生成线性模型。我必须将模型中每个预测变量的所有 beta 值和 p 值存储在数据框中,以及它们的拟合优度度量。

我编写了一个功能性的“for 循环”,可以正确完成任务,但完成它需要 35 多个小时。我知道 R 使用的 8 核 CPU 不到 20%,我想全部使用它。问题是我不知道如何在 foreach 循环中转换我的 for 循环以利用并行计算。

这是我的问题的一些较小规模的示例代码:

library(tidyverse)
library(broom)

## Example data 

outcome_list <- list(as.data.frame(cbind(rnorm(32), dataframe_id = c(1))),
                     as.data.frame(cbind(rnorm(32), dataframe_id =  c(2))),
                     as.data.frame(cbind(rnorm(32), dataframe_id =  c(3)))) ## This represents my list of 198135 dataframes

mtcars <- mtcars #I will use the explanatory variables from here



## Below this line is my current solution with a for loop that works fine

x <- list()
results_df <- as.data.frame(cbind(dataframe_id = c(0), intercept = c(0),
                                b_mpg = c(0), p_mpg = c(0),
                                b_cyl = c(0), p_cyl = c(0),
                                p.model = c(0), AIC = c(0),
                                BIC = c(0)))

for(i in 1:3){
  x[[i]] <- lm(outcome_list[[i]]$V1 ~ mtcars$mpg + mtcars$cyl)
  gof <- broom::glance(x[[i]])
  betas <- broom::tidy(x[[i]])
  results_df <- rbind(results_df, c(outcome_list[[i]]$V2[1], 
                                    betas$estimate[1],
                                    betas$estimate[2], betas$p.value[2], 
                                    betas$estimate[3], betas$p.value[3],
                                    gof$p.value, gof$r.squared, gof$AIC,
                                    gof$BIC))

  if(i %% i == 0){
    message(paste(i, "of 3")) # To know if my machine has not crashed
    x <- list() # To keep RAM clean of useless data
  }
  gc()
}

results_df <- results_df[-1, ]



使用上面显示的代码,我得到了我需要的结果(具有回归参数的数据框和列表中每个结果变量的拟合优度),但它非常慢,因为我无法使用我所有的计算机能力。

我知道使用“foreach”和“doParallel”包可以更快地解决这个问题,但我仍然不明白 foreach 循环结构背后的逻辑,因为这是我第一次需要处理这么多数据。

PS:我已经尝试了几种使用 foreach 函数的方法,但我没有得到任何结果。我没有写我的 foreach 尝试解决方案,因为我不明白我在做什么。

标签: rforeachparallel-processing

解决方案


你可以做:

## Example data 
outcome_list <- list(as.data.frame(cbind(rnorm(32), dataframe_id = c(1))),
                     as.data.frame(cbind(rnorm(32), dataframe_id = c(2))),
                     as.data.frame(cbind(rnorm(32), dataframe_id = c(3))))

## Parallel code
library(doParallel)
registerDoParallel(cl <- makeCluster(3))
results_list <- foreach(i = 1:3) %dopar% {

  mylm <- lm(outcome_list[[i]]$V1 ~ mtcars$mpg + mtcars$cyl)
  gof <- broom::glance(mylm)
  betas <- broom::tidy(mylm)

  c(outcome_list[[i]]$V2[1], 
    betas$estimate[1],
    betas$estimate[2], betas$p.value[2], 
    betas$estimate[3], betas$p.value[3],
    gof$p.value, gof$r.squared, gof$AIC,
    gof$BIC)
}
stopCluster(cl)

results_df <- setNames(as.data.frame(do.call("rbind", results_list)),
                       c("dataframe_id", "intercept", "b_mpg", "p_mpg", 
                         "b_disp", "p_disp", "p.model", "AIC", "BIC"))

你在 foreach 中返回你的结果(就像 lapply 一样)而不是增长一个对象(这在并行 BTW 中是不可能的)。

在此处了解有关如何使用 foreach 的更多信息。


推荐阅读