SEASDECOMP
This function performs classical seasonal decomposition using moving averages. It splits a univariate time series into observed, trend, seasonal, and residual components under either an additive or multiplicative model.
For an additive model, the decomposition is:
y_t = T_t + S_t + R_t
For a multiplicative model, the decomposition is:
y_t = T_t \times S_t \times R_t
The input must contain at least two full seasonal cycles so that seasonal effects can be estimated reliably.
Excel Usage
=SEASDECOMP(data, period, model, two_sided, extrapolate_points)
data(list[list], required): 2D range of time-series values (numeric).period(int, required): Seasonal period in samples (positive integer).model(str, optional, default: “additive”): Seasonal decomposition model type.two_sided(bool, optional, default: true): Use centered moving average when true.extrapolate_points(int, optional, default: 0): Number of nearest points used to extrapolate trend at boundaries.
Returns (list[list]): 2D array with columns [observed, trend, seasonal, residual].
Example 1: Additive decomposition with quarterly-style period
Inputs:
| data | period | model | two_sided | extrapolate_points | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10 | 12 | 14 | 12 | 11 | 13 | 15 | 13 | 12 | 14 | 16 | 14 | 4 | additive | true | 0 |
Excel formula:
=SEASDECOMP({10,12,14,12,11,13,15,13,12,14,16,14}, 4, "additive", TRUE, 0)
Expected output:
| Result | |||
|---|---|---|---|
| 10 | -1.625 | ||
| 12 | 0.125 | ||
| 14 | 12.125 | 1.875 | 0 |
| 12 | 12.375 | -0.375 | 0 |
| 11 | 12.625 | -1.625 | 0 |
| 13 | 12.875 | 0.125 | 0 |
| 15 | 13.125 | 1.875 | 0 |
| 13 | 13.375 | -0.375 | 0 |
| 12 | 13.625 | -1.625 | 0 |
| 14 | 13.875 | 0.125 | 0 |
| 16 | 1.875 | ||
| 14 | -0.375 |
Example 2: Multiplicative decomposition with positive series
Inputs:
| data | period | model | two_sided | extrapolate_points | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10 | 20 | 15 | 25 | 11 | 22 | 16 | 27 | 12 | 24 | 18 | 30 | 4 | multiplicative | true | 0 |
Excel formula:
=SEASDECOMP({10,20,15,25,11,22,16,27,12,24,18,30}, 4, "multiplicative", TRUE, 0)
Expected output:
| Result | |||
|---|---|---|---|
| 10 | 0.599561 | ||
| 20 | 1.16896 | ||
| 15 | 17.625 | 0.844172 | 1.00816 |
| 25 | 18 | 1.38731 | 1.00114 |
| 11 | 18.375 | 0.599561 | 0.998463 |
| 22 | 18.75 | 1.16896 | 1.00375 |
| 16 | 19.125 | 0.844172 | 0.991031 |
| 27 | 19.5 | 1.38731 | 0.998057 |
| 12 | 20 | 0.599561 | 1.00073 |
| 24 | 20.625 | 1.16896 | 0.99545 |
| 18 | 0.844172 | ||
| 30 | 1.38731 |
Example 3: Single-column data is flattened and decomposed
Inputs:
| data | period | model | two_sided | extrapolate_points |
|---|---|---|---|---|
| 10 | 4 | additive | true | 0 |
| 12 | ||||
| 14 | ||||
| 12 | ||||
| 11 | ||||
| 13 | ||||
| 15 | ||||
| 13 | ||||
| 12 | ||||
| 14 | ||||
| 16 | ||||
| 14 |
Excel formula:
=SEASDECOMP({10;12;14;12;11;13;15;13;12;14;16;14}, 4, "additive", TRUE, 0)
Expected output:
| Result | |||
|---|---|---|---|
| 10 | -1.625 | ||
| 12 | 0.125 | ||
| 14 | 12.125 | 1.875 | 0 |
| 12 | 12.375 | -0.375 | 0 |
| 11 | 12.625 | -1.625 | 0 |
| 13 | 12.875 | 0.125 | 0 |
| 15 | 13.125 | 1.875 | 0 |
| 13 | 13.375 | -0.375 | 0 |
| 12 | 13.625 | -1.625 | 0 |
| 14 | 13.875 | 0.125 | 0 |
| 16 | 1.875 | ||
| 14 | -0.375 |
Example 4: One-sided moving average decomposition
Inputs:
| data | period | model | two_sided | extrapolate_points | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10 | 12 | 14 | 12 | 11 | 13 | 15 | 13 | 12 | 14 | 16 | 14 | 4 | additive | false | 1 |
Excel formula:
=SEASDECOMP({10,12,14,12,11,13,15,13,12,14,16,14}, 4, "additive", FALSE, 1)
Expected output:
| Result | |||
|---|---|---|---|
| 10 | 11.125 | -1.625 | 0.5 |
| 12 | 11.375 | 0.125 | 0.5 |
| 14 | 11.625 | 1.875 | 0.5 |
| 12 | 11.875 | -0.375 | 0.5 |
| 11 | 12.125 | -1.625 | 0.5 |
| 13 | 12.375 | 0.125 | 0.5 |
| 15 | 12.625 | 1.875 | 0.5 |
| 13 | 12.875 | -0.375 | 0.5 |
| 12 | 13.125 | -1.625 | 0.5 |
| 14 | 13.375 | 0.125 | 0.5 |
| 16 | 13.625 | 1.875 | 0.5 |
| 14 | 13.875 | -0.375 | 0.5 |
Python Code
import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose as sm_seasonal_decompose
def seasdecomp(data, period, model='additive', two_sided=True, extrapolate_points=0):
"""
Decompose a time series into trend, seasonal, and residual components.
See: https://www.statsmodels.org/stable/generated/statsmodels.tsa.seasonal.seasonal_decompose.html
This example function is provided as-is without any representation of accuracy.
Args:
data (list[list]): 2D range of time-series values (numeric).
period (int): Seasonal period in samples (positive integer).
model (str, optional): Seasonal decomposition model type. Valid options: Additive, Multiplicative. Default is 'additive'.
two_sided (bool, optional): Use centered moving average when true. Default is True.
extrapolate_points (int, optional): Number of nearest points used to extrapolate trend at boundaries. Default is 0.
Returns:
list[list]: 2D array with columns [observed, trend, seasonal, residual].
"""
try:
def to2d(x):
return [[x]] if not isinstance(x, list) else x
data = to2d(data)
if model not in ("additive", "multiplicative"):
return "Error: model must be 'additive' or 'multiplicative'"
if not isinstance(period, int) or period < 2:
return "Error: period must be an integer greater than or equal to 2"
if not isinstance(data, list) or not all(isinstance(row, list) for row in data):
return "Error: data must be a 2D list"
values = []
for row in data:
for item in row:
try:
values.append(float(item))
except (TypeError, ValueError):
continue
if len(values) < 2 * period:
return "Error: data must contain at least two complete seasonal cycles"
result = sm_seasonal_decompose(
values,
model=model,
period=period,
two_sided=two_sided,
extrapolate_trend=extrapolate_points,
)
observed = np.asarray(result.observed)
trend = np.asarray(result.trend)
seasonal = np.asarray(result.seasonal)
resid = np.asarray(result.resid)
output = []
for i in range(len(observed)):
row = []
for arr in (observed, trend, seasonal, resid):
val = arr[i]
row.append(float(val) if np.isfinite(val) else "")
output.append(row)
return output
except Exception as e:
return f"Error: {str(e)}"