【问题标题】:merging endpoints of a range with a sequence将范围的端点与序列合并
【发布时间】:2013-07-09 23:14:59
【问题描述】:

在我的一个应用程序中,有一段代码根据另一个对象中的值从data.table 对象中检索信息。

# say this table contains customers details
dt <- data.table(id=LETTERS[1:4],
                 start=seq(as.Date("2010-01-01"), as.Date("2010-04-01"), "month"),
                 end=seq(as.Date("2010-01-01"), as.Date("2010-04-01"), "month") + c(6,8,10,5),
                 key="id")

# this one has some historical details
dt1 <- data.table(id=rep(LETTERS[1:4], each=120),
                  date=seq(as.Date("2010-01-01"), as.Date("2010-04-30"), "day"),
                  var=rnorm(120),
                  key="id,date")

# and here I finally retrieve my historical information based one customer detail
#
library(data.table)

myfunc <- function(x) {
  # some code
  period <- seq(x$start, x$end, "day")
  dt1[.(x$id, period)][, mean(var)]
  # some code
}

获得所有我使用的结果adply

library(plyr)
library(microbenchmark)
> adply(dt, 1, myfunc)
   id      start        end         V1
1:  A 2010-01-01 2010-01-07  0.3143536
2:  B 2010-02-01 2010-02-09 -0.5796084
3:  C 2010-03-01 2010-03-11  0.1171404
4:  D 2010-04-01 2010-04-06  0.2384237

> microbenchmark(adply(dt, 1, myfunc))
Unit: milliseconds
                 expr      min       lq   median       uq      max neval
 adply(dt, 1, myfunc) 8.812486 8.998338 9.105776 9.223637 88.14057   100

您知道避免adply 调用并在一个data.table 语句中执行上述操作的方法吗?或者无论如何更快的方法? (标题编辑建议非常受欢迎,我想不出更好的建议,谢谢)

【问题讨论】:

    标签: r data.table plyr


    【解决方案1】:

    这是使用data.tableroll 参数的好地方:

    setkey(dt1, id, date)
    setkey(dt, id, start)
    
    dt[dt1, roll = TRUE][end >= start,
       list(start = start[1], end = end[1], result = mean(var)), by = id]
    
    # benchmark
    microbenchmark(OP    = adply(dt, 1, myfunc),
                   Frank = dt[dt1[as.list(dt[,seq.Date(start,end,"day"),by="id"])][,mean(var),by=id]],
                   eddi  = dt[dt1, roll = TRUE][end >= start,list(start = start[1], end = end[1], result = mean(var)), by = id])
    #Unit: milliseconds
    #  expr       min        lq    median        uq       max neval
    #    OP 24.436126 29.184786 30.853094 32.493521 50.898664   100
    # Frank  9.115676 11.303691 12.081000 13.122753 28.370415   100
    #  eddi  5.336315  6.323643  6.771898  7.497285  9.531376   100
    

    随着数据集大小的增加,时间差将变得更加显着。

    【讨论】:

      【解决方案2】:

      我可以给你一堆嵌套的[.data.table 调用:

      set.seed(1)
      require(data.table)
      # generate dt, dt1 as above
      dt[
          dt1[
              as.list(dt[,seq.Date(start,end,"day"),by="id"])
          ][,mean(var),by=id]
      ]
      
      #    id      start        end          V1
      # 1:  A 2010-01-01 2010-01-07  0.04475859
      # 2:  B 2010-02-01 2010-02-09 -0.01681972
      # 3:  C 2010-03-01 2010-03-11  0.39791318
      # 4:  D 2010-04-01 2010-04-06  0.77854732
      

      我正在使用as.list 取消设置密钥。我想知道是否有比这更好的方法...

      require(microbenchmark)
      require(plyr)
      microbenchmark(
          adply=adply(dt, 1, myfunc),
          dtdtdt= dt[dt1[as.list(dt[,seq.Date(start,end,"day"),by="id"])][,mean(var),by=id]]
      )
      
      # Unit: milliseconds
      #    expr       min        lq    median        uq       max neval
      #   adply 12.987334 13.247374 13.477386 14.371258 18.362505   100
      #  dtdtdt  4.854708  4.944596  4.993678  5.233507  7.082461   100
      

      编辑: (eddi) 上述需要较少合并的替代方案(如 cmets 中所述)是:

      setkey(dt, NULL)
      
      dt1[dt[, list(seq.Date(start,end,"day"), end), by=id]][,
          list(start = date[1], end = end[1], result = mean(var)), by = id]
      # or
      dt1[dt[, seq.Date(start,end,"day"), by=id]][,
          list(start = date[1], end = date[.N], result = mean(var)), by = id]
      

      【讨论】:

      • 如果您在第一个[] 中返回end,除了序列之外,您可以更清楚一点,那么您不需要进行最后一次合并。另一种选择是从by 计算end。在任何一种情况下,您都可以通过在开始之前取消设置 dt 的键来摆脱该表达式中的 as.listsetkey
      • @eddi 返回end 以及序列应该与您的第一个[]nomatch=0 innit 相同?无论如何,谢谢你们,你们非常很有帮助!
      • @Michele,我不确定你在说哪一步,但在某些时候它们确实会合 :)
      • @eddi 我也认为我的解决方案很麻烦 :) [根据我的收件箱,正如你在最初的评论中所做的那样。] 我真的不明白如何返回 end in一种使我正在尝试工作的合并方式。随意编辑它,所以我找出来。我想,除了更快之外,当起始范围内的天数未能显示在 dt1 中时,您的 roll 方法不会产生 NA,这很好。我从来不知道这些rollnomatch 参数是干什么用的。 +1 提问和回答以进行说明。
      • @eddi 我的意思是dt[dt1, roll = TRUE, nomatch=0][end &gt;= start]dt[, list(seq.Date(start,end,"day"), end),by="id"]。不同的是,你的也有var 列,而弗兰克需要额外加入才能获得,所以可能是不同的时间。
      猜你喜欢
      • 2016-06-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-07
      • 1970-01-01
      • 2019-03-21
      • 2015-01-23
      • 1970-01-01
      • 2016-07-09
      相关资源
      最近更新 更多