
Analyzing and dealing with Seasonality is a key exercise in time series analysis.
In this article, we’ll describe three types of seasonality and how to detect them.
What is seasonality?
Seasonality is one of the key components that make up a time series. Seasonality refers to systematic movements that repeat over a given period with a similar intensity.
Seasonal variations can be caused by various factors, such as weather, calendar, or economic conditions. Examples abound in various applications. Flights are more expensive in the summer because of vacations and tourism. Another example is consumer spending which increases in December due to holidays.
Seasonality means the average value in some periods will be different than the average value at other times. This issue causes the series to be non-stationary. This is why it is important to analyze seasonality when building a model.
Three Types of Seasonality
There are three types of seasonal patterns that can emerge in time series. Seasonality can be deterministic or stochastic. On the stochastic side, seasonal patterns can be either stationary or not.
These types of seasonality are not mutually exclusive. A time series can have both a deterministic and stochastic seasonal component.
Let’s describe each pattern in turn.
Deterministic seasonality
Time series with a deterministic seasonality have a constant seasonal pattern. It always recurs in a predictable way, both in intensity and periodicity:
- similar intensity: the level of the seasonal pattern stays the same over the same seasonal period;
- unchanged periodicity: the location of the peaks and troughs does not change. In other words, the time between each repetition of the seasonal pattern is constant.
Here’s a synthetic monthly time series with a deterministic seasonality:
import numpy as np
period = 12
size = 120
beta1 = 0.3
beta2 = 0.6
sin1 = np.asarray([np.sin(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
cos1 = np.asarray([np.cos(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
xt = np.cumsum(np.random.normal(scale=0.1, size=size))
series_det = xt + beta1*sin1 + beta2*cos1 + np.random.normal(scale=0.1, size=size)

This time series is adapted from the book in reference [3].
Constant seasonality can be well handled by seasonal dummy explanatory variables. A categorical variable that describes the seasonal period. In this case, the month that corresponds to each time step. This categorical variable is transformed into a set of indicator (dummy) variables by one-hot encoding.
You can also use Fourier series to model seasonality. Fourier series are sine and cosine waves with varying periods. You can learn more about these in a previous article.
Stochastic stationary seasonality
beta1 = np.linspace(-.6, .3, num=size)
beta2 = np.linspace(.6, -.3, num=size)
sin1 = np.asarray([np.sin(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
cos1 = np.asarray([np.cos(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
xt = np.cumsum(np.random.normal(scale=0.1, size=size))
# synthetic series with stochastic seasonality
series_stoc = xt + beta1*sin1 + beta2*cos1 + np.random.normal(scale=0.1, size=size)

A stochastic stationary seasonality evolves over consecutive seasonal periods (e.g. year over year). The intensity is less predictable, but the periodicity stays roughly the same.
With deterministic seasonality, the best prediction for a given month doesn’t change irrespective of the year. For a stochastic stationary seasonality, the best guess depends on the value of the same month from the previous year.
Stochastic non-stationary seasonality
Sometimes, seasonal patterns change significantly over several seasonal periods. These changes can be caused by seasonal unit roots, which means that seasonality is integrated.
Besides the intensity, the periodicity of this type of seasonality also tends to change over time. This means that the peaks and troughs vary in their location.
Examples of this type of seasonal pattern appear in different domains. These include consumption series or industrial production data.
Changes are difficult to predict when time series have an integrated seasonality. Shocks cause permanent changes in the data, leading to scenarios where "spring becomes summer" – quote from reference [1].
Tests for seasonal time series
Visualizing the time series is a simple way of checking the seasonal patterns. But, there are several tests that you can use for a more systematic approach.
Measuring seasonal strength
We can quantify the strength of seasonal patterns according to the following heuristic:
import pandas as pd
from statsmodels.tsa.api import STL
def seasonal_strength(series: pd.Series) -> float:
# time series decomposition
series_decomp = STL(series, period=period).fit()
# variance of residuals + seasonality
resid_seas_var = (series_decomp.resid + series_decomp.seasonal).var()
# variance of residuals
resid_var = series_decomp.resid.var()
# seasonal strength
result = 1 - (resid_var / resid_seas_var)
return result
This function estimates the strength of seasonality, irrespective of whether it is deterministic or stochastic.
# strong seasonality in the deterministic series
seasonal_strength(series_det)
# 0.93
# strong seasonality in the stochastic series
seasonal_strength(series_stoc)
# 0.91
The authors of this metric recommend applying a seasonal differencing filter if the value is above 0.64 [2].
An alternative to detect seasonality is the QS test, which checks the auto-correlations at seasonal lags.
Detecting non-stationary seasonality
There are a few statistical tests designed to check whether the seasonal pattern is non-stationary.
A common example is the Canova-Hansen (CH) test. Its hypotheses are the following:
- H0 (null hypothesis): The seasonal pattern is stationary (no seasonal unit root);
- H1: The series contains a seasonal unit root
The OCSB test and the HEGY test are two alternatives to CH.
These are implemented in the function nsdiffs, which is available in the pmdarima Python library (and in the R forecast package).
from pmdarima.arima import nsdiffs
period = 12 # monthly data
nsdiffs(x=series_det, m=period, test='ch')
nsdiffs(x=series_det, m=period, test='ocsb')
nsdiffs(x=series_stoc, m=period, test='ch')
nsdiffs(x=series_stoc, m=period, test='ocsb')
The function nsdiffs returns the number of seasonal differencing steps required to make the series stationary.
In this case, the result is 0 for all four cases. But, as we’ve seen above, it does not mean that the seasonal pattern is not strong.
Related tests
There are other tests specially designed for seasonal data. For example, the seasonal Kendall test is a nonparametric test that checks seasonal time series for monotonic trends.
Key Takeaways
- The seasonal component of a time series is a systematic pattern that repeats over a given period.
- Seasonality can be deterministic, stochastic, or a mix of both. Stochastic seasonal patterns may or may not be stationary
- You can estimate seasonal strength or use statistical tests (e.g QS test) to detect seasonality
- Seasonal unit roots can also be identified with statistical tests. The function nsdiffs estimates whether seasonal differencing is required for stationarity.
Thank you for reading and see you in the next story!
Related Articles
Code
References
[1] Canova, F. and Hansen, Bruce E. (1995) "Are seasonal patterns constant over time? A test for seasonal stability". Journal of Business & Economic Statistics, 13(3), pp. 237–252
[2] Wang, X, Smith, KA, Hyndman, RJ (2006) "Characteristic-based clustering for time series data", Data Mining and Knowledge Discovery, 13(3), 335–364.
[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).
[4] Time-series: deterministic/stochastic seasonality, in "The Samuelson Condition"