215. STL

215.1. STL Decomposition (Seasonal-Trend using Loess)

Modern alternative to classical decomposition. Robust, handles changing seasonal patterns, and produces complete trend/seasonal estimates with no endpoint gaps.

Developed by Cleveland, Cleveland, McRae, and Terpenning (1990). Standard tool in statsmodels, R::stats, forecast::stl.

215.1.1. What it does

Decompose a series additively:

𝑦𝑡=𝑇𝑡+𝑆𝑡+𝑅𝑡

where 𝑇 is trend, 𝑆 is seasonal, 𝑅 is the remainder (noise). Multiplicative decomposition is handled by first taking log𝑦𝑡, decomposing additively, then exponentiating.

Same components as classical decomposition, but the estimation method is different:

215.1.2. Key parameters

ParameterTypical valueEffect
np (period)seasonal period 𝑚 (e.g., 12 for monthly, 7 for daily-with-weekly-seasonality)Defines the seasonal cycle
ns (seasonal window)7-15 (must be odd ≥ 7)Smaller → seasonal pattern can change rapidly. Larger → seasonal pattern is locked-in over time. Set based on how stable seasonality is.
nt (trend window)auto: 1.5𝑚/(11.5/𝑛𝑠)Larger → smoother trend. Smaller → trend tracks short-term moves.
nl (low-pass window)next odd integer 𝑚Internal; usually leave default.
robustTrue / FalseIf True, downweights outliers via a second pass. Use when there are known anomalies.

215.1.3. Why STL beats classical decomposition

IssueClassicalSTL
Seasonal patternConstant across cyclesCan evolve gradually (controlled by ns)
EndpointsGaps of 𝑚/2 at start and endSmooth all the way through
OutliersSensitive — one anomaly distorts seasonal indexRobust mode downweights anomalies
Multiplicative decompSeparate procedureTake logs, decompose additively, exponentiate

215.1.4. Workflow

  1. Plot the series, look at seasonality and trend.
  2. Choose np (= seasonal period).
  3. Try default STL — usually good. Inspect residuals: should be structureless.
  4. If seasonal pattern is clearly shifting, lower ns. If residuals show seasonal structure (under-fit), lower ns.
  5. If outliers, enable robust mode.
  6. Use the components for: forecasting (forecast each component, recombine), anomaly detection (flag large residuals), seasonal-adjustment (subtract seasonal for trend-only view).

215.1.5. When STL doesn’t work

Example

Given: 5 years of monthly retail sales with growing seasonal swing.

Setup:

  • 𝑚=12 (monthly data with yearly seasonality)
  • 𝑛𝑠=13 (seasonal pattern can evolve slowly)
  • Robust mode on (Black-Friday-style anomalies expected)

Output: three series, each of length 60.

  • Trend 𝑇𝑡: smooth upward curve, captures the multi-year growth.
  • Seasonal 𝑆𝑡: 12-month repeating pattern, but the December peak grows from year 1 (+10%) to year 5 (+18%) — that’s the evolving seasonality STL captures.
  • Remainder 𝑅𝑡: small, mean-zero, ideally autocorrelation-free.

Use the output:

  • Forecast: extend trend (e.g., linear extrapolation or random-walk) + use the most-recent seasonal pattern.
  • Seasonally-adjusted series: 𝑦𝑡𝑆𝑡. Useful for tracking underlying performance ignoring seasonal effects (Black Friday, summer, etc.).
  • Anomaly detection: large residuals |𝑅𝑡| flag anomalous months.

Code (Python):

from statsmodels.tsa.seasonal import STL
result = STL(y, period=12, seasonal=13, robust=True).fit()
trend = result.trend
seasonal = result.seasonal
remainder = result.resid

Plot all four (result.plot()) for visual diagnosis.