Time Series Analysis with Tsibble


tsibble is an R package for structured time-series analysis, offering an alternative to xts with a tidy data format and more intuitive time-based operations. Unlike xts, which stores time indices as row names, tsibble treats time as an explicit column, making it easier to manipulate and integrate with the tidyverse. It simplifies indexing, aggregation, and frequency transformations, automatically handling irregular timestamps and allowing seamless grouping across different time intervals.

Prerequisites

We load daily dollar exchange rates of the yen and the euro:

xrates <- readRDS(here("databases/xrates.rds"))
tail(xrates)
           date    eur    yen
6640 2025-06-13 1.1552 144.09
6641 2025-06-16 1.1561 144.72
6642 2025-06-17 1.1479 145.25
6643 2025-06-18 1.1479 145.10
6644 2025-06-19 1.1494 145.45
6645 2025-06-20     NA     NA

We need to convert the date variable (which is now a string) to a date:

xrates$date <- as.Date(xrates$date)
tail(xrates)
           date    eur    yen
6640 2025-06-13 1.1552 144.09
6641 2025-06-16 1.1561 144.72
6642 2025-06-17 1.1479 145.25
6643 2025-06-18 1.1479 145.10
6644 2025-06-19 1.1494 145.45
6645 2025-06-20     NA     NA

Converting to a Time-Based Tsibble

We can now convert it to a tsibble:

xr <- as_tsibble(xrates, index = date)
print(tail(xr))
# A tsibble: 6 x 3 [1D]
  date         eur   yen
  <date>     <dbl> <dbl>
1 2025-06-13  1.16  144.
2 2025-06-16  1.16  145.
3 2025-06-17  1.15  145.
4 2025-06-18  1.15  145.
5 2025-06-19  1.15  145.
6 2025-06-20 NA      NA 

Aggregating at Different Frequencies

In the examples below, we compute for each period:

  • start_date: the first timestamp in the period
  • end_date: the last timestamp in the period
  • avg_*: the period average of each series
  • first_*: the value at the first timestamp
  • last_*: the value at the last timestamp

Monthly Aggregation

# convert daily tsibble to monthly summaries
xr_monthly_methods <- xr %>%
   index_by(quarter = yearquarter(date)) %>%
  summarise(
    # First and last date in each quarter
    start_date = first(date),
    end_date   = last(date),
    # Numerical summaries for each currency
       avg_yen    = mean(yen, na.rm = TRUE),
    first_yen  = first(yen),
    last_yen   = last(yen),
    avg_eur    = mean(eur, na.rm = TRUE),
    first_eur  = first(eur),
    last_eur   = last(eur)
  ) 


# preview the tail
tail(xr_monthly_methods)
# A tsibble: 6 x 9 [1Q]
  quarter start_date end_date   avg_yen first_yen last_yen avg_eur first_eur
    <qtr> <date>     <date>       <dbl>     <dbl>    <dbl>   <dbl>     <dbl>
1 2024 Q1 2024-01-01 2024-03-29    148.      141.     151.    1.09      1.10
2 2024 Q2 2024-04-01 2024-06-28    156.      152.     161.    1.08      1.07
3 2024 Q3 2024-07-01 2024-09-30    149.      161.     144.    1.10      1.07
4 2024 Q4 2024-10-01 2024-12-31    152.      144.     157.    1.07      1.11
5 2025 Q1 2025-01-01 2025-03-31    153.      157.     150.    1.05      1.04
6 2025 Q2 2025-04-01 2025-06-20    144.      150.      NA     1.13      1.08
# ℹ 1 more variable: last_eur <dbl>

Quarterly Aggregation

# build quarterly tsibble directly from the raw xrates data
xr_quarterly_methods <- xr %>%
  index_by(quarter = yearquarter(date)) %>%
  summarise(
    # First and last date in each quarter
    start_date = first(date),
    end_date   = last(date),
    # Numerical summaries for each currency
    avg_yen    = mean(yen, na.rm = TRUE),
    first_yen  = first(yen),
    last_yen   = last(yen),
    avg_eur    = mean(eur, na.rm = TRUE),
    first_eur  = first(eur),
    last_eur   = last(eur)
  ) 

