高效将非重复序列转换为长格式的R语言实现方案
矩阵RLE序列提取与长格式转换的高效实现
问题与测试数据
先生成测试矩阵:
set.seed(1) m <- matrix(sample(-1:1, 30, replace = TRUE, prob = c(1,5,1)), ncol = 3, dimnames = list(NULL, LETTERS[1:3]))
生成的矩阵内容:
m A B C [1,] 0 0 -1 [2,] 0 0 0 [3,] 0 0 0 [4,] -1 0 0 [5,] 0 1 0 [6,] -1 0 0 [7,] -1 1 0 [8,] 0 -1 0 [9,] 0 0 -1 [10,] 0 1 0
需求:提取每列中游程编码(RLE)对应的非重复序列,转换为如下长格式数据框:
id name val 1 3 A 0 2 4 A -1 3 5 A 0 4 7 A -1 5 10 A 0 6 4 B 0 7 5 B 1 8 6 B 0 9 7 B 1 10 8 B -1 11 9 B 0 12 10 B 1 13 1 C -1 14 8 C 0 15 9 C -1 16 10 C 0
现有实现效率极低,需处理2000行5列的小型矩阵,但要重复运行数十万次,因此需要最优解决方案。
原始低效实现
rid <- apply(m, 2, \(colum) cumsum(rle(colum)$length)) long_df <- NULL for(Name in names(rid)){ df <- cbind.data.frame(id = rid[[Name]], name = Name, val = m[rid[[Name]], Name]) long_df <- rbind.data.frame(long_df, df) } long_df
基准测试结果(单位:毫秒)
| 函数名 | 最小值 | 下四分位数 | 平均值 | 中位数 | 上四分位数 | 最大值 | 测试次数 |
|---|---|---|---|---|---|---|---|
| my_fu(m) | 3.3196 | 3.6157 | 4.293507 | 3.75735 | 4.0036 | 12.2766 | 30 |
| MrFlick_fu(m) | 25.6869 | 26.5682 | 31.062887 | 27.22125 | 34.3767 | 60.2749 | 30 |
| one_fu(m) | 3.0693 | 3.3389 | 3.507363 | 3.51300 | 3.6304 | 4.1176 | 30 |
| jblood94_fu(m) | 1.3574 | 1.4510 | 1.621113 | 1.50865 | 1.5659 | 3.7355 | 30 |
| jblood94_2_fu(m) | 1.0156 | 1.0660 | 1.197143 | 1.12360 | 1.2961 | 1.7586 | 30 |
最优解决方案
根据基准测试,jblood94_2_fu是效率最高的实现,核心通过向量化操作+一次性合并避免循环拼接的开销,代码如下:
jblood94_2_fu <- function(m) { # 对每列计算RLE的结束位置和对应值,直接生成子数据框 rle_results <- apply(m, 2, function(x) { r <- rle(x) data.frame(id = cumsum(r$lengths), val = r$values) }) # 合并所有列的结果,添加列名标识 do.call(rbind, Map(cbind, name = names(rle_results), rle_results)) }
效率提升关键点
- 避免循环中重复调用
rbind.data.frame:每次拼接都会复制整个数据框,几十万次运行时开销呈指数增长;改用do.call(rbind, ...)一次性合并,底层优化更高效 - 直接从RLE结果取值:无需二次索引原矩阵,减少内存访问开销
- 用
Map替代显式循环:简化代码的同时,利用R的内置向量化优化
内容的提问来源于stack exchange,提问作者mr.T
相关产品推荐
相关产品推荐

