如何用data.table高效展开行列数不等的复杂列?
用data.table高效展开行列数不等的嵌套数据集
我有一个数据集,待展开列里的嵌套数据框行列数不一致,希望用data.table实现高效的展开操作。
示例数据集
dt1 <- structure(list(a = c("a1", "a1", "a1", "a1", "a2", "a3", "a3", "a3", "a3", "a3"), df1 = list(structure(list(b = c("b1", "b2" )), class = "data.frame", row.names = c(NA, -2L)), structure(list( b = c("b1", "b2")), class = "data.frame", row.names = c(NA, -2L)), structure(list(b = c("b1", "b2")), class = "data.frame", row.names = c(NA, -2L)), structure(list(b = c("b1", "b2")), class = "data.frame", row.names = c(NA, -2L)), structure(list(b = c("b1", "b2")), class = "data.frame", row.names = c(NA, -2L)), structure(list(b = c("b1", "b2", "b3"), c = c("c1", "c2", "c3")), row.names = c(NA, -3L), class = "data.frame"), structure(list( b = c("b1", "b2", "b3"), c = c("c1", "c2", "c3")), row.names = c(NA, -3L), class = "data.frame"), structure(list(b = c("b1", "b2", "b3"), c = c("c1", "c2", "c3")), row.names = c(NA, -3L), class = "data.frame"), structure(list(b = c("b1", "b2", "b3"), c = c("c1", "c2", "c3")), row.names = c(NA, -3L), class = "data.frame"), structure(list( b = c("b1", "b2", "b3"), c = c("c1", "c2", "c3")), row.names = c(NA, -3L), class = "data.frame"))), row.names = c(NA, -10L), class = c("data.table", "data.frame"))
数据集展示:
a df1 <char> <list> 1: a1 <data.frame[2x1]> 2: a1 <data.frame[2x1]> 3: a1 <data.frame[2x1]> 4: a1 <data.frame[2x1]> 5: a2 <data.frame[2x1]> 6: a3 <data.frame[3x2]> 7: a3 <data.frame[3x2]> 8: a3 <data.frame[3x2]> 9: a3 <data.frame[3x2]> 10: a3 <data.frame[3x2]>
尝试过的方法及问题
- 直接用
data.table::rbindlist()分组展开报错,因为不同组的展开结果列数不一致:
dt1[, data.table::rbindlist(df1, fill = TRUE), by = .(a)]
报错信息:
Error in `[.data.table`(dt1, , data.table::rbindlist(df1, fill = TRUE), : j doesn't evaluate to the same number of columns for each group
- 用
unlist()展开会把所有数据放到单列,不符合需求:
dt1[, unlist(df1, TRUE, FALSE), .(a)]
预期输出
a b c <char> <char> <char> 1: a1 b1 <NA> 2: a1 b2 <NA> 3: a1 b1 <NA> 4: a1 b2 <NA> 5: a1 b1 <NA> 6: a1 b2 <NA> 7: a1 b1 <NA> 8: a1 b2 <NA> 9: a2 b1 <NA> 10: a2 b2 <NA> 11: a3 b1 c1 12: a3 b2 c2 13: a3 b3 c3 14: a3 b1 c1 15: a3 b2 c2 16: a3 b3 c3 17: a3 b1 c1 18: a3 b2 c2 19: a3 b3 c3 20: a3 b1 c1 21: a3 b2 c2 22: a3 b3 c3 23: a3 b1 c1 24: a3 b2 c2 25: a3 b3 c3 a b c
现有低效方案
以下方案可行但速度远慢于tidyr::unnest(dt1, cols = c(df1)):
unnested <- rbindlist( lapply(seq_len(nrow(dt1)), function(i) { inner_dt <- as.data.table(dt1$df1[[i]]) # Convert to data.table inner_dt[, a := dt1$a[i]] # Add outer column return(inner_dt) }), fill = TRUE # Fill missing columns with NA ) setcolorder(unnested, c("a", setdiff(names(unnested), "a")))
请问有什么用data.table高效展开此类数据集的方法?
解决方案
可以利用data.table的行标识(.I)和分组rbindlist的方式实现高效展开,避免逐行循环的开销,速度接近tidyr::unnest:
# 高效展开代码 result <- dt1[, rn := .I][, rbindlist(df1, fill = TRUE), by = rn][ dt1[, .(rn, a)], on = "rn"][, rn := NULL][, setcolorder(.SD, c("a", "b", "c"))]
代码解释
dt1[, rn := .I]:给原始数据集的每一行添加唯一行号rn,用于后续关联原始列和展开后的数据。[, rbindlist(df1, fill = TRUE), by = rn]:按行号分组,对每组的嵌套数据框用rbindlist展开,fill = TRUE自动补全缺失列的NA值。[dt1[, .(rn, a)], on = "rn"]:把原始的a列通过行号关联到展开后的数据。[, rn := NULL]:删除临时行号列。[, setcolorder(.SD, c("a", "b", "c"))]:调整列顺序,和预期输出一致。
效率优势
这个方法避免了lapply逐行处理的循环开销,完全利用data.table的向量化分组和快速合并操作,内部实现更高效,相比原方案速度会有明显提升,性能接近tidyr::unnest。
内容的提问来源于stack exchange,提问作者Steve
相关产品推荐
相关产品推荐

