Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

5.4 STL Decomposition

Having explained classical approaches to time series decomposition and local regression, we are now in a position to understand how Seasonal-Trend decomposition using LOESS[1] (STL). Cleveland et al. (1990) from Bell Labs and the University of Michigan introduced STL to compensate for the shortcomings of classical decomposition. As we shall see, STL grows naturally out of augmenting classical decomposition with LOESS. Using LOESS gives the flexibility to calculate trends for all data points, unlike moving averages which truncate the first and final values. Furthermore, unlike the X-11 family which only handles quarterly or monthly seasonalities, STL can handle any number of seasons per cycle.

STL Algorithm

Log Transformation

Unlike with classical decomposition, STL only works with additive trend and seasonal components. Data with a multiplicative trend must be log transformed to use STL. In practice, this is rarely a major impediment to using STL, though applying a log transform does cause interpretability to go slightly down.

STL Loops

The STL algorithm runs a nested loop of functions until the algorithm reaches a set stopping point determined by the user’s choice of both inner and outer loops. We will defer discussion of deseasonalization for the moment to first understand the overall procedure. Letting nin_{i} be the number of passes in the inner loop and non_{o} be the number in the outer loop, the algorithm is as follows:

  1. Initialize robustness weights for LOESS to 1 and the trend to 0.

    1.1 Detrend the series using the most recently calculated trend (set at 0 initially).

    1.2 Divide the data into mm subseries (e.g. the first subseries consisting of each January, the second subseries consisting of each February, etc.). Generate mm LOESS (weighted by robustness) curves independently for each subseries to smooth the detrended data.

    1.3 Calculate the seasonal component (next section) and deseasonalize the data.

    1.4 Apply another round of (weighted) LOESS on the deseasonalized data to determine the trend to be used as the starting point for the next iteration.

    1.5 Return to step 1.1 until completing nin_i passes.

  2. Calculate the residuals. If no>0n_o>0, calculate robustness weights to quantify each residual’s degree of extremity for the LOESS algorithm (i.e. to what extent it should be considered an outlier) and return to step 1.1 until completing non_o iterations, otherwise stop.

The fact that LOESS can output smoothed data with the same length as the original data is crucial for steps 1.2 and 1.4. With a standard moving average, each pass of a length mm filter would delete mm (mm even) or m1m-1 (mm odd) observations; running multiple loops on a small dataset would quickly whittle it down to nothing. It is only because we use LOESS that we can freely run the inner loop for multiple iterations. Additionally, note that the use of mm distinct subseries in step 1.2 allows the magnitude of each seasonal component to vary over time.

By default, statsmodels does not use robust fitting (i.e. no=0n_o=0) and uses 5 inner loops (ni=5n_i=5). This can be changed by setting robust=True, which by default will use ni=2n_i=2 and no=15n_o=15. nin_i and non_o can also be set manually in the fit method by calling STL(data).fit(inner_iter=n_i, outer_iter=n_o).

STL Deseasonalization

In step 1.3 we referenced deseasonalization without explaining how this is performed. A major advantage of STL over classical decomposition is that it allows the magnitudes of the seasonal component to vary over time instead of locking us into a single set of seasonal magnitudes for the entire series. The algorithm used in 1.3 functions as follows:

  1. The deseasonalization step starts by creating an intermediate series CiC_i consisting of the results of step 1.2 reassembled from the mm constituent subseries and temporarily padded at both ends.

  2. LiL_i is calculated from CiC_i by smoothing CiC_i with a three step “low-pass” filter[2] consisting of two sequential unweighted moving averages of length mm, followed by an unweighted moving average of length 3 (which together remove the padding from step 1), and finishing with LOESS.

  3. Seasonal components SiS_i are calculated as Si=CiLiS_i=C_i-L_i (using the non-padded CiC_i). Subtracting LiL_i removes low-frequency long-term components from the seasonality, leaving solely the short-term seasonal effects in SiS_i. SiS_i is then subtracted from the series for this iteration’s deseasonalized series.

An important advantage of STL is that, while we do still need to specify an mm value for the seasonal length, we are no longer constrained to have a single average S^t\hat{S}_t per season as we were in classical decomposition. Instead, the use of LOESS in step 2 allows for variations in the magnitudes of the seasonal component (though not its length) over the course of the data.

Plotting STL Decomposition Components

Plotting the default statsmodels implementation of STL results in Figure 1:

US employment rate from 1948 through 2025 from the Federal Reserve Bank of St. Louis with STL decomposition using the default settings in statsmodels of n_i=5 and n_o=0. Plot consists of original series (top), trends and cycles (second from top), seasonal contribution (third from top), and residual variation not explained (bottom).

Figure 1:US employment rate from 1948 through 2025 from the Federal Reserve Bank of St. Louis with STL decomposition using the default settings in statsmodels of ni=5n_i=5 and no=0n_o=0. Plot consists of original series (top), trends and cycles (second from top), seasonal contribution (third from top), and residual variation not explained (bottom).

Applying the assessment method from classical decomposition results in FT=0.953F_T=0.953 and FS=0.625F_S=0.625. Compared with the classical decomposition results of FT=0.928F_T=0.928 and FS=0.379F_S=0.379, this indicates that STL has done a marginally better job isolating the trend but a substantially better job isolating the season component.

Multiple Time Scales

As presented, STL can only decompose a single seasonality. Bandara et al. (2021) generalize STL to handle multiple seasonal lengths (e.g. both a daily and yearly seasonality in temperature) in Multiple Seasonal-Trend decomposition using LOESS (MSTL). MSTL was implemented in statsmodels as statsmodels.tsa.seasonal.MSTL in version 0.14.0 released in May of 2023. While MSTL can be a useful tool, in practice it is not currently widely adopted. In general, I would usually recommend use of frequency domain techniques for analyzing multiple seasonal lengths in a single time series. While not as immediately interpretable as STL or MSTL, frequency domain methods more than make up for the increased complexity with their tremendous ability to tease out the contributions of different seasonal trends.

Footnotes
  1. Cleveland et al. (1990) write that the “L” stands for LOESS. We will maintain this terminology, though for the purposes this book we will only need the narrower univariate LOWESS algorithm rather than the multivariate LOESS.

  2. We will explore why this filter is referred to as “low-pass” in the chapter covering the frequency domain.

References
  1. Cleveland, R. B., Cleveland, W. S., McRae, J. E., & Terpenning, I. (1990). STL: A Seasonal-Trend Decomposition Procedure Based on Loess. Journal of Official Statistics, 6(1), 3–73.
  2. Bandara, K., Hyndman, R. J., & Bergmeir, C. (2021). MSTL: A Seasonal-Trend Decomposition Algorithm for Time Series with Multiple Seasonal Patterns. arXiv. 10.48550/ARXIV.2107.13462