\mainmatter
本节中我们将介绍在R中的日期与时间数据类型,以及如何通过lubridate包来处理日期与时间包来处理日期与时间。
R的基础包提供了三种日期-时间数据类型,采用POSIXt标准:
- Date用于处理日期,它是一个整数,储存了距离1970年1月1日的天数,早于1970年1月1日的日期被储存为负数,Date不包括时间和时区信息;
- POSIXct用于处理日期-时间,它也是一个整数,储存了以时间标准时间(UTC^[Coordinated Universal Time,是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林威治标准时间])时区为准的,从1970年1月1日开始计时的秒数;
- POSIXlt也用于处理日期-时间,但是它是将POSIXct储存在一个列表中。
lubridate
是tidyverse家族的成员,同样出自大神Hadley Wickham之手。如同tidyverse的其他成员一样,lubridate
包非常强大,它可以非常直观和灵活的处理日期与时间。lubridate
的所有函数都会返回POSIXct类型的日期-时间,缺省情况下,lubridate
使用UTC时区。lubridate
包括两类函数,一类用于处理时点数据(time instants),另一类则用于处理时段数据(time spans)
我们先加载lubridate包
library(lubridate)
函数today与now可以返回当前的日期和时间
today()
## [1] "2024-02-03"
now()
## [1] "2024-02-03 11:11:14 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
反过来,我们可以使用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
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"
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日。实践中,如果不小心,很容易在这里出问题。
时间周期可以解决上面的问题,例如
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"
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_start
和int_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
包的常规操作,这些知识对于时间序列数据的处理至关重要。