《R for Data Science (2e)》免费中文翻译 (第17章) --- Dates and times(1)

写在前面

本系列推文为《R for Data Science (2)》的中文翻译版本。所有内容都通过开源免费的方式上传至Github,欢迎大家参与贡献,详细信息见:
Books-zh-cn 项目介绍:
Books-zh-cn:开源免费的中文书籍社区
r4ds-zh-cn Github 地址:
https://github.com/Books-zh-cn/r4ds-zh-cn
r4ds-zh-cn 网站地址:
https://books-zh-cn.github.io/r4ds-zh-cn/


目录

  • 17.1 介绍

  • 17.2 创建日期/时间

17.1 介绍

本章将向你展示如何在 R 中处理日期和时间。 乍看之下,日期和时间似乎很简单。 你在日常生活中经常使用它们,而且它们似乎不会引起太多困惑。 然而,你对日期和时间了解得越多,就越会发现它们的复杂之处!

先来思考一下:一年有多少天,一天有多少小时。 你或许记得大多数年份有 365 天,但闰年有 366 天。 你知道判断闰年的完整规则吗? 一天的小时数则稍不明显:大多数日子有 24 小时,但在使用夏令时(DST)的地区,每年会有一天只有 23 小时,另一天则有 25 小时。

日期和时间之所以复杂,是因为它们必须协调两种自然现象(地球自转和绕太阳公转)与一系列地缘政治现象(包括月份、时区和夏令时)。 本章不会涵盖日期和时间的每一个细节,但会为你打下扎实的实践技能基础,帮助你应对常见的数据分析挑战。

我们将首先展示如何从不同输入创建日期时间,然后介绍在获得日期时间后如何提取年、月、日等组成部分。 接着,我们将深入探讨处理时间跨度的棘手主题,根据不同的使用目标,时间跨度有多种表现形式。 最后,我们将简要讨论时区带来的额外挑战。

17.1.1 先决条件

本章将重点介绍 lubridate 包,它能让你在 R 中更轻松地处理日期和时间。 根据 tidyverse 的最新版本,lubridate 已成为核心 tidyverse 的一部分。 我们还需要 nycflights13 包来提供练习数据。

复制代码
library(tidyverse)
library(nycflights13)

17.2 创建日期/时间

有三种类型的日期/时间数据用于指代特定时刻:

  • A date 。 Tibbles 会将其显示为 <date>

  • A time within a day。 Tibbles 会将其显示为 <time>

  • A date-time 是日期加上时间: 它能唯一标识某个时刻(通常精确到秒)。 Tibbles 会将其显示为 <dttm>。 Base R 将其称为 POSIXct, 但这个名称并不直观.

本章我们将重点关注日期和日期时间,因为 R 没有原生类来存储时间。 如果你需要处理时间,可以使用 hms 包。

你应始终使用能满足需求的最简单数据类型。 这意味着如果可以用 date 代替 date-time,就应该使用 date。 Date-times 要复杂得多,因为需要处理时区问题,我们将在本章末尾讨论这一点。

要获取当前 date 或 date-time,可以使用 today()now()

复制代码
today()
#> [1] "2025-12-23"
now()
#> [1] "2025-12-23 23:18:35 UTC"

除此之外,以下部分将介绍四种常见的创建 date/time 的方法:

  • 使用 readr 读取文件时。

  • 从字符串解析。

  • 从独立的 date-time 组件组合。

  • 从已有的 date/time 对象转换。

17.2.1 导入期间

如果你的 CSV 文件包含 ISO8601 格式的 date 或 date-time,你无需进行额外操作;readr 会自动识别它:

复制代码
csv <- "
  date,datetime
  2022-01-02,2022-01-02 05:12
"
read_csv(csv)
#> # A tibble: 1 × 2
#>   date       datetime           
#>   <date>     <dttm>             
#> 1 2022-01-02 2022-01-02 05:12:00

如果你之前没听说过 ISO8601 ,它是一种国际标准,用于书写日期,其日期组成部分按从大到小的顺序排列,并用 - 分隔。 例如,在 ISO8601 标准中,2022 年 5 月 3 日写作 2022-05-03。ISO8601 日期也可以包含时间,其中时、分、秒用 : 分隔,而日期和时间部分则用 T 或空格分隔。 例如,2022 年 5 月 3 日下午 4:26 可以写作 2022-05-03 16:262022-05-03T16:26

