Seasonal Decomposition: Understanding Time Series Components

Author

AS

Published

February 22, 2026

Seasonal Decomposition

What is Seasonal Decomposition?

Seasonal decomposition is a fundamental technique in time series analysis that breaks down a complex time series into its underlying components. Think of it as separating the “signal from the noise” to understand what’s really driving your data.

The Three Core Components

Trend (T): The long-term underlying direction of your data

  • Shows whether values are generally increasing, decreasing, or stable over time
  • Reveals the “big picture” movement stripped of short-term fluctuations
  • Example: Overall growth in company sales over several years

Seasonal (S): Predictable, recurring patterns that repeat at fixed intervals

  • Daily patterns (rush hour traffic), weekly patterns (weekend sales spikes), monthly patterns (holiday shopping), quarterly patterns (business cycles)
  • These patterns are systematic and predictable
  • Example: Ice cream sales consistently peak every summer

Remainder/Irregular (R): Everything else - the unpredictable component

  • Random fluctuations, one-off events, measurement errors
  • What’s left after removing trend and seasonal effects
  • Contains both true randomness and patterns we haven’t captured
  • Example: Sales spike due to viral social media post

Why Decompose? The Business Value

1. Clarity Through Separation

Raw time series data is often noisy and hard to interpret. Decomposition reveals the true underlying patterns by isolating each component.

Real-world insight: If your monthly revenue is declining, is it due to a genuine business problem (trend) or just normal seasonal variation? Decomposition tells you.

2. Superior Forecasting

Instead of trying to predict the complex original series, you can: - Model each component separately using appropriate techniques - Combine forecasts for more accurate predictions - Apply domain knowledge to each component

Example: Use linear regression for trend, seasonal naïve for seasonal patterns, and ARIMA for remainder.

3. Intelligent Anomaly Detection

Unusual values in the remainder component indicate genuine anomalies, not just seasonal highs/lows.

Business application: A retail company can detect if a sales dip is abnormal (remainder spike) versus expected (seasonal pattern).

4. Strategic Business Insights

  • Seasonally adjusted data shows true business performance
  • Separate seasonal effects from genuine growth/decline
  • Make fair comparisons across different time periods

The Two Fundamental Models

Additive Model: Y(t) = T(t) + S(t) + R(t)

When to use: Seasonal fluctuations remain constant in absolute terms regardless of the trend level.

Visual clue: The seasonal “waves” have the same height throughout the series, even as the overall level changes.

Example: Monthly temperature variations (always varies by ~20°C) or daily website traffic patterns (consistent patterns regardless of growth).

# Seasonal variation stays constant (~same amplitude)
# Jan always ~1000 units below average, July always ~1000 above

Multiplicative Model: Y(t) = T(t) × S(t) × R(t)

When to use: Seasonal fluctuations are proportional to the current trend level.

Visual clue: Seasonal “waves” get bigger as the trend increases, smaller as it decreases.

Example: Retail sales where holiday peaks grow proportionally with business size, or stock returns where volatility scales with price level.

# Seasonal variation scales with trend
# When baseline is 1000: Jan -10%, July +15%  
# When baseline is 5000: Jan -10% (500 units), July +15% (750 units)

Practical R Implementation

Registered S3 method overwritten by 'tsibble':
  method               from 
  as_tibble.grouped_df dplyr
── Attaching packages ──────────────────────────────────────────── fpp3 1.0.2 ──
✔ tibble      3.3.0     ✔ tsibble     1.1.6
✔ dplyr       1.1.4     ✔ tsibbledata 0.4.1
✔ tidyr       1.3.1     ✔ feasts      0.4.2
✔ lubridate   1.9.4     ✔ fable       0.4.1
✔ ggplot2     3.5.2     
── Conflicts ───────────────────────────────────────────────── fpp3_conflicts ──
✖ lubridate::date()    masks base::date()
✖ dplyr::filter()      masks stats::filter()
✖ tsibble::intersect() masks base::intersect()
✖ tsibble::interval()  masks lubridate::interval()
✖ dplyr::lag()         masks stats::lag()
✖ tsibble::setdiff()   masks base::setdiff()
✖ tsibble::union()     masks base::union()

Attaching package: 'seasonal'
The following object is masked from 'package:tibble':

    view
