未知最终数据框大小时,如何提升R语言行绑定(rowbind)的速度?
优化多行不固定数据框的行绑定性能
我需要将多个行数不固定的数据框(dataframe)进行行绑定(rowbind)。我知道在for循环里每次覆盖final_df的方式速度极慢,因为R要保存每次修改的副本。通常可以通过预分配数据框的行列数并原地修改解决,但我的场景里每个数据框行数不同(实际代码处理大量XML文件,提取信息的行数随文件变化)。
目前我尝试用dplyr::bind_rows()或data.table::rbindlist(),二者性能相近且远优于do.call("rbind"),但发现随着数据框数量增加,计算速度仍会非线性上升。
请问有什么进一步优化代码速度的建议?
测试代码
create_df <- function() { nrow <- sample(12:15, 1) ncol <- 10 randomdf <- matrix(rnorm(nrow*ncol), nrow=nrow, ncol=ncol) |> data.frame() return(randomdf) } approach1 <- function(n) { final_df <<- matrix(ncol=ncol, nrow=0) for(i in 1:n) { current_df <- create_df() final_df <<- rbind(final_df, current_df) } } approach2 <- function(n) { df_list <<- vector("list", n) for(i in 1:n) { df_list[[i]] <- create_df() } final_df <<- do.call("rbind", df_list) } approach3 <- function(n) { df_list <<- vector("list", n) for(i in 1:n) { df_list[[i]] <- create_df() } final_df <<- dplyr::bind_rows(df_list) } approach4 <- function(n) { df_list <<- vector("list", n) for(i in 1:n) { df_list[[i]] <- create_df() } final_df <<- data.table::rbindlist(df_list) } microbenchmark::microbenchmark( approach1(5), approach2(5), approach3(5), approach4(5), approach1(50), approach2(50), approach3(50), approach4(50), approach1(500), approach2(500), approach3(500), approach4(500), times = 10 )
基准测试结果
Unit: microseconds expr min lq mean median uq max neval approach1(5) 1173.5 1201.1 1317.12 1285.30 1402.2 1557.0 10 approach2(5) 771.6 781.8 1121.18 829.15 944.6 3573.1 10 approach3(5) 543.7 613.4 966.10 672.15 952.4 3131.8 10 approach4(5) 520.8 586.5 641.18 621.65 663.8 818.8 10 approach1(50) 12186.9 12381.4 13932.40 12760.10 14518.8 18537.4 10 approach2(50) 6497.6 6766.0 7160.26 6967.55 7230.3 8390.6 10 approach3(50) 3681.3 4143.1 4258.44 4233.10 4347.8 5022.8 10 approach4(50) 3806.7 3821.8 4166.71 3962.95 4190.6 5900.4 10 approach1(500) 275530.0 285779.1 326732.16 294302.30 304461.0 622130.3 10 approach2(500) 65243.8 67456.7 72789.76 74422.30 77063.0 79485.0 10 approach3(500) 38600.0 39328.4 41372.67 41215.80 42345.2 47488.8 10 approach4(500) 32496.5 36788.1 41160.35 39940.10 46043.2 49752.9 10
优化建议
1. 批量合并而非全量合并
当数据框数量极大时,不要一次性合并所有数据框,可分批次处理:比如每100个数据框合并一次,将中间结果存入列表,最后再合并这些中间结果。能减少单次合并的内存开销和计算量,缓解速度非线性增长的问题。示例代码:
batch_merge <- function(n, batch_size = 100) { batches <- split(1:n, ceiling(1:n / batch_size)) batch_results <- lapply(batches, function(batch_idx) { df_list <- lapply(batch_idx, function(i) create_df()) data.table::rbindlist(df_list) }) final_df <- data.table::rbindlist(batch_results) return(final_df) }
2. 直接操作矩阵而非数据框
如果数据都是数值型(如测试场景),全程用矩阵处理,最后再转成数据框。矩阵是连续内存块,合并效率远高于数据框(数据框是列的列表结构)。修改示例:
create_mat <- function() { nrow <- sample(12:15, 1) ncol <- 10 matrix(rnorm(nrow*ncol), nrow=nrow, ncol=ncol) } matrix_approach <- function(n) { mat_list <- lapply(1:n, function(i) create_mat()) final_mat <- do.call(rbind, mat_list) final_df <- as.data.frame(final_mat) return(final_df) }
3. 预估算总行数(若可行)
虽然每个XML文件行数不固定,但如果能根据历史数据或文件特征估算最大总行数,可先预分配足够大的矩阵/数据框,再逐行填充,避免多次内存分配。示例:
prealloc_approach <- function(n) { # 假设每个文件最多15行,预分配最大可能行数 max_total_rows <- n * 15 ncol <- 10 final_mat <- matrix(NA, nrow = max_total_rows, ncol = ncol) current_row <- 1 for(i in 1:n) { mat <- create_mat() nr <- nrow(mat) final_mat[current_row:(current_row + nr - 1), ] <- mat current_row <- current_row + nr } # 移除多余的NA行 final_df <- as.data.frame(final_mat[1:(current_row - 1), ]) return(final_df) }
4. 避免全局赋值
测试代码中使用<<-全局赋值会拖慢速度且易引发副作用,实际代码应尽量用函数返回值,避免修改全局变量。
内容的提问来源于stack exchange,提问作者degeso
相关产品推荐
相关产品推荐