对于其他日期时间格式,你需要使用 col_types 以及 col_date()col_datetime(),并指定日期时间格式。 readr 使用的日期时间格式是多种编程语言通用的标准,它用 % 后跟一个字符来描述日期组成部分。 例如,%Y-%m-%d 指定了一个日期格式为年、-、月(数字形式)、-、日。 表 Table 17.1 列出了所有选项。

Table 17.1: readr 能理解的所有日期格式

Type Code Meaning Example
Year %Y 4 digit year 2021
%y 2 digit year 21
Month %m Number 2
%b Abbreviated name Feb
%B Full name February
Day %d Two digits 02
%e One or two digits 2
Time %H 24-hour hour 13
%I 12-hour hour 1
%p AM/PM pm
%M Minutes 35
%S Seconds 45
%OS Seconds with decimal component 45.35
%Z Time zone name America/Chicago
%z Offset from UTC +0800
Other %. Skip one non-digit :
%* Skip any number of non-digits

这段代码显示了一些应用于一个非常模糊的日期的选项:

复制代码
csv <- "
  date
  01/02/15
"

read_csv(csv, col_types = cols(date = col_date("%m/%d/%y")))
#> # A tibble: 1 × 1
#>   date      
#>   <date>    
#> 1 2015-01-02

read_csv(csv, col_types = cols(date = col_date("%d/%m/%y")))
#> # A tibble: 1 × 1
#>   date      
#>   <date>    
#> 1 2015-02-01

read_csv(csv, col_types = cols(date = col_date("%y/%m/%d")))
#> # A tibble: 1 × 1
#>   date      
#>   <date>    
#> 1 2001-02-15

请注意,无论你如何指定日期格式,一旦将其导入 R,它总是以相同的方式显示。

如果你使用 %b%B 并处理非英语日期,还需要提供 locale() 参数。 可查看 date_names_langs() 中内置的语言列表,或使用 date_names() 创建自定义语言设置。

17.2.2 从字符串

日期时间规范语言功能强大,但需要仔细分析日期格式。 另一种方法是使用 lubridate 的辅助函数,它们能在你指定组成部分的顺序后自动判断格式。 使用时,只需识别你的日期中年、月、日出现的顺序,然后按相同顺序排列 "y"、"m" 和 "d"。 即可得到能解析该日期的 lubridate 函数名称。 例如:

复制代码
ymd("2017-01-31")
#> [1] "2017-01-31"
mdy("January 31st, 2017")
#> [1] "2017-01-31"
dmy("31-Jan-2017")
#> [1] "2017-01-31"

ymd() 等创建 data。 要创建 date-time,请在解析函数的名称中添加下划线和一个或多个 "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"

你也可以通过提供时区来强制从 date 创建 date-time:

复制代码
ymd("2017-01-31", tz = "UTC")
#> [1] "2017-01-31 UTC"

这里我使用了 UTC 时区,你可能也知道它被称为 GMT(格林威治标准时间),即经度 0° 的时间。该时区不采用夏令时,这使得计算更为简便。

17.2.3 从各个组件

有时,你会遇到 date-time 的各个组成部分分散在多个列中,而不是单个字符串。flights 数据集中的情况正是如此:

复制代码
flights |> 
  select(year, month, day, hour, minute)
#> # A tibble: 336,776 × 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
#> # ℹ 336,770 more rows

要从这类输入创建 date/time,请对 dates 使用 make_date(),对 date-times 使用 make_datetime()

复制代码
flights |> 
  select(year, month, day, hour, minute) |> 
  mutate(departure = make_datetime(year, month, day, hour, minute))
#> # A tibble: 336,776 × 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
#> # ℹ 336,770 more rows

让我们对 flights 中的四个时间列进行同样的处理。 时间的表示格式有些特殊,因此我们使用模运算来提取小时和分钟部分。 创建 date-time 变量后,我们将重点关注本章后续部分要探索的变量。

