R 数据处理(十七)—— lubridate
前言
我们接着上节未讲完的日期/时间处理
4. 时间跨度
接下来,我们将介绍日期算术运算。包括加法、减法和除法。
在这里,我们要先了解三个代表时间跨度的类:
-
durations
: 持续时间,表示精确的秒数 -
periods
: 周期,代表有单位的日期,如几周和几个月 -
intervals
: 区间,代表起点和终点的区间
4.1 periods
在 R
中,当两个日期相减时,将得到一个 difftime
对象
> h_age <- today() - ymd(19791014)
> h_age
Time difference of 15082 days
difftime
类对象记录了秒,分钟,小时,天或周的时间跨度
这种模糊性会使 difftime
有点难以处理,因此 lubridate
提供了一种始终用秒来表示的方法:duration
。
> as.duration(h_age)
[1] "1303084800s (~41.29 years)"
duration
有许多方便的构造函数
> dseconds(15)
[1] "15s"
> dminutes(10)
[1] "600s (~10 minutes)"
> dhours(c(12, 24))
[1] "43200s (~12 hours)" "86400s (~1 days)"
> ddays(0:5)
[1] "0s" "86400s (~1 days)" "172800s (~2 days)" "259200s (~3 days)" "345600s (~4 days)"
[6] "432000s (~5 days)"
> dweeks(3)
[1] "1814400s (~3 weeks)"
> dyears(1)
[1] "31557600s (~1 years)"
duration
始终以秒为单位来记录时间,可以通过传入分钟、小时、天、周和年等单位来创建较大的单位。
你可以使用加法或乘法
> 2 * dyears(1)
[1] "63115200s (~2 years)"
> dyears(1) + dweeks(12) + dhours(15)
[1] "38869200s (~1.23 years)"
对天的加法减法
tomorrow <- today() + ddays(1)
last_year <- today() - dyears(1)
但是由于 duration
代表的是精确的时间,因此有时可能会得到意外的结果
> one_pm <- ymd_hms("2016-03-12 13:00:00", tz = "America/New_York")
> one_pm
[1] "2016-03-12 13:00:00 EST"
> one_pm + ddays(1)
[1] "2016-03-13 14:00:00 EDT"
为什么会变成下午两点呢?你可以注意到了,时区已经变了。
由于 DST
的原因,3
月 12
日只有 23
个小时,因此,如果加上一天的秒数,我们将得到不同的时间。
4.2 Periods
为了解决这个问题,lubridate
提供了 periods
,它是一个时间跨度,但是没有固定的秒数。
而是人为定义的时间单位,如天或月。
> one_pm
[1] "2016-03-12 13:00:00 EST"
> one_pm + days(1)
[1] "2016-03-13 13:00:00 EDT"
类似 duration
,也有许多函数用于创建 periods
> seconds(15)
[1] "15S"
> minutes(10)
[1] "10M 0S"
> hours(c(12, 24))
[1] "12H 0M 0S" "24H 0M 0S"
> days(7)
[1] "7d 0H 0M 0S"
> months(1:6)
[1] "1m 0d 0H 0M 0S" "2m 0d 0H 0M 0S" "3m 0d 0H 0M 0S" "4m 0d 0H 0M 0S" "5m 0d 0H 0M 0S"
[6] "6m 0d 0H 0M 0S"
> weeks(3)
[1] "21d 0H 0M 0S"
> years(1)
[1] "1y 0m 0d 0H 0M 0S"
对 periods
进行加法和乘法
> 10 * (months(6) + days(1))
[1] "60m 10d 0H 0M 0S"
> days(50) + hours(25) + minutes(2)
[1] "50d 25H 2M 0S"
与 durations
相比较,periods
更符合我们的预期
> ymd("2016-01-01") + dyears(1)
[1] "2016-12-31 06:00:00 UTC"
> ymd("2016-01-01") + years(1)
[1] "2017-01-01"
> one_pm + ddays(1)
[1] "2016-03-13 14:00:00 EDT"
> one_pm + days(1)
[1] "2016-03-13 13:00:00 EDT"
来让我用 periods
来解决与航班日期有关的问题。有些飞机在离开纽约市之前似乎已经到达目的地
> flights_dt %>%
+ filter(arr_time < dep_time)
# A tibble: 10,633 x 9
origin dest dep_delay arr_delay dep_time sched_dep_time arr_time
<chr> <chr> <dbl> <dbl> <dttm> <dttm> <dttm>
1 EWR BQN 9 -4 2013-01-01 19:29:00 2013-01-01 19:20:00 2013-01-01 00:03:00
2 JFK DFW 59 NA 2013-01-01 19:39:00 2013-01-01 18:40:00 2013-01-01 00:29:00
3 EWR TPA -2 9 2013-01-01 20:58:00 2013-01-01 21:00:00 2013-01-01 00:08:00
4 EWR SJU -6 -12 2013-01-01 21:02:00 2013-01-01 21:08:00 2013-01-01 01:46:00
5 EWR SFO 11 -14 2013-01-01 21:08:00 2013-01-01 20:57:00 2013-01-01 00:25:00
6 LGA FLL -10 -2 2013-01-01 21:20:00 2013-01-01 21:30:00 2013-01-01 00:16:00
7 EWR MCO 41 43 2013-01-01 21:21:00 2013-01-01 20:40:00 2013-01-01 00:06:00
8 JFK LAX -7 -24 2013-01-01 21:28:00 2013-01-01 21:35:00 2013-01-01 00:26:00
9 EWR FLL 49 28 2013-01-01 21:34:00 2013-01-01 20:45:00 2013-01-01 00:20:00
10 EWR FLL -9 -14 2013-01-01 21:36:00 2013-01-01 21:45:00 2013-01-01 00:25:00
# … with 10,623 more rows, and 2 more variables: sched_arr_time <dttm>, air_time <dbl>
这些是夜间航班。起飞和到达时间使用了相同的日期信息,但这些航班是在第二天到达的。
我们可以通过在每个夜间航班的到达时间上加上 days(1)
来解决这个问题
flights_dt <- flights_dt %>%
mutate(
overnight = arr_time < dep_time,
arr_time = arr_time + days(overnight * 1),
sched_arr_time = sched_arr_time + days(overnight * 1)
)
现在,我们所有的飞行都遵循物理定律
> flights_dt %>%
+ filter(overnight, arr_time < dep_time)
# A tibble: 0 x 10
# … with 10 variables: origin <chr>, dest <chr>, dep_delay <dbl>, arr_delay <dbl>, dep_time <dttm>,
# sched_dep_time <dttm>, arr_time <dttm>, sched_arr_time <dttm>, air_time <dbl>, overnight <lgl>
4.3 intervals
dyears(1) / ddays(365)
应该返回的是 1
,因此 durations
总是表示秒数,而一年表示为 365
天的秒数
那 years(1) / days(1)
返回的是什么?如果是 2015
年返回的是 365
,而 2016
返回的是 366
对于 lubridate
来说,当没有足够的信息来给出一个明确的答案。它会给出一个估计值
> years(1) / days(1)
[1] 365.25
如果你想要更精确的值,可以使用 interval
。
interval
是一个有起点的 durations
,因此您可以准确地确定它的持续时间
> next_year <- today() + years(1)
> (today() %--% next_year) / ddays(1)
[1] 365
要找出间隔中有多少个周期,您需要使用整数除法
> (today() %--% next_year) %/% days(1)
[1] 365
4.4 总结
如何在持续时间、周期和间隔之间进行选择?一如既往,选择最简单的数据结构来解决您的问题。
- 如果您只关心物理时间,使用持续时间;
- 如果需要增加人为定义的时间,使用周期;
- 如果需要计算人为定义的单位长度的跨度,使用一个间隔。
4.5 思考练习
-
创建一个向量,存储了
2015
年每月的第一天。创建一个包含本年度每月的第一天的日期向量。 -
编写一个函数,给定您的生日(以日期为单位),返回您的年龄(岁)。
5. 时区
获取当前时区
> Sys.timezone()
[1] "Asia/Shanghai"
使用 OlsonNames()
查看所有时区名称的完整列表:
> length(OlsonNames())
[1] 593
> head(OlsonNames())
[1] "Africa/Abidjan" "Africa/Accra" "Africa/Addis_Ababa" "Africa/Algiers"
[5] "Africa/Asmara" "Africa/Asmera"
在 R
中,时区仅仅是控制打印日期时间的属性。
例如,下面这三个对象表示同一时间点
> (x1 <- ymd_hms("2015-06-01 12:00:00", tz = "America/New_York"))
[1] "2015-06-01 12:00:00 EDT"
> (x2 <- ymd_hms("2015-06-01 18:00:00", tz = "Europe/Copenhagen"))
[1] "2015-06-01 18:00:00 CEST"
> (x3 <- ymd_hms("2015-06-02 04:00:00", tz = "Pacific/Auckland"))
[1] "2015-06-02 04:00:00 NZST"
您可以使用减法验证它们是否是同一时间
> x1 - x2
Time difference of 0 secs
> x1 - x3
Time difference of 0 secs
除非另有说明,否则 lubridate
始终使用 UTC
。
UTC
(协调世界时)是科学界使用的标准时区,大致相当于其前身 GMT
(格林威治标准时间)
> x4 <- c(x1, x2, x3)
> x4
[1] "2015-06-01 12:00:00 EDT" "2015-06-01 12:00:00 EDT" "2015-06-01 12:00:00 EDT"
你可以通过两种方式来更改时区
- 保持时间不变,只更改其显示方式。即时间正确但您想要更自然的显示时,可以使用此功能
> x4a <- with_tz(x4, tzone = "Australia/Lord_Howe")
> x4a
[1] "2015-06-02 02:30:00 +1030" "2015-06-02 02:30:00 +1030" "2015-06-02 02:30:00 +1030"
> x4a - x4
Time differences in secs
[1] 0 0 0
- 及时更改基础时刻。当您的时间被标记为不正确的时区,并且需要修复时,可以请使用此选项
> x4b <- force_tz(x4, tzone = "Australia/Lord_Howe")
> x4b
[1] "2015-06-01 12:00:00 +1030" "2015-06-01 12:00:00 +1030" "2015-06-01 12:00:00 +1030"
> x4b - x4
Time differences in hours
[1] -14.5 -14.5 -14.5