按年分组data.table结果的有效方法

时间:2014-09-19 14:40:01

标签: r data.table

我正在寻找有关我是否有效使用data.table的建议。

我有一个描述事件的数据集,每个事件占一行。在每一行我都有事件发生的日期。现在我只想计算每年有多少事件。我使用下面的代码完成了这项工作,但感觉效率低下。我很感激有关如何改进这一点的任何建议。 (数据集远远大于下图所示,我还必须做其他类似但更复杂的计数)

创建从2000年到2012年底的日期列表:

dates <- seq(as.Date("1/1/2000", format="%d/%m/%Y"), 
  as.Date("31/12/2012", format="%d/%m/%Y"), 
  "day")

# Choose one million occurrences on various dates:    

sampleDate <- sample(dates, 1000000, replace=TRUE)

# Create `data.table`, one line per incident:   

library(data.table)
DT.dt <- data.table(Date=sampleDate, incident=1)

# Time how long it takes to count the number of indidents in each year: 

system.time(result <- DT.dt[,count(format(Date,"%Y"))])

user  system elapsed 
11.83    0.10   11.95 

result[1:3,]
x    freq
2000 76930
2001 77101
2002 76666

所以它有效(我认为),但我怀疑有更有效的解决方案......

1 个答案:

答案 0 :(得分:3)

当您使用data.tables特别是对大型数据集进行聚合操作(分组)时,您应该将要分组的字段设置为key(使用setkeyv(DT, "your_key_field")等等......)此外,我无法就该主题发表明确的演讲,但一般来说,我认为使用data.table::对象中的本地data.table函数/操作比使用其他软件包时更好。& 39;函数,例如plyr::count。下面,我制作了一些data.table个对象 - 第一个与你的例子相同;第二个添加列Year(而不是在函数执行时计算format(Date,"%Y")),但将Date设置为key;第三个与第二个相同,只是它使用Year作为key。我还提出了一些以不同方式进行分组的功能(用于基准测试)。

library(data.table)
library(plyr) # for 'count' function
library(microbenchmark)
##
dates <- seq.Date(
  from=as.Date("2000-01-01"),
  to=as.Date("2012-12-31"),
  by="day")
##
set.seed(123)
sampleDate <- sample(
  dates,
  1e06,
  replace=TRUE)
##
DT.dt <- data.table(
  Date=sampleDate,
  incident=1)
##
DT.dt2 <- copy(DT.dt)
DT.dt2[,Year:=format(Date,"%Y")]
setkeyv(DT.dt2,"Date")
##
DT.dt3 <- copy(DT.dt2)
setkeyv(DT.dt3,"Year")
##
> head(DT.dt,3)
         Date incident
1: 2003-09-27        1
2: 2010-04-01        1
3: 2005-04-26        1
> head(DT.dt2,3)
         Date incident Year
1: 2000-01-01        1 2000
2: 2000-01-01        1 2000
3: 2000-01-01        1 2000
> head(DT.dt3,3)
         Date incident Year
1: 2000-01-01        1 2000
2: 2000-01-01        1 2000
3: 2000-01-01        1 2000

## your original method
f1 <- function(dt)
{
  dt[,count(format(Date,"%Y"))]
}
## your method - using 'Year' column
f1.2 <- function(dt)
{
  dt[,count(Year)]
}
## use 'Date' column; '.N' and 
## 'by=' instead of 'count'
f2 <- function(dt)
{
  dt[,.N,by=format(Date,"%Y")]
}
## use 'Year' and '.N','by='
f3 <- function(dt)
{
  dt[,.N,by=Year]
}
##
Res <- microbenchmark(
  f1(DT.dt),
  f1.2(DT.dt2),
  f1.2(DT.dt3),
  f2(DT.dt2),
  f3(DT.dt3))
##
> Res
Unit: milliseconds
         expr        min         lq     median         uq      max neval
    f1(DT.dt) 478.941767 515.144253 557.428159 585.579862 706.8724   100
 f1.2(DT.dt2)  98.722062 115.588034 126.332104 137.792116 223.4967   100
 f1.2(DT.dt3)  97.475673 118.134788 125.836817 136.136156 238.2697   100
   f2(DT.dt2) 352.767219 373.337958 387.759996 429.301164 542.1674   100
   f3(DT.dt3)   7.912803   8.441159   8.736887   9.685267  76.9629   100

<强>观察:

  1. 按预先计算的字段Year进行分组,而不是计算 执行时format(Date,"%Y")是一个明显的改进 - 适用于count.N方法。你可以看到这个 将f1()f2()次与f1.2()次进行比较。

  2. count方法似乎比.N&amp; &#39;可以通过=&#39;方法(f1()f2()进行比较。

  3. 到目前为止,最好的方法是使用预先计算的字段Year和原生的data.table分组.N&amp; by=; f3()比其他四个时间要快得多。
  4. 在SO上有一些非常体验的data.table用户,当然比我自己更多,所以可能有更快的方法来做到这一点。除此之外,在key上设置data.table绝对是个好主意。并且看起来你预算计算像Year这样的字段要好得多,而不是这样做#34;在飞行中&#34 ;;如果您不需要使用DT.dt[,Year:=NULL]

    ,则可以随后将其删除

    另外,您说您正在尝试计算每年incident的数量 - 并且因为您的示例数据对所有行都有incident = 1,所以计数与求和相同。但假设您的实际数据具有不同的incident值,您可以这样:

    > DT.dt3[,list(Incidents=sum(incident)),by=Year]
        Year Incidents
     1: 2000     77214
     2: 2001     77385
     3: 2002     77080
     4: 2003     76609
     5: 2004     77197
     6: 2005     76994
     7: 2006     76560
     8: 2007     76904
     9: 2008     76786
    10: 2009     76765
    11: 2010     76675
    12: 2011     76868
    13: 2012     76963
    

    (我在上面调用了setkeyv(DT.dt3,cols="Year"))。