Warning: package 'ggtime' was built under R version 4.4.3
Registered S3 methods overwritten by 'ggtime':
  method                        from      
  +.gg_tsensemble               feasts    
  autolayer.fbl_ts              fabletools
  autolayer.tbl_ts              fabletools
  autoplot.dcmp_ts              fabletools
  autoplot.fbl_ts               fabletools
  autoplot.tbl_cf               feasts    
  autoplot.tbl_ts               fabletools
  chooseOpsMethod.gg_tsensemble feasts    
  fortify.fbl_ts                fabletools
  grid.draw.gg_tsensemble       feasts    
  print.gg_tsensemble           feasts    
  scale_type.cf_lag             feasts    

Attaching package: 'ggtime'
The following objects are masked from 'package:feasts':

    gg_arma, gg_irf, gg_lag, gg_season, gg_subseries, gg_tsdisplay,
    gg_tsresiduals, scale_x_cf_lag
remove(list=ls())

?tsibble::tourism
head(tourism)
tail(tourism)
# Load and prepare Australian tourism data
tourism <- tourism |> 
  summarise(Trips = sum(Trips)) |>
  mutate(Quarter = yearquarter(Quarter))

glimpse(tourism)
Rows: 80
Columns: 2
$ Quarter <qtr> 1998 Q1, 1998 Q2, 1998 Q3, 1998 Q4, 1999 Q1, 1999 Q2, 1999 Q3,…
$ Trips   <dbl> 23182.20, 20323.38, 19826.64, 20830.13, 22087.35, 21458.37, 19…
# First, always visualize your raw data
tourism |> 
  autoplot(Trips) +
  labs(title = "Australian Tourism: Raw Time Series",
       subtitle = "Look for trend and seasonal patterns",
       y = "Total Trips")

Method 1: Classical Decomposition

Classical decomposition is the traditional approach - simple but has limitations.

?fabletools::components()

# Additive decomposition
tourism_add <- tourism |> 
  model(classical_decomposition(Trips, type = "additive")) |>
  components()

# Multiplicative decomposition  
tourism_mult <- tourism |> 
  model(classical_decomposition(Trips, type = "multiplicative")) |>
  components()

# Compare both models

# Additive decomposition
tourism |> 
  model(classical_decomposition(Trips, type = "additive")) |>
  components() |> 
  autoplot() +
  labs(title = "Classical Additive Decomposition")
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_line()`).

# Multiplicative decomposition
tourism |> 
  model(classical_decomposition(Trips, type = "multiplicative")) |>
  components() |> 
  autoplot() +
  labs(title = "Classical Multiplicative Decomposition")
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_line()`).

Classical Method Limitations:

  • Only handles additive or multiplicative models

  • Cannot handle missing values

  • Trend estimates poor at series endpoints

  • Assumes seasonal component is perfectly regular

  • Less robust to outliers

Classical Decomposition Algorithm

Classical decomposition is one of the oldest methods for decomposing time series, and the “OG” method for breaking a time series into its constituent parts. In the fpp3 framework (based on Rob Hyndman’s Forecasting: Principles and Practice), the process follows a very specific sequence to isolate those components.

How classical decomposition (specifically additive) is calculated:


1. Calculate the Trend-Cycle (\(T_t\))

The first goal is to “smooth out” the noise and seasonality to see the underlying direction. We use a Moving Average (MA).

  • If the period (\(m\)) is even (e.g., \(m=12\) for monthly data), we use a \(2 \times m\)-MA to ensure the trend is centered correctly on the time points.

  • If the period (\(m\)) is odd (e.g., \(m=7\) for daily data), a simple \(m\)-MA is used.

Moving Averages

The trend-cycle component is estimated using moving averages:

# Calculate moving averages for tourism data (quaterly data)

tourism_ma <- tourism |>
  mutate(
    `4-MA` = slider::slide_dbl(Trips, mean, .before = 1, .after = 2, .complete = TRUE),
    `2x4-MA` = slider::slide_dbl(`4-MA`, mean, .before = 1, .after = 0, .complete = TRUE)
  )

