首页 文章

正确/最快的方式来重塑data.table

提问于
浏览
66

我在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操作将这些操作合并为一个步骤?

4 回答

  • 32

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

    data.table的融合/ 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,因此效率更高 .

    • NEW: 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?

  • 21

    此功能现已实现到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
    

    UPDATE

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

    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)]
    

    Summary

    结合上述评论,这是一线解决方案:

    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
    
  • 7

    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
    
  • 73

    您可以使用 reshape2 来自 reshape2 库 . 这是代码

    # 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 解决方案会快得多 .

相关问题