复制代码
make_datetime_100 <- function(year, month, day, time) {
  make_datetime(year, month, day, time %/% 100, time %% 100)
}

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 × 9
#>   origin dest  dep_delay arr_delay dep_time            sched_dep_time     
#>   <chr>  <chr>     <dbl>     <dbl> <dttm>              <dttm>             
#> 1 EWR    IAH           2        11 2013-01-01 05:17:00 2013-01-01 05:15:00
#> 2 LGA    IAH           4        20 2013-01-01 05:33:00 2013-01-01 05:29:00
#> 3 JFK    MIA           2        33 2013-01-01 05:42:00 2013-01-01 05:40:00
#> 4 JFK    BQN          -1       -18 2013-01-01 05:44:00 2013-01-01 05:45:00
#> 5 LGA    ATL          -6       -25 2013-01-01 05:54:00 2013-01-01 06:00:00
#> 6 EWR    ORD          -4        12 2013-01-01 05:54:00 2013-01-01 05:58:00
#> # ℹ 328,057 more rows
#> # ℹ 3 more variables: arr_time <dttm>, sched_arr_time <dttm>, ...

利用这些数据,我们可以可视化一年中起飞时间的分布情况:

复制代码
flights_dt |> 
  ggplot(aes(x = dep_time)) + 
  geom_freqpoly(binwidth = 86400) # 86400 seconds = 1 day

或者在一天之内:

复制代码
flights_dt |> 
  filter(dep_time < ymd(20130102)) |> 
  ggplot(aes(x = dep_time)) + 
  geom_freqpoly(binwidth = 600) # 600 s = 10 minutes

请注意,当您在数字上下文中(如直方图中)使用 date-times 时,1 表示 1 秒,因此 binwidth 为 86400 表示一天。 对于 dates,1 表示 1 天。

17.2.4 从其他类型

你可能需要在 date-time 和 date 之间进行转换。 这可以通过 as_datetime()as_date() 函数来实现:

复制代码
as_datetime(today())
#> [1] "2025-12-23 UTC"
as_date(now())
#> [1] "2025-12-23"

有时你会遇到以数字偏移量形式表示的 date/times,其基准是 "Unix Epoch",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"

17.2.5 练习

  1. 如果解析包含无效日期的字符串会发生什么?

    复制代码
    ymd(c("2010-10-10", "bananas"))
  2. today() 函数中的 tzone 参数有什么作用? 为什么它很重要?

  3. 针对以下每个日期时间,请分别说明如何使用 readr 的列规范解析以及 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
    t1 <- "1705"
    t2 <- "11:15:10.12 PM"

  1. 如果能被 4 整除,那就是闰年,除非它能被 100 整除,除非它能被 400 整除。换句话说,在每一组 400 年里,有 97 个闰年。

  2. https://xkcd.com/1179/

  3. 你可能会好奇 UTC 代表什么。它是英文 "Coordinated Universal Time" 与法文 "Temps Universel Coordonné" 之间的折中简称。

  4. 无需猜测是哪个国家提出了这个经度系统。

--------------- 未完待续 ---------------

本期翻译贡献:

  • @TigerZ生信宝库
相关推荐
杜子不疼.1 天前
计算机视觉热门模型手册:Faster R-CNN / YOLO / SAM 技术原理 + 应用场景对比
人工智能·计算机视觉·r语言·cnn
Davina_yu2 天前
Windows 下升级 R 语言至最新版
开发语言·windows·r语言
青春不败 177-3266-05202 天前
基于R语言生物信息学大数据分析与绘图技术应用
数据分析·r语言·生物信息·生信·高通量
Catherinemin2 天前
【R语言】2.注释&基础运算
开发语言·r语言
邢博士谈科教3 天前
TCGA单基因高低分组的差异分析后的GSEA-GO和KEGG富集分析教程
数据分析·r语言
Catherinemin3 天前
【R语言】1.安装&基础语法
开发语言·r语言
Tiger Z4 天前
R 语言科研绘图 --- 3D绘图-汇总1
r语言·论文·科研·绘图·研究生
kisshuan123964 天前
黄芪属植物物种识别与分类:基于 Faster R-CNN C4 模型的深度学习实现
深度学习·分类·r语言
七夜zippoe5 天前
Python多进程编程实战:彻底突破GIL限制的完整指南
python·编程·多进程·process·gil