Seasonality, Trend, and Irregular Contribution Kit

The greybox.stick module decomposes the variance of a time series into seasonal, trend and irregular parts based on an Analysis of Variance (ANOVA) of the series on the seasonal and trend factors, and measures the contribution of each component. It is a Python port of R’s greybox::stick() and implements the Seasonality, Trend, and Irregular (STI) classification of Hans Levenbach.

stick()

greybox.stick.stick(y, lags=None) StickResult[source]

Seasonality, Trend, and Irregular Contribution Kit.

Decomposes the variance of a time series into seasonal, trend and irregular parts based on an Analysis of Variance (ANOVA) of the series on the seasonal and trend factors, and measures the contribution of each component. This is a Python port of R’s greybox::stick().

A data frame is formed internally with the response y, a categorical (factor) variable for each of the provided seasonal lags and a “trend” factor. For a monthly series with lags=12, the seasonal factor takes values 1..12 repeated throughout the sample (the month of the year), while the trend factor takes values 1..ceil(T/12), each repeated 12 times (the year). The trend factor is constructed based on the longest of the provided lags. An ANOVA is then applied to y ~ seasonal + trend and the strength of each component is measured as the share of the respective Sum of Squares in the total Sum of Squares. The irregular component corresponds to the share of the residual Sum of Squares; the strengths sum up to one.

The function implements the Seasonality, Trend, and Irregular (STI) classification of Hans Levenbach.

Parameters:
  • y (array-like) – The time series to analyse.

  • lags (int or sequence of int) – The seasonal lags (periodicities) in the data, e.g. lags=[24, 168] for hourly data or lags=12 for monthly data. Values not greater than one are dropped.

Returns:

Object with the original data, the lags used, the ANOVA table and the strength of each component.

Return type:

StickResult

References

Levenbach, H. (2021). Four P’s in a Pod: e-Commerce Forecasting and Planning for Supply Chain Practitioners. Independently published. ISBN 979-8461733575.

Examples

>>> import numpy as np
>>> t = np.arange(1, 121)
>>> y = 100 + 0.3 * t + 20 * np.sin(2 * np.pi * t / 12)
>>> result = stick(y, lags=12)
>>> bool(np.isclose(result.strength.sum(), 1.0))
True

Example:

import numpy as np
from greybox import stick

# Monthly series with a trend and a seasonal pattern
t = np.arange(1, 121)
y = 100 + 0.3 * t + 20 * np.sin(2 * np.pi * t / 12)

result = stick(y, lags=12)
print(result)
# Seasonality, Trend, and Irregular Contribution Kit
# Seasonal lags: 12
#
# Strength of the components:
# seasonal12    ...
# trend         ...
# irregular     ...

# Multiple seasonal lags (e.g. hourly data)
result2 = stick(y_hourly, lags=[24, 168])

Accessing the result

The StickResult exposes the strength of each component as a pandas.Series and the full ANOVA table as a pandas.DataFrame:

result.strength            # one entry per lag, plus trend & irregular
result.strength["trend"]   # strength of the trend
result.anova               # Df / Sum Sq / Mean Sq / F value / Pr(>F)

The strengths are the shares of the respective Sum of Squares in the total Sum of Squares and sum up to one.

Plotting

StickResult.plot() mirrors R’s plot.stick(). For every seasonal lag it draws a seasonal plot (the series reshaped into one grey line per cycle, with the average seasonal profile overlaid as a bold dashed black line); the final plot is the trend plot (one grey line per seasonal position drawn across the cycles, with the average level per cycle – the trend – overlaid as a bold dashed black line). The which argument selects which panes to draw: 1, ..., k-1 are the seasonal plots, k is the trend plot; None (the default) draws all of them:

import matplotlib.pyplot as plt

axes = result.plot()          # all panes
ax = result.plot(which=2)     # only the trend plot
plt.show()

Result class

class greybox.stick.StickResult(y: ndarray, lags: ndarray, anova: DataFrame, strength: Series)[source]

Bases: object

Result of stick(). Mirrors R’s stick S3 class.

y

The original time series.

Type:

numpy.ndarray

lags

The seasonal lags used in the analysis (unique, ascending).

Type:

numpy.ndarray

anova

The ANOVA table, with one row per seasonal lag, a trend row (when a trend was fitted) and a Residuals row. Columns are Df, Sum Sq, Mean Sq, F value and Pr(>F).

Type:

pandas.DataFrame

strength

The strength of each component: one entry per seasonal lag, the trend and the irregular component. The values are the shares of the respective Sum of Squares in the total Sum of Squares and sum up to one.

Type:

pandas.Series

plot(which=None, axes=None, **kwargs)[source]

Plot the seasonal and trend components.

Mirrors R’s plot.stick(). For every seasonal lag a “seasonal plot” is produced (the series reshaped into one grey line per cycle, with the average seasonal profile overlaid as a bold dashed black line); the final plot is the “trend plot” (one grey line per seasonal position drawn across the cycles, with the average level per cycle – the trend – overlaid as a bold dashed black line).

Parameters:
  • which (int or sequence of int, optional) – Which plots to produce: 1, ..., k-1 correspond to the seasonal plots for each of the seasonal lags (in ascending order), while k corresponds to the trend plot (the last one). If None (the default), all the plots are produced. Values outside the valid range are dropped with a warning.

  • axes (matplotlib.axes.Axes or sequence of Axes, optional) – Axes to draw on, one per requested plot. A new figure with a row of subplots is created if None.

  • **kwargs – Forwarded to matplotlib.axes.Axes.plot() for the grey component lines.

Returns:

The axes containing the plots.

Return type:

numpy.ndarray of matplotlib.axes.Axes