在本章中,我们将介绍lubridate包,它提供了方便的处理日期和时间的函数。
library(lubridate) library(dplyr) library(ggplot2) library(nycflights13)
2017年12月14日
在本章中,我们将介绍lubridate包,它提供了方便的处理日期和时间的函数。
library(lubridate) library(dplyr) library(ggplot2) library(nycflights13)
在tibble中有到3类日期时间变量,分别为:
<date> 日期<time> 时间<dttm> 日期+时间如果需要得到当前的日期和时间:
today()
[1] "2017-12-11"
now()
[1] "2017-12-11 15:17:51 CST"
CST:(美)中央时区(Central Standard Time)。
三种创建日期时间变量的方法:
ymd家族函数是最常用的创建日期时间变量的方法。根据字符串中年月日的排序,以同样的顺序安排y、m、d作为函数名。
ymd("2017-01-31")
[1] "2017-01-31"
ymd("17-01-31")
[1] "2017-01-31"
ymd("17 01 31")
[1] "2017-01-31"
ymd("170131")
[1] "2017-01-31"
mdy("01-31-17")
[1] "2017-01-31"
mdy("01/31/17")
[1] "2017-01-31"
dmy("31-Jan-2017")
[1] "2017-01-31"
ymd家族函数能够猜测一些不规则分隔的字符串。
ymd("201002-01", "201002-1", "20102-1")
[1] "2010-02-01" "2010-02-01" "2010-02-01"
dmy("0312-2010", "312-2010")
Warning: 1 failed to parse.
[1] "2010-12-03" NA
使用数值也可以创建日期时间变量。
ymd(20170131)
[1] "2017-01-31"
ymd家族函数与h、m、s合并使用可以创建日期-时间变量。
ymd_hms("2017-01-31 20:11:59")
[1] "2017-01-31 20:11:59 UTC"
mdy_hm("01/31/2017 08:01")
[1] "2017-01-31 08:01:00 UTC"
UTC:世界调整时间(Universal Time Coordinated)。
通过设置tz函数可以人为设置时区。
ymd(20170131, tz = "UTC")
[1] "2017-01-31 UTC"
使用make_date()可以创建日期变量,make_datetime()可以创建日期-时间变量。
flights %>% select(year, month, day, hour, minute) %>% mutate(departure = make_datetime(year, month, day, hour, minute))
# A tibble: 336,776 x 6
year month day hour minute departure
<int> <int> <int> <dbl> <dbl> <dttm>
1 2013 1 1 5 15 2013-01-01 05:15:00
2 2013 1 1 5 29 2013-01-01 05:29:00
3 2013 1 1 5 40 2013-01-01 05:40:00
4 2013 1 1 5 45 2013-01-01 05:45:00
5 2013 1 1 6 0 2013-01-01 06:00:00
6 2013 1 1 5 58 2013-01-01 05:58:00
7 2013 1 1 6 0 2013-01-01 06:00:00
8 2013 1 1 6 0 2013-01-01 06:00:00
9 2013 1 1 6 0 2013-01-01 06:00:00
10 2013 1 1 6 0 2013-01-01 06:00:00
# ... with 336,766 more rows
创建另外4个日期-时间变量。
flights_dt <- flights %>%
filter(!is.na(dep_time), !is.na(arr_time)) %>%
mutate(
dep_time = make_datetime(year, month, day, dep_time %/% 100,
dep_time %% 100),
arr_time = make_datetime(year, month, day, arr_time %/% 100,
arr_time %% 100),
sched_dep_time = make_datetime(year, month, day, sched_dep_time %/% 100,
sched_dep_time %% 100),
sched_arr_time = make_datetime(year, month, day, sched_arr_time %/% 100,
sched_arr_time %% 100)
) %>%
select(origin, dest, ends_with("delay"), ends_with("time"))
flights_dt
# A tibble: 328,063 x 9
origin dest dep_delay arr_delay dep_time
<chr> <chr> <dbl> <dbl> <dttm>
1 EWR IAH 2 11 2013-01-01 05:17:00
2 LGA IAH 4 20 2013-01-01 05:33:00
3 JFK MIA 2 33 2013-01-01 05:42:00
4 JFK BQN -1 -18 2013-01-01 05:44:00
5 LGA ATL -6 -25 2013-01-01 05:54:00
6 EWR ORD -4 12 2013-01-01 05:54:00
7 EWR FLL -5 19 2013-01-01 05:55:00
8 LGA IAD -3 -14 2013-01-01 05:57:00
9 JFK MCO -3 -8 2013-01-01 05:57:00
10 LGA ORD -2 8 2013-01-01 05:58:00
# ... with 328,053 more rows, and 4 more variables: sched_dep_time <dttm>,
# arr_time <dttm>, sched_arr_time <dttm>, air_time <dbl>
使用创建的日期-时间变量,可视化dep_time在年分布。
# 86400 s = 1 day qplot(dep_time, data = flights_dt, geom = "freqpoly", binwidth = 86400)
可视化dep_time在2013年1月2日的分布。
flights_plot <- flights_dt %>% filter(dep_time < ymd(20130102)) # 600 s = 10 minutes qplot(dep_time, data = flights_plot, geom = "freqpoly", binwidth = 600)
使用as_datetime()和as_date()做日期和时间日期的转换。
as_datetime(today())
[1] "2017-12-11 UTC"
as_date(now())
[1] "2017-12-11"
尝试转换无效日期时会发生什么?
ymd(c("2010-10-10", "bananas"))使用合适的函数转换下面的日期。
d1 <- "1 1, 2010"
d2 <- "2015-05-07"
d3 <- "06-06-2017"
d4 <- c("8 19 (2015)", "7 1 (2015)")
d5 <- "12/30/14" # Dec 30, 2014year()、month()、hour()、minute()、second()。wday()(一星期中的第几天)、mday()(一月中的第几天)、 yday()(一年中的第几天)。
datetime <- ymd_hms("2016-07-08 12:34:56")
year(datetime)
[1] 2016
month(datetime)
[1] 7
yday(datetime)
[1] 190
wday(datetime)
[1] 6
mday(datetime)
[1] 8
month()和wday()可以设置参数label = TRUE返回几月份和星期几的缩写,设置参数abbr = FALSE返回全称。
month(datetime, label = TRUE)
[1] Jul 12 Levels: Jan < Feb < Mar < Apr < May < Jun < Jul < Aug < Sep < ... < Dec
wday(datetime, label = TRUE, abbr = FALSE)
[1] Friday 7 Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < ... < Saturday
使用wday()可以看出工作日比休息日航班数量更多。
flights_plot <- flights_dt %>% mutate(wday = wday(dep_time, label = TRUE)) qplot(wday, data = flights_plot, geom = "bar")
观察不同分钟实际起飞航班的平均延迟,在20-30和50-60分钟起飞航班的平均延误比其他航班低很多。
dep <- flights_dt %>% mutate(minute = minute(dep_time)) %>%
group_by(minute) %>%
summarise(avg_delay = mean(arr_delay, na.rm = TRUE), n = n())
qplot(minute, avg_delay, data = dep, geom = "line")
有趣的是,如果观察不同分钟预计起飞航班的平均延误,并没有之前的模式。
sched_dep <- flights_dt %>% mutate(minute = minute(sched_dep_time)) %>%
group_by(minute) %>% summarise(avg_delay = mean(arr_delay, na.rm = TRUE), n = n())
qplot(minute, avg_delay, data = sched_dep, geom = "line")
为什么只有实际起飞时间的平均延误才能观察这个模式呢?与很多人为采集的数据一样,人为使飞机在“好”的时间起飞的倾向会造成偏差。当使用人为采集的数据时,需要注意这种模式。
qplot(minute, n, data = sched_dep, geom = "line")
另一种从日期时间变量中获取元素的方法是使用floor_date()、 round_date()、ceiling_date()将日期近似到临近时间。每个函数包含两个参数——日期时间向量和单位。
绘制每周的航班数量。
flights_plot <- flights_dt %>% count(week = floor_date(dep_time, "week")) qplot(week, n, data = flights_plot, geom = "line")
通过赋值可以设置时间日期变量中的元素。
datetime <- ymd_hms("2016-07-08 12:34:56")
(year(datetime) <- 2020)
[1] 2020
(month(datetime) <- 01)
[1] 1
(hour(datetime) <- hour(datetime) + 1)
[1] 13
也可以使用update()同时设置多个元素。
update(datetime, year = 2020, month = 2, mday = 2, hour = 2)
[1] "2020-02-02 02:34:56 UTC"
如果设置的元素值太大,会“滚动”。
ymd("2015-02-01") %>% update(mday = 30)
[1] "2015-03-02"
ymd("2015-02-01") %>% update(hour = 400)
[1] "2015-02-17 16:00:00 UTC"
使用update()将日期设置为常数,观察起飞时间的日分布。
flights_plot <- flights_dt %>% mutate(dep_hour = update(dep_time, yday = 1)) qplot(dep_hour, data = flights_plot, geom = "freqpoly", binwidth = 300)
dep_time - sched_dep_time和dep_delay的关系,可以一一对应吗,解释你的发现。air_time和到达时间与出发时间的间隔,解释你的发现。(提示:考虑机场位置)dep_time或sched_dep_time吗?为什么?日期时间变量可以进行算数运算,包括-、+、/。有三个表示时间跨度的类。
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)" [4] "259200s (~3 days)" "345600s (~4 days)" "432000s (~5 days)"
dweeks(3)
[1] "1814400s (~3 weeks)"
dyears(1)
[1] "31536000s (~52.14 weeks)"
duration以精确的秒数记录时间跨度。分钟、小时、天、周、年等更大的单位均转换为秒。
duration可以进行+、x运算。
2 * dyears(1)
[1] "63072000s (~2 years)"
dyears(1) + dweeks(12) + dhours(15)
[1] "38847600s (~1.23 years)"
可以从日期中加和减duration。
tomorrow <- today() + ddays(1) last_year <- today() - dyears(1)
因为duration由精确的秒数表示,有时会有意想不到的结果。
(one_pm <- ymd_hms("2016-03-12 13:00:00", tz = "America/New_York"))
[1] "2016-03-12 13:00:00 EST"
one_pm + ddays(1)
[1] "2016-03-13 14:00:00 EDT"
为什么是2016年3月12日下午1点后的一天,是2016年3月13日下午2点?如果仔细观察日期,会注意到时区发生改变。由于夏时制的原因,2016年3月12日只有23个小时。
为了解决上述问题,lubridate包提供了period。period以日常的时间对时间跨度进行计算,更符合日常认知。
one_pm
[1] "2016-03-12 13:00:00 EST"
one_pm + days(1)
[1] "2016-03-13 13:00:00 EDT"
与duration相同,可以通过构造函数生成period。
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" [5] "5m 0d 0H 0M 0S" "6m 0d 0H 0M 0S"
weeks(3)
[1] "21d 0H 0M 0S"
years(1)
[1] "1y 0m 0d 0H 0M 0S"
period可以进行+、x运算。
10 * (months(6) + days(1))
[1] "60m 10d 0H 0M 0S"
days(50) + hours(25) + minutes(2)
[1] "50d 25H 2M 0S"
将period与日期相加,与duration相比,更符合日常认知(避免闰年、夏令时的问题)。
ymd("2016-01-01") + dyears(1)
[1] "2016-12-31"
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"
使用period修正某些奇怪的航班时间,即一些飞机的到达时间要比起飞的时间早。
flights_dt %>% filter(arr_time < dep_time) %>% select(arr_time, dep_time)
# A tibble: 10,633 x 2
arr_time dep_time
<dttm> <dttm>
1 2013-01-01 00:03:00 2013-01-01 19:29:00
2 2013-01-01 00:29:00 2013-01-01 19:39:00
3 2013-01-01 00:08:00 2013-01-01 20:58:00
4 2013-01-01 01:46:00 2013-01-01 21:02:00
5 2013-01-01 00:25:00 2013-01-01 21:08:00
6 2013-01-01 00:16:00 2013-01-01 21:20:00
7 2013-01-01 00:06:00 2013-01-01 21:21:00
8 2013-01-01 00:26:00 2013-01-01 21:28:00
9 2013-01-01 00:20:00 2013-01-01 21:34:00
10 2013-01-01 00:25:00 2013-01-01 21:36:00
# ... with 10,623 more rows
由于生成起飞和到达时间时使用了相同的日期,而这些飞机实际是后一天到达的,需要在到达时间中加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>
dyears(1) / ddays(1)的结果是365。因为duration是用秒数表示的,一年的duration等于365天的duration。years(1) / days(1)的结果如果是2015年,为365;如果是2016年,为366。由于没有年份信息,给出估计值并警告(warning)。
years(1) / days(1)
estimate only: convert to intervals for accuracy
[1] 365.25
如果要更精确计算,需要用interval。interval是记录了起始时间的时间跨度。
next_year <- today() + years(1) today() %--% next_year
[1] 2017-12-12 UTC--2018-12-12 UTC
(today() %--% next_year) / ddays(1)
[1] 365
使用整数除法计算interval包含多少period。
(today() %--% next_year) %/% days(1)
Note: method with signature 'Timespan#Timespan' chosen for function '%/%', target signature 'Interval#Period'. "Interval#ANY", "ANY#Period" would also be valid
[1] 365
如何选择duration、period、interval?选择能够解决问题的最简单的类。如果只关心实际时间,使用duration; 如果需要日常认知的时间,使用period;如果需要知道时间跨度有多少个日常单位(月、日),使用interval。 数据类型类型与允许使用的算数运算。 
months()而没有dmonths()?Sys.timezone()可以查看R中的当前时区。
Sys.timezone()
[1] "Asia/Taipei"
时区是日期-时间变量是一个属性。以下三个对象表示相同的时间。
(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(格林尼治标准时间)。UTC没有夏时制,因此计算也更加方便。
如果使用c()等,会丢掉对象原先的时区,而显示在本地时区。
x4 <- c(x1, x2, x3) x4
[1] "2015-06-02 CST" "2015-06-02 CST" "2015-06-02 CST"