# preview the tail
tail(xr_quarterly_methods)
# A tsibble: 6 x 9 [1Q]
  quarter start_date end_date   avg_yen first_yen last_yen avg_eur first_eur
    <qtr> <date>     <date>       <dbl>     <dbl>    <dbl>   <dbl>     <dbl>
1 2024 Q1 2024-01-01 2024-03-29    148.      141.     151.    1.09      1.10
2 2024 Q2 2024-04-01 2024-06-28    156.      152.     161.    1.08      1.07
3 2024 Q3 2024-07-01 2024-09-30    149.      161.     144.    1.10      1.07
4 2024 Q4 2024-10-01 2024-12-31    152.      144.     157.    1.07      1.11
5 2025 Q1 2025-01-01 2025-03-31    153.      157.     150.    1.05      1.04
6 2025 Q2 2025-04-01 2025-06-20    144.      150.      NA     1.13      1.08
# ℹ 1 more variable: last_eur <dbl>

Annual Aggregation

xr_annual_methods <- xr %>%
    index_by(year = year(date)) %>%
  summarise(
    # First and last date in each quarter
    start_date = first(date),
    end_date   = last(date),
    # Numerical summaries for each currency
       avg_yen    = mean(yen, na.rm = TRUE),
    first_yen  = first(yen),
    last_yen   = last(yen),
    avg_eur    = mean(eur, na.rm = TRUE),
    first_eur  = first(eur),
    last_eur   = last(eur)
  ) 


tail(xr_annual_methods)
# A tsibble: 6 x 9 [1Y]
   year start_date end_date   avg_yen first_yen last_yen avg_eur first_eur
  <dbl> <date>     <date>       <dbl>     <dbl>    <dbl>   <dbl>     <dbl>
1  2020 2020-01-01 2020-12-31    107.      109.      NA     1.14      1.12
2  2021 2021-01-01 2021-12-31    110.      103.     115.    1.18      1.21
3  2022 2022-01-03 2022-12-30    132.      115.     131.    1.05      1.13
4  2023 2023-01-02 2023-12-29    141.      131.     141.    1.08      1.07
5  2024 2024-01-01 2024-12-31    151.      141.     157.    1.08      1.10
6  2025 2025-01-01 2025-06-20    149.      157.      NA     1.09      1.04
# ℹ 1 more variable: last_eur <dbl>

Handling Lags in Tsibble

In tsibble, the standard lag() (and dplyr::lag()) always shifts by rows, not by actual calendar intervals—even if your data are daily, any implicit gaps (e.g. weekends, holidays, or just missing dates) aren’t accounted for. To get a true “lag by 2 calendar days,” you use fill_gaps() to turn every implicit missing date into an explicit NA, so that one row = one calendar day. After that, lag(..., 2) really means “two days ago.”

# Fill the calendar gaps, then lag by rows
xr<-xr %>%
  fill_gaps() %>%              # ensure every calendar day is present
  mutate(
    eur_lag2 = dplyr::lag(eur, 2)         # now lags EUR by 2 calendar days
  )
tail(xr)
# A tsibble: 6 x 4 [1D]
  date         eur   yen eur_lag2
  <date>     <dbl> <dbl>    <dbl>
1 2025-06-15 NA      NA      1.16
2 2025-06-16  1.16  145.    NA   
3 2025-06-17  1.15  145.    NA   
4 2025-06-18  1.15  145.     1.16
5 2025-06-19  1.15  145.     1.15
6 2025-06-20 NA      NA      1.15

If the original data skipped a date, lag2 on that date will be NA rather than pulling the last available value.

Is Tsibble Easier?

tsibble simplifies time-series operations by eliminating the need for manual date conversion, making frequency aggregation more intuitive, and automatically detecting missing timestamps. Unlike other time-series packages that require additional steps for handling irregular data, tsibble provides built-in tools to manage gaps and ensure accurate time alignment, making it a more efficient and user-friendly choice for working with time-based data. However, for high-frequency financial data, other packages like xts might still be preferred.