写在前面
本期依旧由村长为大家供稿,主要讲述R语言在时间格式处理中的很多问题。
问题提出
把“以字符格式存储”(chr)的时间日期数据解析成R中的时间日期(Date,POSIXct, POSIXlt...)格式是一项非常常见的工作。虽然有时候我们会发现有些任务不一定需要转换成日期格式就能完成,但是很多时候转化成日期格式是更安全的做法,而且会大大提高工作效率。
把字符形式的时间转换成专门的时间格式的优点有:
1
排序安全
例如我们有两个以字符存储的时间“2018-8-3”和“2018-12-2”,虽然“2018-8-3”显然比“2018-12-2”小,但是在R中如果运行"2018-8-3" < "2018-12-2",我们会得到FALSE。原因就在于后者的月份是“12”,他的第一个字符“1”要笔前者的“8”来的小。当然,如果我们日期严格按照ISO-8601标准,把所有空位都用0来补上,那么R的确能够得到正确的结果("2018-08-03" < "2018-12-02")。然而,谁能保证我们拿到的数据都是完美遵循ISO标准的呢?
2
速度快
一般来说,时间日期格式在R内部都是用整数来代表的,因为整数占用空间小,运算速度特别块。举个例子,如果用ASCII编码字符“2019-01-19”,里面有10个字符,每个字符占用8 bit,那么一共要用80 bit,但是在R的“Date”格式中,这个日期实际上用整数“17951”代表(R中的代码是:as.integer(as.Date('2019-01-19'))),该整数表示从1970-01-01开始所经过的天数。由于在R中整数只占用32 bit,很显然,用整数存储占用空间小。其次,很多运算都在底层对整数做了优化,因此处理起来要远远快于字符。
3
提供给你无穷可能
一旦把字符时间转换成特定的时间日期格式,那么我们就可能充分利用R中众多的时间日期函数。例如weekday(date),可以直接返回该日期对应星期几。我们甚至可以直接对日期进行运算,例如我们运行 as.Date('2019-01-10') - as.Date('2019-01-01'),其返回结果就是“Time difference of 9 days”。
“
那么现在问题来了,R中提供了许多函数来完成字符时间的解析,我们究竟应该用哪个好呢?具体而言,最常见的解析函数有 as.Date() (R自带), as.IDate() (data.table包),ymd() (lubridate包), fast_strptime() (lubridate包)。接下来我们就逐一测过来!
”
函
数测试
先来看以下我们的样例数据集:
非常简单,只有两列变量“id”和“date”,其中date是字符格式,从“0001-01-01”开始逐日递增一直到“2738-11-28”,共有100万行。我们选择这样一个大数是因为只有数据比较多的情况下不同函数运算时间的差别才会比较明显。
以下是生成样例数据集的代码,其中,生成的数据集叫做“dt”:
# 我们这里生成100万行日期数据。如果这个数字太小,可能不同函数的用时差别并不明显。
n <- 1e6
dt <- data.table(id = seq_len(n), date = seq(as.Date("0001-01-01"), len = n, by = "day") %>% as.character)
测试方法
我们使用“microbenchmark”这个包来完成测试。这个包很简单,只要输入你的代码,并且指定“times=N”,程序就会重复运行你的代码N次,然后返回运行时间的平均值。默认的话times=100,由于大猫比较懒,因此只设置了times=5.
测试结果
microbenchmark(dt[, .(id, date = as.Date(date))], times = 5) # 0.93 s
microbenchmark(dt[, .(id, date = as.IDate(date))], times = 5) # 0.93 s
microbenchmark(dt[, .(id, date = ymd(date))], times = 5) # 0.018 s
microbenchmark(dt[, .(id, date = fast_strptime(date, "%Y-%m-%d", lt = F) %>% as.IDate())], times = 5) # 0.006 s
# 结论:fast_strptime最快,但如果for less key stoke,推荐用ymd
很明显,来自lubridate包的fast_strptime同学遥遥领先,它的性能达到了R自带的as.Date函数的155x!同样来自lubirdate的ymd同学虽然稍逊一筹,但是仍然是as.Date的51x。不过,快也是有代价的,为了使用fast_strptime,我们必须手动指定需要解析的日期格式,在我们的例子中就是"%Y-%m-%d"。这些%是国际通用的表示时间日期的符号,有兴趣的小伙伴可以搜“ISO-8601标准”(懒人劝退)。
为
什么fast_strptime那么快?
因为fast_strptime是用C实现的,根据文档,
> fast_strptime() is a fast C parser of numeric formats only that accepts explicit format arguments, just as base::strptime().
结
论
fast_strptime最快,但如果想偷懒ymd似乎是一个很好的折中。
写在最后
实际上在lubridate包中,ymd并不是一个函数,而是一个家族,包括了ymd_hms(年月日_时分秒),mdy等多个变体。而且fast_strptime也有对应的变体,例如parse_date_time和parse_date_time2。不过这些变体所用的时间和其原型几乎没有差别,因此并没有放到本次测试中。
大猫的R语言课堂
我是大猫,一个高中读文科但却在代码、数学的路上狂奔不止的Finance Ph. D Candidate。
我是村长,一个玩了9年指弹吉他,却被代码深深吸引的博士候选人。
大猫的微信号是:
iRoss2007
村长的B站主页是:http://space.bilibili.com/40771572