正确理解R语言replicate()函数用法及批量运行函数报错修复
问题描述
我编写了如下自定义R函数:
library(dplyr) var_1 <- rnorm(100, 10, 10) var_2 <- rnorm(100, 1, 10) var_3 <- rnorm(100, 5, 10) response <- rnorm(100, 1, 1) my_data <- data.frame(var_1, var_2, var_3, response) my_data$id <- 1:100 simulate <- function() { results <- list() results2 <- list() for (i in 1:100) { iteration_i <- i sample_i <- my_data[sample(nrow(my_data), 10), ] results_tmp <- data.frame(iteration_i, sample_i) results[[i]] <- results_tmp } results_df <- do.call(rbind.data.frame, results) test_1 <- data.frame(results_df %>% group_by(id) %>% filter(iteration_i == min(iteration_i)) %>% distinct) summary_file <- data.frame(test_1 %>% group_by(iteration_i) %>% summarise(Count=n())) cumulative <- cumsum(summary_file$Count) summary_file$Cumulative <- cumulative summary_file$unobserved <- 100 - cumulative return(summary_file) }
单独调用该函数可得到正常输出:
> head(simulate()) iteration_i Count Cumulative unobserved 1 1 10 10 90 2 2 7 17 83 3 3 10 27 73 4 4 5 32 68 5 5 7 39 61 6 6 8 47 53
我的需求是将该函数重复运行10次,把所有运行结果合并为单个数据文件。尝试用4种批量运行写法均失败,失败代码如下:
# Method 1 : Did not work n_replicates = 10 iterations_required <- replicate(n_replicates, { simulate() }) # Method 2: Did not work lapply(seq_len(10), simulate(1)) # Method 3: Did Not Work library(purrr) rerun(10, simulate(1)) # Method 4: Did Not Work lapply(seq_len(10), simulate)
目前仅手动编写for循环可以正常得到目标结果,可运行代码如下:
# works fine! results <- list() for (i in 1:10) { game_i <- i s_i <- simulate() results_tmp <- data.frame(game_i, s_i) results[[i]] <- results_tmp } final_file <- do.call(rbind.data.frame, results)
我的问题:上述4种批量运行方法无法正常工作的原因是什么?如何修正这些写法实现需求?
失败原因
- 方法1(replicate写法):
replicate()默认参数simplify=TRUE,会自动把每次运行返回的数据框按列拆解,拼接成二维矩阵/数组,不会保留列表结构,最终得到的是宽格式的拼接结果,完全不符合按行合并、带批次号的需求。 - 方法2(lapply写
simulate(1)):存在两个错误:一是lapply的第二个参数需要接收函数对象,直接写simulate(1)会在调用lapply的瞬间就执行1次simulate,把执行结果而非函数传给lapply,无法实现迭代运行;二是定义的simulate()是无参数函数,传入参数1会直接触发unused argument (1)的报错。 - 方法3(purrr::rerun写
simulate(1)):错误和方法2完全一致:直接写simulate(1)会当场执行一次函数,且多余的参数1会触发报错,无法完成10次重复运行。 - 方法4(lapply传simulate):这个写法本身可以正常完成10次simulate运行,返回包含10个数据框的列表,但缺少两个关键步骤:没有给每次运行结果添加
game_i批次标识,也没有最后调用行合并函数把列表拼成单个数据框,拿到的是列表而非预期的最终数据框,因此误以为运行失败。
修正方案
以下所有修正写法的输出,和手写for循环得到的final_file结构完全一致。
方法1修正:replicate版本
replicate必须设置simplify = FALSE禁止自动简化结果,保留数据框列表结构,再添加批次号合并即可:
n_replicates = 10 # 得到10次运行结果的列表 iterations_list <- replicate(n_replicates, simulate(), simplify = FALSE) # 加批次号+合并为单个数据框 final_file <- do.call( rbind.data.frame, lapply(seq_along(iterations_list), function(i) { data.frame(game_i = i, iterations_list[[i]]) }) )
如果已经加载dplyr,合并步骤可以简化:
final_file <- bind_rows(iterations_list, .id = "game_i") %>% mutate(game_i = as.integer(game_i))
方法2修正:lapply版本
lapply第二个参数必须传入函数对象,不能直接传入函数执行结果,无参数函数可以用匿名函数包裹调用逻辑:
iterations_list <- lapply(seq_len(10), function(x) simulate()) # 后续合并逻辑和方法1一致 final_file <- do.call( rbind.data.frame, lapply(seq_along(iterations_list), function(i) { data.frame(game_i = i, iterations_list[[i]]) }) )
方法3修正:purrr::rerun版本
rerun内直接传入无参数的函数调用表达式(不要传多余参数),配合purrr的行绑定函数可以快速得到结果:
library(purrr) iterations_list <- rerun(10, simulate()) # 直接合并并自动生成批次号 final_file <- list_rbind(iterations_list, names_to = "game_i") %>% mutate(game_i = as.integer(game_i))
方法4修正:原lapply传simulate版本
原写法已经可以得到10次运行的结果列表,只需要补充加批次号、合并的步骤即可:
iterations_list <- lapply(seq_len(10), simulate) # 后续合并逻辑和方法1一致 final_file <- do.call( rbind.data.frame, lapply(seq_along(iterations_list), function(i) { data.frame(game_i = i, iterations_list[[i]]) }) )
内容的提问来源于stack exchange,提问作者stats_noob
相关产品推荐
相关产品推荐

