【问题标题】:Proper/fastest way to reshape a data.table重塑 data.table 的正确/最快方法
【发布时间】:2011-10-17 15:24:12
【问题描述】:

我在 R 中有一个 data table

library(data.table)
set.seed(1234)
DT <- data.table(x=rep(c(1,2,3),each=4), y=c("A","B"), v=sample(1:100,12))
DT
      x y  v
 [1,] 1 A 12
 [2,] 1 B 62
 [3,] 1 A 60
 [4,] 1 B 61
 [5,] 2 A 83
 [6,] 2 B 97
 [7,] 2 A  1
 [8,] 2 B 22
 [9,] 3 A 99
[10,] 3 B 47
[11,] 3 A 63
[12,] 3 B 49

我可以很容易地按 data.table 中的组对变量 v 求和:

out <- DT[,list(SUM=sum(v)),by=list(x,y)]
out
     x  y SUM
[1,] 1 A  72
[2,] 1 B 123
[3,] 2 A  84
[4,] 2 B 119
[5,] 3 A 162
[6,] 3 B  96

但是,我希望将组 (y) 作为列,而不是行。我可以使用reshape 完成此操作:

out <- reshape(out,direction='wide',idvar='x', timevar='y')
out
     x SUM.A SUM.B
[1,] 1    72   123
[2,] 2    84   119
[3,] 3   162    96

