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

R 从数字YYYYMMDD到日期并返回数字YYYYMMDD的最快方式,r,data.table,lubridate,R,Data.table,Lubridate,我需要优化的是以下过程:从格式为yyyyymmdd的数值开始,将其转换为日期,再添加3个月(不是90天,月日必须保持不变),然后使用yyyymmdd格式将其转换回数值。下面的代码正好实现了这一点: library(lubridate) a = 20180223 b = as.numeric(gsub("-", "", x = as.character(ymd(a) %m+% months(3)), fixed =

我需要优化的是以下过程:从格式为
yyyyymmdd
的数值开始,将其转换为日期,再添加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
时,它运行得太慢了。下面是在我的机器上运行13秒的100万行代码。有没有办法优化这个?我在想,也许我不需要转换成约会,但我不能绕着它转

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 

这个答案是一个团队的努力,给出了很多有价值的评论

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)
在我的机器上计时:

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
当然,我不知道这是否是“最快的可能”,但我认为重新实现所有用于更快计算的日期小技巧所需的时间可能超过运行此代码所需的时间

编辑添加:这里是@BogdanC算术答案的清理版本(即不抛出警告)。免费享受闰年!性能相似

add_months_2 <- function(dt, n_months, month_days) {
  dt[, year := StartDate %/% 10000][
    , month := (StartDate - year * 10000) %/% 100][
    , day := StartDate %% 100][
    , new_month := c(1:12, 1:3)[month + n_months]][
    , leap_year := (!(year %% 4) & (year %% 100)) | !(year %% 400)][
    , max_d := (month_days + leap_year * c(0, 1, rep(0, 10)))[new_month]][  
    , StartDate_PlusM := year * 10000 + new_month * 100 + pmin(day, max_d)]
  dt
}

add\u months\u 2我继续,尝试了一个算术实现。这不是我能写的最干净的,但这是一个很好的起点。我用于添加一个月的逻辑与
ymd()%m+%months(3)
相同,但显著的例外是不处理闰年(也可以这样做,但我的业务逻辑不需要它)。
下面是完整的代码和基准测试——它比我最初的想法快96%,比团队工作快94%(我仍然非常重视)

库(data.table)
图书馆(lubridate)
图书馆(微基准)
DT=CJ(代表(2016:2018,1000),1:12,1:28)
DT[,起始日期:=V1*10000+V2*100+V3]

diff_3m@MKR运行时间降至10.6秒,感谢您的建议。应该记住,
as.numeric
采用
格式
参数另一个建议是将
(StartDate)
转换为
ymd
格式并保持该格式。它将消除您的业务逻辑中可能不需要的大量转换时间。您也可以提前计算
monds(3)
,然后在需要时使用它。@MKR我刚刚计时,加速率约为38%。@RuiBarradas现在运行时间降到了8.3秒。我同意最后一句话,加速效果非常好。感谢大家的团队努力。添加了一个新的答案,仅基于数学运算(因此速度非常快),但它需要一些清理。我已经在我的答案中添加了此代码的一个版本,我认为这加强了逻辑,更重要的是消除了警告。我加上闰年仅仅是因为。@ngm太好了,谢谢你花时间清理这个,并把它添加到公认的答案中
add_months_2 <- function(dt, n_months, month_days) {
  dt[, year := StartDate %/% 10000][
    , month := (StartDate - year * 10000) %/% 100][
    , day := StartDate %% 100][
    , new_month := c(1:12, 1:3)[month + n_months]][
    , leap_year := (!(year %% 4) & (year %% 100)) | !(year %% 400)][
    , max_d := (month_days + leap_year * c(0, 1, rep(0, 10)))[new_month]][  
    , StartDate_PlusM := year * 10000 + new_month * 100 + pmin(day, max_d)]
  dt
}
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