避免日期操作中的舍入陷阱的最佳实践

Ben*_*ker 17 datetime r date lubridate

我正在进行一些日期/时间操作,并在转换日期 - >时间 - >日期时遇到可解释但令人不愉快的往返问题.我已经通过在适当的点进行四舍五入来暂时克服这个问题,但我想知道是否有更好的日期处理方法会更清晰.我正在使用base-R和lubridate函数的混合.

tl; dr有一个很好的,简单的方法从十进制日期(YYYY.fff)转换到Date类(和返回)而不经过POSIXt并导致四舍五入(和可能的时区)并发症?

从1918年的几天开始,作为单独的年/月/日列(不是我的问题的关键部分,但它是我的管道恰好开始的地方):

library(lubridate)
dd <- data.frame(year=1918,month=9,day=1:12)
Run Code Online (Sandbox Code Playgroud)

转换年/月/日 - >日期 - >时间:

dd <- transform(dd,
                time=decimal_date(make_date(year, month, day)))
Run Code Online (Sandbox Code Playgroud)

由于收尾,所得时间向量的连续差异并不完全是1:这是可以理解的,但会导致问题.

table(diff(dd$time)*365)
## 0.999999999985448  1.00000000006844 
##                 9                 2 
Run Code Online (Sandbox Code Playgroud)

现在假设我转换回日期:日期在午夜之前或之后(在任一方向偏离<1秒):

d2 <- lubridate::date_decimal(dd$time)
#  [1] "1918-09-01 00:00:00 UTC" "1918-09-02 00:00:00 UTC"
#  [3] "1918-09-03 00:00:00 UTC" "1918-09-03 23:59:59 UTC"
#  [5] "1918-09-04 23:59:59 UTC" "1918-09-05 23:59:59 UTC"
#  [7] "1918-09-07 00:00:00 UTC" "1918-09-08 00:00:00 UTC"
#  [9] "1918-09-09 00:00:00 UTC" "1918-09-09 23:59:59 UTC"
# [11] "1918-09-10 23:59:59 UTC" "1918-09-12 00:00:00 UTC"
Run Code Online (Sandbox Code Playgroud)

如果我现在想要日期(而不是POSIXct对象)我可以使用as.Date(),但令我沮丧的是.Date()截断而不是舍入 ...

tt <- as.Date(d2)
## [1] "1918-09-01" "1918-09-02" "1918-09-03" "1918-09-03" "1918-09-04"
## [6] "1918-09-05" "1918-09-07" "1918-09-08" "1918-09-09" "1918-09-09"
##[11] "1918-09-10" "1918-09-12"
Run Code Online (Sandbox Code Playgroud)

所以差异现在是0/1/2天:

table(diff(tt))
# 0 1 2 
# 2 7 2 
Run Code Online (Sandbox Code Playgroud)

我可以通过先舍入来解决这个问题:

table(diff(as.Date(round(d2))))
## 1 
## 11
Run Code Online (Sandbox Code Playgroud)

但我想知道是否有更好的方法(例如将POSIXct保留在我的管道中并保持日期...

正如Grothendieck和Petzoldt 在2004年R-help服务台文章中所建议的那样:

在考虑使用哪个类时,请始终选择支持该应用程序的最不复杂的类.也就是说,Date尽可能使用,否则使用 chron和以其他方式使用POSIX类.这样的策略将大大减少错误的可能性并提高应用程序的可靠性.

本文中的扩展表显示了如何在Date,chronPOSIXct,但不包括小数时间作为候选者之一进行翻译...

d.b*_*d.b 6

如果可能的话,似乎最好避免从十进制时间转换回来.

从日期转换为十进制日期时,还需要考虑时间.由于Date没有与之相关的特定时间,因此decimal_date固有地认为它是00:00:00.

但是,如果我们只关心日期(而不是时间),我们可以假设时间成为任何东西.可以说,一天的中间(12:00:00)与一天的开始()一样好00:00:00.这将使转换Date更加可靠,因为我们不在午夜标记,几秒钟关闭不会影响输出.一要做到这一点的方法是添加12*60*60/(365*24*60*60)dd$time

dd$time2 = dd$time + 12*60*60/(365*24*60*60)
data.frame(dd[1:3],
           "00:00:00" = as.Date(date_decimal(dd$time)),
           "12:00:00" = as.Date(date_decimal(dd$time2)),
           check.names = FALSE)
#   year month day        00:00:00        12:00:00
#1  1918     9   1      1918-09-01      1918-09-01
#2  1918     9   2      1918-09-02      1918-09-02
#3  1918     9   3      1918-09-03      1918-09-03
#4  1918     9   4      1918-09-03      1918-09-04
#5  1918     9   5      1918-09-04      1918-09-05
#6  1918     9   6      1918-09-05      1918-09-06
#7  1918     9   7      1918-09-07      1918-09-07
#8  1918     9   8      1918-09-08      1918-09-08
#9  1918     9   9      1918-09-09      1918-09-09
#10 1918     9  10      1918-09-09      1918-09-10
#11 1918     9  11      1918-09-10      1918-09-11
#12 1918     9  12      1918-09-12      1918-09-12
Run Code Online (Sandbox Code Playgroud)

然而,应该注意,以这种方式获得的小数时间的值将是不同的.