Mathew K Analytics

Lesson 21 · Python For Time Series

Seasonal Decomposition in Python: Techniques for Time Series Analysis and Interpretation

In this lesson, you will learn to break down time series data into trend, seasonal, and random parts. No previous experience required! Let us explore using…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Welcome to Seasonal Decomposition in Python!#

In this lesson, you will learn to break down time series data into trend, seasonal, and random parts.

No previous experience required!

Let us explore using real-world datasets and fun visualizations.

# Let us get started with some basic setup
import warnings; warnings.filterwarnings("ignore")
import pandas as pd
import matplotlib.pyplot as plt
 
 

What is Seasonal Decomposition?#

Many datasets collected over time, like temperatures or sales, show repeating patterns. Seasonal decomposition helps us separate these patternstrend, seasonality, and randomness.

This is a key tool for understanding and forecasting with time series data.

# Data setup
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-min-temperatures.csv"
df = pd.read_csv(url)
print("Shape of DataFrame:", df.shape)
print(df.head())
 
 
Shape of DataFrame: (3650, 2)
         Date  Temp
0  1981-01-01  20.7
1  1981-01-02  17.9
2  1981-01-03  18.8
3  1981-01-04  14.6
4  1981-01-05  15.8
# Let us plot the temperature data
plt.figure(figsize=(10,4))
plt.plot(pd.to_datetime(df["Date"]), df["Temp"])
plt.title("Daily Minimum Temperatures")
plt.xlabel("Date")
plt.ylabel("Temperature (Celsius)")
plt.show()
No description has been provided for this image

Breaking Down Time Series Data#

Time series signals can have:

  • A trend: the general direction up or down over time.
  • Seasonality: repeating patterns at regular periods (weeks, months, years).
  • Residuals: what is left, randomness or noise.

Decomposition means splitting a time series into these parts.

# Let us convert the date column and set as index for easy analysis
df["Date"] = pd.to_datetime(df["Date"])
df = df.set_index("Date")
 
 
# Let us see if our index is a DatetimeIndex now
print(type(df.index))
print(df.index[:5])
<class 'pandas.core.indexes.datetimes.DatetimeIndex'>
DatetimeIndex(['1981-01-01', '1981-01-02', '1981-01-03', '1981-01-04',
               '1981-01-05'],
              dtype='datetime64[ns]', name='Date', freq=None)
# Decompose with statsmodels
from statsmodels.tsa.seasonal import seasonal_decompose
result = seasonal_decompose(df["Temp"], model="additive", period=365)
 
 
# Let us plot all the parts of decomposition
result.plot()
plt.tight_layout()
plt.show()
No description has been provided for this image
# Let us access just the trend as an example
trend = result.trend
plt.plot(trend, color="red")
plt.title("Trend Component Only")
plt.ylabel("Temperature")
plt.show()
No description has been provided for this image

What about the seasonal part?#

Seasonality repeats at regular intervalslike summer and winter.

By looking at the seasonal component, we can see how values bounce up and down the same way each year.

# Plotting the seasonal component alone
seasonal = result.seasonal
plt.plot(seasonal, color="green")
plt.title("Seasonal Component Only")
plt.show()
No description has been provided for this image

What are residuals?#

After removing the trend and seasonality, what is left over is the residual. The residual is the random, unpredictable part of the data.

High randomness in the residual may mean the data is hard to forecast.

# Try plotting only the residual for a closer look at randomness
residual = result.resid
residual.plot(style=".", alpha=0.7)
plt.title("Residual (Random) Component Only")
plt.show()
No description has been provided for this image
# Safe Access: Checking for missing values in each part
print("Missing in trend:", trend.isna().sum())
print("Missing in seasonal:", seasonal.isna().sum())
print("Missing in residual:", residual.isna().sum())
Missing in trend: 364
Missing in seasonal: 0
Missing in residual: 364
# Fill missing values if needed, using 'bfill' (backward fill) method
trend_filled = trend.fillna(method="bfill")
seasonal_filled = seasonal.fillna(method="bfill")
residual_filled = residual.fillna(method="bfill")
# Using Decomposition in a Mini Project: Finding the hottest months
monthly = df.resample("M").mean()
seasonal_month = seasonal.groupby(seasonal.index.month).mean()
plt.bar(range(1,13), seasonal_month)
plt.title("Average Seasonal Effect by Month")
plt.xlabel("Month")
plt.ylabel("Seasonal Offset")
plt.show()
No description has been provided for this image
# Try your own! Ask the user for a month and report the seasonal effect
month = input("Enter a month number (1-12): ")
try:
    num = int(month)
    if 1 <= num <= 12:
        print(f"Seasonal effect for month {num}: {seasonal_month[num]}")
    else:
        print("Please pick a number from 1 to 12.")
except ValueError:
    print("That was not a valid number.")
    
Seasonal effect for month 7: -4.59757895709619
# Best Practices: Try using multiplicative mode if your data multiplies, not adds
result_mul = seasonal_decompose(df["Temp"], model="multiplicative", period=365)
result_mul.plot()
plt.suptitle("Multiplicative Decomposition", y=1.02)
plt.show()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[15], line 2
      1 # Best Practices: Try using multiplicative mode if your data multiplies, not adds
----> 2 result_mul = seasonal_decompose(df["Temp"], model="multiplicative", period=365)
      3 result_mul.plot()
      4 plt.suptitle("Multiplicative Decomposition", y=1.02)

File c:\Users\makmw\AppData\Local\Programs\Python\Python312\Lib\site-packages\statsmodels\tsa\seasonal.py:157, in seasonal_decompose(x, model, filt, period, two_sided, extrapolate_trend)
    155 if model.startswith("m"):
    156     if np.any(x <= 0):
--> 157         raise ValueError(
    158             "Multiplicative seasonality is not appropriate "
    159             "for zero and negative values"
    160         )
    162 if period is None:
    163     if pfreq is not None:

ValueError: Multiplicative seasonality is not appropriate for zero and negative values
# Troubleshooting: What if your period is wrong?
try:
    seasonal_decompose(df["Temp"], model="additive", period=7)
    print("No error, but the patterns might look strange!")
except Exception as e:
    print("Error:", e)
    
No error, but the patterns might look strange!
# Extra Tip: Save your trend to a new file
trend_filled.to_csv("trend_component.csv")
print("Trend component saved!")
Trend component saved!

Recap: What have we learned?#

  • Loaded real time series data.
  • Plotted and explored changes through time.
  • Broke the series down into trend, seasonality, and randomness.
  • Used the decomposition to spot patterns, cycles, and surprises.
  • Saved our results for future use.

You are ready for time series analysis!

Challenge: Try more!#

Can you:

  • Repeat this for another year or different temperature dataset?
  • Apply decomposition to the 'Airline Passengers' dataset?
  • Explore trend and seasonal changes for your favorite city?

Share your ideas or questions in the comments below.

Thank you for learning with us! Enjoy time series in Python.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.