美文网首页
11.日期和时间

11.日期和时间

作者: denghb001 | 来源:发表于2022-01-07 09:22 被阅读0次

    1 简介

    如何在 R 中使用日期和时间呢?你可能觉得日期和时间似乎很简单。我们在日常生活中一直在使用它们,它们似乎不会引起太多混乱。但当你对日期和时间了解得越多,它们似乎就越复杂。下面我们来看看这三个简单的问题:

    • 每年有365天吗?
    • 每天有24小时吗?
    • 每分钟有60秒吗?

    我们都知道不是每年都有 365 天,但是你知道如何确定闰年的规则吗?(它分为三个部分)。您也许还记得世界上很多地方都使用夏令时 (DST),因此有些日子有 23 小时,有些日子有 25 小时。您可能不知道有些分钟有 61 秒,因为地球的自转正在逐渐减慢,所以不时添加闰秒。

    日期和时间很难计算,因为它们必须将两种物理现象(地球自转及其绕太阳公转)与月份、时区和夏令时在内的大量现象协调起来。下面关于日期和时间的处理,会帮助你解决常见的数据分析所面临的问题。

    1.1 加载包

    下面将介绍lubridate包,它可以更轻松地在 R 中处理日期和时间。 lubridate 不是 tidyverse 的核心部分,因为只有在处理日期/时间时才需要它。我们还需要 nycflights13 来获取练习的数据。

    library(tidyverse)
    
    library(lubridate)
    library(nycflights13)
    

    2 日期/时间的创建

    有三种类型的日期/时间数据表示时间:

    • data:Tibbles 将此打印为<date>.

    • time一天中:Tibbles 将此打印为<time>.

    • data-time是一个日期加上时间:它唯一地标识一个瞬间(通常是最近的秒)。Tibbles 将此打印为<dttm>。在 R 的其他地方,这些被称为 POSIXct,但这个名字不一定有用。

    接下来我们将学习日期和日期-时间。由于R没有一个本地类来存储时间,如果需要可以使用hms包。我们应该时刻想到用最简单的数据类型解决我们的问题,如果可以使用日期就绝不会使用日期时间。在实际应用中,由于存在时区问题,日期时间往往就有很多的问题。

    获取当前日期或日期时间,您可以使用today()now()

    today()
    #> [1] "2022-01-05"
    now()
    #> [1] "2022-01-05 09:54:52 CST"
    

    你可以通过三种方式创建日期/时间:

    • 字符串。
    • 日期时间组件。
    • 现有的日期/时间对象。

    2.1 字符串

    日期/时间数据通常以字符串形式出现。你已经在 date-times 中看到了一种将字符串解析为日期时间的方法。另一种方法是使用 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()函数明确:

    ymd(20170131)
    #> [1] "2017-01-31"
    

    要创建日期时间,请在解析函数的名称中添加下划线以及“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 日期时间组件

    有时日期时间的各个组件分布在多个列中,而不是单个字符串。这是在filghts数据中的内容:

    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
    #> # … with 336,770 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
    #> # … with 336,770 more rows
    

    让我们对flights中的四个时间列中的每一个都做同样的事情。以稍微奇怪的格式表示时间,因此我们使用模算法来提取小时和分钟。

    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 x 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
    #> # … with 328,057 more rows, and 3 more variables: arr_time <dttm>,
    #> #   sched_arr_time <dttm>, air_time <dbl>
    

    有了这些数据,查看一年中出发时间的分布:

    flights_dt %>% 
      ggplot(aes(dep_time)) + 
      geom_freqpoly(binwidth = 86400) # 86400 seconds = 1 day
    
    image

    在一天内出发时间的分布:

    flights_dt %>% 
      filter(dep_time < ymd(20130102)) %>% 
      ggplot(aes(dep_time)) + 
      geom_freqpoly(binwidth = 600) # 600 s = 10 minutes
    
    image

    请注意,在数字上下文中(如在直方图中)使用日期时间时,1 表示 1 秒,因此 86400 的 binwidth 表示一天。对于日期,1 表示 1 天。

    2.3 其它类型

    你可能想要在日期时间和日期之间切换。可以使用as_datetime()as_date()

    as_datetime(today())
    #> [1] "2022-01-05 UTC"
    as_date(now())
    #> [1] "2022-01-05"
    

    有时你会得到日期/时间作为“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"
    

    3 日期时间组件

    前面我们已经知道如何将日期时间数据放入 R 的日期时间数据结构中,那这些日期时间可以做什么呢?下面将介绍获取和设置单个组件的访问器函数。

    3.1 组件获取

    可以使用访问器函数来提取日期的各个部分:year()month()mday()(月份中的某天)、yday()(一年中的某天)、wday()(一周中的某天)hour()minute()、 和second()

    datetime <- ymd_hms("2016-07-08 12:34:56")
    
    year(datetime)
    #> [1] 2016
    month(datetime)
    #> [1] 7
    mday(datetime)
    #> [1] 8
    
    yday(datetime)
    #> [1] 190
    wday(datetime)
    #> [1] 6
    

    对于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_dt %>% 
      mutate(wday = wday(dep_time, label = TRUE)) %>% 
      ggplot(aes(x = wday)) +
        geom_bar()
    
    image

    如果我们查看一小时内按分钟计算出发的平均延迟,看起来在 20-30 分钟和 50-60 分钟后起飞的航班的延误比其他时间少得多!

    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
    如果我们查看预定的出发时间,我们并没有看到如此强烈的模式:
    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)
    
    ggplot(sched_dep, aes(minute, avg_delay)) +
      geom_line()
    
    image

    那么为什么我们会在实际出发时间中看到这种模式呢?好吧,就像人类收集的许多数据一样,对在“合适”起飞时间起飞的航班存在强烈偏见。每当您处理涉及人类判断的数据时,请始终警惕这种模式!

    ggplot(sched_dep, aes(minute, n)) +
      geom_line()
    
    image

    3.2 凑整

    的另一种方法绘制各个部件是日期舍入到的时间附近的一个单元,与[floor_date()](http://lubridate.tidyverse.org/reference/round_date.html)[round_date()](http://lubridate.tidyverse.org/reference/round_date.html),和[ceiling_date()](http://lubridate.tidyverse.org/reference/round_date.html)。每个函数都需要一个日期向量来调整,然后是单位的名称向下舍入(地板)、向上舍入(天花板)或舍入到。例如,这允许我们绘制每周的航班数量:
    绘制单个组件的另一种方法是使用floor_date()round_date()ceiling_date()将日期四舍零入到附近的时间单位。每个函数接受一个日期向量来调整,然后单位的名称向下四舍五入(floor)、向上四舍五入(ceiling)或四舍五入。例如,这可以让我们绘制每周的航班数量:

    flights_dt %>% 
      count(week = floor_date(dep_time, "week")) %>% 
      ggplot(aes(week, n)) +
        geom_line()
    
    image

    计算四舍五入日期和非四舍五入日期之间的差异可能特别有用。

    3.3 组件设置

    可以使用每个访问器函数来重新设置日期/时间的组成部分:

    (datetime <- ymd_hms("2016-07-08 12:34:56"))
    #> [1] "2016-07-08 12:34:56 UTC"
    
    year(datetime) <- 2020
    datetime
    #> [1] "2020-07-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

    4 时间跨度

    下面将了解日期算术的工作原理,包括减法、加法和除法。明确时间跨度的三个重要类:

    • durations,代表精确的秒数。
    • period,代表人类单位,如周和月。
    • interval,代表起点和终点。

    4.1 持续时间

    在 R 中,当两个日期相减时,你会得到一个 difftime 对象:

    # How old is Hadley?
    h_age <- today() - ymd(19791014)
    h_age
    #> Time difference of 15424 days
    

    difftime 类对象记录秒、分钟、小时、天或周的时间跨度。这种歧义会使 difftimes 使用起来有点痛苦,所以 lubridate 提供了一个使用秒的替代方法:duration

    as.duration(h_age)
    #> [1]"1332633600s (~42.23 years)"
    

    持续时间还有其他的构造函数:

    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] "31557600s (~1 years)"
    

    持续时间总是以秒为单位记录时间跨度。更大的单位是通过标准速率将分钟、小时、天、周和年转换为秒来创建的(一分钟 60 秒,一小时 60 分钟,一天 24 小时,一周 7 天,一年 365 天)。

    可以乘以持续时间:

    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)
    

    但是,由于持续时间代表精确的秒数,有时您可能会得到意外的结果:

    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"
    

    为什么3 月 12 日下午 1 点之后的一天,是 3 月 13 日下午 2 点这一天?!如果您仔细查看日期,您可能还会注意到时区已更改。由于 DST,3 月 12 日只有 23 小时,所以如果我们加上一整天的秒数,我们就会得到一个不同的时间。

    4.2 周期

    为了解决这个问题,lubridate 提供了period。周期是时间跨度,但没有以秒为单位的固定长度,而是使用“人类”时间,例如天和月。这使他们能够以更直观的方式工作:

    one_pm
    #> [1] "2016-03-12 13:00:00 EST"
    one_pm + days(1)
    #> [1] "2016-03-13 13:00:00 EDT"
    

    像持续时间一样,可以使用许多友好的构造函数来创建周期。

    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"
    

    可以添加和乘:

    10 * (months(6) + days(1))
    #> [1] "60m 10d 0H 0M 0S"
    days(50) + hours(25) + minutes(2)
    #> [1] "50d 25H 2M 0S"
    

    当然,将它们添加到日期。与持续时间相比,周期可能是你想要的结果:

    # A leap year
    ymd("2016-01-01") + dyears(1)
    #> [1] "2016-12-31 06:00:00 UTC"
    ymd("2016-01-01") + years(1)
    #> [1] "2017-01-01"
    
    # Daylight Savings Time
    one_pm + ddays(1)
    #> [1] "2016-03-13 14:00:00 EDT"
    one_pm + days(1)
    #> [1] "2016-03-13 13:00:00 EDT"
    

    让我们使用周期来解决与我们的航班日期相关的奇怪问题。一些飞机似乎在离开纽约市之前就已经到达目的地。

    flights_dt %>% 
      filter(arr_time < dep_time) 
    #> # A tibble: 10,633 x 9
    #>   origin dest  dep_delay arr_delay dep_time            sched_dep_time     
    #>   <chr>  <chr>     <dbl>     <dbl> <dttm>              <dttm>             
    #> 1 EWR    BQN           9        -4 2013-01-01 19:29:00 2013-01-01 19:20:00
    #> 2 JFK    DFW          59        NA 2013-01-01 19:39:00 2013-01-01 18:40:00
    #> 3 EWR    TPA          -2         9 2013-01-01 20:58:00 2013-01-01 21:00:00
    #> 4 EWR    SJU          -6       -12 2013-01-01 21:02:00 2013-01-01 21:08:00
    #> 5 EWR    SFO          11       -14 2013-01-01 21:08:00 2013-01-01 20:57:00
    #> 6 LGA    FLL         -10        -2 2013-01-01 21:20:00 2013-01-01 21:30:00
    #> # … with 10,627 more rows, and 3 more variables: arr_time <dttm>,
    #> #   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 间隔

    dyears(1) / ddays(365)应该返回什么:很明显是一,因为持续时间总是用秒数表示,一年的持续时间被定义为 365 天的秒数。

    years(1)应该返回什么?如果年份是 2015 年,它应该返回 365,但如果是 2016 年,它应该返回 366!lubridate 没有足够的信息来给出一个明确的答案。它的作用是给出一个估计值,并给出警告:

    years(1) / days(1)
    #> [1] 365.25
    

    如果您想要更准确的测量,则必须使用interval。间隔是具有起点的持续时间:这使其精确,因此可以确切地确定它是多长时间:

    next_year <- today() + years(1)
    (today() %--% next_year) / ddays(1)
    #> [1] 365
    

    要找出一个区间内有多少个周期,您需要使用整数除法:

    (today() %--% next_year) %/% days(1)
    #> [1] 365
    

    4.4 总结

    如何在持续时间、周期和间隔之间进行选择?与往常一样,选择最简单的数据结构来解决问题。如果只关心物理时间,请使用持续时间;如果需要添加人工时间,请使用周期;如果您需要计算以人为单位的跨度有多长,请使用间隔。

    下图总结了不同数据类型之间允许的算术运算。

    日期/时间类对之间允许的算术运算。

    5 时区

    在 R 中查看当前的时区:Sys.timezone()

    Sys.timezone()
    #> [1] "Asia/Taipei"
    

    查看所有时区名称:OlsonNames()

    length(OlsonNames())
    #> [1] 593
    head(OlsonNames())
    #> [1] "Africa/Abidjan"     "Africa/Accra"       "Africa/Addis_Ababa"
    #> [4] "Africa/Algiers"     "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"
    #> [3] "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"
      #> [3] "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"
      #> [3] "2015-06-01 12:00:00 +1030"
      x4b - x4
      #> Time differences in hours
      #> [1] -14.5 -14.5 -14.5
      

    相关文章

      网友评论

          本文标题:11.日期和时间

          本文链接:https://www.haomeiwen.com/subject/dsgqcrtx.html