The world’s leading publication for data science, AI, and ML professionals.

How to Model Multiple Seasonality in Time Series

Handling seasonal effects in several periods

Photo by Joshua Woroniecki on Unsplash
Photo by Joshua Woroniecki on Unsplash

In this article, you’ll learn how to model multiple seasonality in time series. We’ll cover:

  • How to decompose a time series using MSTL
  • Creating explanatory variables that capture complex seasonality
  • Using off-the-shelf methods, with an example based on orbit‘s forecasting package.

Complex Seasonality

Seasonality refers to systematic changes that repeat with a regular periodicity. These patterns are connected with the frequency at which a time series is observed. A low-frequency time series usually contains a single seasonal period. For example, monthly time series exhibit yearly seasonality.

Increasingly, time series are collected at higher sampling frequencies, such as daily or hourly. This leads to larger datasets with a complex seasonality. A daily time series may show weekly, monthly, and yearly repeating patterns.

Here’s an example of an hourly time series with daily and weekly seasonality:

Hourly time series with daily and weekly seasonality. Artificial data and image created by author.
Hourly time series with daily and weekly seasonality. Artificial data and image created by author.

At first glance, it’s not clear that the above time series contains more than one seasonal pattern. Multiple seasonal effects can overlap each other, which makes it difficult to identify all relevant periods.


Decomposition with Multiple Seasonality

Decomposition methods aim at splitting time series into its basic parts: trend, seasonality, and residuals.

Most methods were designed to handle seasonality at a single predefined period. Examples include the classical method, x11, and STL, among others.

The STL method has been extended to handle multiple seasonality. MSTL (for Multiple STL) is available on statsmodels Python package:

import numpy as np
from statsmodels.tsa.seasonal import MSTL

# creating an artificial time series with complex seasonality
# daily and weekly seasonality
period1, period2 = 24, 24 * 7
# 500 data points
size = 500
beta1 = np.linspace(-.6, .3, num=size)
beta2 = np.linspace(.6, -.3, num=size)
sin1 = np.asarray([np.sin(2 * np.pi * i / period1) for i in np.arange(1, size + 1)])
sin2 = np.asarray([np.sin(2 * np.pi * i / period2) for i in np.arange(1, size + 1)])
cos1 = np.asarray([np.cos(2 * np.pi * i / period1) for i in np.arange(1, size + 1)])
cos2 = np.asarray([np.cos(2 * np.pi * i / period2) for i in np.arange(1, size + 1)])

xt = np.cumsum(np.random.normal(scale=0.1, size=size))
noise = np.random.normal(scale=0.1, size=size)

# combining parts
yt = xt + beta1 * sin1 + beta2 * cos1 + sin2 + cos2 + noise

# hourly time series
ind = pd.date_range(end=pd.Timestamp('2023-07-10'), periods=size, freq='H')
yt = pd.Series(yt, index=ind)
yt.name = 'Series'
yt.index.name = 'Date'

# decomposition with MSTL
decomp = MSTL(endog=yt, periods=(period1, period2)).fit()

The decomposition results in the following parts:

A time series and its basic parts including two seasonal components. Image by author.
A time series and its basic parts including two seasonal components. Image by author.

So, MSTL can be used to adjust a time series with complex seasonality.

You can check a previous article to learn how to build a forecasting model with a decomposed time series.


Modeling Multiple Seasonality

Besides decomposition, there are other approaches to model seasonality for forecasting. These usually focus on time series with a single seasonal period. Yet, some methods can handle complex seasonality as well.

For example, you get seasonal dummies for different periods. With an hourly time series, you can get information such as the day, week, or month of each observation.

With Fourier series, you can compute sine and cosine waves using different periods. Here’s how to do that with sktime:

from sktime.transformations.series.fourier import FourierFeatures

# Fourier series with two periods
# 4 terms for the first period
# 2 terms for the second period
fourier = FourierFeatures(sp_list=[period1, period2],
                          fourier_terms_list=[4, 2],
                          keep_original_columns=False)

fourier_feats = fourier.fit_transform(yt)

You can get radial basis functions for several periods as well.

Off-the-shelf methods

There are a few off-the-shelf methods that handle complex seasonality. Examples include TBATS, Prophet, MSTL, or KTR (Kernel-based time-varying regression).

With MSTL, you approach the task by forecasting each component separately and then combining forecasts.

Here’s an example of how to use KTR, which is available on the orbit Python package:

from orbit.models import KTR
from orbit.diagnostics.plot import plot_predicted_data, plot_predicted_components
from sklearn.model_selection import train_test_split

df = yt.reset_index()

# train test split
train, test = train_test_split(df, shuffle=False, test_size=100)

# creating a KTR instance with the required periods
ktr_with_seas = KTR(
    response_col='Series',
    date_col='Date',
    seed=1,
    seasonality=[24, 24 * 7],
    estimator='pyro-svi',
    n_bootstrap_draws=1e4,
    # pyro training config
    num_steps=301,
    message=100,
)

# fitting the model
ktr_with_seas.fit(train)

# inference
predicted_df = ktr_with_seas.predict(df=df, decompose=True)

_ = plot_predicted_data(training_actual_df=train,
                        predicted_df=predicted_df,
                        date_col='Date',
                        actual_col='Series',
                        test_actual_df=test,
                        markersize=10, lw=.5)

_ = plot_predicted_components(predicted_df=predicted_df,
                              date_col='Date',
                              plot_components=['trend',
                                               'seasonality_24',
                                               'seasonality_168'])

Each seasonal component is modeled with Fourier series, which are used as explanatory variables. Here’s what they look like:

Results obtained from the function plot_predicted_components. Image by author.
Results obtained from the function plot_predicted_components. Image by author.

Each Fourier wave has different characteristics that capture the periodicity of each seasonality.


Key Takeaways

High-frequency time series can exhibit seasonality in several periods. Capturing all seasonal patterns is important for the optimal modeling of time series.

In this article, you learned how to extend common approaches to model seasonality to handle several seasonal periods.

Some off-the-shelf methods are also able to cope with this problem, such as Prophet, or KTR.

Thanks for reading, and see you in the next story!


Related articles

Code

References

[1] Orbit’s KTR documentation: https://orbit-ml.readthedocs.io/en/stable/tutorials/ktr2.html

[2] Forecasting: Principles and Practice, Complex seasonality section: https://otexts.com/fpp3/complexseasonality.html

[3] Holmes, Elizabeth E., Mark D. Scheuerell, and E. J. Ward. "Applied Time Series Analysis for fisheries and environmental data." NOAA Fisheries, Northwest Fisheries Science Center, Seattle, WA (2020).


Related Articles