3 min read

"R语言技巧:lubridate包日期时间处理实战指南

{r setup, include=FALSE} knitr::opts_chunk$set(eval = FALSE) {r message=FALSE, warning=FALSE, include=FALSE} library(data.table) library(tidyverse) library(lubridate)

新增

  • duration in filter
  • 目录
  • duration
  • 整理的lubridate的使用技巧

整理

{r echo=FALSE, message=FALSE, warning=FALSE} library(tidyverse) installed.packages() %>% as_tibble() %>% filter(Package == "lubridate") %>% select(Package,Version,Built)

{r} library(lubridate)

as date-times 系列

和 @base 重合很多,但是也很比较好的功能。

as_date

{r} as_date(0) as_date(1)

一天一单位

{r} sample_date <- "2017-01-01 as_date(sample_date) as.Date(sample_date)

as_date替换as.Date

as_datetime

{r} as_datetime(0) as_datetime(1) as_datetime(10)

一秒一单位。

as.hms

{r} hms::as.hms(0) hms::as.hms(1)

这个功能很好, @base 应该没有。

round

{r} d <- "2018-05-03 11:19:10 CST d <- as_datetime(d) d floor_date(d,unit = "hour") ceiling_date(d,unit = "hour") round_date(d,unit = "hour") rollback(d) rollback(d,roll_to_first=T)

  • floor_date()$\to$floor$\to$round down$\to$向下取整
  • ceiling_date()$\to$ceiling$\to$round up$\to$向上取整
  • round_date()$\to$round$\to$round nearest$\to$就近取整
  • rollback方便去月初月末数据,这里最好的是月末数据的取法,因为每个月最后一天不是一致的。

parse date-times

在 @base 中,

  • deparse函数将表达式string化
  • parse函数将string表达式化

一因此这里也是将string转化为时间变量的意思。 因此要控制这里的输入是string。

{r} ymd_hms("2018-05-03 11:19:10 CST") yq("2018:Q1")

{r} date_decimal(2017.5, tz = "Asia/Shanghai")

  • date_decimal函数的输入是numeric2017.5等价于$2017+\frac{5}{10} \cdot 1年$
  • 时区指定tz = "Asia/Shanghai"

time zones

查看时区可以用 @base 的OlsonNames()函数。

{r} OlsonNames() %>% as_tibble() %>% filter(value %in% str_subset(value, "Shanghai|Chicago"))

{r} d2 <- ymd_hms(d) d2 with_tz(d2,tzone = "US/Pacific") force_tz(d2,tzone = "US/Pacific")

  • with_tz的输入必须是时间变量而非string。
  • with_tz是转换时差
  • force_tz是保持时间不变,改变时区。

Math with Date-times

吃完饭,来搞一搞。

这里主要区分periods和durations两种函数

  • periods: minutes*s
  • durations: dminutesd*s

它们之间的区别是periods不考虑美国冬令时等转换、闰年转换等,因此推荐使用durations。

{r} ymd_hms("2018-05-03 12:52:10 CST") + dminutes(30)

interval()函数会建立成特定的时间变量,但是我觉得我使用的少。

{r} interval( ymd_hms("2018-05-03 12:52:10 CST") + dminutes(30), ymd_hms("2018-05-03 12:52:10 CST") ) %>% as_tibble()

duration

区别于interval

{r} library(lubridate) interval(ymd("2019-03-01"),ymd("2019-02-01")) %>% as.duration() (ymd("2019-03-01")-ymd("2019-02-01")) (ymd("2019-03-01")-ymd("2019-02-01")) %>% as.integer()

{r} (ymd_hms("2019-03-01 00:00:00")-ymd_hms("2019-03-01 01:00:00")) %>% as.integer() %>% dhours()

d*s 函数只要回传的数字,并且*反馈正确的单位即可。

未整理

{r} # 生成 100 个日期,从2018-01-01开始 set.seed(42) n <- 100 dt <- data.table(date = seq(ymd("2018-01-01"),length.out = n, by = "day"), x = runif(n) ) dt %>% head()

by = "day"是递增按日计算。

按照周进行分类[@大猫的R语言课堂2018]

{r} dt %>% mutate(week = week(date)) %>% group_by(week) %>% summarise(avg = mean(x))

weeklubridate的函数。

按照星期进行分类[@大猫的R语言课堂2018]

{r} dt %>% mutate(weekday = wday(date)) %>% group_by(weekday) %>% summarise(avg = mean(x))

