You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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"))]

代码解释

  1. dt1[, rn := .I]:给原始数据集的每一行添加唯一行号rn,用于后续关联原始列和展开后的数据。
  2. [, rbindlist(df1, fill = TRUE), by = rn]:按行号分组,对每组的嵌套数据框用rbindlist展开,fill = TRUE自动补全缺失列的NA值。
  3. [dt1[, .(rn, a)], on = "rn"]:把原始的a列通过行号关联到展开后的数据。
  4. [, rn := NULL]:删除临时行号列。
  5. [, setcolorder(.SD, c("a", "b", "c"))]:调整列顺序,和预期输出一致。

效率优势

这个方法避免了lapply逐行处理的循环开销,完全利用data.table的向量化分组和快速合并操作,内部实现更高效,相比原方案速度会有明显提升,性能接近tidyr::unnest。


内容的提问来源于stack exchange,提问作者Steve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 08:54:51