问
题引入
对日期进行插值是一项非常常见的任务。很多时候我们手头的时间序列都是不完整的,当中总会因为这样那样的原因漏了几天的观测,例如股票停牌了,观测仪器坏了,值班工人生病了等等。在分析时,我们为了获得完整的时间序列就需要“插入”那些丢失的日期。
举一个例子:
这个数据集中有5行观测,2组分类(id等于1和2)。我们看到每个id对应的date都是有缺失的,例如从2001-01-09直接跳到了2001-01-12,当中少了10号和11号。
如何只用一行代码就高效优美地把这些缺失的日期补上呢?
附:生成样例数据集的文件:
# sample dataset
# id变量用于分组
dt <- data.table(id = c(1, 1, 1, 2, 2), date = c(as.Date("2000-01-09"), as.Date("2000-01-12"), as.Date("2000-01-14"), as.Date("2000-02-09"), as.Date("2000-02-12")), val = rnorm(5))
本文需要用到data.table包!
情
况1:每个group起讫时间相等
首先来说第一种情况,在这种情况下,每个id都对应着“相同”的日期起讫点,例如,全都是从2000-01-08至2000-02-13。此时,我们相当于要构造出一个“平衡的”面板数据。
解决思路是运用data.table包的merge功能。首先我们建立一个CJ(cross join)数据集,这个数据集包含每个id所对应的“完整”日期。
# 建立“完整”的日期序列
CJ <- CJ(id = unique(dt$id),
date = seq(as.Date("2000-01-08"),
as.Date("2000-02-13"),
by = "day"))
CJ数据集长这个样子(节选前11行和后11行):
我们看到CJ数据集中,每个id所对应的时间都被填充完整了。
(在建立CJ数据集的过程中,我们使用了seq函数来建立完整的时间序列)
接下来,我们把CJ数据集merge回原来的数据集dt。在merge的过程中,我们指定id和date变量必须匹配,也即on = .(id, date)语句的作用:
# 把CJ函数merge回原始数据集
dt[CJ, on = .(id, date), nomatch = NA]
结果为:
我们看到,原数据集存在观测的那些日期,val值都被保留,而被插入的那些日期,val是NA。当然,我们可以修改上一条语句中的nomatch参数把填充指改成任意值,例如0。
情
况2:每个group起讫时间不等
另一种情况是每个group的起讫时间不等。例如,在我们的样例数据集sample中,id=1的观测对应的日期最小值的为01-08,最大值为01-14,而我们希望填充这两个日期“之间”的所有值。同理,对于id=2的观测,日期最大值为02-09,最小值为02-12,我希望填充就是02-10,02-11这两天。
思路和情况1类似,我们先构造CJ数据集,只不过在这里我们seq函数的起讫点不再是固定值,而是每个id对应日期的最大值与最小值:
# 建立完整的日期序列
# 注意min和max函数的作用
CJ <- dt[, .(date = seq(min(date),
max(date), by = "day")),
keyby = id]
接下来我们把CJ数据集merge回dt数据集:
# 把CJ数据集merge回dt数据集
dt[CJ, on = .(id, date), nomatch = NA]
结果是:
大功告成!
拓
展
等等,你不是说可以在一行当中搞定的吗?当然没问题,以上文提到的第二种情况为例,我们可以把两行合并为一行:
# 把两行代码合并成一行
dt[dt[, .(date = seq(min(date),
max(date), by = "day")),
keyby = id],
on = .(id, date),
nomatch = NA]
这也是大猫喜欢data.table的一个原因:由于语法的灵活性,可以少生成很多中间数据集,这样也就不用绞尽脑汁为那些中间数据集命名了。处女座无数次为了给数据集取一个合适的名字心力交瘁……
下
期预告
根据官网公告,Microsoft R Open 3.4版本将会“coming soon in May”,大猫会在第一时间给大家发布号外~