wdaylubridate的函数,表达星期几。

按照"每个三天"分类[@大猫的R语言课堂2018]

{r} dt %>% mutate(three.day = ceiling_date(date,unit = "3 days")) %>% head()

ceiling_datelubridate的函数, unit = "3 days"表达间隔三天。

转换成"%Y-%m"的方法[@大猫的R语言课堂2018]

format(transactiondate, "%Y-%m") 但是这是文本格式。

这是DataCamp出的xts包的 cheatsheet

当对月份设为group,进行汇总时,可以使用xts包,也可以使用lubridate包,进行时间变量的计算。 通过yearmonth函数提取时间变量的年和月,仿造day=1, 然后通过make_date(year,month,day)函数进行合并。 这里需要对时间变量再转换as.POSIXct.Date。因为在ggplot中表示时,scale_x_datetime(date_breaks = "1 month")函数需要xPOSIXct.Date格式。

时区的bug解决[@大猫的R语言课堂2018]

我估计是我的时区选的有问题。 发现我的input的时候是UTC时区。 所以要修改成r Sys.timezone()。 并且with_tz(.,tzone = "Asia/Shanghai")可以查看具体时间在本时区的表达情况。 mutate(start = ymd_hms(as.character(start), tz = "Asia/Shanghai"))这是一个置换不同时区的方式。 综上,excel处理时间的函数有毒。

少用ymd_hms函数[@大猫的R语言课堂2018]

最后转化成double了。 mutate(start = ymd_hms(start))ymd_hms常常会让一个时间变量变成double格式,这个很麻烦,因为转换都需要as.POSIXct(as.numeric(time), origin='1970-01-01')中的origin,这个不知道啊,所以坑。

duration in filter

as.integer函数使得duration可以在filter中进行筛选。

{r eval=F} library(lubridate) data_3 %>% select(1:5) %>% transmute(datetime = make_datetime(X1,X2,X3,X4,X5)) %>% arrange(datetime) %>% mutate(duration = interval(datetime,lag(datetime)) %>% as.duration(), duration_int = as.integer(duration)) %>% filter(duration_int != -300) ## %within% 函数

```{r} ref_tbl <- tibble( placement = c(“NewYorkTimes_iPhone”,“NewYorkTimes_iPhone”), start = c(“2018-06-01”,“2018-06-26”), end = c(“2018-06-25”,“2018-06-30”), rate = c(5,7) ) %>% mutate_at(vars(start, end),as.Date) des_tbl <- tibble( placement = “NewYorkTimes_iPhone”, date = “2018-06-15”, rate = 5 ) %>% mutate(date = as.Date(date)) ref_tbl des_tbl

ref_tbl %>% left_join(des_tbl, by = c(“placement”,“rate”)) %>% mutate( ifelse(date %within% interval(start,end),1,0) )

`%within%` 和 `interval` 是`lubridate`的函数,主要算时间区间的。
I post an answer related to this function on [Stack Overflow](https://stackoverflow.com/questions/50751794/if-date-is-between-two-dates-find-value-using-shared-reference/50752551#50752551).

## 月首日 `floor_date`

参考 @Spring2018 的思路。

```
x <- ymd(c("2012-03-26", "2012-05-04", "2012-09-23", "2012-12-31"))
floor_date(x, "1 month") 
floor_date(x, "1 month") %>% decimal_date()

以周四为首日的周度数据

date_add(date_add(makedate(year(inserttime),1), interval week(date_sub(inserttime,interval 4 day)) week), interval 3 day) as 发标时间,
  • date_add(makedate(year(inserttime),1): 201X-01-1
  • date_sub(inserttime,interval 4 day): 倒退四天
  • week(date_sub(inserttime,interval 4 day)): 倒退四天的占今年的星期数,所有的周五、周六、周日星期数不变,周一、周二、周三、周四都少加一个星期。
  • date_sub(...,interval 3 day): 这里的计算还是不清楚

使用seq函数创建时间序列 [@LaBarr2018, Chapter 1.2]

seq(as.Date("2014-01-19"), length = 176, by = 7)

随机生成时间

as.POSIXct(" 2017-10-08 07:00:00") + runif(n=100, min=0, max=3600)

Stack Overflow

unix time converting

参考 @Eddelbuettel2012

{r} as.POSIXct(1352068320, origin="1970-01-01")

月度变化

floor(cohort_index/30/3600/24) 比这个包的函数精确好用。