从数字YYYYMMDD到日期并返回数字YYYYMMDD的最快方式

时间:2018-04-02 14:34:23

标签: r data.table lubridate

我需要优化的是以下过程:从yyyymmdd形式的数值开始,将其转换为日期,添加3个月(不是90天,月份必须保持为相同),然后将其转换回yyyymmdd格式的数字。以下代码实现了这一点:

library(lubridate)
a = 20180223
b = as.numeric(gsub("-", "", 
                    x = as.character(ymd(a) %m+% months(3)),
                    fixed = TRUE))
b
20180523

然而,在我看来,将它应用于大型data.table时运行速度太慢。下面是100万行的代码,在我的机器上运行13秒。有没有办法优化这个?我想,也许我不需要转换到约会,但不能绕过它。

library(data.table)
library(lubridate)

DT = CJ(rep(2016:2018, 1000), 1:12, 1:28)
DT[, StartDate := V1*10000 + V2*100 + V3]

system.time({
  DT[, StartDate_3M :=  as.numeric(gsub("-", "", 
                             x = as.character(ymd(StartDate) %m+% months(3)),
                             fixed = TRUE))]
}) 
   user  system elapsed 
  13.03    0.04   13.18 

2 个答案:

答案 0 :(得分:2)

这个答案是小组的努力,给出了很多有价值的评论。

library(data.table)
library(lubridate)
library(microbenchmark)

DT = CJ(rep(2016:2018, 100), 1:12, 1:28)
DT[, StartDate := V1*10000 + V2*100 + V3]

我将数据集缩小,以便在我的机器上进行比较以更快地完成。

预先计算期间:

diff_3m <- months(3)

这个建议的代码可以准确地为您提供所需内容,并指出20180131之类的日期不会超过月末,因此您最终会使用20180430来自%m+%的{​​{1}}运算符。

lubridate

我的机器上的时间:

DT[, StartDate_3M_new := as.numeric(format(ymd(StartDate) %m+% diff_3m, "%Y%m%d"))]

当然,我不知道这是否是最快的&#34;但是我认为重新实现所有小日期技巧只需要更快的计算所花费的时间可能会超过运行此代码所需的时间。

编辑添加:这是@BogdanC的算术答案的清理版(即不会发出警告)。随着闰年免费!表现类似。

microbenchmark(
  orig = DT[, StartDate_3M_orig :=  as.numeric(gsub("-", "", 
                                               x = as.character(ymd(StartDate) %m+% months(3)),
                                               fixed = TRUE))],
  new = DT[, StartDate_3M_new := as.numeric(format(ymd(StartDate) %m+% diff_3m, "%Y%m%d"))], times=10)


Unit: milliseconds
 expr      min       lq     mean   median       uq      max neval
 orig 713.2652 734.5133 798.1475 749.9207 883.2163 912.6070    10
  new 458.8666 483.0388 523.6454 502.4709 518.7074 665.7304    10

答案 1 :(得分:0)

我继续前进并进行算术实现。我写的不是最干净的,但这是一个很好的起点。我用于添加一个月的逻辑与ymd() %m+% months(3)相同,但有一个明显的例外是不处理闰年(也可以这样做,但我的业务逻辑并不需要它)。 下面的完整代码和基准 - 它比我原来的想法快96%,比团队努力快94%(我仍然非常重视)。

library(data.table)
library(lubridate)
library(microbenchmark)

DT = CJ(rep(2016:2018, 1000), 1:12, 1:28)
DT[, StartDate := V1*10000 + V2*100 + V3]

diff_3m <- months(3)

# arithmetic implementation
month_max_days <- c(31,28,31,30,31,30,31,31,30,31,30,31)

add_months <- function(dt, n_months, month_days) {
  dt[, year := StartDate %/% 10000]
  dt[, month := (StartDate - year * 10000) %/% 100]
  dt[, day := StartDate %% 100]
  dt[month + n_months <= 12, new_month := month + n_months]
  dt[month + n_months > 12, new_month := (month + n_months) ]
  dt[month + n_months > 12, new_month := new_month %% 12 ]
  dt[month + n_months <= 12, StartDate_3M := year * 10000 + new_month * 100 + pmin(day,month_days[new_month])]
  dt[month + n_months > 12, StartDate_3M := (year + (month + n_months) %/% 12) * 10000 + new_month * 100 + pmin(day,month_days[new_month])]
  dt
}

microbenchmark(
  orig = DT[, StartDate_3M_orig :=  as.numeric(gsub("-", "", 
                                                    x = as.character(ymd(StartDate) %m+% months(3)),
                                                    fixed = TRUE))],
  new = DT[, StartDate_3M_new := as.numeric(format(ymd(StartDate) %m+% diff_3m, "%Y%m%d"))],
  new_v2 = add_months(DT,3,month_max_days),
  times=10)
Unit: milliseconds
   expr        min         lq       mean     median        uq       max neval
   orig 11445.7826 12749.9895 13260.7266 13021.3065 13952.189 15247.221    10
    new  7953.0839  8643.7795 10004.3372  9484.5933 11104.017 13002.025    10
 new_v2   215.5608   309.2308   570.2348   408.0091   761.361  1196.798    10