在分组的时间序列中填写缺少的日期 - tidyverse-way?

时间:2017-09-09 11:38:45

标签: r dplyr time-series tidyverse

给定一个包含时间序列和一个或多个分组字段的data.frame。所以我们有几个时间序列 - 每个分组组合一个。 但有些日期不见了。 那么,使用正确的分组值添加这些日期最简单(就最“整齐”的方式而言)是什么?

通常情况下,我会说我生成一个包含所有日期的data.frame,并使用我的时间序列执行full_join。但现在我们必须为每个分组值组合执行此操作 - 并填写分组值。

让我们看一个例子:

首先,我创建一个缺少值的data.frame:

library(dplyr)
library(lubridate)

set.seed(1234)
# Time series should run vom 2017-01-01 til 2017-01-10
date <- data.frame(date = seq.Date(from=ymd("2017-01-01"), to=ymd("2017-01-10"), by="days"), v = 1)
# Two grouping dimensions
d1   <- data.frame(d1 = c("A", "B", "C", "D"), v = 1)
d2   <- data.frame(d2 = c(1, 2, 3, 4, 5), v = 1)

# Generate the data.frame
df <- full_join(date, full_join(d1, d2)) %>%
  select(date, d1, d2) 
# and ad to value columns
df$v1 <- runif(200)
df$v2 <- runif(200)

# group by the dimension columns
df <- df %>% 
  group_by(d1, d2)

# create missing dates
df.missing <- df %>%
  filter(v1 <= 0.8)

# So now  2017-01-01 and 2017-01-10, A, 5 are missing now
df.missing %>%
  filter(d1 == "A" & d2 == 5)

# A tibble: 8 x 5
# Groups:   d1, d2 [1]
        date     d1    d2         v1        v2
      <date> <fctr> <dbl>      <dbl>     <dbl>
1 2017-01-02      A     5 0.21879954 0.1335497
2 2017-01-03      A     5 0.32977018 0.9802127
3 2017-01-04      A     5 0.23902573 0.1206089
4 2017-01-05      A     5 0.19617465 0.7378315
5 2017-01-06      A     5 0.13373890 0.9493668
6 2017-01-07      A     5 0.48613541 0.3392834
7 2017-01-08      A     5 0.35698708 0.3696965
8 2017-01-09      A     5 0.08498474 0.8354756

因此,要添加缺少的日期,我会生成一个包含所有日期的data.frame:

start <- min(df.missing$date)
end   <- max(df.missing$date)

all.dates <- data.frame(date=seq.Date(start, end, by="day"))

不,我想做点什么(记住:df.missing是group_by(d1,d2))

df.missing %>%
  do(my_join())

所以让我们定义my_join():

my_join <- function(data) {
  # get value of both dimensions
  d1.set <- data$d1[[1]]
  d2.set <- data$d2[[1]]

  tmp <- full_join(data, all.dates) %>%
    # First we need to ungroup.  Otherwise we can't change d1 and d2 because they are grouping variables
    ungroup() %>%
    mutate(
      d1 = d1.set,
      d2 = d2.set 
    ) %>%
    group_by(d1, d2)

  return(tmp)
}

现在我们可以为每个组合调用my_join()并查看“A / 5”

df.missing %>%
  do(my_join(.)) %>%
  filter(d1 == "A" & d2 == 5)

# A tibble: 10 x 5
# Groups:   d1, d2 [1]
         date     d1    d2         v1        v2
       <date> <fctr> <dbl>      <dbl>     <dbl>
 1 2017-01-02      A     5 0.21879954 0.1335497
 2 2017-01-03      A     5 0.32977018 0.9802127
 3 2017-01-04      A     5 0.23902573 0.1206089
 4 2017-01-05      A     5 0.19617465 0.7378315
 5 2017-01-06      A     5 0.13373890 0.9493668
 6 2017-01-07      A     5 0.48613541 0.3392834
 7 2017-01-08      A     5 0.35698708 0.3696965
 8 2017-01-09      A     5 0.08498474 0.8354756
 9 2017-01-01      A     5         NA        NA
10 2017-01-10      A     5         NA        NA

大!这就是我们正在寻找的东西。 但我们需要在my_join中定义d1和d2,感觉有点笨拙。

那么,这个解决方案有什么不可逆转的方式吗?

P.S。:我把代码放在一个要点:https://gist.github.com/JerryWho/1bf919ef73792569eb38f6462c6d7a8e

4 个答案:

答案 0 :(得分:12)

tidyr有一些很好的工具可以解决这些问题。看看complete


