为何R中purrr::flatten函数合并矩阵列表为data.frame时性能极差?
为什么
flatten_dfc比reduce(full_join)慢这么多?还有哪些替代方案? 这个问题挺有意思的,我之前也碰到过类似的场景——明明看起来更直观的flatten_dfc反而比用full_join绕一圈慢这么多,这里面主要是两者底层实现的差异导致的,咱们来拆解下:
性能差异的核心原因
1. flatten_dfc的增量式绑定开销
flatten_dfc(以及map_dfc结合bind_cols)的工作逻辑是逐步累加合并:它会从第一个tibble开始,每次把下一个tibble的列绑定到当前结果的右侧。每一次绑定都需要做这些操作:
- 检查所有列的名称是否冲突(哪怕你这里列名本来就重复,它也要处理后缀)
- 验证每一列的数据类型兼容性
- 在内存中复制已有的全部数据,再把新列追加进去
当你循环10次的时候,这个过程会重复9次,每次都要复制越来越大的数据框,这种"小步累加"的操作在R里会产生大量的内存冗余和计算开销,尤其是在RStudio Server这类有额外环境开销的场景下,这个问题会被放大。
2. reduce(full_join)的意外高效
你用的reduce(full_join)看起来是绕了个弯,但在这个场景下(所有数据框行数相同、rowid完全匹配),它的底层逻辑其实是基于行索引的快速对齐合并:
full_join在这里因为rowid是唯一且完全匹配的,不需要做复杂的行匹配,本质上就是按行把两个数据框的列拼在一起reduce的处理方式是两两合并,每次只需要处理两个数据框,而不是像flatten_dfc那样每次都要处理整个累加后的大框dplyr的join系列函数底层有不少C++实现的优化,尤其是对于小数据量的匹配,哈希表的查找速度极快,反而比纯R层面的列绑定更高效
更高效的替代解决方案
1. 直接用base R的do.call(cbind, ...)
因为你的原始数据是矩阵,直接把矩阵列表合并成大矩阵再转成data.frame是最快的方式——矩阵是连续的内存结构,合并的开销几乎可以忽略:
# 直接合并矩阵再转成data.frame result <- do.call(cbind, rerun(10, matrix(rnorm(36), nrow=6))) %>% as.data.frame() # 如果需要tibble格式 result_tbl <- do.call(cbind, rerun(10, matrix(rnorm(36), nrow=6))) %>% as_tibble()
这个方法的速度会比你用的reduce(full_join)还要快好几倍。
2. 用data.table的高效合并
如果你已经在使用data.table,它的合并逻辑是C实现的,开销极小:
library(data.table) result_dt <- rerun(10, as.data.table(matrix(rnorm(36), nrow=6))) %>% do.call(cbind, .)
3. 优化purrr的使用方式
如果一定要坚持用purrr的生态,可以跳过flatten_dfc,先合并所有矩阵再转成tibble,避免增量绑定的开销:
library(purrr) library(tibble) result_purrr <- rerun(10, matrix(rnorm(36), nrow=6)) %>% reduce(cbind) %>% as_tibble()
内容的提问来源于stack exchange,提问作者Roey Angel
相关产品推荐
相关产品推荐

