MSTL
This function applies MSTL to decompose a univariate series that contains multiple seasonal cycles. It estimates trend, one or more seasonal components, and residuals.
The additive structure is:
y_t = T_t + \sum_{k=1}^{K} S_{k,t} + R_t
where K is the number of seasonal periods, S_{k,t} are seasonal components for each period, and R_t is the remainder.
Excel Usage
=MSTL(data, periods, iterate)
data(list[list], required): 2D range of time-series values (numeric).periods(list[list], required): 2D range containing one or more seasonal periods (integers greater than 1).iterate(int, optional, default: 2): Number of seasonal refinement iterations.
Returns (list[list]): 2D array with columns [observed, trend, seasonal_1, seasonal_2, …, residual].
Example 1: MSTL with two seasonal periods
Inputs:
| data | periods | iterate | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 20 | 23 | 25 | 22 | 21 | 24 | 26 | 23 | 22 | 25 | 27 | 24 | 23 | 26 | 28 | 25 | 24 | 27 | 29 | 26 | 28 | 25 | 24 | 27 | 29 | 5 | 10 | 2 |
Excel formula:
=MSTL({20,23,25,22,21,24,26,23,22,25,27,24,23,26,28,25,24,27,29,26,28,25,24,27,29}, {5,10}, 2)
Expected output:
| Result | ||||
|---|---|---|---|---|
| 20 | 21.479 | 0.0202247 | -0.878662 | -0.620525 |
| 23 | 21.815 | 2.23377 | -1.36666 | 0.31791 |
| 25 | 22.1505 | 1.49776 | 0.566002 | 0.785779 |
| 22 | 22.4859 | -1.56819 | 1.20793 | -0.125661 |
| 21 | 22.8203 | -1.44844 | 0.223642 | -0.595456 |
| 24 | 23.1516 | 0.2956 | 0.505432 | 0.0473239 |
| 26 | 23.4782 | 1.02264 | 1.45145 | 0.0477026 |
| 23 | 23.7966 | 0.510197 | -1.35486 | 0.0480551 |
| 22 | 24.1 | -0.687053 | -1.46141 | 0.0484991 |
| 25 | 24.4101 | -0.412508 | 0.953209 | 0.0492167 |
| 27 | 24.7222 | 0.523653 | 0.360175 | 1.39397 |
| 24 | 25.0108 | -0.156962 | -0.355014 | -0.498848 |
| 23 | 25.2588 | -0.405613 | -0.411149 | -1.44203 |
| 26 | 25.465 | 0.186737 | -0.0501534 | 0.398366 |
| 28 | 25.6346 | 0.580126 | 0.437742 | 1.34757 |
| 25 | 25.7859 | 0.650289 | -1.48776 | 0.0516016 |
| 24 | 25.9359 | -1.28232 | -0.705539 | 0.0519384 |
| 27 | 26.053 | -1.18458 | 2.07879 | 0.0528121 |
| 29 | 26.1462 | 1.04578 | 1.75426 | 0.0537813 |
| 26 | 26.2296 | 1.48962 | -1.77401 | 0.0547463 |
| 28 | 26.3074 | 0.725616 | 1.57736 | -0.610343 |
| 25 | 26.3814 | -2.37883 | 0.668545 | 0.328856 |
| 24 | 26.4536 | -1.89141 | -1.35967 | 0.797503 |
| 27 | 26.5242 | 1.9012 | -1.3123 | -0.113133 |
| 29 | 26.5924 | 2.35882 | 0.630889 | -0.582089 |
Example 2: MSTL with a single seasonal period
Inputs:
| data | periods | iterate | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10 | 12 | 14 | 12 | 11 | 13 | 15 | 13 | 12 | 14 | 16 | 14 | 4 | 2 |
Excel formula:
=MSTL({10,12,14,12,11,13,15,13,12,14,16,14}, {4}, 2)
Expected output:
| Result | |||
|---|---|---|---|
| 10 | 11.625 | -1.625 | 5.32907e-15 |
| 12 | 11.875 | 0.125 | 3.55271e-15 |
| 14 | 12.125 | 1.875 | 1.77636e-15 |
| 12 | 12.375 | -0.375 | 5.32907e-15 |
| 11 | 12.625 | -1.625 | -1.77636e-15 |
| 13 | 12.875 | 0.125 | 0 |
| 15 | 13.125 | 1.875 | 3.55271e-15 |
| 13 | 13.375 | -0.375 | -3.55271e-15 |
| 12 | 13.625 | -1.625 | 0 |
| 14 | 13.875 | 0.125 | 1.77636e-15 |
| 16 | 14.125 | 1.875 | 0 |
| 14 | 14.375 | -0.375 | 3.55271e-15 |
Example 3: MSTL with column-oriented input
Inputs:
| data | periods | iterate |
|---|---|---|
| 10 | 4 | 2 |
| 12 | ||
| 14 | ||
| 12 | ||
| 11 | ||
| 13 | ||
| 15 | ||
| 13 | ||
| 12 | ||
| 14 | ||
| 16 | ||
| 14 |
Excel formula:
=MSTL({10;12;14;12;11;13;15;13;12;14;16;14}, {4}, 2)
Expected output:
| Result | |||
|---|---|---|---|
| 10 | 11.625 | -1.625 | 5.32907e-15 |
| 12 | 11.875 | 0.125 | 3.55271e-15 |
| 14 | 12.125 | 1.875 | 1.77636e-15 |
| 12 | 12.375 | -0.375 | 5.32907e-15 |
| 11 | 12.625 | -1.625 | -1.77636e-15 |
| 13 | 12.875 | 0.125 | 0 |
| 15 | 13.125 | 1.875 | 3.55271e-15 |
| 13 | 13.375 | -0.375 | -3.55271e-15 |
| 12 | 13.625 | -1.625 | 0 |
| 14 | 13.875 | 0.125 | 1.77636e-15 |
| 16 | 14.125 | 1.875 | 0 |
| 14 | 14.375 | -0.375 | 3.55271e-15 |
Example 4: MSTL with additional refinement iterations
Inputs:
| data | periods | iterate | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 20 | 23 | 25 | 22 | 21 | 24 | 26 | 23 | 22 | 25 | 27 | 24 | 23 | 26 | 28 | 25 | 24 | 27 | 29 | 26 | 28 | 25 | 24 | 27 | 29 | 5 | 10 | 3 |
Excel formula:
=MSTL({20,23,25,22,21,24,26,23,22,25,27,24,23,26,28,25,24,27,29,26,28,25,24,27,29}, {5,10}, 3)
Expected output:
| Result | ||||
|---|---|---|---|---|
| 20 | 21.479 | 0.15935 | -1.01817 | -0.620191 |
| 23 | 21.815 | 2.37819 | -1.51093 | 0.317729 |
| 25 | 22.1505 | 1.55633 | 0.507639 | 0.785544 |
| 22 | 22.4859 | -1.6703 | 1.31013 | -0.125763 |
| 21 | 22.8203 | -1.60248 | 0.377451 | -0.595234 |
| 24 | 23.1516 | 0.350146 | 0.450885 | 0.0473218 |
| 26 | 23.4782 | 1.11296 | 1.36114 | 0.0477006 |
| 23 | 23.7966 | 0.574082 | -1.41874 | 0.0480534 |
| 22 | 24.1 | -0.728905 | -1.41956 | 0.0484975 |
| 25 | 24.4101 | -0.493507 | 1.0342 | 0.0492154 |
| 27 | 24.7222 | 0.493575 | 0.390932 | 1.39329 |
| 24 | 25.0108 | -0.120351 | -0.391981 | -0.498489 |
| 23 | 25.2588 | -0.336597 | -0.480626 | -1.44157 |
| 26 | 25.465 | 0.204942 | -0.0685571 | 0.398567 |
| 28 | 25.6346 | 0.572455 | 0.445857 | 1.34712 |
| 25 | 25.7859 | 0.534735 | -1.37222 | 0.051601 |
| 24 | 25.9359 | -1.29901 | -0.688858 | 0.0519379 |
| 27 | 26.053 | -1.10988 | 2.00411 | 0.0528118 |
| 29 | 26.1462 | 1.12428 | 1.67578 | 0.0537813 |
| 26 | 26.2296 | 1.55476 | -1.83912 | 0.0547465 |
| 28 | 26.3073 | 0.524293 | 1.77839 | -0.610006 |
| 25 | 26.3814 | -2.44893 | 0.738879 | 0.328678 |
| 24 | 26.4535 | -1.81052 | -1.44027 | 0.797272 |
| 27 | 26.5242 | 2.04037 | -1.45131 | -0.113232 |
| 29 | 26.5923 | 2.49624 | 0.493316 | -0.581864 |
Python Code
import numpy as np
from statsmodels.tsa.seasonal import MSTL as sm_MSTL
def mstl(data, periods, iterate=2):
"""
Perform multi-seasonal STL decomposition on a time series.
See: https://www.statsmodels.org/stable/generated/statsmodels.tsa.seasonal.MSTL.html
This example function is provided as-is without any representation of accuracy.
Args:
data (list[list]): 2D range of time-series values (numeric).
periods (list[list]): 2D range containing one or more seasonal periods (integers greater than 1).
iterate (int, optional): Number of seasonal refinement iterations. Default is 2.
Returns:
list[list]: 2D array with columns [observed, trend, seasonal_1, seasonal_2, ..., residual].
"""
try:
def to2d(x):
return [[x]] if not isinstance(x, list) else x
data = to2d(data)
periods = to2d(periods)
if not isinstance(iterate, int) or iterate < 1:
return "Error: iterate must be an integer greater than or equal to 1"
if not isinstance(data, list) or not all(isinstance(row, list) for row in data):
return "Error: data must be a 2D list"
if not isinstance(periods, list) or not all(isinstance(row, list) for row in periods):
return "Error: periods must be a 2D list"
values = []
for row in data:
for item in row:
try:
values.append(float(item))
except (TypeError, ValueError):
continue
period_values = []
for row in periods:
for item in row:
try:
p = int(item)
if p > 1:
period_values.append(p)
except (TypeError, ValueError):
continue
if not values:
return "Error: data must contain at least one numeric value"
if not period_values:
return "Error: periods must contain at least one integer greater than 1"
if len(values) < 2 * max(period_values):
return "Error: data must contain at least two cycles of the largest period"
period_arg = period_values[0] if len(period_values) == 1 else period_values
result = sm_MSTL(values, periods=period_arg, iterate=iterate).fit()
observed = np.asarray(values)
trend = np.asarray(result.trend)
seasonal = np.asarray(result.seasonal)
resid = np.asarray(result.resid)
if seasonal.ndim == 1:
seasonal = seasonal.reshape(-1, 1)
output = []
for i in range(len(observed)):
row = [float(observed[i]) if np.isfinite(observed[i]) else ""]
row.append(float(trend[i]) if np.isfinite(trend[i]) else "")
for j in range(seasonal.shape[1]):
val = seasonal[i, j]
row.append(float(val) if np.isfinite(val) else "")
row.append(float(resid[i]) if np.isfinite(resid[i]) else "")
output.append(row)
return output
except Exception as e:
return f"Error: {str(e)}"Online Calculator
2D range of time-series values (numeric).
2D range containing one or more seasonal periods (integers greater than 1).
Number of seasonal refinement iterations.