library(dplyr)
library(tidyr)
library(lubridate)

want <- df.missing %>% 
  ungroup() %>%
  complete(nesting(d1, d2), date = seq(min(date), max(date), by = "day"))

want %>% filter(d1 == "A" & d2 == 5) 

#> # A tibble: 10 x 5
#> # Groups:   d1 [1]
#>        d1    d2       date         v1        v2
#>    <fctr> <dbl>     <date>      <dbl>     <dbl>
#>  1      A     5 2017-01-01         NA        NA
#>  2      A     5 2017-01-02 0.21879954 0.1335497
#>  3      A     5 2017-01-03 0.32977018 0.9802127
#>  4      A     5 2017-01-04 0.23902573 0.1206089
#>  5      A     5 2017-01-05 0.19617465 0.7378315
#>  6      A     5 2017-01-06 0.13373890 0.9493668
#>  7      A     5 2017-01-07 0.48613541 0.3392834
#>  8      A     5 2017-01-08 0.35698708 0.3696965
#>  9      A     5 2017-01-09 0.08498474 0.8354756
#> 10      A     5 2017-01-10         NA        NA

答案 1 :(得分:0)

这是从df.missing

开始的一种整齐的方式
library(tidyverse)
ans <- df.missing %>% 
          nest(date) %>% 
          mutate(data = map(data, ~seq.Date(start, end, by="day"))) %>% 
          unnest(data) %>%
          rename(date = data) %>%
          left_join(., df.missing, by=c("date","d1","d2"))

ans %>% filter(d1 == "A" & d2 == 5) 

输出

      d1    d2       date         v1        v2
   <fctr> <dbl>     <date>      <dbl>     <dbl>
 1      A     5 2017-01-01         NA        NA
 2      A     5 2017-01-02 0.21879954 0.1335497
 3      A     5 2017-01-03 0.32977018 0.9802127
 4      A     5 2017-01-04 0.23902573 0.1206089
 5      A     5 2017-01-05 0.19617465 0.7378315
 6      A     5 2017-01-06 0.13373890 0.9493668
 7      A     5 2017-01-07 0.48613541 0.3392834
 8      A     5 2017-01-08 0.35698708 0.3696965
 9      A     5 2017-01-09 0.08498474 0.8354756
10      A     5 2017-01-10         NA        NA

<强> -------------------------------------------- -------------------------------------------------- ---
这是使用expand.griddplyr动词

的替代方法
with(df.missing, expand.grid(unique(date), unique(d1), unique(d2))) %>%
  setNames(c("date", "d1", "d2")) %>%
  left_join(., df.missing, by=c("date","d1","d2"))

输出(头)

          date d1 d2          v1          v2
1   2017-01-01  A  1 0.113703411 0.660754634
2   2017-01-02  A  1 0.316612455 0.422330675
3   2017-01-03  A  1 0.553333591 0.424109178
4   2017-01-04  A  1          NA          NA
5   2017-01-05  A  1          NA          NA
6   2017-01-06  A  1 0.035456727 0.352998502   

答案 2 :(得分:0)

这里read.zoo创建一个宽格式的动物园对象,然后合并日期。然后,我们使用fortify.zoo将其转换回长数据框,并使用v1展开v2spread

请注意:

  • 如果我们可以假设每个日期出现在至少一个拆分变量的组合中,即sort(unique(df.missing$date))包含所有日期,那么我们可以省略merge行,并且不会加入必须要做。问题中显示的测试数据df.missing确实具有以下属性:

    all(all.dates$date %in% df.missing$date)
    ## [1] TRUE
    
  • 如果可以使用宽格式动物园对象,我们可以在merge之后(或read.zoo之后,如果每个日期至少出现一次,如前一点那样)所有日期。

在下面的代码中,使用zoo(1.8.1)的开发版本可以省略标记为###的行:

library(dplyr)
library(tidyr)
library(zoo)

split.vars <- c("d1", "d2")
df.missing %>%
   as.data.frame %>%     ###
   read.zoo(split = split.vars) %>%
   merge(zoo(, seq(start(.), end(.), "day"))) %>%
   fortify.zoo(melt = TRUE) %>%
   separate(Series, c("v", split.vars)) %>%
   spread(v, Value)

更新:请注意动物园1.8.1中的简化。

答案 3 :(得分:0)

软件包tsibble函数fill_gaps应该可以轻松完成这项工作。

library(tsibble)
df.missing %>% 
  # tsibble format
  as_tsibble(key = c(d1, d2), index = date) %>% 
  # fill gaps
  fill_gaps(.full = TRUE)