如何在 R 中表示和合并具有*日期范围*的时间序列数据帧?
Posted
技术标签:
【中文标题】如何在 R 中表示和合并具有*日期范围*的时间序列数据帧?【英文标题】:How do I represent and merge time-series data-frames with a *date range* in R? 【发布时间】:2022-01-21 17:43:53 【问题描述】:我在R
工作,我有一些时间序列数据,其中相关时间是日期范围 --- 即,每一行都有开始日期和结束日期,并且该记录指定了该期间的结果。我希望能够在R
中以适当的方式表示此对象,并将其与具有特定日期的其他时间序列合并。在合并的情况下,我希望我的第一个时间序列(使用日期范围的那个)中的每条记录都与该范围内的日期相匹配,如果有两个或范围内的更具体的日期。这是我希望它使用模拟数据执行的一个简单示例:
#Time series with a date-range
TIME.SERIES1
Start End Value
2018-01-01 2018-01-31 80.6
2018-02-01 2018-02-28 140.5
2018-03-01 2018-03-31 122.1
2018-04-01 2018-04-30 108.5
2018-05-01 2018-05-31 96.0
2018-06-01 2018-06-30 103.3
#Time series using specific dates
TIME.SERIES2
Date Staff
2018-01-15 551
2018-02-14 606
2018-05-18 640
2018-06-02 635
2018-07-15 633
#Merge these using left-join of TIME.SERIES1 and TIME.SERIES2
[some merge command]
Start End Value Staff
2018-01-01 2018-01-31 80.6 551
2018-02-01 2018-02-28 140.5 606
2018-03-01 2018-03-31 122.1 NA
2018-04-01 2018-04-30 108.5 NA
2018-05-01 2018-05-31 96.0 640
2018-06-01 2018-06-30 103.3 635
我知道您可以在 SQL
中相当轻松地进行这种类型的合并,但我不确定如何在 R
中进行。我之前使用过xls
包进行时间序列分析,但不知道它是否能完成我想做的事情。这个包似乎允许我指定一个日期作为索引,但不是一个日期范围。同样,它似乎允许我在一个日期合并两个时间序列对象,但我不确定如何根据日期范围进行合并(以上面显示的方式)。
问题:R
中的时间序列工具能否处理时间索引为日期范围而不是特定日期的对象(即,可以您有一个包含开始时间和结束时间的索引)?我们如何将具有日期范围的数据与具有单个日期的日期合并?
【问题讨论】:
您的数据有多大? @kybazzi:虽然上面的例子很小,但实际上我的数据集非常大(几百万行)。 【参考方案1】:一个想法是创建一个笛卡尔积,然后应用过滤器以仅保留有效的案例。这种方法的好处是您可以对数据使用常规操作(分组、变异、过滤),从而为解决关系带来了灵活性。
感谢 G. Grothendieck 从问题中创建数据集的代码。
library(tidyverse)
library(lubridate)
# See G. Grothendieck's answer for ts1 and ts2
value = tibble(ts1)
staff = tibble(ts2)
full_join(value, staff, by = character()) %>%
mutate(valid = Date %within% interval(Start, End)) %>%
group_by(Start, End, Value) %>%
arrange(Date) %>%
summarise(Staff = Staff[valid][1])
输出
Start End Value Staff
<date> <date> <dbl> <int>
1 2018-01-01 2018-01-31 80.6 551
2 2018-02-01 2018-02-28 140. 606
3 2018-03-01 2018-03-31 122. NA
4 2018-04-01 2018-04-30 108. NA
5 2018-05-01 2018-05-31 96 640
6 2018-06-01 2018-06-30 103. 635
请注意,在此解决方案中,我使用了范围内最早的 Staff
值。您可以轻松更改此设置以满足您的要求。
sqldf
解决方案
library(sqldf)
library(tidyverse)
sqldf(
"SELECT *
FROM value
LEFT JOIN staff
ON staff.Date <= value.End AND staff.Date >= value.Start"
) %>%
group_by(Start, End, Value) %>%
summarise(Staff = Staff[Date == min(Date)], .groups = "drop")
【讨论】:
(+1) 这是一个很好的解决方案。我唯一担心的是,对于大型数据帧,笛卡尔积会很大,这可能使其在计算上不可行。 @Ben 你提到你可以在 SQL 中轻松地做到这一点,这是一个很好的观点 - 我已经使用sqldf
包添加了一个解决方案。
tidyverse 部分可能不需要,它可以简化为:library(sqldf); sqldf("SELECT * FROM ts1 v LEFT JOIN ts2 s ON s.Date between v.Start and v.End")
虽然问题中没有要求,但如果有多个匹配项并且您只想要最早匹配的 Date 它可能完全是像这样在 SQL 中完成:library(sqldf); sqldf("SELECT v.*, min(s.Date) Date FROM ts1 v LEFT JOIN ts2 s ON s.Date between v.Start and v.End group by v.rowid")
【参考方案2】:
由于期间始终是完整的单月,并且假设输入是末尾注释中显示的数据框,我们假设所有日期都属于 Date 类,因此向两者添加 yearmon 类列并加入该列。
library(zoo)
ts1$ym <- as.yearmon(ts1$Start)
ts2$ym <- as.yearmon(ts2$Date)
library(dplyr)
left_join(ts1, ts2, "ym")
给予:
Start End Value ym Date Staff
1 2018-01-01 2018-01-31 80.6 Jan 2018 2018-01-15 551
2 2018-02-01 2018-02-28 140.5 Feb 2018 2018-02-14 606
3 2018-03-01 2018-03-31 122.1 Mar 2018 <NA> NA
4 2018-04-01 2018-04-30 108.5 Apr 2018 <NA> NA
5 2018-05-01 2018-05-31 96.0 May 2018 2018-05-18 640
6 2018-06-01 2018-06-30 103.3 Jun 2018 2018-06-02 635
注意
TIME.SERIES1
Lines1 <- "
Start End Value
2018-01-01 2018-01-31 80.6
2018-02-01 2018-02-28 140.5
2018-03-01 2018-03-31 122.1
2018-04-01 2018-04-30 108.5
2018-05-01 2018-05-31 96.0
2018-06-01 2018-06-30 103.3"
ts1 <- transform(read.table(text = Lines1, header = TRUE),
Start = as.Date(Start), End = as.Date(End))
Lines2 <- "
Date Staff
2018-01-15 551
2018-02-14 606
2018-05-18 640
2018-06-02 635
2018-07-15 633"
ts2 <- transform(read.table(text = Lines2, header = TRUE),
Date = as.Date(Date))
【讨论】:
(+1) 我的错 --- 我可能让我的例子比我真正想知道的更简单 --- 为了简单起见,我给出的例子是用整整几个月构建的,但我当您有 任何 个日期范围时,即使该日期范围不方便跨越一个月,我也会更广泛地加入。以上是关于如何在 R 中表示和合并具有*日期范围*的时间序列数据帧?的主要内容,如果未能解决你的问题,请参考以下文章