rtime-seriestsibble

Fill gaps in a grouped tsibble with group-specific starts and ends


I've got a tsibble where timestamped observational data has been aggregated to 30-minute intervals. The data is in several groups, and I'd like to make sure that each 30-minute group appears in the tsibble, even when there were no observations in that time period.

Let's return to the birdwatching example from my previous question about tsibbles. Suppose I'm watching duck and geese at a certain location from 8:00 to 18:00 each day and recording, for each observation, a) the time, b) the type of bird observed, and c) the number of birds in the flock observed.

library(tidyverse) # includes lubridate
library(tsibble)

N <- 10
set.seed(42)

# suppose we're observing ducks and geese between 8:00 and 18:00.
d       <- as_datetime("2023-03-08 08:00:00")
times   <- d + seconds(unique(round(sort(runif(N, min = 0, max = 36e3)))))
nObs    <- 1 + rpois(length(times), lambda = 1)
birdIdx <- 1 + round(runif(length(times)))
birds   <- c("Duck", "Goose")[birdIdx]

# Tibble of observations
waterfowl <- tibble(Timestamp = times, Count = nObs, Bird = as_factor(birds))

# Convert to tsibble (time series tibble) and aggregate on a 30-minute basis
waterfowl |>
    as_tsibble(index = Timestamp) |>
    group_by(Bird) |>
    index_by(Interval = floor_date(Timestamp, "30 minute")) |>
    summarize(`Total birds` = sum(Count)) -> waterfowlSumm

waterfowlSumm |> print(n = Inf)

This gives

# A tsibble: 10 x 3 [30m] <UTC>
# Key:       Bird [2]
   Bird  Interval            `Total birds`
   <fct> <dttm>                      <dbl>
 1 Goose 2023-03-08 09:00:00             2
 2 Goose 2023-03-08 13:00:00             4
 3 Goose 2023-03-08 14:00:00             1
 4 Goose 2023-03-08 15:00:00             4
 5 Goose 2023-03-08 16:00:00             1
 6 Goose 2023-03-08 17:00:00             2
 7 Duck  2023-03-08 10:30:00             2
 8 Duck  2023-03-08 14:30:00             2
 9 Duck  2023-03-08 15:00:00             4
10 Duck  2023-03-08 17:00:00             2

What I'd like to do is fill missing intervals. I can use fill_gaps for this:

> waterfowlSumm |> fill_gaps(`Total birds` = 0) |> print(n = Inf)
# A tsibble: 31 x 3 [30m] <UTC>
# Key:       Bird [2]
   Bird  Interval            `Total birds`
   <fct> <dttm>                      <dbl>
 1 Goose 2023-03-08 09:00:00             2
 2 Goose 2023-03-08 09:30:00             0
 3 Goose 2023-03-08 10:00:00             0
...
15 Goose 2023-03-08 16:00:00             1
16 Goose 2023-03-08 16:30:00             0
17 Goose 2023-03-08 17:00:00             2
18 Duck  2023-03-08 10:30:00             2
19 Duck  2023-03-08 11:00:00             0
20 Duck  2023-03-08 11:30:00             0
...
29 Duck  2023-03-08 16:00:00             0
30 Duck  2023-03-08 16:30:00             0
31 Duck  2023-03-08 17:00:00             2

However, since I start watching birds at 8:00 and stop at 18:00, I'd like to fill in missing intervals beyond the times where I actually observed birds. So I might do

> waterfowlSumm |> fill_gaps(`Total birds` = 0, .start = d, .end = d + hours(9) + minutes(30)) |> print(n = Inf)
# A tsibble: 40 x 3 [30m] <UTC>
# Key:       Bird [2]
   Bird  Interval            `Total birds`
   <fct> <dttm>                      <dbl>
 1 Goose 2023-03-08 08:00:00             0
 2 Goose 2023-03-08 08:30:00             0
 3 Goose 2023-03-08 09:00:00             2
...
18 Goose 2023-03-08 16:30:00             0
19 Goose 2023-03-08 17:00:00             2
20 Goose 2023-03-08 17:30:00             0
21 Duck  2023-03-08 08:00:00             0
22 Duck  2023-03-08 08:30:00             0
23 Duck  2023-03-08 09:00:00             0
...
38 Duck  2023-03-08 16:30:00             0
39 Duck  2023-03-08 17:00:00             2
40 Duck  2023-03-08 17:30:00             0

This works. However, now suppose that my data has additional grouping variables --- say, I'm observing birds at several sites. Of course, since I can't be in two places at the same time, each site has a different observer. And different observers have different working hours, so .start and .end must be set on a per-group basis.

