68

我在 R 中有一个数据表

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

4 回答 4

76

data.table包实现了更快的melt/dcast功能(在 C 中)。它还具有允许熔化和铸造多个列的附加功能。请参阅Github 上使用 data.tables进行的新的高效重塑。

从 v1.9.0 开始提供 data.table 的 melt/dcast 功能,功能包括:

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

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

  • melt

    • 能够融化在“列表”类型的列上。

    • 增益variable.factorvalue.factor默认情况下TRUE分别FALSE是为了与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:为什么不向 提交dcast拉取请求reshape2

于 2011-08-02T13:52:14.200 回答
33

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

我刚刚在 SO 上看到了来自 Arun 的大量代码。所以我想有一个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)]

概括

将评论与上述结合起来,这是 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
于 2013-03-19T23:25:57.550 回答
22

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
于 2011-08-01T17:31:43.813 回答
7

您可以dcastreshape2库中使用。这是代码

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

于 2011-08-01T17:35:09.860 回答