nest+rsample::bootstraps+unnest与rsample::group_bootstraps是否等价?
group_bootstraps与nest+bootstraps替代方案的等价性验证
问题背景
发现rsample包的group_bootstraps()函数运行速度远慢于bootstraps(),因此采用tidyr的nest()+unnest()组合作为替代方案,现验证两种实现是否等价,以及是否存在遗漏细节。
代码示例
library(tidyr) library(rsample) dat <- tibble(x = rep(1:1000, 2), y = 1:2000) f <- function(df, column){ tibble( "estimate" = mean(pull(df, {{column}})), "term" = "mean" ) } # 版本1:使用rsample::group_bootstraps start_time <- Sys.time() dat %>% group_bootstraps(group = x, times = 10) %>% mutate(mean_stats = purrr::map(splits, ~ f(analysis(.), y))) %>% int_pctl(mean_stats) print(difftime(Sys.time(), start_time, units = "secs")) # 版本2:使用tidyr nest + unnest替代 start_time <- Sys.time() dat %>% nest(.by = x) %>% bootstraps(times = 10) %>% mutate(mean_stats = purrr::map(splits, ~ f(unnest(analysis(.), data), y))) %>% int_pctl(mean_stats) print(difftime(Sys.time(), start_time, units = "secs"))
运行时间对比
- 版本1耗时:超过9秒
- 版本2耗时:不足0.5秒
等价性分析
两种实现在抽样逻辑和统计结果上完全等价:
group_bootstraps(group = x)的核心逻辑是:以x对应的分组为单位进行有放回抽样,每个bootstrap样本由若干完整分组的观测组成。- 版本2的逻辑是:先通过
nest(.by = x)将每个x分组的数据嵌套为单独行,再用bootstraps()对这些分组行进行有放回抽样,最后通过unnest()展开为完整观测集。本质上同样是基于分组的有放回抽样,和group_bootstraps()的抽样规则完全一致。
后续对每个bootstrap样本计算y的均值、生成置信区间的流程也完全相同,因此最终的统计结果(均值估计值、置信区间)会完全一致。
潜在遗漏细节
- 抽样索引的记录方式:
group_bootstraps()生成的splits对象中,记录的是原始数据中每个观测是否被选中;- 版本2的
splits记录的是嵌套后分组行的选中状态,再通过unnest()展开为观测集。但两种方式最终生成的分析数据集完全一致,不影响统计结果。
- 分组异常情况的处理:
- 如果原始数据中存在空分组、观测数为0的分组,两种方式都会自动忽略这类无效分组,不影响抽样逻辑;
- 若分组内观测数不一致,两种方式均以分组为单位抽样,不会改变分组内的观测构成,结果仍等价。
- 性能差异的根源:
group_bootstraps()内部可能针对每个分组单独处理抽样逻辑,带来了额外的循环开销;- 版本2通过
nest()将分组转换为行级数据,一次性完成抽样,避免了分组级别的循环,因此效率大幅提升。
内容的提问来源于stack exchange,提问作者PalimPalim
相关产品推荐
相关产品推荐

