R 数据处理(十六)—— lubridate
1. 前言
本节我们将介绍 R
中的时间和日期的处理。乍一看,你可能觉得这很简单。
让我们来看看下面三个问题:
- 每年都是
365
天? - 每天都有
24
小时? - 每分钟是
60
秒?
当然,你肯定知道一年不一定都是 365
天,但是你知道哪一年是闰年吗?
世界上很多地方都使用夏令时,所以有些日子是 23
小时,有些日子是 25
小时。
你可能不知道有些分钟有 61
秒,因为地球自转正逐渐减慢,时不时地增加闰秒。
有时候,时间和日期是很困难的。因为需要协调地球的自转和公转与地区的月份、时区、夏令时之间的关系。
下面我们将详细介绍如何处理这些数据
1.1 导入
本节的重点是使用 lubridate
包来处理 R
中的日期和时间。
lubridate
不是核心的 tidyverse
包,所以需要手动导入。并利用 nycflights13
作为例子数据来练习
library(tidyverse)
library(lubridate)
library(nycflights13)
2. 创建时间/日期
总共有 3 种形式的日期/时间数据
-
date
:tibble
中标题的<date>
-
time
: 一天内的时间,tibble
中标题的<time>
-
date-time
: 日期+时间,唯一标识时间点,tibble
中标题的<dttm>
本节我们只关注日期和日期+时间,并尽可能使其简单化,如果能用日期就不用日期+时间
要获取当前的日期和日期+时间,可以
> today()
[1] "2021-01-27"
> now()
[1] "2021-01-27 19:48:46 CST"
此外,有三种方法创建日期/时间
- 根据字符串创建
- 单个日期-时间组件
- 现存的日期-时间
它们的工作方式如下
2.1 字符串
日期/时间通常以字符串的形式出现,一种方式我们已经在前面的 readr
中介绍过了,另一种方法是使用 lubridate
包提供的工具
通过制定字符串中年(y
)、月(m
)、日(d
)的出现顺序,会自动转换为日期格式。
> ymd("2017-01-31")
[1] "2017-01-31"
> mdy("January 31st, 2017")
[1] "2017-03-01"
> dmy("31-Jan-2017")
[1] "2017-01-31"
这些函数也可以接受不带引号的数字
> ymd(20170131)
[1] "2017-01-31"
想要创建日期时间的话,可以在 ymd
(mdy
、dmy
等) 后面添加一个下划线以及一个或多个 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"
还可以添加时区
> ymd(20170131, tz = "UTC")
[1] "2017-01-31 UTC"
2.2 单个组件
有时候我们的数据会像 flights
表一样,日期时间是分开的
> flights %>%
+ select(year, month, day, hour, minute)
# A tibble: 336,776 x 5
year month day hour minute
<int> <int> <int> <dbl> <dbl>
1 2013 1 1 5 15
2 2013 1 1 5 29
3 2013 1 1 5 40
4 2013 1 1 5 45
5 2013 1 1 6 0
6 2013 1 1 5 58
7 2013 1 1 6 0
8 2013 1 1 6 0
9 2013 1 1 6 0
10 2013 1 1 6 0
# … with 336,766 more rows
对于这种数据,可以使用 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
让我们对 flights
剩下的其他四列时间进行格式化
> flights_dt <- flights %>%
+ filter(!is.na(dep_time), !is.na(arr_time)) %>%
+ mutate(
+ dep_time = make_datetime_100(year, month, day, dep_time),
+ arr_time = make_datetime_100(year, month, day, arr_time),
+ sched_dep_time = make_datetime_100(year, month, day, sched_dep_time),
+ sched_arr_time = make_datetime_100(year, month, day, sched_arr_time)
+ ) %>%
+ 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 sched_dep_time arr_time
<chr> <chr> <dbl> <dbl> <dttm> <dttm> <dttm>
1 EWR IAH 2 11 2013-01-01 05:17:00 2013-01-01 05:15:00 2013-01-01 08:30:00
2 LGA IAH 4 20 2013-01-01 05:33:00 2013-01-01 05:29:00 2013-01-01 08:50:00
3 JFK MIA 2 33 2013-01-01 05:42:00 2013-01-01 05:40:00 2013-01-01 09:23:00
4 JFK BQN -1 -18 2013-01-01 05:44:00 2013-01-01 05:45:00 2013-01-01 10:04:00
5 LGA ATL -6 -25 2013-01-01 05:54:00 2013-01-01 06:00:00 2013-01-01 08:12:00
6 EWR ORD -4 12 2013-01-01 05:54:00 2013-01-01 05:58:00 2013-01-01 07:40:00
7 EWR FLL -5 19 2013-01-01 05:55:00 2013-01-01 06:00:00 2013-01-01 09:13:00
8 LGA IAD -3 -14 2013-01-01 05:57:00 2013-01-01 06:00:00 2013-01-01 07:09:00
9 JFK MCO -3 -8 2013-01-01 05:57:00 2013-01-01 06:00:00 2013-01-01 08:38:00
10 LGA ORD -2 8 2013-01-01 05:58:00 2013-01-01 06:00:00 2013-01-01 07:53:00
# … with 328,053 more rows, and 2 more variables: sched_arr_time <dttm>, air_time <dbl>
然后,我们可以看看全年的出发时间的分布
> flights_dt %>%
+ ggplot(aes(dep_time)) +
+ geom_freqpoly(binwidth = 86400) # 86400 seconds = 1 day
image.png
也可以查看一天内的出发情况
> flights_dt %>%
+ filter(dep_time < ymd(20130102)) %>%
+ ggplot(aes(dep_time)) +
+ geom_freqpoly(binwidth = 600) # 600 s = 10 minutes
image.png
注意:在日期中 1
表示一天,在时间中 1
表示一秒,86400
秒为一天
2.3 其他类型
有时你可能需要自日期-时间和日期之间相互切换,可以使用 as_datetime()
和 as_date()
> as_datetime(today())
[1] "2021-01-27 UTC"
> as_date(now())
[1] "2021-01-27"
如果想获取相对于 1970-01-01
的偏移的日期/时间,如果偏移是秒,使用 as_datetime()
,如果是天,使用 as_date()
> as_datetime(60 * 60 * 10)
[1] "1970-01-01 10:00:00 UTC"
> as_date(365 * 10 + 2)
[1] "1980-01-01"
2.4 思考练习
- 如果解析包含无效日期的字符串会怎样?
ymd(c("2010-10-10", "bananas"))
-
tzone
参数对today()
有什么作用? -
使用相应的
lubridate
函数来解析以下每个日期
d1 <- "January 1, 2010"
d2 <- "2015-Mar-07"
d3 <- "06-Jun-2017"
d4 <- c("August 19 (2015)", "July 1 (2015)")
d5 <- "12/30/14" # Dec 30, 2014
3. 日期-时间组件
这一部分的重点是介绍访问器函数,用于获取或设置日期-时间组件。
3.1 获取组件
你可以使用下面的访问器函数来获取日期时间组件:
year()
month()
-
mday()
月份的天, -
yday()
一年中的天, -
wday()
星期, hour()
minute()
second()
> datetime <- ymd_hms("2020-07-08 12:34:56")
> year(datetime)
[1] 2020
> month(datetime)
[1] 7
> mday(datetime)
[1] 8
> yday(datetime)
[1] 190
> wday(datetime)
[1] 4
对于 month()
和 wday()
,您可以设置 label = TRUE
来返回一个月或星期的缩写名称。
设置 abbr = FALSE
返回全名
> month(datetime, label = TRUE)
[1] 7
Levels: 1 < 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < 11 < 12
> wday(datetime, label = TRUE, abbr = FALSE)
[1] 星期三
Levels: 星期日 < 星期一 < 星期二 < 星期三 < 星期四 < 星期五 < 星期六
我们可以使用 wday()
来查看一周的航班
> flights_dt %>%
+ mutate(wday = wday(dep_time, label = TRUE)) %>%
+ ggplot(aes(x = wday)) +
+ geom_bar() +
+ theme(text = element_text(family='Kai')) # 显示中文
image.png
如果我们看一小时内每分钟的平均延迟,会看到一个有趣的现象。
> flights_dt %>%
+ mutate(minute = minute(dep_time)) %>%
+ group_by(minute) %>%
+ summarise(
+ avg_delay = mean(arr_delay, na.rm = TRUE),
+ n = n()) %>%
+ ggplot(aes(minute, avg_delay)) +
+ geom_line()
`summarise()` ungrouping output (override with `.groups` argument)
image.png
我们可以看到,在 20-30
分钟和 50-60
分钟起飞的航班延误要比其他时间少得多
有趣的是,如果我们看预计出发时间,则没有这样的模式
> sched_dep <- flights_dt %>%
+ mutate(minute = minute(sched_dep_time)) %>%
+ group_by(minute) %>%
+ summarise(
+ avg_delay = mean(arr_delay, na.rm = TRUE),
+ n = n())
`summarise()` ungrouping output (override with `.groups` argument)
> #> `summarise()` ungrouping output (override with `.groups` argument)
>
> ggplot(sched_dep, aes(minute, avg_delay)) +
+ geom_line()
image.png
3.2 四舍五入
类似于浮点数,我们可以使用 floor_date()
,round_date()
和 ceiling_date()
将日期四舍五入到附近的时间单位
> flights_dt %>%
+ count(week = floor_date(dep_time, "week")) %>%
+ ggplot(aes(week, n)) +
+ geom_line()
3.3 设置组件
您还可以使用每个访问器函数来设置日期/时间的组成部分
> (datetime <- ymd_hms("2010-10-08 12:34:56"))
[1] "2010-10-08 12:34:56 UTC"
> year(datetime) <- 2020
> datetime
[1] "2020-10-08 12:34:56 UTC"
> month(datetime) <- 01
> datetime
[1] "2020-01-08 12:34:56 UTC"
> hour(datetime) <- hour(datetime) + 1
> datetime
[1] "2020-01-08 13:34:56 UTC"
此外,您可以使用 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_dt %>%
+ mutate(dep_hour = update(dep_time, yday = 1)) %>%
+ ggplot(aes(dep_hour)) +
+ geom_freqpoly(binwidth = 300)
image.png
3.4 思考练习
-
一天中的飞行时间分布在一年中如何变化
-
将
air_time
与出发和到达之间的持续时间进行比较,解释您的发现(提示:考虑机场的位置)。 -
平均延迟时间在一天中如何变化?您应该使用
dep_time
还是sched_dep_time
?为什么? -
如果想最大程度地减少飞机晚点的情况,应该在一周中的哪一天离开?