从数字 YYYYMMDD 到日期再回到数字 YYYYMMDD 的最快方法
Fastest way from numeric YYYYMMDD to date and back to numeric YYYYMMDD
我需要优化的是以下过程:从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
时运行速度太慢。下面是在我的机器上运行 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]
我缩小了数据集,以便在我的机器上更快地完成比较。
Pre-compute期间:
diff_3m <- months(3)
这个建议的代码可以准确地满足您的需求,注意像 20180131
这样的日期不会超过一个月的月底,因此您最终会得到 20180430
因为使用了来自 lubridate
.
的 %m+%
运算符
DT[, StartDate_3M_new := as.numeric(format(ymd(StartDate) %m+% diff_3m, "%Y%m%d"))]
我机器上的时间:
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
当然我不知道这是否是 "fastest possible" 但我认为 re-implement 所有用于更快计算的小日期技巧所花费的时间可能会超过运行 这段代码所花费的时间。
编辑添加:这是@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
}
我继续尝试算术实现。不是我能写的最干净的,但它是一个很好的起点。我用于添加一个月的逻辑与 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
我需要优化的是以下过程:从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
时运行速度太慢。下面是在我的机器上运行 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]
我缩小了数据集,以便在我的机器上更快地完成比较。
Pre-compute期间:
diff_3m <- months(3)
这个建议的代码可以准确地满足您的需求,注意像 20180131
这样的日期不会超过一个月的月底,因此您最终会得到 20180430
因为使用了来自 lubridate
.
%m+%
运算符
DT[, StartDate_3M_new := as.numeric(format(ymd(StartDate) %m+% diff_3m, "%Y%m%d"))]
我机器上的时间:
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
当然我不知道这是否是 "fastest possible" 但我认为 re-implement 所有用于更快计算的小日期技巧所花费的时间可能会超过运行 这段代码所花费的时间。
编辑添加:这是@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
}
我继续尝试算术实现。不是我能写的最干净的,但它是一个很好的起点。我用于添加一个月的逻辑与 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