有没有更有效的方法在聚合数据后重塑数据?有没有办法使用 data.table 操作将这些操作合并为一个步骤?

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    data.table 包实现了更快的melt/dcast 函数(在 C 中)。它还具有允许熔化和铸造多个列的附加功能。请在 Github 上查看新的Efficient reshaping using data.tables

    data.table 的melt/dcast 函数从 v1.9.0 开始可用,功能包括:

    • 在转换之前无需加载 reshape2 包。但是如果你想加载它以进行其他操作,请在加载之前加载它加载data.table

    • dcast 也是 S3 泛型。没有更多的dcast.data.table()。只需使用dcast()

    • melt:

      • 能够在“list”类型的列上融合。

      • 获得variable.factorvalue.factor,默认情况下分别为TRUEFALSE,以便与reshape2 兼容。这允许直接控制variablevalue 列的输出类型(作为因素或不作为因素)。

      • melt.data.tablena.rm = TRUE 参数经过内部优化,可在熔解过程中直接去除 NA,因此效率更高。

      • 新:melt 可以接受measure.vars 的列表,并且列表的每个元素中指定的列将组合在一起。通过使用patterns() 可以进一步促进这一点。请参阅小插图或?melt

    • dcast

      • 接受多个fun.aggregate 和多个value.var。请参阅小插图或?dcast

      • 直接在公式中使用rowid() 函数来生成一个id 列,有时需要它来唯一标识行。请参阅 ?dcast。

    • 旧基准:

      • melt:1000万行5列,61.3秒减少到1.2秒。
      • dcast:100 万行 4 列,192 秒减少到 3.6 秒。

    科隆提醒(2013 年 12 月)演示幻灯片 32:Why not submit a dcast pull request to reshape2?

    【讨论】:

    • 公平地说,这需要一段时间......但 Arun 在我在此处复制的另一篇文章中发布了一个解决方案。你怎么看?
    • @Zach,只要你在编辑,为什么不提供更多关于在哪里/如何获得它的信息......?
    • @Arun 完成。感谢您的建议。
    • Zach,我对它进行了一些扩展,还提供了来自 NEWS 的信息,以便用户可以轻松获得想法。希望没事。
    【解决方案2】:

    此功能现在已在 data.table 中实现(从 1.8.11 版开始),如上面 Zach 的回答所示。

    我刚刚看到来自 Arun here on SO 的大量代码。所以我想有一个data.table 解决方案。应用于这个问题:

    library(data.table)
    set.seed(1234)
    DT <- data.table(x=rep(c(1,2,3),each=1e6), 
                      y=c("A","B"), 
                      v=sample(1:100,12))
    
    out <- DT[,list(SUM=sum(v)),by=list(x,y)]
    # edit (mnel) to avoid setNames which creates a copy
    # when calling `names<-` inside the function
    out[, as.list(setattr(SUM, 'names', y)), by=list(x)]
    })
       x        A        B
    1: 1 26499966 28166677
    2: 2 26499978 28166673
    3: 3 26500056 28166650
    

    这给出了与 DWin 方法相同的结果:

    tapply(DT$v,list(DT$x, DT$y), FUN=sum)
             A        B
    1 26499966 28166677
    2 26499978 28166673
    3 26500056 28166650
    

    而且速度很快:

    system.time({ 
       out <- DT[,list(SUM=sum(v)),by=list(x,y)]
       out[, as.list(setattr(SUM, 'names', y)), by=list(x)]})
    ##  user  system elapsed 
    ## 0.64    0.05    0.70 
    system.time(tapply(DT$v,list(DT$x, DT$y), FUN=sum))
    ## user  system elapsed 
    ## 7.23    0.16    7.39 
    

    更新

    因此该解决方案也适用于非平衡数据集(即某些组合不存在),您必须先在数据表中输入它们:

    library(data.table)
    set.seed(1234)
    DT <- data.table(x=c(rep(c(1,2,3),each=4),3,4), y=c("A","B"), v=sample(1:100,14))
    
    out <- DT[,list(SUM=sum(v)),by=list(x,y)]
    setkey(out, x, y)
    
    intDT <- expand.grid(unique(out[,x]), unique(out[,y]))
    setnames(intDT, c("x", "y"))
    out <- out[intDT]
    
    out[, as.list(setattr(SUM, 'names', y)), by=list(x)]
    

    总结

    将 cmets 与上述结合,这是 1 行解决方案:

    DT[, sum(v), keyby = list(x,y)][CJ(unique(x), unique(y)), allow.cartesian = T][,
       setNames(as.list(V1), paste(y)), by = x]
    

    也很容易将其修改为不仅仅是总和,例如:

    DT[, list(sum(v), mean(v)), keyby = list(x,y)][CJ(unique(x), unique(y)), allow.cartesian = T][,
       setNames(as.list(c(V1, V2)), c(paste0(y,".sum"), paste0(y,".mean"))), by = x]
    #   x A.sum B.sum   A.mean B.mean
    #1: 1    72   123 36.00000   61.5
    #2: 2    84   119 42.00000   59.5
    #3: 3   187    96 62.33333   48.0
    #4: 4    NA    81       NA   81.0
    

    【讨论】:

    • 谢谢!这是一些很棒的代码。一个问题:如果每个子组不一定都有所有列,我该怎么办?例如。如果 C 的 y 有一个值,那只有在 x=4 时才存在?
    • @Zach 很棒的评论!我最近在一个大型数据集上尝试了我的解决方案,但没有奏效,但不知道为什么。感谢您的评论,我现在知道了。所以基本上,您必须先更新 data.table 并手动插入所有组合。 (我用expand.grid 做到这一点,但我确信那里有更好的解决方案)。我想知道这是否是矫枉过正,但我​​不明白如何。一旦您将表格重塑为宽格式,无论如何您都在创建所有组合。我认为这是长格式的一大优势:对于稀疏密集的矩阵,这更有效。
    • 我认为 data.table 的交叉连接 (CJ) 可以替代上面的 expand.gridintDT&lt;-out[,list(x,y)]; setkey(intDT,x,y); intDT&lt;-intDT[CJ(unique(x),unique(y))]; 它在我的系统上运行得更快,我希望这是一个纯 data.table 解决方案。
    • 我们能把这个解决方案做得更通用一点吗?这里的问题是你必须在最后一行之后更改名称,如果你想扩展多于一列,这将不起作用......假设你有 SUM、DIFF、AVG 并且你想一次扩展它们?
    • @Frank 我的答案现在浮到了顶部。有关重塑 data.table 的最新方法,请参见该内容。如果您有旧版本的 data.table 或想自己破解某些东西,此答案将有效。
    【解决方案3】:

    Data.table 对象继承自“data.frame”,因此您可以使用 tapply:

    > tapply(DT$v,list(DT$x, DT$y), FUN=sum)
       AA  BB
    a  72 123
    b  84 119
    c 162  96
    

    【讨论】:

    • 这个函数会比在 data.frame 上使用 tapply 快很多吗?
    • 根据我所做的快速测试,tapply 在 data.table 上并不比在 data.frame 上快。我想我会坚持使用更丑但更快的代码。
    • 我不知道。我猜不是。最快的是 DT[, sum(v), by=list(x, y) ] 但它不会导致您请求的布局。
    • 我想最好将此视为两步操作。第一步是DT[, sum(v), by=list(x, y)],效果很好。第 2 步是将结果从长到宽重塑...我正在尝试找出使用数据表执行此操作的最佳方法
    • 我使用dcasttapplydata.table 对这三种方法进行了基准测试,发现tapply 的工作速度最快,提高了一个数量级,考虑到data.table 进行了优化,这令人惊讶。我怀疑这是因为没有定义 keys 优化在哪个 data.table 上起作用
    【解决方案4】:

    您可以使用来自reshape2 库的dcast。这是代码

    # DUMMY DATA
    library(data.table)
    mydf = data.table(
      x = rep(1:3, each = 4),
      y = rep(c('A', 'B'), times = 2),
      v = rpois(12, 30)
    )
    
    # USE RESHAPE2
    library(reshape2)
    dcast(mydf, x ~ y, fun = sum, value_var = "v")
    

    注意:tapply 解决方案会快得多。

    【讨论】:

    • 现在有data.tables的melt和dcast方法,哇!
    • 我认为dcast 函数使用data.frame 而不是data.tables 的自定义函数。
    • 我觉得data.table包里多了个自定义函数,见?dcast.data.table
    • 你是对的。已经添加到1.8.11,CRAN 上还没有。
    • 嗯,有道理。我正在使用 r-forge 版本。
    猜你喜欢
    • 2012-08-08
    • 2011-12-29
    • 1970-01-01
    • 2019-02-28
    • 2011-09-29
    • 2012-04-13
    • 2013-04-17
    相关资源
    最近更新 更多