正确/最快的方式重塑数据表


67

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

Answers:


75

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,默认情况下分别为和,以TRUEFALSE兼容reshape2。这样可以直接控制输出的类型variablevalue列(是否为因子)。

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

    • 新增:melt可以接受列表,列表measure.vars中每个元素中指定的列将合并在一起。通过使用进一步简化了此过程patterns()。参见小插图或?melt

  • dcast

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

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

  • 旧基准:

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

提醒您注意科隆(2013年12月)演示幻灯片32:为什么不向以下用户提交dcast请求请求reshape2


1
公平地说,花了一段时间...但是Arun在我在此处复制的另一篇文章中发布了一个解决方案。你怎么看?
Christoph_J

@Zach,只要您正在编辑,为什么不提供更多有关在何处/如何获得它的信息...?
阿伦(Arun)2013年

1
@Arun完成。谢谢你的建议。
Zach 2013年

1
Zach,我对其进行了扩展,还提供了NEWS的信息,以便用户可以轻松地获得想法。希望一切都好。
阿伦(Arun)

32

现在可以在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)]

概要

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

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

谢谢!那是一些出色的代码。一个问题:如果每个子组不一定都具有所有列,该怎么办?例如,如果y的值为C,则仅当x = 4时才存在?
Zach 2013年

@Zach很棒的评论!我最近在大型数据集上尝试了我的解决方案,但没有用,但没有弄清楚原因。感谢您的评论,我现在知道了。因此,基本上,您必须先更新data.table并手动插入所有组合。(我使用来做到这一点expand.grid,但是我敢肯定那里有更好的解决方案)。我想知道这是否太过分了,但我不知道怎么做。一旦将表格重整为宽格式,无论如何您将创建所有组合。我认为这是长格式的一大优势:对于稀疏密集的矩阵,这样做效率更高。
Christoph_J

2
我认为data.table的交叉联接(CJ)可以替代expand.grid上述内容。 intDT<-out[,list(x,y)]; setkey(intDT,x,y); intDT<-intDT[CJ(unique(x),unique(y))]; 它在我的系统上运行得更快,我希望使用纯data.table解决方案。
马特

我们可以更一般地执行此解决方案吗?这里的问题是,您必须在最后一行之后更改名称,如果要扩展多个列,这将不起作用...假设您有SUM,DIFF,AVG,并且想一次扩展它们?
statquant

2
@Frank我的答案现在已经浮到顶部。请参阅有关重塑data.table的最新方法。如果您有旧版本的data.table或想自己一起破解某些东西,那么此答案将起作用。
扎克2014年

21

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显着更快?
扎克(Zach)

根据我所做的快速测试,tapply在data.table上没有比在data.frame上更快。我想我会坚持使用丑陋但速度更快的代码。
Zach

1
我不知道。我猜不是。最快的将是DT [,sum(v),by = list(x,y)],但不会导致您请求的布局。
IRTFM

1
我想最好将其视为两步操作。第一步是DT[, sum(v), by=list(x, y)],效果很好。第2步是从长到宽重塑结果。我试图找出用数据表做到这一点的最佳方法
Zach

2
我基准三个使用方法dcasttapplydata.table发现tapply工作最快将它奇怪,因为一个数量级的data.table优化。我怀疑这是考虑不限定keys在其上data.table的优化工作
Ramnath

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解决方案将更快。


1
现在有一种wahoo data.tables!的熔铸方法。
Zach 2013年

我认为该dcast函数使用data.frame而不是自定义函数data.tables
Ramnath 2013年

我认为data.table包中有一个新的自定义函数,请参见?dcast.data.table
Zach

你是对的。它已添加到中1.8.11,但尚未在CRAN上添加。
Ramnath

啊,这是有道理的。我正在使用r-forge版本。
Zach 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.