tourism_ma |>
  autoplot(Trips, colour = "gray") +
  geom_line(aes(y = `2x4-MA`), colour = "#D55E00", size = 1) +
  labs(y = "Trips (millions)",
       title = "Australian tourism data",
       subtitle = "with 2×4 moving average overlay")
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_line()`).

Important

Average of Q1, Q2, Q3, Q4 → Where does this go? Between Q2 and Q3?

We want it to sit exactly on Q2 or Q3.

Solution (2×4-MA):

  • Calculate a 4-quarter average

  • Calculate another 4-quarter average (shifted by 1)

  • Average those two averages

Result: The final number sits exactly on a quarter, not between quarters.

Think of it like this:

  • Simple 4-average = finding the middle of 4 people standing in a line

  • 2×4-average = adjusting so the “middle” lands exactly on one person

We still use 4 quarters total, just arranged to center properly on each time period.

2. Calculate the Detrended Series

Once you have the trend, you remove it from the original data (\(y_t\)) to leave behind everything else (seasonality and noise).

  • Formula: \(y_t - T_t\)

3. Calculate the Seasonal Component (\(S_t\))

This is where it gets clever. To find the “typical” effect for a specific time of year (e.g., every Q1):

  • Average the detrended values: For all “Q1” in your data, you calculate the average of those detrended values. gg_subseries will be useful here.

  • Adjust: These averages are then adjusted so that they sum to approximately zero (ensuring the seasonality doesn’t “leak” into the trend).

  • Repeat: This single set of averages is then tiled (repeated) for every year in the series.

tourism_stl <- tourism |>
  model(STL(Trips)) |>
  components()

# Seasonal subseries plot - understand seasonal patterns
tourism_stl |> 
  ggtime::gg_subseries(season_year) +
  labs(title = "Seasonal Patterns by Quarter",
       subtitle = "Shows consistency of seasonal effects")

4. Calculate the Remainder (\(R_t\))

The remainder is whatever is left over after you’ve stripped away both the trend and the seasonal patterns.

  • Formula: \(R_t = y_t - T_t - S_t\)

Summary Table: Additive vs. Multiplicative

Step Additive Calculation Multiplicative Calculation
Detrending \(y_t - T_t\) \(y_t / T_t\)
Seasonality Average of \((y_t - T_t)\) Average of \((y_t / T_t)\)
Remainder \(y_t - T_t - S_t\) \(y_t / (T_t \times S_t)\)

Putting it all together:

# Apply classical decomposition
tourism |>
  model(
    classical_decomposition(Trips, type = "additive")
  ) |>
  components() |>
  autoplot() +
  labs(title = "Classical additive decomposition of Australian tourism")
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_line()`).

Pros:

  • Simple and intuitive
  • Fast computation
  • Easy to understand

Cons:

  • Cannot handle missing values
  • Poor trend estimates at endpoints (open dataset and see how many missing values)
  • Assumes seasonal component is constant over time
  • Not robust to outliers

X-11 and SEATS Decomposition

More sophisticated methods include X-111 and SEATS (Signal extraction in ARIMA time series) decomposition:

?feasts::X_13ARIMA_SEATS    

References

Gomez, Victor, and Agustin Maravall. “Automatic modeling methods for univariate series.” A course in time series analysis (2001): 171-201.

Dagum, E.B. (1988), The X11 ARIMA/88 Seasonal Adjustment Method - Foundations And User’s Manual, Time Series Research and Analysis Division Statistics Canada, Ottawa.

Dagum, E. B., & Bianconcini, S. (2016) “Seasonal adjustment methods and real time trend-cycle estimation”. Springer.

X-13ARIMA-SEATS Documentation from the seasonal package’s website: http://www.seasonal.website/seasonal.html

Important

Official X-13ARIMA-SEATS documentation: https://www.census.gov/data/software/x13as.html

x11 The optional x11 special is used to invoke seasonal adjustment by an enhanced version of the methodology of the Census Bureau X-11 and X-11Q programs. The user can control the type of seasonal adjustment decomposition calculated (mode), the seasonal and trend moving averages used (seasonalma and trendma), and the type of extreme value adjustment performed during seasonal adjustment (sigmalim).

# X-11 decomposition

x11_dcmp <- tourism |>
  model(x11 = X_13ARIMA_SEATS(Trips ~ x11())) |>
  components()

x11_dcmp |>
  autoplot() +
  labs(title = "X-11 decomposition of Australian tourism")

X-11 Pros:

  • Better trend estimation than classical
  • Handles irregularities well
  • Industry standard for official statistics
  • More sophisticated outlier detection

X-11 Cons:

  • Complex algorithm
  • Requires specialized software
  • Less transparent than simpler methods
  • Many parameters to tune

Dagum, E. B., & Bianconcini, S. (2016). Seasonal adjustment methods and real time trend-cycle estimation. Springer.

# SEATS decomposition
seats_dcmp <- tourism |>
  model(seats = X_13ARIMA_SEATS(Trips ~ seats())) |>
  components()

seats_dcmp |>
  autoplot() +
  labs(title = "SEATS decomposition of Australian tourism")

