lubridate—轻松处理日期时间

本文尝试翻译了Garrett Grolemund(《R语言入门与实践》作者)和Hadley Wickham两位大神发表的关于lubridate包的文章,该包专注于对日期时间数据的处理。

本人英文水平有限,翻译难免有纰漏,如果有朋友发现了问题,欢迎指正~

另,原文在此: http://vita.had.co.nz/papers/lubridate.pdf

本文介绍了R中的lubridate包,该包有利于灵活处理日期和时间数据。日期时间数据为数据分析家们造成了各种各样的技术问题。本文列举了这些问题,并提供了解决问题方法。

本文还介绍了R中日期时间算法的概念框架。

关键词:日期 时间 时区 夏时制 R

日期有许多不同的格式,识别和分析它们通常比较麻烦。即便我们能识别出不同格式的日期,仍然面临特定日期时间的问题。我们怎么才能轻易提取出日期时间的元素,例如年、月或秒?怎么才能在时区之间进行切换,或者比较夏时制地区和非夏时制地区的日期时间呢?当我们试着用它们做算术时,日期时间数据会产生更复杂的问题。像闰年和夏时制这样的惯例,使所谓的“一天之后”或“确切的两年”变得不那么清晰,甚至闰秒也会破坏看似简单的计算。这种复杂性也会影响其他任务,例如为绘制日期时间数据构建合理的刻度。

