如何将foreach循环转换为函数?输出差异与替代方案疑问
问题描述
我以mtcars数据集为示例,需要计算变量间的Spearman相关系数及p值(实际场景是450条观测、3400个变量的数据集,因此需要并行处理),用combn避免重复相关和自相关。最初用foreach实现的代码如下:
combo_cars <- data.frame(t(combn(names(mtcars),2))) library(foreach) cars_res <- foreach(i=1:nrow(combo_cars), .combine=rbind, .packages=c("magrittr", "dplyr")) %dopar% { out2 <- broom::tidy(cor.test(mtcars[, combo_cars[i,1]], mtcars[,combo_cars[i,2]], method = "spearman")) %>% mutate(Var1=combo_cars[i,1], Var2=combo_cars[i,2]) }
为了适配future包做更高效的子集并行处理,我尝试用combn直接嵌入函数实现:
car_res2 <- data.frame(t(combn(names(mtcars), 2, function(x) cor.test(mtcars[[x[1]]], mtcars[[x[2]]], method="spearman"), simplify=TRUE)))
但car_res2输出有8列(第二列为空),且列顺序、标签和cars_res完全不同:cars_res有明确的列标签(如estimate、statistic、p.value等),而car_res2列顺序混乱且无语义化标签。
现有两个疑问:
- 为何两种方式的输出顺序不同且无标签?
- 能否用apply类函数替代上述实现方式?
解答
问题1:输出顺序与标签差异原因
这两种方式的核心区别在于是否对cor.test的结果做了标准化整理:
cars_res中用broom::tidy()处理cor.test返回的htest对象,tidy()会将这类假设检验结果统一转换成结构规范的整洁数据框,列名是语义化的(如estimate对应相关系数、p.value对应p值),顺序也是固定的。car_res2直接提取cor.test返回的原始htest对象元素,htest对象的元素顺序是固定的(statistic、parameter、p.value、estimate、null.value、alternative、method、data.name),转置后生成的数据框会沿用这个顺序,且列名默认是X1-X8。其中第二列空值是因为Spearman秩相关的cor.test结果中,parameter(自由度)参数为NULL,转成数据框后就显示为空。
问题2:apply类函数替代方案
完全可以用apply类函数替代,同时保留broom::tidy()的标准化输出,还能方便结合future实现并行。以下是几种可行方案:
方案1:基础lapply+do.call(rbind, ...)
先生成变量组合,再用lapply遍历每个组合,用tidy()整理结果,最后合并成数据框:
library(broom) library(dplyr) # 生成变量组合 combo_list <- combn(names(mtcars), 2, simplify = FALSE) # 用lapply遍历处理 cars_res_apply <- lapply(combo_list, function(x) { cor.test(mtcars[[x[1]]], mtcars[[x[2]]], method = "spearman") %>% tidy() %>% mutate(Var1 = x[1], Var2 = x[2]) }) %>% do.call(rbind, .)
方案2:purrr::map_dfr(更简洁)
用purrr的map_dfr可以直接将结果合并成数据框,无需额外的do.call(rbind, ...):
library(purrr) library(broom) library(dplyr) combo_list <- combn(names(mtcars), 2, simplify = FALSE) cars_res_map <- map_dfr(combo_list, function(x) { cor.test(mtcars[[x[1]]], mtcars[[x[2]]], method = "spearman") %>% tidy() %>% mutate(Var1 = x[1], Var2 = x[2]) })
方案3:结合future.apply实现并行(适配大数据集)
如果要结合future做并行处理,用future.apply::future_lapply替代基础lapply即可,无需修改核心逻辑:
library(future.apply) library(broom) library(dplyr) # 设置并行策略(根据自己的环境选择,比如multisession) plan(multisession) combo_list <- combn(names(mtcars), 2, simplify = FALSE) # 并行处理 cars_res_parallel <- future_lapply(combo_list, function(x) { cor.test(mtcars[[x[1]]], mtcars[[x[2]]], method = "spearman") %>% tidy() %>% mutate(Var1 = x[1], Var2 = x[2]) }) %>% do.call(rbind, .) # 关闭并行 plan(sequential)
封装成函数(方便处理子集)
可以把逻辑封装成函数,方便对数据集子集进行批量处理:
library(future.apply) library(broom) library(dplyr) compute_spearman <- function(data, var_combinations) { future_lapply(var_combinations, function(x) { cor.test(data[[x[1]]], data[[x[2]]], method = "spearman") %>% tidy() %>% mutate(Var1 = x[1], Var2 = x[2]) }) %>% do.call(rbind, .) } # 使用示例 plan(multisession) combo_list <- combn(names(mtcars), 2, simplify = FALSE) result <- compute_spearman(mtcars, combo_list) plan(sequential)
内容的提问来源于stack exchange,提问作者pemb_bex6789
相关产品推荐
相关产品推荐