SEATS Pros:

  • Model-based approach using ARIMA
  • Theoretically sound
  • Good for forecasting
  • Handles complex seasonal patterns

SEATS Cons:

  • Complex parameter selection
  • Requires ARIMA modeling expertise
  • May be overfitted for simple series
  • Computationally intensive

STL Decomposition

Decompose a time series into seasonal, trend and remainder components. Seasonal components are estimated iteratively using STL. Multiple seasonal periods are allowed. The trend component is computed for the last iteration of STL. Non-seasonal time series are decomposed into trend and remainder only. In this case, supsmu is used to estimate the trend. Optionally, the time series may be Box-Cox transformed before decomposition. Unlike stl, mstl is completely automated.

References

R. B. Cleveland, W. S. Cleveland, J.E. McRae, and I. Terpenning (1990) STL: A Seasonal-Trend Decomposition Procedure Based on Loess. Journal of Official Statistics, 6, 3–73.

STL (Seasonal and Trend decomposition using Loess) is a versatile and robust method:

?feasts::STL
?stats::supsmu

# STL decomposition
tourism |>
  model(
    STL(Trips ~ trend(window = 7) + 
                season(window = "periodic"),
        robust = TRUE)) |>
  components() |>
  autoplot() +
  labs(title = "STL decomposition of Australian tourism")

STL Features

# Varying STL parameters
stl_models <- tourism |>
  model(
    `STL(13)` = STL(Trips ~ trend(window = 13) + season(window = 13)),
    `STL(49)` = STL(Trips ~ trend(window = 49) + season(window = 13)),
    `STL(periodic)` = STL(Trips ~ season(window = "periodic"))
  ) 

# Plot the different STL decompositions
stl_models |>
  components() |> # not working 
  autoplot() +
  facet_wrap(~ .model, ncol = 1) +
  theme(legend.position = "bottom")

# Keeps the metadata intact so autoplot() knows exactly how to format the axes)
?autoplot
Help on topic 'autoplot' was found in the following packages:

  Package               Library
  fabletools            /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
  ggplot2               /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
  ggtime                /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
  feasts                /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library


Using the first match ...
# For Model 1
stl_models |> select(`STL(13)`) |> components() |> autoplot() + ggtitle("STL(13)")

# For Model 2
stl_models |> select(`STL(49)`) |> components() |> autoplot() + ggtitle("STL(49)")

# For Model 3
stl_models |> select(`STL(periodic)`) |> components() |> autoplot() + ggtitle("Periodic Seasonality")

STL Window Size: Controlling Smoothness vs Flexibility

Trend window

  • Small (13) → Flexible, follows data closely (≈ 3 years of quarterly data)
  • Large (49) → Very smooth, ignores short-term bumps (≈ 12 years of quarterly data)

Season window

  • Small (13) → Seasonal pattern can evolve over time
  • periodic → Seasonality is fixed, same every year (baseline case)

Why these numbers?

  • STL(13): ≈ 3 years → captures local trend and seasonal changes
  • STL(49): ≈ 12 years → highlights only long-term trend
  • STL(periodic): assumes seasonal pattern never changes

Rule of thumb

  • Smaller windows → More flexible, captures short-term variation
  • Larger windows → Smoother, only long-term patterns remain
  • periodic → Seasonality is fixed across the whole sample

Same output, but preferred if you have multiple models.

model_names <- c("STL(13)", "STL(49)", "STL(periodic)")

for (name in model_names) {
  p <- stl_models |>
    select(all_of(name)) |>
    components() |>
    autoplot() +
    labs(title = paste("Decomposition for", name))
  
  print(p) # You must use print() inside a loop to show the plot
}

STL Pros:

  • Can handle any type of seasonality

  • Seasonal component can vary over time

  • Robust to outliers

  • Can be used with any frequency of data

  • Flexible parameter control

  • Handles missing values well

STL Cons:

  • Only provides additive decomposition

  • Can be sensitive to parameter choices

  • More complex than classical methods

  • Requires understanding of loess smoothing

Comparison of All Methods

# Apply all four methods to the same data
all_decomp <- tourism |>
  model(
    Classical = classical_decomposition(Trips, type = "additive"),
    X11 = X_13ARIMA_SEATS(Trips ~ x11()),
    SEATS = X_13ARIMA_SEATS(Trips ~ seats()),
    STL = STL(Trips)
  ) |>
  components()

# Create individual plots
p1 <- tourism |>
  model(classical_decomposition(Trips, type = "additive")) |>
  components() |>
  autoplot() +
  labs(title = "Classical Decomposition")

