B R日期时间

本节中我们将介绍在R中的日期与时间数据类型,以及如何通过lubridate包来处理日期与时间包来处理日期与时间。

B.1 日期-时间数据类型

R的基础包提供了三种日期-时间数据类型,采用POSIXt标准: - Date用于处理日期,它是一个整数,储存了距离1970年1月1日的天数,早于1970年1月1日的日期被储存为负数,Date不包括时间和时区信息; - POSIXct用于处理日期-时间,它也是一个整数,储存了以时间标准时间(UTC1)时区为准的,从1970年1月1日开始计时的秒数; - POSIXlt也用于处理日期-时间,但是它是将POSIXct储存在一个列表中。

B.2 lubridate包

lubridate是tidyverse家族的成员,同样出自大神Hadley Wickham之手。如同tidyverse的其他成员一样,lubridate包非常强大,它可以非常直观和灵活的处理日期与时间。lubridate的所有函数都会返回POSIXct类型的日期-时间,缺省情况下,lubridate使用UTC时区。lubridate包括两类函数,一类用于处理时点数据(time instants),另一类则用于处理时段数据(time spans)

B.2.1 定义日期-时间变量

我们先加载lubridate包

library(lubridate)

函数today与now可以返回当前的日期和时间

today()
## [1] "2023-11-11"
now()
## [1] "2023-11-11 20:19:18 CST"

lubridate包提供了将字符串转化成为日期-时间的函数族。在该函数族中,y表示年,m表示月份或者分钟,d表示日期,h表示小时,s表示秒,日期与时间的关键词之间通过_连接,从而组成非常灵活多变的函数组合,将各种形式的字符串方便的转换为日期-时间,整个过程的用户体验非常友好。例如ymd_hms函数可以转化各种形式的完整记录日期-时间的字符串:

ymd_hms("2017-11-28T14:02:00")
## [1] "2017-11-28 14:02:00 UTC"
ymd_hms("2017-11-28 14:02:00")
## [1] "2017-11-28 14:02:00 UTC"
ymd_hms("2017/11/28 14:02:00")
## [1] "2017-11-28 14:02:00 UTC"
ymd_hms("2017 Nov 28 14:02:00")
## [1] "2017-11-28 14:02:00 UTC"

感觉到lubridate包的强大之处了吧!我们还可以随意组合关键词,从而转化不部分记录日期-时间信息的字符串,例如:

ymd(20170131)
## [1] "2017-01-31"
hms::hms(3,4,4)
## 04:04:03
yq("2001: Q3") 
## [1] "2001-07-01"

上面的例子中,hms::hms可以将输入信息的三个元素对应为小时-分钟-秒,而yq函数可以转化季度信息到日期。

ymd()
## Date of length 0
hms::hms(3,4,4)
## 04:04:03
yq("2001: Q3") 
## [1] "2001-07-01"

最后,我们也可以直接将整数转化为日期时间,lubridate为我们提供了三个函数。as_datetime函数可以将”1970-01-01 00:00:00 UTC”以来的秒数转化为日期-时间,as_date将”1970-01-01”以来的天数转化为日期,hms::as.hms将”00:00:00”以来的秒数转化为时间。

as_datetime(1511870400)
## [1] "2017-11-28 12:00:00 UTC"
as_date(17498)
## [1] "2017-11-28"
hms::as.hms(85) 
## 00:01:25

B.3 从日期-时间中取出元素

反过来,我们可以使用lubridate包的date,year,month,quarter,day,wday,hour,minute,second函数可以取出日期-时间的日期、年、月、季度、日、星期、小时、分钟以及秒钟。如下:

dt <- ymd_hms("2017-11-28T14:02:00")
date(dt)
## [1] "2017-11-28"
year(dt) 
## [1] 2017
month(dt)
## [1] 11
day(dt)
## [1] 28
wday(dt)
## [1] 3

update函数可以直接修改日期-时间,例如:

update(dt, day = 3, hour = 1)
## [1] "2017-11-03 01:02:00 UTC"

lubridate还提供了其他一些便利的函数

am(dt) # 判断是否是上午
## [1] FALSE
pm(dt) # 判断是否是下午
## [1] TRUE
leap_year(dt) # 判断是否是闰年
## [1] FALSE

B.4 日期-时间取整

lubridate提供了四个日期和时间取整函数,这极大简化了我们的数据分析。在实践中,我们经常需要把数据聚合起来分析,一种常用的方式就是把同一月份的数据都取整到当月的第一天。

floor_date(dt, unit = "day") # 向下取整到最近的unit,此处是天
## [1] "2017-11-28 UTC"
round_date(dt, unit = "month") # 就近取整到最近的unit,此处是月份
## [1] "2017-12-01 UTC"
ceiling_date(dt, unit = "hour") # 向上取整到最近的unit,此处是小时
## [1] "2017-11-28 15:00:00 UTC"
rollback(dt, roll_to_first = FALSE, preserve_hms = TRUE) #回滚到上一个月的最后一天
## [1] "2017-10-31 14:02:00 UTC"

