Skip to content
Ivan Svetunkov edited this page Jun 24, 2026 · 1 revision

Exploratory Data Analysis

Function signatures

R

stick(y, lags=frequency(y), ...)

# Methods on the returned "stick" object
print(x, digits=4, ...)
plot(x, which=NULL, ask, ...)

Python

def stick(y, lags=None) -> StickResult: ...

# Methods on the returned StickResult object
StickResult.__str__(self) -> str           # print(result)
StickResult.plot(self, which=None, axes=None, **kwargs)

Overview

Before building a forecasting model it helps to understand what is in a time series: how much of its variation comes from seasonality, how much from a trend, and how much is left over as irregular noise. The stick() function — the Seasonality, Trend, and Irregular Contribution Kit — answers exactly this question.

stick() implements the STI classification of Hans Levenbach. It fits an Analysis of Variance (ANOVA) of the series on seasonal and trend factors and reports the share of the total variation (sum of squares) explained by each component. The three shares sum to one, so they can be read directly as proportions.

Import

# R — function is in the greybox namespace
library(greybox)
# Python
from greybox import stick

How it works

  1. A data frame is built internally with the series y in the first column, a categorical (factor) variable for each seasonal lag, and a trend ("year") factor in the last column.
    • For monthly data (lags=12) the seasonal factor cycles through 1, 2, …, 12 (the month of the year), and the trend factor takes values 1, 2, …, ceil(T/12), each repeated 12 times (the year).
    • The trend factor is built from the longest lag provided.
  2. An ANOVA is fitted: y ~ seasonal + trend.
  3. The strength of each component is its sum of squares divided by the total sum of squares. The residual sum of squares is the irregular component.

stick — Seasonality, Trend, Irregular Contribution

Parameters

Parameter R Python Type Default Description
y y y ts / array-like The time series to analyse
lags lags lags numeric / int or list frequency(y) (R) / required (Python) Seasonal periodicity/-ies, e.g. 12 for monthly, c(24, 168) for hourly

Return Value

Field R Python Type Description
y $y .y ts / np.ndarray The original series
lags $lags .lags vector / np.ndarray Seasonal lags used (unique, ascending)
anova $anova .anova data.frame / pd.DataFrame ANOVA table (Df, Sum Sq, Mean Sq, F value, Pr(>F))
strength $strength .strength named vector / pd.Series Share of variation per component; sums to 1

Example: AirPassengers

The classic monthly airline-passengers series (Jan 1949 – Dec 1960, 144 observations) has both a strong upward trend and a pronounced yearly seasonal pattern — an ideal showcase.

R

AirPassengers ships with base R, so no extra data package is needed:

library(greybox)

result <- stick(AirPassengers, lags=12)
print(result)
#> Seasonality, Trend, and Irregular Contribution Kit
#> Seasonal lags: 12
#>
#> Strength of the components:
#> seasonal12      trend  irregular
#>     0.1061     0.8613     0.0326

# Visualise the components (seasonal plot + trend plot)
plot(result)

Python

In Python the AirPassengers series is available from the fcompdata package (pip install fcompdata); .y returns the full 144-point series:

import fcompdata
from greybox import stick

y = fcompdata.AirPassengers.y      # full 144-point monthly series

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

# Visualise the components (seasonal plot + trend plot)
result.plot()

What the numbers show

Component Strength Interpretation
trend 0.8613 ~86% of the series' total variation is explained by the year-to-year level change — the airline traffic grows steadily over the 12 years. This is by far the dominant signal.
seasonal12 0.1061 ~11% comes from the repeating within-year (monthly) pattern — the summer peaks and winter troughs.
irregular 0.0326 Only ~3% is left unexplained (the residual / noise). A small irregular share means the series is highly structured and very predictable.

The three shares add up to 1 (0.8613 + 0.1061 + 0.0326 = 1.000), so they can be read directly as percentages of the total variation. The ANOVA table behind these numbers is available via result$anova (R) / result.anova (Python), including the F-statistics and p-values for each factor.

A practical reading: because the trend dominates, a forecasting model for AirPassengers must capture the growth first; seasonality is a meaningful secondary effect, and there is very little irregular variation to worry about.

Multiple seasonalities

For high-frequency data with more than one cycle, pass several lags. The trend is then formed from the longest one:

# R — hourly data with daily (24) and weekly (168) seasonality
result <- stick(y, lags=c(24, 168))
# Python
result = stick(y, lags=[24, 168])

Each seasonal lag receives its own strength entry (seasonal24, seasonal168), alongside trend and irregular.

R vs Python Function Names

R Function Python Function
stick() stick()
print(x) print(result) / str(result)
plot(x, which=...) result.plot(which=...)

References

  • Levenbach, H. (2021). Four P's in a Pod: e-Commerce Forecasting and Planning for Supply Chain Practitioners. Independently published. ISBN 979-8461733575.
  • Svetunkov, I. (2023). Statistics for Business Analytics. https://openforecast.org/sba/

Clone this wiki locally