我正在进行一些日期/时间操作,并在转换日期时遇到可解释但令人不愉快的往返问题 - >时间 - >约会。我已经通过在适当的点进行四舍五入来暂时克服这个问题,但我想知道是否有更好的日期处理方法会更清晰。我正在使用base-R和lubridate
函数的混合。
tl; dr 是一种很好的,简单的方法,可以从十进制日期(YYYY.fff)转换为Date
类(并返回),而无需通过POSIXt并产生回合 - 关闭(和潜在的时区)并发症??
从1918年的几天开始,作为单独的年/月/日列(不是我的问题的关键部分,但它是我的管道恰好开始的地方):
library(lubridate)
dd <- data.frame(year=1918,month=9,day=1:12)
转换年/月/日 - &gt;日期 - &gt;时间:
dd <- transform(dd,
time=decimal_date(make_date(year, month, day)))
由于结果,结果时间向量的连续差异不完全是1:这是可以理解的,但会导致问题。
table(diff(dd$time)*365)
## 0.999999999985448 1.00000000006844
## 9 2
现在假设我转换回日期:日期在午夜之前或之后(在任何一个方向关闭<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"
如果我现在想要日期(而不是POSIXct对象)我可以使用as.Date()
,但令我沮丧的是 as.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"
所以差异现在是0/1/2天:
table(diff(tt))
# 0 1 2
# 2 7 2
我可以先解决这个问题来解决这个问题:
table(diff(as.Date(round(d2))))
## 1
## 11
但我想知道是否有更好的方法(例如将POSIXct保留在我的管道中并保持日期......
根据this R-help desk article from 2004由Grothendieck和Petzoldt的建议:
在考虑使用哪个班级时,总是如此 选择最不复杂的类来支持 应用。也就是说,如果可能的话,使用
Date
,否则使用chron
并以其他方式使用POSIX
类。这样的策略将大大减少出错的可能性并提高应用程序的可靠性。
本文中的扩展表显示了如何在Date
,chron
和POSIXct
之间进行翻译,但不包括小数时间作为候选人之一...
答案 0 :(得分: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
但应注意,以这种方式获得的小数时间值会有所不同。
答案 1 :(得分:3)
lubridate::decimal_date()
正在返回numeric
。如果我理解正确,那么问题是如何将numeric
转换为Date
,并在不通过POSIXct
的情况下适当地进行调整。
as.Date(1L, origin = '1970-01-01')
告诉我们,我们可以为as.Date
提供自某些指定来源以来的天数,并立即转换为日期类型。知道这一点,我们可以完全跳过年份部分并将其设置为原点。然后我们可以将小数日期转换为天数:
as.Date((dd$time-trunc(dd$time)) * 365, origin = "1918-01-01")
。
所以,像这样的函数可能会起作用(至少在没有闰日的情况下数年):
date_decimal2 <- function(decimal_date) {
years <- trunc(decimal_date)
origins <- paste0(years, "-01-01")
# c.f. https://stackoverflow.com/questions/14449166/dates-with-lapply-and-sapply
do.call(c, mapply(as.Date.numeric, x = (decimal_date-years) * 365, origin = origins, SIMPLIFY = FALSE))
}
旁注:我承认自己因为试图在1970年前的日期处理起源而试图移动一个兔子洞。我发现进一步的起源偏离了目标日期,结果越奇怪(而不是以闰日似乎很容易解释的方式)。由于原点很灵活,我决定将其定位在目标值之上。对于闰日,秒,以及其他任何奇怪的时间,我们都可以自己动手。 =)