OP的问题为什么我的lubridate日期函数如此缓慢?和一些概括性陈述,例如根据我的经验,Lubridate速度较慢表明特定软件包可能是低性能的原因。
我想通过一些基准测试来验证这个问题。
使用双冒号运算符::
的惩罚
Frank在他的评论中提到,使用双冒号运算符::
访问命名空间中导出的变量或函数会有一个惩罚。
n <- 10^1L
fmt <- "%F"
chr_dates <- format(Sys.Date() + seq_len(n), "%F")
library(lubridate)
microbenchmark::microbenchmark(
base1 = r1 <- as.Date(chr_dates),
base2 = r2 <- base::as.Date(chr_dates),
lubr1 = r3 <- as_date(chr_dates),
lubr2 = r4 <- lubridate::as_date(chr_dates),
times = 100L
)
Unit: microseconds
expr min lq mean median uq max neval cld
base1 87.977 89.1100 92.03587 89.865 90.9980 128.756 100 a
base2 94.018 95.7175 100.64848 97.039 99.3045 179.351 100 b
lubr1 92.508 94.2070 98.21307 95.151 97.7940 175.954 100 b
lubr2 101.569 103.0800 109.98974 104.024 107.9885 258.643 100 c
使用双冒号运算符::
的惩罚大约为10微秒。
这只有在反复调用函数时才会产生影响(正如在OP的代码中使用sapply()
一样)。个人认为,在调试命名空间冲突或维护代码方面,函数来源不清晰的痛苦要高得多。当然,具体情况具体分析。
可以验证n = 100
的时间:
Unit: microseconds
expr min lq mean median uq max neval cld
base1 556.933 561.0855 580.3382 562.9730 590.7250 812.176 100 a
base2 564.483 568.2600 588.5695 570.9030 596.2010 989.262 100 a
lubr1 562.596 565.9935 587.4443 568.4480 594.8790 1039.480 100 a
lubr2 572.036 575.9995 597.1557 578.4545 601.1085 1230.159 100 a
将字符日期转换为Date类
有许多软件包处理不同格式的字符日期转换为Date或POSIXct类。其中一些旨在提高性能,其他则旨在提供方便。
这里比较了base、lubridate、anytime、fasttime和data.table(因为其中一个答案中提到了它)。
输入是采用标准明确格式YYYY-MM-DD的字符日期。时区被忽略。
fasttime仅接受1970年至2199年之间的日期,因此必须修改创建示例数据以创建一个包含100K日期的样本数据集。
n <- 10^5L
fmt <- "%F"
set.seed(123L)
chr_dates <- format(
sample(
seq(as.Date("1970-01-01"), as.Date("2199-12-31"), by = 1L),
n, replace = TRUE),
"%F")
因为Frank怀疑猜测格式可能会增加惩罚,所以尽可能在调用函数时使用给定的格式和不使用格式。所有函数都使用双冒号运算符::
调用。
microbenchmark::microbenchmark(
base_ = r1 <- base::as.Date(chr_dates),
basef = r1 <- base::as.Date(chr_dates, fmt),
lub1_ = r2 <- lubridate::as_date(chr_dates),
lub1f = r2 <- lubridate::as_date(chr_dates, fmt),
lub2_ = r3 <- lubridate::ymd(chr_dates),
anyt_ = r4 <- anytime::anydate(chr_dates),
idat_ = r5 <- data.table::as.IDate(chr_dates),
idatf = r5 <- data.table::as.IDate(chr_dates, fmt),
fast_ = r6 <- fasttime::fastPOSIXct(chr_dates),
fastd = r6 <- as.Date(fasttime::fastPOSIXct(chr_dates)),
times = 5L
)
all.equal(r1, r2)
all.equal(r1, r3)
all.equal(r1, c(r4))
all.equal(r1, as.Date(r5))
all.equal(r1, as.Date(r6))
Unit: milliseconds
expr min lq mean median uq max neval cld
base_ 641.799082 645.008517 648.128466 648.791875 649.149444 655.893411 5 d
basef 69.377419 69.937371 73.888828 71.403139 76.022083 82.704127 5 b
lub1_ 644.199361 645.217696 680.542327 649.855896 652.887492 810.551189 5 d
lub1f 69.769726 69.947943 70.944605 70.795234 71.365759 72.844364 5 b
lub2_ 18.672495 27.025711 26.990218 28.180730 29.944409 31.127747 5 ab
anyt_ 381.870316 384.513758 386.211134 384.992152 385.159043 394.520400 5 c
idat_ 643.386808 644.312259 649.385356 648.204359 651.666396 659.356958 5 d
idatf 69.844109 71.188673 75.319481 77.142365 78.156923 80.265334 5 b
fast_ 4.994637 5.363533 5.748137 5.601031 5.760370 7.021112 5 a
fastd 5.230625 6.296157 6.686500 6.345998 6.538941 9.020780 5 a
计时表明,Frank的怀疑是正确的。猜测格式是代价高昂的。将格式作为参数传递给as.Date()
、as_date()
和as.IDate()
比不带参数调用快十倍。
fasttime::fastPOSIXct()
确实是最快的。即使需要将POSIXct
转换为Date
,它也比第二快的lubridate::ymd()
快四倍。
as_date
可能需要猜测格式,因为您没有传递其他参数。因为您选择使用循环(与sapply
一起)而不是向量化函数运行它,所以它肯定会很慢。此外,::
有一些开销。 - Frankprevious_business_date_if_weekend
是我的瓶颈。编辑问题,删除所有关于EOMonth
的引用。 - lebelinoz::
运算符会增加几微秒的执行时间,但只有在重复调用函数时才会有所影响(就像在这里使用sapply()
函数一样)。然而,相对于调试命名空间冲突或维护来源不清晰的代码而言,这点延迟算不了什么。当然,个人观点因人而异。 - Uwe