B.5 日期-时间的数学运算

lubridate支持三种类型的时间段数据,时间长度(duration),按整秒计算;时间周期(period),以相应单位如年、日计算;时间区间(interval),包含一个开始时间和一个结束时间。在此基础上,lubridate支持针对日期-时间的数学运算 ### 时间长度 时间长度以整秒计数,以d开头的关键词函数dseconds, dminutes, dhours, ddays, dweeks, dyears生成,例如ddays(1)返回的是一天的秒数,即24*3600秒:

ddays(1)
## [1] "86400s (~1 days)"

对于时间尺度小于1秒的,会返回浮点数,例如:

dmilliseconds(1)
## [1] "0.001s"

由于duration统一用整秒计数,所以他们很方便进行计算,例如

dhours(1) + dseconds(5)
## [1] "3605s (~1 hours)"
ddays(1) == dhours(1)*24
## [1] TRUE

使用as.duration函数可以加个两个日期-时间相减的结果转化为时间长度,如:

dt1 <- ymd_hms("2017-11-28T14:02:00")
dt2 <- ymd_hms("2020-12-10T14:54:00")
as.duration(dt2 - dt1)
## [1] "95734320s (~3.03 years)"

我们也可以对日期-时间,加上或者减去时间长度,计算结果是按照整秒推移的,例如:

ymd("2020-01-01") + dyears(1)
## [1] "2020-12-31 06:00:00 UTC"

细心的读者肯定已经发现了这里的问题,上面代码得到的结果是2020年12月31日而不是2021年1月1日,这是因为2020年是闰年,有366天,而dyears只会返回356天对应描述,因此相加的结果便是2020年12月31日。实践中,如果不小心,很容易在这里出问题。

B.5.1 时间周期

时间周期可以解决上面的问题,例如

ymd("2020-01-01") + years(1)
## [1] "2021-01-01"

实际上,时间周期是一种有歧义的,翻译成日历时间长度可能更合适。在前面的例子中时间周期函数years在运算时考虑到了日历时间的实际情况,当遇到闰年时,years会自动转换为366天。因此years对应的是一个日历年。类似的函数还有seconds, minutes, hours, days, weeks

时间周期也可以进行数学运算,如:

years(2) + 10*days(1)
## [1] "2y 0m 10d 0H 0M 0S"

特别地,lubridate没有提供月份周期的函数,需要使用lubridate::period(num, units="month")生成月度周期,其中num是几个月的数值。

years(2) + 10*period(1, units="month")
## [1] "2y 10m 0d 0H 0M 0S"

B.5.2 时间区间

lubridate有三种方式构造时间区间,时间区间可以类比于一个实数区间,可以对其进行集合运算。第一种是使用%--%运算符构造时间区间:

dt1 <- ymd_hms("2017-11-28T14:02:00")
dt2 <- ymd_hms("2020-12-10T14:54:00")
intv <- (dt1 %--% dt2)
intv
## [1] 2017-11-28 14:02:00 UTC--2020-12-10 14:54:00 UTC

也可以使用interval函数来构造,如:

interval(dt1, dt2)
## [1] 2017-11-28 14:02:00 UTC--2020-12-10 14:54:00 UTC

还可以使用as.interval函数,如:

as.interval(weeks(1),start = dt1)
## [1] 2017-11-28 14:02:00 UTC--2017-12-05 14:02:00 UTC

注意,时间区间是一个左闭右开的区间,也就是结束端点并没有被计算在内,所以上面的时间区间虽然涉及到了8个日期,但时长确实7天。

int_startint_end可以分别取出时间区间的起始与结束端点:

int_start(intv)
## [1] "2017-11-28 14:02:00 UTC"
int_end(intv)
## [1] "2020-12-10 14:54:00 UTC"

%within%算符可以用于判断一个日期-时间是否位于时间区间内:

now() %within% intv
## [1] FALSE

int_shift可以平移一个时间区间:

int_shift(intv,by = ddays(3))
## [1] 2017-12-01 14:02:00 UTC--2020-12-13 14:54:00 UTC

int_flip可以反转一个时间区间:

int_flip(intv)
## [1] 2020-12-10 14:54:00 UTC--2017-11-28 14:02:00 UTC

int_length可以用于计算时间区间的长度,以秒计数:

int_length(intv)
## [1] 95734320

我们也可以使用除法计算时间长度,好处在于可以使用不同的单位:

intv/dyears(1)
## [1] 3.033638

int_overlaps用于判断两个时间区间是否相交,int_aligns用于判断两个时间区间是否有相同的端点:

intv2 <- int_shift(intv,by = ddays(3))
int_overlaps(intv, intv2)
## [1] TRUE
int_aligns(intv, intv2)
## [1] FALSE

以上便是R中日期-时间基本知识以及lubirdate包的常规操作,这些知识对于时间序列数据的处理至关重要。


  1. Coordinated Universal Time,是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林威治标准时间↩︎