Chapter 12 日期時間處理

日期時間在醫學研究中, 是一個非常重要的變數, 然而對於日期時間的儲存, 列印與計算, 每一個軟體各不相同的處理方法. 原則上, 軟體對於日期時間的輸入通常是以文字型式輸入 日歷時間(**calendar date and time), 例如, "12/10/1979", "12/10/1979 20:30:10", "2/28/1947", "2-28-1947", 022847, 2Feb47, "Feburary 2 1947" 等, {R} 讀入文字型式的日歷時間, 轉換成 {R} 的 **日期時間類別物件` (**date-time class object`). {R} 的日期類別物件'' (**dates class object). {R} 有不同日期時間類別格式存在, 非別為 Date, 以及 Date-Time 類別, 而 Time 包含 POSIXltPOSIXct 等 2 時間類別. Date 類別 僅考慮以天數計算, Date-Time 類別 考慮以總秒數計算, {R} 以 January 1, 1970 為 第 0 天.

使用日期函式 as.Date() 可以將文字型式的日歷日期 (calendar date) 轉換成 {R} 的 日期類別(**Dates class) 物件. 使用日期函式 strptime() 可以將文字型式的日歷時間轉換成 {R} 的 日期-時間類別物件''; 使用日期函式 `as.POSIXlt()` 與 `as.POSIXct()` 可以將日期-時間類別物件’’ 分別轉換成 POSIXlt, POSIXct類別格式. 使用日期函式format()可以將 {R} 的 ``日期-時間類別物件'' 轉換成一般人可讀的文字型式的日期, 日, 星期, 月, 與時間.POSIXct: 非常大的正整數, 通常儲存在data.frame中使用,POSIXlt: 是一個列表物件 (list), 分別儲存 星期中的第幾天, 一年中的第幾天, 月份, 月中的第幾天等等. {R} 的日期時間的內設有些複雜, 因此 {R} 有些專門日期時間套件, 例如tidyverse套件系統提供lubridate套件, 或更完整的財務金融Rmetrics` 套件, 這些套件可以在處裡時間上變得較為方便.

12.1 lubridate 套件的日期時間

lubridate 套件內日期時間資料, 在tidyverse 套件系統的 tibble 物件呈現 3 種類型.

  • date tibble 列印日期成 <date>.

  • time tibble 列印時間成 <time>.

  • date-time 資料同時包含日期時間 tibble 列印日期成 <dttm>. 這是 {R} 的 POSIXct 物件.

使用 lubridate 套件的函式 today()now() 取得今日的日期時間. {R} base 函式 Sys.time() 取得今日的日期時間之絕對數值為 POSIXct 物件, 可依照時區進行轉喚. 而函式 Sys.Date() 則依照電腦使用時區傳回 Date 物件.

op <- options(digits.secs = 6)
Sys.time()
## [1] "2020-10-29 14:53:21.91146 CST"
Sys.Date()
## [1] "2020-10-29"
class(Sys.time())
## [1] "POSIXct" "POSIXt"
class(Sys.Date())
## [1] "Date"
## locale-specific version of date()
format(Sys.time(), "%a %b %d %X %Y")
## [1] "週四 十月 29 下午 02:53:21 2020"
Sys.Date()
## [1] "2020-10-29"
## lubriate package
library(tidyverse)
library(lubridate)
today()
## [1] "2020-10-29"
now()
## [1] "2020-10-29 14:53:21.985474 CST"
class(today())
## [1] "Date"
class(now())
## [1] "POSIXct" "POSIXt"

不同程式語言系統的時間日期參照個不相同, {R} 是以 UTC 時區 (Universal Time, Coordinated) January 1, 1970, 0 時, UTC 0 分 0 秒 為 `{0’’ (origin), UTC 以國際原子時為計算基準, 修正了一些時間微小的飄移, 而 UTC 與 GMT (格林威治標準時間) 相差不多.

有 3 種方法可以創件日期時間物件.

  • 從文字或字串轉換創件日期時間物件.
  • 從個別 date-time 資料, 創件日期時間物件.
  • 從已經建立的 date-time 物件或其他資料物件, 進行運算而創件日期時間物件.

12.1.1 從文字或字串轉換創件日期時間物件

只要文字輸入時, 依照適當的日期時間順序即可創件日期時間物件. 也可加入時區.

ymd(..., quiet = FALSE, tz = NULL, 
    locale = Sys.getlocale("LC_TIME"), truncated = 0
)

其中引數 tz 為時區識別, locale 為地區識別.

## strings -> date-time 
## date
library(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"
## date and time
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"
## without quotation
ymd(20170131)
## [1] "2017-01-31"
## add time-zone
ymd(20170131, tz = "UTC")
## [1] "2017-01-31 UTC"
##
x <- c("09-01-01", "09-01-02", "09-01-03")
ymd(x)
## [1] "2009-01-01" "2009-01-02" "2009-01-03"
x <- c("2009-01-01", "2009-01-02", "2009-01-03")
ymd(x)
## [1] "2009-01-01" "2009-01-02" "2009-01-03"
ymd(090101, 90102)
## [1] "2009-01-01" "2009-01-02"
now() > ymd(20090101)
## [1] TRUE
dmy(010210)
## [1] "2010-02-01"
mdy(010210)
## [1] "2010-01-02"
yq('2014.2')
## [1] "2014-04-01"
##
## heterogeneous formats in a single vector:
x <- c(20090101, "2009-01-02", "2009 01 03", "2009-1-4",
       "2009-1, 5", "Created on 2009 1 6", "200901 !!! 07")
ymd(x)
## [1] "2009-01-01" "2009-01-02" "2009-01-03" "2009-01-04" "2009-01-05" "2009-01-06"
## [7] "2009-01-07"

12.2 從date-time 資料個別成分, 創件日期時間物件.

許多資料在儲存時, 是將年, 月, 日, 時, 分, 秒 等分開以變數 (欄位) 儲存. 在資料分析時才創件成為日期時間物件, 例如 {R} 的 nycflights13 套件的 flights 資料, 此資料是有關紐約機場班機起降時間記錄. 可使用函式 make_datetime() 創件日期時間物件.

## nycflights13 package
library(nycflights13)
## store as separate variables
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
## create date-time
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.000000
##  2  2013     1     1     5     29 2013-01-01 05:29:00.000000
##  3  2013     1     1     5     40 2013-01-01 05:40:00.000000
##  4  2013     1     1     5     45 2013-01-01 05:45:00.000000
##  5  2013     1     1     6      0 2013-01-01 06:00:00.000000
##  6  2013     1     1     5     58 2013-01-01 05:58:00.000000
##  7  2013     1     1     6      0 2013-01-01 06:00:00.000000
##  8  2013     1     1     6      0 2013-01-01 06:00:00.000000
##  9  2013     1     1     6      0 2013-01-01 06:00:00.000000
## 10  2013     1     1     6      0 2013-01-01 06:00:00.000000
## # ... with 336,766 more rows
##

12.2.1 從已經建立的其他資料物件創件日期時間物件

若資料以其他的物件形式儲存在 {R} 系統內, 常使用函式 as_datetime()as_date() 創件日期時間物件. 若是以數值輸入, 可分成以秒為單位或以日為單位輸入.

as_datetime(today())
## [1] "2020-10-29 UTC"
as_date(now())
## [1] "2020-10-29"
## import as numeric values
## import as seconds from 1970-01-01 0:00:00
as_datetime(60 * 60 * 10) 
## [1] "1970-01-01 10:00:00 UTC"
## import days from 1970-01-01
as_date(365 * 10 + 2) 
## [1] "1980-01-01"

12.3 時區轉換

時區的變動與轉換, 常互造成錯誤. 函式 with_tz() 可以進行正確時區轉換, 而函式 force_tz() 則強迫變動時區. 例如台北時區為 2011-07-01 09:00:00 Asia/Taipei, 轉換成美國 Hawaii時區為 HST. 時區可參考 (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).

## change time zone
meeting <- ymd_hms("2020-10-10 09:00:00", tz = "Asia/Taipei")
with_tz(meeting, "HST")
## [1] "2020-10-09 15:00:00 HST"
## force change 
mistake <- force_tz(meeting, "America/Chicago")
mistake
## [1] "2020-10-10 09:00:00 CDT"
with_tz(mistake, "Asia/Taipei") 
## [1] "2020-10-10 22:00:00 CST"

12.4 取出 date-time 資料的個別成分

一個完整 date-time 物件, 其資料包含年, 月, 日, 時, 分, 秒, 時區等訊息. 若要取出 date-time 資料的個別成分, 可藉由函式 year(), month(), mday() (一個月內的天數 1–31), yday() (一年內的天數 1–366), wday() (一週內的天數 1–7), hour(), minute(),second(). 其中month()wday()可使用引數label = TRUE回傳簡寫標註文字, 如 Jan, Mon. 而使用引數bbr = FALSE` 回傳完整標註文字, 如 January, Monday. 這些函式也可用來更動 date-time 物件部分成分.

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
## labels 
month(datetime, label = TRUE)
## [1] 七月
## 12 Levels: 一月 < 二月 < 三月 < 四月 < 五月 < 六月 < 七月 < 八月 < 九月 < ... < 十二月
wday(datetime, label = TRUE, abbr = FALSE)
## [1] 星期五
## Levels: 星期日 < 星期一 < 星期二 < 星期三 < 星期四 < 星期五 < 星期六
## change components
(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"

函式 floor_date(), round_date(), ceiling_date() 可將 date-time 物件簡化成最近的時間單位.

round_date(x, unit = "second", week_start = getOption("lubridate.week.start", 7))

其中引數 unit 可為 second, minute, hour, day, week, month, bimonth, quarter, season, halfyear year. 引數 week_start 為每週起始計算, 1 = Monday, 7 = Sunday.

x <- ymd_hms("2009-08-03 12:01:59.23")
floor_date(x, ".1s")
## [1] "2009-08-03 12:01:59.2 UTC"
floor_date(x, "second")
## [1] "2009-08-03 12:01:59 UTC"
floor_date(x, "minute")
## [1] "2009-08-03 12:01:00 UTC"
floor_date(x, "hour")
## [1] "2009-08-03 12:00:00 UTC"
floor_date(x, "day")
## [1] "2009-08-03 UTC"
floor_date(x, "week")
## [1] "2009-08-02 UTC"
floor_date(x, "month")
## [1] "2009-08-01 UTC"
floor_date(x, "bimonth")
## [1] "2009-07-01 UTC"
floor_date(x, "quarter")
## [1] "2009-07-01 UTC"
floor_date(x, "season")
## [1] "2009-06-01 UTC"
floor_date(x, "halfyear")
## [1] "2009-07-01 UTC"
floor_date(x, "year")
## [1] "2009-01-01 UTC"

可自行比較 round_date(), ceiling_date() 的差異.

12.5 計算時間長度

資料分析常常需要輸入持續時間長度或是計算二個 date-time 物件之間的時間長度. lubridate 套件有數種計算時間長度的工具.

  • duration 創建以秒計算的時間長度的物件.
  • period 創建以人類文明的時間單位計算時間長度, 例如依照日歷的 years, months, days.
  • interval 計算二個 date-time 物件之間的時間長度.

12.5.1 duration 創建以秒計算的時間長度的物件

duration() 系列函式用來輸入持續時間長度, 創建以秒計算的時間長度的物件

duration(num = NULL, units = "seconds", ...)

其中引數 num 為數值, units 為計算時間的單位. duration 物件的時間計算是固定的, 例如, Minutes = 60 seconds, hours = 3600 seconds, days = 86400 seconds, weeks = 604800. 這些 duration() 系列函式包含 dseconds(x = 1), dminutes(x = 1), dhours(x = 1), ddays(x = 1), dweeks(x = 1), dmonths(x = 1), dyears(x = 1), dmilliseconds(x = 1), dmicroseconds(x = 1), dnanoseconds(x = 1), dpicoseconds(x = 1), is.duration(x) 等.

## ### Separate period and units vectors
duration(num = 90, units = "seconds")
## [1] "90s (~1.5 minutes)"
duration(num = 1.5, units = "minutes")
## [1] "90s (~1.5 minutes)"
## Units as arguments
duration(day = -1)
## [1] "-86400s (~-1 days)"
duration(second = 90)
## [1] "90s (~1.5 minutes)"
duration(second = 3, minute = 1.5, hour = 2, day = 6, week = 1)
## [1] "1130493s (~1.87 weeks)"
duration(hour = 1, minute = -60)
## [1] "0s"
## Elementary construction
dseconds(x = 1)
## [1] "1s"
dminutes(x = 3.5)
## [1] "210s (~3.5 minutes)"

12.5.2 period 創建以人類文明的時間單位計算時間長度

period() 系列函式用來輸入持續時間長度, 依照日歷的 years, months, days 計算. 例如, 2009-01-01 起計算 1 year = 365 days, 但 2012-01-01 起計算 1 year = 366 days. period 物件的時間長度並不固定, 而是依據 date-time 物件所在的時際日歷而定. 若當年度有潤秒, 則 1 min > 60 seconds. period 物件是人類文明使用的 clock time.

seconds(), minutes(), hours(), days(), weeks(), months(), years(), milliseconds(), microseconds(), nanoseconds(),picoseconds(),period_to_seconds(),seconds_to_period()` 等.

period(num = 1, units = "days")
## [1] "1d 0H 0M 0S"
period(c(3, 1, 2, 13, 1), c("second", "minute", "hour", "day", "week"))
## [1] "20d 2H 1M 3S"
## Units as arguments
period (second = 90, minute = 5)
## [1] "5M 90S"
period(second = 3, minute = 1, hour = 2, day = 13, week = 1)
## [1] "20d 2H 1M 3S"
## Elementary Construction
x <- ymd("2009-08-03")
x
## [1] "2009-08-03"
x + days(1) + hours(6) + minutes(30)
## [1] "2009-08-04 06:30:00 UTC"
x + days(100) - hours(8)
## [1] "2009-11-10 16:00:00 UTC"
## compare DST handling to durations
boundary <- ymd_hms("2009-03-08 01:59:59", tz="America/Chicago")
boundary + days(1) # period
## [1] "2009-03-09 01:59:59 CDT"
boundary + ddays(1) # duration
## [1] "2009-03-09 02:59:59 CDT"
is.duration(as.Date("2009-08-03")) # FALSE
## [1] FALSE
is.duration(duration(days = 12.4)) # TRUE
## [1] TRUE
is.period(as.Date("2009-08-03")) # FALSE
## [1] FALSE
is.period(period(months= 1, days = 15)) # TRUE
## [1] TRUE

12.5.3 interval 計算二個 date-time 物件之間的時間長度.

interval() 系列函式用來計算二個 date-time 物件之間的時間長度.

interval(start = NULL, end = NULL, tzone = tz(start))

若起始 date-time 物件早發生於結束 date-time 物件, 則 interval 物件計算為正值. interval 物件計算依照日歷的 years, months, days 計算. start %--% end 等同於 interval(), is.interval(x) 檢查是否為 interval 物件, int_start(int), int_start(int) <- value, int_end(int), int_end(int) <- value等可變更起始與結束時間點, int_length(int) 以秒計算, int_flip(int) 翻轉起始與結束時間點, int_shift(int, by) 起始與結束時間點進行前後移動, int_overlaps(int1, int2) 檢查二個 interval 物件是否重覆, int_standardize(int) 確保所有 interval 物件為正值, 若無則翻轉, int_aligns(int1, int2)檢查二個 interval 物件是否有相同起始與結束時間點, int_diff(times) 類似 {R} base POSIXtDate 的 diff() 函式, 但回傳 interval 物件, 而不是 {R} base 的 difftime 物件.

interval(ymd(20090201), ymd(20090101))
## [1] 2009-02-01 UTC--2009-01-01 UTC
date1 <- ymd_hms("2009-03-08 01:59:59")
date2 <- ymd_hms("2000-02-29 12:00:00")
interval(date2, date1)
## [1] 2000-02-29 12:00:00 UTC--2009-03-08 01:59:59 UTC
interval(date1, date2)
## [1] 2009-03-08 01:59:59 UTC--2000-02-29 12:00:00 UTC
## Elementary Construction
is.interval(period(months = 1, days = 15)) # FALSE
## [1] FALSE
is.interval(interval(ymd(20090801), ymd(20090809))) # TRUE
## [1] TRUE
int <- interval(ymd("2001-01-01"), ymd("2002-01-01"))
int_start(int)
## [1] "2001-01-01 UTC"
int_start(int) <- ymd("2001-06-01")
int
## [1] 2001-06-01 UTC--2002-01-01 UTC
int <- interval(ymd("2001-01-01"), ymd("2002-01-01"))
int_end(int)
## [1] "2002-01-01 UTC"
int_end(int) <- ymd("2002-06-01")
int
## [1] 2001-01-01 UTC--2002-06-01 UTC
int <- interval(ymd("2001-01-01"), ymd("2002-01-01"))
int_length(int)
## [1] 31536000
int <- interval(ymd("2001-01-01"), ymd("2002-01-01"))
int_flip(int)
## [1] 2002-01-01 UTC--2001-01-01 UTC
int <- interval(ymd("2001-01-01"), ymd("2002-01-01"))
int_shift(int, duration(days = 11))
## [1] 2001-01-12 UTC--2002-01-12 UTC
int_shift(int, duration(hours = -1))
## [1] 2000-12-31 23:00:00 UTC--2001-12-31 23:00:00 UTC
int1 <- interval(ymd("2001-01-01"), ymd("2002-01-01"))
int2 <- interval(ymd("2001-06-01"), ymd("2002-06-01"))
int3 <- interval(ymd("2003-01-01"), ymd("2004-01-01"))
int_overlaps(int1, int2) # TRUE
## [1] TRUE
int_overlaps(int1, int3) # FALSE
## [1] FALSE
int <- interval(ymd("2002-01-01"), ymd("2001-01-01"))
int_standardize(int)
## [1] 2001-01-01 UTC--2002-01-01 UTC
int1 <- interval(ymd("2001-01-01"), ymd("2002-01-01"))
int2 <- interval(ymd("2001-06-01"), ymd("2002-01-01"))
int3 <- interval(ymd("2003-01-01"), ymd("2004-01-01"))
int_aligns(int1, int2) # TRUE
## [1] TRUE
int_aligns(int1, int3) # FALSE
## [1] FALSE
dates <- now() + days(1:3)
int_diff(dates)
## [1] 2020-10-30 14:53:24.003588 CST--2020-10-31 14:53:24.003588 CST
## [2] 2020-10-31 14:53:24.003588 CST--2020-11-01 14:53:24.003588 CST