虽然Base R能够处理其中一些问题,但使用的语法会根据日期时间的类型发生变化,可能会令人感到混乱和难以记住。lubridate包重视这些问题,以新颖但有用的方式来处理R中的日期时间。使用lubridate包将增强使用者任何数据分析,包括日期时间数据分析方面的体验。特别是,lubridate将帮助用户:

  • 1.识别和解析日期时间数据,见第3节。
  • 2.提取和修改日期时间数据的成分,如年、月、日、小时、分钟和秒,见第4节。
  • 3.对日期时间和时间间隔进行精确的计算,参见第5和6节。
  • 4.处理时区和夏令制,见第7和8节。
    lubridate兼容多种常见的日期和时间序列对象,包括字符串,POSIXct,POSIXlt,Date,chron,timeDate,zoo,xts,its,tis,timeSeries,fts,以及tseries对象。
  • lubridate能覆盖Base R中对POSIXt, Date, 以及difftime对象使用的加减运算。这保证了用户能够用lubridate中的timespan(处理时段数据的方法)对日期时间执行简单的运算,但它并不改变R对非lubridate对象实行加减运算。

    lubridate引进了Joda-Time项目介绍的四种时间对象,以及对四种时间对象进行了测量的概念模型。第5节描述了这一模型,并解释了lubridate 如何使用它对R数据进行简单而精确的运算。

    本文展示了lubridate包提供的实用工具,并以一个应用范例结尾。本文的lubridate 0.2版本可从“the Comprehensive R Archive ”下载,网址: http://CRAN.R-project.org/package=lubridate 。开发版本在此: https://github.com/hadley/lubridate%E3%80%82

    2.研究动机

    为了体现lubridate的简单之处,我们考虑一个常见的场景:给定一个字符串,我们希望把它作为日期时间对象提取出月份,并将其更改为二月。左边是用Base R的方法完成这三个任务,右边是lubridate方法。

    在时间的不同时刻,月、周、日、小时、甚至分钟的长度也会有所不同。我们可以认为它们是时间的相对单位,它们的长度取决于开始的时间。相比之下,秒总是有一个一致的长度。因此,秒是精确的时间单位。

    研究人员可能对绝对长度、相对长度或两者都感兴趣。例如,物理物体的速度适合用绝对长度衡量。股票市场的开盘时间比较容易用相对长度来模拟。

    Lubridate引入了四个与时间对象,从而使相对和绝对单位能用数学运算。这四个时间对象借用了Joda Time项目的术语,分别是instants,intervals,durations和periods。

    5.1.Instants 时点

    时点Instant是一个特定的时刻,比如2012年1月1日。我们每次将日期解析进R时都会创建一个时点。

    我们可以用now()获取当前的时点:年,月,日,时,分,秒,用today()获取当前时点:年,月,日。

    5.2. Intervals时间间隔

    intervals、durations、periods都是记录时间跨度的方法。其中, interval是最简单的,是特定的时间跨度。一个interval是两个特定时刻之间的时间。时间间隔的长度从不模棱两可,因为我们知道它的发生时间,任何时间都可以计算确切长度。

    我们可以通过两时点相减或使用命令new_interval()来创建interval间隔对象。

    5.3. Durations时间跨度

    如果我们从一个interval中删除开始和结束日期,我们将得到一个可以添加到任何日期上的通用的时间跨度。但我们要如何衡量这段时间呢?如果我们以绝对长度的秒为单位记录,它将有精确的长度,这种时间跨度为duration。如果我们用更大的单位记录,比如分或年,由于这些单位的长度随时间而变化,时间跨度的确切长度将取决于开始时间,这些非精确的时间跨度称为period,我们将在下一节讨论。

    duration的长度与闰年、闰秒和夏时制无关,因为它是以秒为单位计算的。因此,duration具有一致的长度,durations之间可以互相比较。duration是比较基于时间的属性(如速度、速率和寿命)的合适对象。

    lubridate兼容Base R 中difftime类型对象,并且difftime帮助进行duration计算。

    对于比较大的时间跨度,用秒来描述长度是不方便的。例如,没有多少人会认为31536000秒是标准年的长度。因此,lubridate也使用其它标准的时间单位来显示duration 。然而,这些单位只是为了方便起见而给出的近似数。duration底层对象总是以秒记录。近似的单位适用于以下关系:一分钟是60秒,一小时是3600秒,一天是86400秒,一个星期是604800秒,一年是31536000秒。月单位不适用,因为它很多变。

    通过函数dyears(),dweeks(),ddays(),dhours(),dminutes(),和dseconds(),可以轻松的创建duration时间对象。开头的d代表duration,就与第5.4节中讨论的period对象区分开了。

    用上面给的近似关系可以为每个对象创建以秒为单位的duration。例如(目前已经用d-代替e-,下面例子没有修改),

    这些函数名称中不包含字母e/d,因为他们不是近似值。例如,month(2)总是表示两个月的长度,即便2个月代表的总时间会跟随具体开始时间变动。因此,我们无法精确计算一个period的秒数,直到我们知道它的开始时间。但是,我们仍然可以用period进行日期时间计算。当我们为instant时点加上或减去一个period,这个period就绑定在了instant上。这个instant告诉我们这个period的开始时间,这使得我们能够以秒计算出精确长度。

    换句话说,我们可以用period来精确地表述时钟时间,而不用知道闰秒、闰天以及夏时制等是否发生。

    不同的时区使时点有多个不同的名称。例如,“2010-03-26 11:53:24 CDT”和“2010-03-26 12:53:24 EDT”都描述了相同的时点。前者是美国中央时区(CDT),后者是美国东部时区(EDT)。时区使日期时间数据复杂化,但对于将时钟时间转换为夏时制时非常有用。

    instant的时间是世界协调时区(UTC),它与标准时钟时间是一致的,这就节省了计算量,但如果你的计算机坚持将时间转换为你当前的时区,可能会很烦人,并且在讨论时间时也不方便。

    lubridate用两种方式减轻时区造成的差异。我们可以用with_tz()去改变时区,显示同一个时间点在不同时区的时间,例如,

    如果我们尝试在2010-03-14 01:59:59 CST和2010-03-14 03:00:00 CDT之间创造一个instant时点,lubridate会返回NA,因为这样的时间是无效的,两个时间是相等的。

    我们也可以通过固定时区来避免夏时制造成的时间偏差,例如将时区固定在不采用夏时制的“UTC”。

    9.范例1

    接下来的两节讲解lubridate的使用技巧。首先,我们将使用lubridate计算节日的具体日期。然后我们会用lubridate探索实例数据集(湖人)。

    9.1. Thanksgiving

    有些节日,如感恩节(美国)和阵亡将士纪念日(美国)并不发生在固定的日期。相反,他们是按照一个规则来庆祝的。例如,感恩节是在十一月的第四个星期四庆祝的。为了得到感恩节是在2010年什么时间举行的,我们可以从2010的第一天开始计算。

    10.范例2(不清楚篮球比赛的专业术语,翻译可能有误)

    湖人数据集包含了洛杉矶湖人队在2008-2009赛季,各大联盟比赛的统计数据。此数据来自 http://www.basketballgeek.com/downloads/ (Parker 2010),另lubridate包已内含该数据集。我们将探索湖人全年的比赛分布以及湖人队在比赛中的战术分配情况。我们使用ggplot2。

    湖人数据集用date来记录每场比赛的日期。使用str()命令,我们看到R将日期识别为整数型。

    图1显示了整个赛季比赛不断,但比赛之间会有短时相隔。赛季开始时,比赛的频率看起来较低,而且比赛似乎被均匀分为主场和客场。X轴上的刻度和刻度间隔是由lubridate包中pretty.dates()自动生成的。

    接下来我们看看湖人比赛次数的周分布情况。我们用wday()命令获取每个日期具体是星期几。

    现在让我们看看每场比赛,特别是整个赛季的投球情况分布。湖人队数据集中的time列出了每场比赛中投篮、篮板、罚球等技术时,距离单节比赛结束的时间。比赛单节时长12分钟,从12:00倒计时至00:00,分号之前的数字代表比赛剩下的分钟,后两个数字代表剩下的秒数。

    time仅包含分钟和秒,无法确定唯一日期-时间,因此它不是R中的标准日期时间类型。我们用ms()函数将分钟和秒存储为5.4节中定义的period对象。

    11.结论