The start/end times are available in my data, but .start and .end apparently can't be pulled from the tsibble being operated on:

> waterfowlSumm |> mutate(Start = d, End = d + hours(9) + minutes(30)) |> fill_gaps(`Total birds` = 0, .start = Start, .end = End)
Error in scan_gaps.tbl_ts(.data, .full = !!enquo(.full), .start = .start,  : 
  object 'Start' not found

So my question is: how do I do this? I'd really like to be able to use grouping (in this example I only have one group to begin with, but in reality there are many) so I only have to invoke fill_gaps once, with the correct start/end being pulled from the tsibble.

Thanks!


Solution

  • The fill_gaps() function converts the implicit missing values into explicit missing values, based on either the local (per series) or global (per dataset) start and end dates and the index class.

    Using fill_gaps() without specifying the .start and .end date will compute the time range for each series, and fill in any missing time points based on the data's time interval. This should work for your problem of different counting ranges for sites and birds.

    However if you are working with multiple days, the fill_gaps() function will also add in the overnight hours between working days (as the interval is 30 minutes, and data is missing overnight). So you might want to instead fill implicit missing values with NA, and then maintain a working hours dataset that can be joined onto your observations data and used to convert NA to 0 if someone was working. For example:

    library(tidyverse) # includes lubridate
    library(tsibble)
    #> 
    #> Attaching package: 'tsibble'
    #> The following object is masked from 'package:lubridate':
    #> 
    #>     interval
    #> The following objects are masked from 'package:base':
    #> 
    #>     intersect, setdiff, union
    library(fable)
    #> Loading required package: fabletools
    
    N <- 10
    set.seed(42)
    
    # suppose we're observing ducks and geese between 8:00 and 18:00.
    d       <- as_datetime("2023-03-08 08:00:00")
    times   <- d + seconds(unique(round(sort(runif(N, min = 0, max = 36e3)))))
    nObs    <- 1 + rpois(length(times), lambda = 1)
    birdIdx <- 1 + round(runif(length(times)))
    birds   <- c("Duck", "Goose")[birdIdx]
    
    # Tibble of observations
    waterfowl <- tibble(Timestamp = times, Count = nObs, Bird = as_factor(birds))
    
    # Add day 2
    waterfowl <- bind_rows(waterfowl, waterfowl |> mutate(Timestamp = Timestamp + days(1)))
    
    # Convert to tsibble (time series tibble) and aggregate on a 30-minute basis
    waterfowl |>
      as_tsibble(index = Timestamp) |>
      group_by(Bird) |>
      index_by(Interval = floor_date(Timestamp, "30 minute")) |>
      summarize(`Total birds` = sum(Count)) -> waterfowlSumm
    
    waterfowlSumm |> print(n = Inf)
    #> # A tsibble: 20 x 3 [30m] <UTC>
    #> # Key:       Bird [2]
    #>    Bird  Interval            `Total birds`
    #>    <fct> <dttm>                      <dbl>
    #>  1 Goose 2023-03-08 09:00:00             2
    #>  2 Goose 2023-03-08 13:00:00             4
    #>  3 Goose 2023-03-08 14:00:00             1
    #>  4 Goose 2023-03-08 15:00:00             4
    #>  5 Goose 2023-03-08 16:00:00             1
    #>  6 Goose 2023-03-08 17:00:00             2
    #>  7 Goose 2023-03-09 09:00:00             2
    #>  8 Goose 2023-03-09 13:00:00             4
    #>  9 Goose 2023-03-09 14:00:00             1
    #> 10 Goose 2023-03-09 15:00:00             4
    #> 11 Goose 2023-03-09 16:00:00             1
    #> 12 Goose 2023-03-09 17:00:00             2
    #> 13 Duck  2023-03-08 10:30:00             2
    #> 14 Duck  2023-03-08 14:30:00             2
    #> 15 Duck  2023-03-08 15:00:00             4
    #> 16 Duck  2023-03-08 17:00:00             2
    #> 17 Duck  2023-03-09 10:30:00             2
    #> 18 Duck  2023-03-09 14:30:00             2
    #> 19 Duck  2023-03-09 15:00:00             4
    #> 20 Duck  2023-03-09 17:00:00             2
    
    # This adds 0 between the days
    waterfowlSumm |> fill_gaps(`Total birds` = 0) |> print(n = Inf)
    #> # A tsibble: 127 x 3 [30m] <UTC>
    #> # Key:       Bird [2]
    #>     Bird  Interval            `Total birds`
    #>     <fct> <dttm>                      <dbl>
    #>   1 Goose 2023-03-08 09:00:00             2
    #>   2 Goose 2023-03-08 09:30:00             0
    #>   3 Goose 2023-03-08 10:00:00             0
    #>   4 Goose 2023-03-08 10:30:00             0
    #>   5 Goose 2023-03-08 11:00:00             0
    #>   6 Goose 2023-03-08 11:30:00             0
    #>   7 Goose 2023-03-08 12:00:00             0
    #>   8 Goose 2023-03-08 12:30:00             0
    #>   9 Goose 2023-03-08 13:00:00             4
    #>  10 Goose 2023-03-08 13:30:00             0
    #>  11 Goose 2023-03-08 14:00:00             1
    #>  12 Goose 2023-03-08 14:30:00             0
    #>  13 Goose 2023-03-08 15:00:00             4
    #>  14 Goose 2023-03-08 15:30:00             0
    #>  15 Goose 2023-03-08 16:00:00             1
    #>  16 Goose 2023-03-08 16:30:00             0
    #>  17 Goose 2023-03-08 17:00:00             2
    #>  18 Goose 2023-03-08 17:30:00             0
    #>  19 Goose 2023-03-08 18:00:00             0
    #>  20 Goose 2023-03-08 18:30:00             0
    #>  21 Goose 2023-03-08 19:00:00             0
    #>  22 Goose 2023-03-08 19:30:00             0
    #>  23 Goose 2023-03-08 20:00:00             0
    #>  24 Goose 2023-03-08 20:30:00             0
    #>  25 Goose 2023-03-08 21:00:00             0
    #>  26 Goose 2023-03-08 21:30:00             0
    #>  27 Goose 2023-03-08 22:00:00             0
    #>  28 Goose 2023-03-08 22:30:00             0
    #>  29 Goose 2023-03-08 23:00:00             0
    #>  30 Goose 2023-03-08 23:30:00             0
    #>  31 Goose 2023-03-09 00:00:00             0
    #>  32 Goose 2023-03-09 00:30:00             0
    #>  33 Goose 2023-03-09 01:00:00             0
    #>  34 Goose 2023-03-09 01:30:00             0
    #>  35 Goose 2023-03-09 02:00:00             0
    #>  36 Goose 2023-03-09 02:30:00             0
    #>  37 Goose 2023-03-09 03:00:00             0
    #>  38 Goose 2023-03-09 03:30:00             0
    #>  39 Goose 2023-03-09 04:00:00             0
    #>  40 Goose 2023-03-09 04:30:00             0
    #>  41 Goose 2023-03-09 05:00:00             0
    #>  42 Goose 2023-03-09 05:30:00             0
    #>  43 Goose 2023-03-09 06:00:00             0
    #>  44 Goose 2023-03-09 06:30:00             0
    #>  45 Goose 2023-03-09 07:00:00             0
    #>  46 Goose 2023-03-09 07:30:00             0
    #>  47 Goose 2023-03-09 08:00:00             0
    #>  48 Goose 2023-03-09 08:30:00             0
    #>  49 Goose 2023-03-09 09:00:00             2
    #>  50 Goose 2023-03-09 09:30:00             0
    #>  51 Goose 2023-03-09 10:00:00             0
    #>  52 Goose 2023-03-09 10:30:00             0
    #>  53 Goose 2023-03-09 11:00:00             0
    #>  54 Goose 2023-03-09 11:30:00             0
    #>  55 Goose 2023-03-09 12:00:00             0
    #>  56 Goose 2023-03-09 12:30:00             0
    #>  57 Goose 2023-03-09 13:00:00             4
    #>  58 Goose 2023-03-09 13:30:00             0
    #>  59 Goose 2023-03-09 14:00:00             1
    #>  60 Goose 2023-03-09 14:30:00             0
    #>  61 Goose 2023-03-09 15:00:00             4
    #>  62 Goose 2023-03-09 15:30:00             0
    #>  63 Goose 2023-03-09 16:00:00             1
    #>  64 Goose 2023-03-09 16:30:00             0
    #>  65 Goose 2023-03-09 17:00:00             2
    #>  66 Duck  2023-03-08 10:30:00             2
    #>  67 Duck  2023-03-08 11:00:00             0
    #>  68 Duck  2023-03-08 11:30:00             0
    #>  69 Duck  2023-03-08 12:00:00             0
    #>  70 Duck  2023-03-08 12:30:00             0
    #>  71 Duck  2023-03-08 13:00:00             0
    #>  72 Duck  2023-03-08 13:30:00             0
    #>  73 Duck  2023-03-08 14:00:00             0
    #>  74 Duck  2023-03-08 14:30:00             2
    #>  75 Duck  2023-03-08 15:00:00             4
    #>  76 Duck  2023-03-08 15:30:00             0
    #>  77 Duck  2023-03-08 16:00:00             0
    #>  78 Duck  2023-03-08 16:30:00             0
    #>  79 Duck  2023-03-08 17:00:00             2
    #>  80 Duck  2023-03-08 17:30:00             0
    #>  81 Duck  2023-03-08 18:00:00             0
    #>  82 Duck  2023-03-08 18:30:00             0
    #>  83 Duck  2023-03-08 19:00:00             0
    #>  84 Duck  2023-03-08 19:30:00             0
    #>  85 Duck  2023-03-08 20:00:00             0
    #>  86 Duck  2023-03-08 20:30:00             0
    #>  87 Duck  2023-03-08 21:00:00             0
    #>  88 Duck  2023-03-08 21:30:00             0
    #>  89 Duck  2023-03-08 22:00:00             0
    #>  90 Duck  2023-03-08 22:30:00             0
    #>  91 Duck  2023-03-08 23:00:00             0
    #>  92 Duck  2023-03-08 23:30:00             0
    #>  93 Duck  2023-03-09 00:00:00             0
    #>  94 Duck  2023-03-09 00:30:00             0
    #>  95 Duck  2023-03-09 01:00:00             0
    #>  96 Duck  2023-03-09 01:30:00             0
    #>  97 Duck  2023-03-09 02:00:00             0
    #>  98 Duck  2023-03-09 02:30:00             0
    #>  99 Duck  2023-03-09 03:00:00             0
    #> 100 Duck  2023-03-09 03:30:00             0
    #> 101 Duck  2023-03-09 04:00:00             0
    #> 102 Duck  2023-03-09 04:30:00             0
    #> 103 Duck  2023-03-09 05:00:00             0
    #> 104 Duck  2023-03-09 05:30:00             0
    #> 105 Duck  2023-03-09 06:00:00             0
    #> 106 Duck  2023-03-09 06:30:00             0
    #> 107 Duck  2023-03-09 07:00:00             0
    #> 108 Duck  2023-03-09 07:30:00             0
    #> 109 Duck  2023-03-09 08:00:00             0
    #> 110 Duck  2023-03-09 08:30:00             0
    #> 111 Duck  2023-03-09 09:00:00             0
    #> 112 Duck  2023-03-09 09:30:00             0
    #> 113 Duck  2023-03-09 10:00:00             0
    #> 114 Duck  2023-03-09 10:30:00             2
    #> 115 Duck  2023-03-09 11:00:00             0
    #> 116 Duck  2023-03-09 11:30:00             0
    #> 117 Duck  2023-03-09 12:00:00             0
    #> 118 Duck  2023-03-09 12:30:00             0
    #> 119 Duck  2023-03-09 13:00:00             0
    #> 120 Duck  2023-03-09 13:30:00             0
    #> 121 Duck  2023-03-09 14:00:00             0
    #> 122 Duck  2023-03-09 14:30:00             2
    #> 123 Duck  2023-03-09 15:00:00             4
    #> 124 Duck  2023-03-09 15:30:00             0
    #> 125 Duck  2023-03-09 16:00:00             0
    #> 126 Duck  2023-03-09 16:30:00             0
    #> 127 Duck  2023-03-09 17:00:00             2
    
    # Instead consider using NA, and adding 0 after
    waterfowlSumm |> fill_gaps() |> print(n = Inf)
    #> # A tsibble: 127 x 3 [30m] <UTC>
    #> # Key:       Bird [2]
    #>     Bird  Interval            `Total birds`
    #>     <fct> <dttm>                      <dbl>
    #>   1 Goose 2023-03-08 09:00:00             2
    #>   2 Goose 2023-03-08 09:30:00            NA
    #>   3 Goose 2023-03-08 10:00:00            NA
    #>   4 Goose 2023-03-08 10:30:00            NA
    #>   5 Goose 2023-03-08 11:00:00            NA
    #>   6 Goose 2023-03-08 11:30:00            NA
    #>   7 Goose 2023-03-08 12:00:00            NA
    #>   8 Goose 2023-03-08 12:30:00            NA
    #>   9 Goose 2023-03-08 13:00:00             4
    #>  10 Goose 2023-03-08 13:30:00            NA
    #>  11 Goose 2023-03-08 14:00:00             1
    #>  12 Goose 2023-03-08 14:30:00            NA
    #>  13 Goose 2023-03-08 15:00:00             4
    #>  14 Goose 2023-03-08 15:30:00            NA
    #>  15 Goose 2023-03-08 16:00:00             1
    #>  16 Goose 2023-03-08 16:30:00            NA
    #>  17 Goose 2023-03-08 17:00:00             2
    #>  18 Goose 2023-03-08 17:30:00            NA
    #>  19 Goose 2023-03-08 18:00:00            NA
    #>  20 Goose 2023-03-08 18:30:00            NA
    #>  21 Goose 2023-03-08 19:00:00            NA
    #>  22 Goose 2023-03-08 19:30:00            NA
    #>  23 Goose 2023-03-08 20:00:00            NA
    #>  24 Goose 2023-03-08 20:30:00            NA
    #>  25 Goose 2023-03-08 21:00:00            NA
    #>  26 Goose 2023-03-08 21:30:00            NA
    #>  27 Goose 2023-03-08 22:00:00            NA
    #>  28 Goose 2023-03-08 22:30:00            NA
    #>  29 Goose 2023-03-08 23:00:00            NA
    #>  30 Goose 2023-03-08 23:30:00            NA
    #>  31 Goose 2023-03-09 00:00:00            NA
    #>  32 Goose 2023-03-09 00:30:00            NA
    #>  33 Goose 2023-03-09 01:00:00            NA
    #>  34 Goose 2023-03-09 01:30:00            NA
    #>  35 Goose 2023-03-09 02:00:00            NA
    #>  36 Goose 2023-03-09 02:30:00            NA
    #>  37 Goose 2023-03-09 03:00:00            NA
    #>  38 Goose 2023-03-09 03:30:00            NA
    #>  39 Goose 2023-03-09 04:00:00            NA
    #>  40 Goose 2023-03-09 04:30:00            NA
    #>  41 Goose 2023-03-09 05:00:00            NA
    #>  42 Goose 2023-03-09 05:30:00            NA
    #>  43 Goose 2023-03-09 06:00:00            NA
    #>  44 Goose 2023-03-09 06:30:00            NA
    #>  45 Goose 2023-03-09 07:00:00            NA
    #>  46 Goose 2023-03-09 07:30:00            NA
    #>  47 Goose 2023-03-09 08:00:00            NA
    #>  48 Goose 2023-03-09 08:30:00            NA
    #>  49 Goose 2023-03-09 09:00:00             2
    #>  50 Goose 2023-03-09 09:30:00            NA
    #>  51 Goose 2023-03-09 10:00:00            NA
    #>  52 Goose 2023-03-09 10:30:00            NA
    #>  53 Goose 2023-03-09 11:00:00            NA
    #>  54 Goose 2023-03-09 11:30:00            NA
    #>  55 Goose 2023-03-09 12:00:00            NA
    #>  56 Goose 2023-03-09 12:30:00            NA
    #>  57 Goose 2023-03-09 13:00:00             4
    #>  58 Goose 2023-03-09 13:30:00            NA
    #>  59 Goose 2023-03-09 14:00:00             1
    #>  60 Goose 2023-03-09 14:30:00            NA
    #>  61 Goose 2023-03-09 15:00:00             4
    #>  62 Goose 2023-03-09 15:30:00            NA
    #>  63 Goose 2023-03-09 16:00:00             1
    #>  64 Goose 2023-03-09 16:30:00            NA
    #>  65 Goose 2023-03-09 17:00:00             2
    #>  66 Duck  2023-03-08 10:30:00             2
    #>  67 Duck  2023-03-08 11:00:00            NA
    #>  68 Duck  2023-03-08 11:30:00            NA
    #>  69 Duck  2023-03-08 12:00:00            NA
    #>  70 Duck  2023-03-08 12:30:00            NA
    #>  71 Duck  2023-03-08 13:00:00            NA
    #>  72 Duck  2023-03-08 13:30:00            NA
    #>  73 Duck  2023-03-08 14:00:00            NA
    #>  74 Duck  2023-03-08 14:30:00             2
    #>  75 Duck  2023-03-08 15:00:00             4
    #>  76 Duck  2023-03-08 15:30:00            NA
    #>  77 Duck  2023-03-08 16:00:00            NA
    #>  78 Duck  2023-03-08 16:30:00            NA
    #>  79 Duck  2023-03-08 17:00:00             2
    #>  80 Duck  2023-03-08 17:30:00            NA
    #>  81 Duck  2023-03-08 18:00:00            NA
    #>  82 Duck  2023-03-08 18:30:00            NA
    #>  83 Duck  2023-03-08 19:00:00            NA
    #>  84 Duck  2023-03-08 19:30:00            NA
    #>  85 Duck  2023-03-08 20:00:00            NA
    #>  86 Duck  2023-03-08 20:30:00            NA
    #>  87 Duck  2023-03-08 21:00:00            NA
    #>  88 Duck  2023-03-08 21:30:00            NA
    #>  89 Duck  2023-03-08 22:00:00            NA
    #>  90 Duck  2023-03-08 22:30:00            NA
    #>  91 Duck  2023-03-08 23:00:00            NA
    #>  92 Duck  2023-03-08 23:30:00            NA
    #>  93 Duck  2023-03-09 00:00:00            NA
    #>  94 Duck  2023-03-09 00:30:00            NA
    #>  95 Duck  2023-03-09 01:00:00            NA
    #>  96 Duck  2023-03-09 01:30:00            NA
    #>  97 Duck  2023-03-09 02:00:00            NA
    #>  98 Duck  2023-03-09 02:30:00            NA
    #>  99 Duck  2023-03-09 03:00:00            NA
    #> 100 Duck  2023-03-09 03:30:00            NA
    #> 101 Duck  2023-03-09 04:00:00            NA
    #> 102 Duck  2023-03-09 04:30:00            NA
    #> 103 Duck  2023-03-09 05:00:00            NA
    #> 104 Duck  2023-03-09 05:30:00            NA
    #> 105 Duck  2023-03-09 06:00:00            NA
    #> 106 Duck  2023-03-09 06:30:00            NA
    #> 107 Duck  2023-03-09 07:00:00            NA
    #> 108 Duck  2023-03-09 07:30:00            NA
    #> 109 Duck  2023-03-09 08:00:00            NA
    #> 110 Duck  2023-03-09 08:30:00            NA
    #> 111 Duck  2023-03-09 09:00:00            NA
    #> 112 Duck  2023-03-09 09:30:00            NA
    #> 113 Duck  2023-03-09 10:00:00            NA
    #> 114 Duck  2023-03-09 10:30:00             2
    #> 115 Duck  2023-03-09 11:00:00            NA
    #> 116 Duck  2023-03-09 11:30:00            NA
    #> 117 Duck  2023-03-09 12:00:00            NA
    #> 118 Duck  2023-03-09 12:30:00            NA
    #> 119 Duck  2023-03-09 13:00:00            NA
    #> 120 Duck  2023-03-09 13:30:00            NA
    #> 121 Duck  2023-03-09 14:00:00            NA
    #> 122 Duck  2023-03-09 14:30:00             2
    #> 123 Duck  2023-03-09 15:00:00             4
    #> 124 Duck  2023-03-09 15:30:00            NA
    #> 125 Duck  2023-03-09 16:00:00            NA
    #> 126 Duck  2023-03-09 16:30:00            NA
    #> 127 Duck  2023-03-09 17:00:00             2
    
    # Then add 0 based on working hours
    # I'm adding this with a mutate(), but if it was more complicated you could left join a 'working time' dataset.
    waterfowl_complete <- waterfowlSumm |> 
      fill_gaps() |> 
      mutate(
        working = between(hour(Interval), 9, 17),
        `Total birds` = case_when(
          !is.na(`Total birds`) ~ `Total birds`,
          working ~ 0,
          TRUE ~ NA_real_
        )
      )
    
    waterfowl_complete |> 
      print(n=Inf)
    #> # A tsibble: 127 x 4 [30m] <UTC>
    #> # Key:       Bird [2]
    #>     Bird  Interval            `Total birds` working
    #>     <fct> <dttm>                      <dbl> <lgl>  
    #>   1 Goose 2023-03-08 09:00:00             2 TRUE   
    #>   2 Goose 2023-03-08 09:30:00             0 TRUE   
    #>   3 Goose 2023-03-08 10:00:00             0 TRUE   
    #>   4 Goose 2023-03-08 10:30:00             0 TRUE   
    #>   5 Goose 2023-03-08 11:00:00             0 TRUE   
    #>   6 Goose 2023-03-08 11:30:00             0 TRUE   
    #>   7 Goose 2023-03-08 12:00:00             0 TRUE   
    #>   8 Goose 2023-03-08 12:30:00             0 TRUE   
    #>   9 Goose 2023-03-08 13:00:00             4 TRUE   
    #>  10 Goose 2023-03-08 13:30:00             0 TRUE   
    #>  11 Goose 2023-03-08 14:00:00             1 TRUE   
    #>  12 Goose 2023-03-08 14:30:00             0 TRUE   
    #>  13 Goose 2023-03-08 15:00:00             4 TRUE   
    #>  14 Goose 2023-03-08 15:30:00             0 TRUE   
    #>  15 Goose 2023-03-08 16:00:00             1 TRUE   
    #>  16 Goose 2023-03-08 16:30:00             0 TRUE   
    #>  17 Goose 2023-03-08 17:00:00             2 TRUE   
    #>  18 Goose 2023-03-08 17:30:00             0 TRUE   
    #>  19 Goose 2023-03-08 18:00:00            NA FALSE  
    #>  20 Goose 2023-03-08 18:30:00            NA FALSE  
    #>  21 Goose 2023-03-08 19:00:00            NA FALSE  
    #>  22 Goose 2023-03-08 19:30:00            NA FALSE  
    #>  23 Goose 2023-03-08 20:00:00            NA FALSE  
    #>  24 Goose 2023-03-08 20:30:00            NA FALSE  
    #>  25 Goose 2023-03-08 21:00:00            NA FALSE  
    #>  26 Goose 2023-03-08 21:30:00            NA FALSE  
    #>  27 Goose 2023-03-08 22:00:00            NA FALSE  
    #>  28 Goose 2023-03-08 22:30:00            NA FALSE  
    #>  29 Goose 2023-03-08 23:00:00            NA FALSE  
    #>  30 Goose 2023-03-08 23:30:00            NA FALSE  
    #>  31 Goose 2023-03-09 00:00:00            NA FALSE  
    #>  32 Goose 2023-03-09 00:30:00            NA FALSE  
    #>  33 Goose 2023-03-09 01:00:00            NA FALSE  
    #>  34 Goose 2023-03-09 01:30:00            NA FALSE  
    #>  35 Goose 2023-03-09 02:00:00            NA FALSE  
    #>  36 Goose 2023-03-09 02:30:00            NA FALSE  
    #>  37 Goose 2023-03-09 03:00:00            NA FALSE  
    #>  38 Goose 2023-03-09 03:30:00            NA FALSE  
    #>  39 Goose 2023-03-09 04:00:00            NA FALSE  
    #>  40 Goose 2023-03-09 04:30:00            NA FALSE  
    #>  41 Goose 2023-03-09 05:00:00            NA FALSE  
    #>  42 Goose 2023-03-09 05:30:00            NA FALSE  
    #>  43 Goose 2023-03-09 06:00:00            NA FALSE  
    #>  44 Goose 2023-03-09 06:30:00            NA FALSE  
    #>  45 Goose 2023-03-09 07:00:00            NA FALSE  
    #>  46 Goose 2023-03-09 07:30:00            NA FALSE  
    #>  47 Goose 2023-03-09 08:00:00            NA FALSE  
    #>  48 Goose 2023-03-09 08:30:00            NA FALSE  
    #>  49 Goose 2023-03-09 09:00:00             2 TRUE   
    #>  50 Goose 2023-03-09 09:30:00             0 TRUE   
    #>  51 Goose 2023-03-09 10:00:00             0 TRUE   
    #>  52 Goose 2023-03-09 10:30:00             0 TRUE   
    #>  53 Goose 2023-03-09 11:00:00             0 TRUE   
    #>  54 Goose 2023-03-09 11:30:00             0 TRUE   
    #>  55 Goose 2023-03-09 12:00:00             0 TRUE   
    #>  56 Goose 2023-03-09 12:30:00             0 TRUE   
    #>  57 Goose 2023-03-09 13:00:00             4 TRUE   
    #>  58 Goose 2023-03-09 13:30:00             0 TRUE   
    #>  59 Goose 2023-03-09 14:00:00             1 TRUE   
    #>  60 Goose 2023-03-09 14:30:00             0 TRUE   
    #>  61 Goose 2023-03-09 15:00:00             4 TRUE   
    #>  62 Goose 2023-03-09 15:30:00             0 TRUE   
    #>  63 Goose 2023-03-09 16:00:00             1 TRUE   
    #>  64 Goose 2023-03-09 16:30:00             0 TRUE   
    #>  65 Goose 2023-03-09 17:00:00             2 TRUE   
    #>  66 Duck  2023-03-08 10:30:00             2 TRUE   
    #>  67 Duck  2023-03-08 11:00:00             0 TRUE   
    #>  68 Duck  2023-03-08 11:30:00             0 TRUE   
    #>  69 Duck  2023-03-08 12:00:00             0 TRUE   
    #>  70 Duck  2023-03-08 12:30:00             0 TRUE   
    #>  71 Duck  2023-03-08 13:00:00             0 TRUE   
    #>  72 Duck  2023-03-08 13:30:00             0 TRUE   
    #>  73 Duck  2023-03-08 14:00:00             0 TRUE   
    #>  74 Duck  2023-03-08 14:30:00             2 TRUE   
    #>  75 Duck  2023-03-08 15:00:00             4 TRUE   
    #>  76 Duck  2023-03-08 15:30:00             0 TRUE   
    #>  77 Duck  2023-03-08 16:00:00             0 TRUE   
    #>  78 Duck  2023-03-08 16:30:00             0 TRUE   
    #>  79 Duck  2023-03-08 17:00:00             2 TRUE   
    #>  80 Duck  2023-03-08 17:30:00             0 TRUE   
    #>  81 Duck  2023-03-08 18:00:00            NA FALSE  
    #>  82 Duck  2023-03-08 18:30:00            NA FALSE  
    #>  83 Duck  2023-03-08 19:00:00            NA FALSE  
    #>  84 Duck  2023-03-08 19:30:00            NA FALSE  
    #>  85 Duck  2023-03-08 20:00:00            NA FALSE  
    #>  86 Duck  2023-03-08 20:30:00            NA FALSE  
    #>  87 Duck  2023-03-08 21:00:00            NA FALSE  
    #>  88 Duck  2023-03-08 21:30:00            NA FALSE  
    #>  89 Duck  2023-03-08 22:00:00            NA FALSE  
    #>  90 Duck  2023-03-08 22:30:00            NA FALSE  
    #>  91 Duck  2023-03-08 23:00:00            NA FALSE  
    #>  92 Duck  2023-03-08 23:30:00            NA FALSE  
    #>  93 Duck  2023-03-09 00:00:00            NA FALSE  
    #>  94 Duck  2023-03-09 00:30:00            NA FALSE  
    #>  95 Duck  2023-03-09 01:00:00            NA FALSE  
    #>  96 Duck  2023-03-09 01:30:00            NA FALSE  
    #>  97 Duck  2023-03-09 02:00:00            NA FALSE  
    #>  98 Duck  2023-03-09 02:30:00            NA FALSE  
    #>  99 Duck  2023-03-09 03:00:00            NA FALSE  
    #> 100 Duck  2023-03-09 03:30:00            NA FALSE  
    #> 101 Duck  2023-03-09 04:00:00            NA FALSE  
    #> 102 Duck  2023-03-09 04:30:00            NA FALSE  
    #> 103 Duck  2023-03-09 05:00:00            NA FALSE  
    #> 104 Duck  2023-03-09 05:30:00            NA FALSE  
    #> 105 Duck  2023-03-09 06:00:00            NA FALSE  
    #> 106 Duck  2023-03-09 06:30:00            NA FALSE  
    #> 107 Duck  2023-03-09 07:00:00            NA FALSE  
    #> 108 Duck  2023-03-09 07:30:00            NA FALSE  
    #> 109 Duck  2023-03-09 08:00:00            NA FALSE  
    #> 110 Duck  2023-03-09 08:30:00            NA FALSE  
    #> 111 Duck  2023-03-09 09:00:00             0 TRUE   
    #> 112 Duck  2023-03-09 09:30:00             0 TRUE   
    #> 113 Duck  2023-03-09 10:00:00             0 TRUE   
    #> 114 Duck  2023-03-09 10:30:00             2 TRUE   
    #> 115 Duck  2023-03-09 11:00:00             0 TRUE   
    #> 116 Duck  2023-03-09 11:30:00             0 TRUE   
    #> 117 Duck  2023-03-09 12:00:00             0 TRUE   
    #> 118 Duck  2023-03-09 12:30:00             0 TRUE   
    #> 119 Duck  2023-03-09 13:00:00             0 TRUE   
    #> 120 Duck  2023-03-09 13:30:00             0 TRUE   
    #> 121 Duck  2023-03-09 14:00:00             0 TRUE   
    #> 122 Duck  2023-03-09 14:30:00             2 TRUE   
    #> 123 Duck  2023-03-09 15:00:00             4 TRUE   
    #> 124 Duck  2023-03-09 15:30:00             0 TRUE   
    #> 125 Duck  2023-03-09 16:00:00             0 TRUE   
    #> 126 Duck  2023-03-09 16:30:00             0 TRUE   
    #> 127 Duck  2023-03-09 17:00:00             2 TRUE
    waterfowl_complete |> 
      autoplot(`Total birds`)
    

    Created on 2023-03-10 with reprex v2.0.2