Seasonal Decomposition: Understanding Time Series Components
Author
AS
Published
September 29, 2025
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
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)
library(dplyr)# Load and prepare Australian tourism datatourism<-tourism|>summarise(Trips =sum(Trips))|>mutate(Quarter =yearquarter(Quarter))glimpse(tourism)
# First, always visualize your raw datatourism|>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.
# Additive decompositiontourism_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 decompositiontourism|>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 decompositiontourism|>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
Classical decomposition is one of the oldest methods for decomposing time series.
Moving Averages
The trend-cycle component is estimated using moving averages:
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.
Classical Decomposition Method
# Apply classical decompositiontourism|>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
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:
# X-11 decompositionx11_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
# SEATS decompositionseats_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
STL (Seasonal and Trend decomposition using Loess) is a versatile and robust method:
# STL decompositiontourism|>model(STL(Trips~trend(window =7)+season(window ="periodic"), robust =TRUE))|>components()|>autoplot()+labs(title ="STL decomposition of Australian tourism")
STL Features
# Varying STL parametersstl_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")))|>components()# Plot the different STL decompositionsstl_models|>autoplot()+facet_wrap(~.model, ncol =1)+labs(title ="STL decomposition with different parameters")
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
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 dataall_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 plotsp1<-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 gridlibrary(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 decompositiontourism_stl<-tourism|>model(STL(Trips))|>components()# Examine the strength of seasonal vs trend componentstourism_stl|>as_tibble()|>summarise( trend_strength =1-var(remainder)/var(trend+remainder), seasonal_strength =1-var(remainder)/var(season_year+remainder))
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
Some data has multiple seasonal patterns (daily + weekly, monthly + yearly):
# Multiple seasonality example - electricity demand# Daily data with both daily and weekly patternsvic_elec_sample<-vic_elec|>filter(year(Time)==2014, month(Time)==7)|># July 2014 onlyselect(Time, Demand)# Plot showing multiple seasonal patternsvic_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 patternsvic_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 - Victoria, Australia 2014", y ="Daily Demand (GWh)", x ="Date")
# STL decomposition capturing multiple seasonalities (if using mstl)# Note: This requires daily data with multiple seasonal periodsvic_elec_sample|>model(STL(Demand~trend(window =21)+season(window ="periodic")))|>components()|>autoplot()+labs(title ="STL Decomposition of Half-Hourly Electricity Demand", subtitle ="Captures both daily and weekly seasonal patterns")
This shows:
Half-hourly data with daily cycles (48 periods per day)
Daily data with weekly cycles (7 periods per week)
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).
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 datatourism_sa<-tourism|>model(STL(Trips))|>components()|>select(Quarter, Trips, season_year)|>mutate(seasonally_adjusted =Trips-season_year)# Forecast the seasonally adjusted seriessa_forecast<-tourism_sa|>model(ETS(seasonally_adjusted))|>forecast(h =8)# 2 years = 8 quarterssa_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:
Not plotting first: Always visualize before choosing a method
Ignoring domain knowledge: Your business understanding should guide model choice
Over-interpreting remainder: Some “noise” is just random - don’t chase every spike
Wrong seasonal period: Ensure you specify the correct seasonal frequency
Next Steps:
Apply decomposition to your own data
Create seasonally adjusted versions for reporting
Use components for targeted forecasting
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.
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.↩︎