p2 <- tourism |>
  model(X_13ARIMA_SEATS(Trips ~ x11())) |>
  components() |>
  autoplot() +
  labs(title = "X-11 Decomposition")

p3 <- tourism |>
  model(X_13ARIMA_SEATS(Trips ~ seats())) |>
  components() |>
  autoplot() +
  labs(title = "SEATS Decomposition")

p4 <- tourism |>
  model(STL(Trips)) |>
  components() |>
  autoplot() +
  labs(title = "STL Decomposition")

# Arrange in 2x2 grid
library(patchwork)
(p1 | p2) / (p3 | p4)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_line()`).

Method Selection Guidelines

Use Classical when:

  • You need a simple, quick decomposition

  • Data has no missing values

  • Teaching/explaining concepts

Use X-11 when:

  • Working with official statistics

  • Need industry-standard results

  • Have complex irregular patterns

Use SEATS when:

  • Model-based approach is preferred

  • Forecasting is the main goal

  • Have ARIMA modeling experience

Use STL when:

  • Need flexible, robust decomposition

  • Seasonal patterns change over time

  • Data has outliers or missing values

  • General-purpose analysis

Advanced Analysis: Component Inspection

# First create the STL decomposition
tourism_stl <- tourism |>
  model(STL(Trips)) |>
  components()

# Examine the strength of seasonal vs trend components
tourism_stl |> 
  as_tibble() |> 
  summarise(
    trend_strength = 1 - var(remainder) / var(trend + remainder),
    seasonal_strength = 1 - var(remainder) / var(season_year + remainder)
  )
# Plot remainder to identify outliers/anomalies
tourism_stl |> 
  autoplot(remainder) +
  labs(title = "Remainder Component: Anomaly Detection",
       subtitle = "Large deviations indicate unusual events")

Choosing the Right Model: Decision Framework

Statistical Tests (Less Common)

Advanced Insights and Best Practices

1. The Seasonally Adjusted Series

This is often what business stakeholders really want to see:

Seasonally_Adjusted = Original - Seasonal_Component  # (additive)
Seasonally_Adjusted = Original / Seasonal_Component  # (multiplicative)

It shows the true underlying business performance stripped of predictable seasonal effects.

Caution

Why Seasonal Adjustment Matters

  • The raw series mixes true growth/decline with seasonal ups and downs.

  • If you don’t adjust, comparing (say) December vs January is apples to oranges because December always has a holiday spike.

  • Seasonal adjustment strips out the expected seasonal effect → so you’re comparing December vs January on equal footing (apples to apples).

2. Multiple Seasonal Patterns: Use STL

Some data has multiple seasonal patterns (daily + weekly, monthly + yearly):

# Multiple seasonality example - electricity demand
# Daily data with both daily and weekly patterns
vic_elec_sample <- vic_elec |>
  filter(year(Time) == 2014, month(Time) == 7) |>  # July 2014 only
  select(Time, Demand)

# Plot showing multiple seasonal patterns
vic_elec_sample |>
  autoplot(Demand) +
  labs(title = "Electricity Demand: Multiple Seasonal Patterns",
       subtitle = "Daily patterns within weekly patterns - July 2014",
       y = "Demand (MW)", x = "Time")

# Aggregate to daily level to show weekly patterns
vic_elec_daily <- vic_elec |>
  filter(year(Time) == 2014) |>
  index_by(Date = date(Time)) |>
  summarise(
    Demand = sum(Demand)/1e3,
    Temperature = max(Temperature)
  )

vic_elec_daily |>
  autoplot(Demand) +
  labs(title = "Daily Electricity Demand: Weekly Seasonal Patterns",
       subtitle = "Lower demand on weekends - Australia 2014",
       y = "Daily Demand (GWh)", x = "Date")

# STL decomposition capturing multiple seasonalities (if using mstl)
# Note: This requires daily data with multiple seasonal periods
vic_elec_sample |>
  model(
    STL(Demand ~ trend(window = 48*7) + season(window = "periodic")
        )
  ) |>
  components() |>
  autoplot() +
  labs(title = "STL Decomposition of Half-Hourly Electricity Demand",
       subtitle = "Captures both daily and weekly seasonal patterns")

Lets restrict ourselves to a few days only to see the hourly seasonality pattern.

vic_elec_sample |>
  model(
    STL(Demand ~ trend(window = 48*7) + 
           season("day", window = "periodic") + 
           season("hour", window = "periodic"))
  ) |>
  components() |>
  # Filter for just the first few days of July to "zoom in"
  filter(Time < as.POSIXct("2014-07-05", tz = "Australia/Melbourne")) |>
  autoplot() +
  theme(legend.position = "bottom")

This shows:

  1. Half-hourly data with daily cycles (48 periods per day)
  2. Daily data with weekly cycles (7 periods per week)
  3. STL decomposition that can handle these multiple seasonal patterns

The multiple seasonality comes from electricity demand having both daily patterns (peak usage times) and weekly patterns (different weekday vs weekend usage). Note we do not use x11 and SEATS as they are designed for monthly or quarterly data.

STL allows you to model more explicitly.

vic_elec_sample |>
  model(
    STL(Demand ~ trend(window = 48*7) + 
           season("day", window = "periodic") + 
           season("week", window = 13) +
           season("hour", window = 47)) 
  ) |>
  components() |>
  autoplot() +
  labs(title = "Multi-Seasonal STL Decomposition",
       subtitle = "Capturing Daily and Weekly patterns for Electricity Demand")

Finally, make sure you chosen chart has nice labels.

3. Forecasting after Decomposition

STL is for decomposition only - not forecasting. However, we can use decomposition insights to improve forecasting

# Method 1: Forecast seasonally adjusted data
tourism_sa <- tourism |>
  model(STL(Trips)) |>
  components() |>
  select(Quarter, Trips, season_year) |>
  mutate(seasonally_adjusted = Trips - season_year)

# Forecast the seasonally adjusted series
sa_forecast <- tourism_sa |>
  model(ETS(seasonally_adjusted)) |>
  forecast(h = 8)  # 2 years = 8 quarters

sa_forecast |>
  autoplot(tourism_sa) +
  labs(title = "Forecast of Seasonally Adjusted Tourism",
       y = "Seasonally Adjusted Trips")

# Method 2: Use ARIMA on original data (handles seasonality automatically)
arima_forecast <- tourism |>
  model(ARIMA(Trips)) |>
  forecast(h = 8)

arima_forecast |>
  autoplot(tourism) +
  labs(title = "Tourism Forecast Using ARIMA",
       subtitle = "ARIMA automatically handles seasonal patterns",
       y = "Trips")

Key Takeaways and Action Items

Quick Reference Decision Tree:

  • Seasonal variation constant? → Additive model
  • Seasonal variation proportional to level? → Multiplicative model
  • Want robust, flexible method? → Always use STL
  • Need to remove seasonality? → Use seasonally adjusted series
  • Looking for anomalies? → Examine remainder component

Common Pitfalls to Avoid:

  1. Not plotting first: Always visualize before choosing a method
  2. Ignoring domain knowledge: Your business understanding should guide model choice
  3. Over-interpreting remainder: Some “noise” is just random - don’t chase every spike
  4. Wrong seasonal period: Ensure you specify the correct seasonal frequency

Next Steps:

  1. Apply decomposition to your own data
  2. Create seasonally adjusted versions for reporting
  3. Use components for targeted forecasting
  4. Monitor remainder for anomaly detection

Remember: Decomposition is not just a statistical exercise - it’s a powerful tool for understanding your business and making data-driven decisions.

Appendix

  1. https://link.springer.com/book/10.1007/978-3-319-31822-6

  2. https://jdemetradocumentation.github.io/JDemetra-documentation/pages/theory/SA_X11.html

  3. https://www.bundesbank.de/resource/blob/621580/2b7fafe37e0ecf8469d083dbbd3e402c/mL/1999-09-census-x-12-arima-data.pdf

Method Best Use Case Seasonality Handling Key Strength Main Weakness
Classical Simple, steady data Fixed: Assumes the same pattern every year. Easy to understand and calculate. Loses data at the beginning and end; sensitive to outliers.
X-11 Economic and retail data Evolving: Seasonal patterns can vary slowly. Handles trading days and holiday effects (like Easter). Limited to monthly or quarterly data only.
SEATS Complex economic indicators Model-based: Uses ARIMA logic to find patterns. Highly robust and mathematically sophisticated. Extremely complex to interpret; limited to quarterly/monthly.
STL Modern, irregular, or high-frequency data Flexible: User controls exactly how fast seasonality changes. Robust to outliers; handles any frequency (daily, weekly, etc.). Only handles additive models directly (requires logs for multiplicative).

Footnotes

  1. The original X-11 method has been enhanced and is now available in more sophisticated versions, such as X-12-ARIMA and X-13ARIMA-SEATS, which add modeling capabilities to handle calendar effects and outliers.↩︎