Mathew K Analytics

Lesson 25 · Python For Time Series

Understanding Exponential Smoothing and Holt-Winters Forecasting Models in Python

In this lesson, you will learn to forecast time series data using easy and powerful methods. What is exponential smoothing? How do you handle seasonality…

⬇ 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 Exponential Smoothing and Holt-Winters in Python!#

In this lesson, you will learn to forecast time series data using easy and powerful methods.

  • What is exponential smoothing?
  • How do you handle seasonality and trend?
  • How to use real-world datasets for forecasting?

Let us get started step by step.

import warnings
warnings.filterwarnings("ignore")  # Silence any warnings for a clean start

# First, let us import the libraries we will use
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

print("Ready to go!")
Ready to go!

What is Exponential Smoothing?#

Exponential smoothing is a method for predicting the next value in a series. It gives more weight to recent data points, so your forecasts respond to changes quickly.

This is very useful in real-life data, like sales, temperature, or demand over time.

# Data setup: Load a real sales dataset
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/shampoo.csv"
data = pd.read_csv(url)
print("Rows, columns:", data.shape)
data.head()
Rows, columns: (36, 2)
Month Sales
0 1-01 266.0
1 1-02 145.9
2 1-03 183.1
3 1-04 119.3
4 1-05 180.3
# Let us see all column names
print(data.columns.tolist())
['Month', 'Sales']
# Plot the sales data to understand its pattern
plt.figure(figsize=(8, 4))
plt.plot(data['Sales'], marker='o')
plt.title('Monthly Shampoo Sales')
plt.xlabel('Month')
plt.ylabel('Sales')
plt.grid(True)
plt.show()
No description has been provided for this image

Why smooth data?#

Raw sales data jumps up and down. Smoothing helps find the real pattern. Exponential smoothing is like "forgetting" old noise.

You can use it to prepare for holidays or busy times in business.

# Simple exponential smoothing: apply to sales data
from statsmodels.tsa.holtwinters import SimpleExpSmoothing

sales = data['Sales'].astype(float)
model = SimpleExpSmoothing(sales)
fit = model.fit(smoothing_level=0.4, optimized=False)
pred = fit.fittedvalues

plt.figure(figsize=(8,4))
plt.plot(sales, label='Original')
plt.plot(pred, label='Smoothed', color='red')
plt.legend()
plt.title('Simple Exponential Smoothing')
plt.show()
No description has been provided for this image

How does smoothing work?#

You just saw a smoother line. Exponential smoothing uses a "smoothing level".

A value near 1 follows recent data closely. A value near 0 is smoother. You can adjust this to suit noisy or calm data.

# Try a different smoothing level
fit2 = model.fit(smoothing_level=0.9, optimized=False)
pred2 = fit2.fittedvalues

plt.figure(figsize=(8,4))
plt.plot(sales, label='Original')
plt.plot(pred2, color='green', label='Level 0.9')
plt.plot(pred, color='red', label='Level 0.4')
plt.legend()
plt.title('Compare Smoothing Levels')
plt.show()
No description has been provided for this image

What about trend and seasonality?#

Simple smoothing is not enough if data grows over time or has repeating seasons. You might see sales increase every year, or every holiday month.

That is where Holt and Holt-Winters methods come in.

# Holt: Handles trends in data
from statsmodels.tsa.holtwinters import ExponentialSmoothing

holt_model = ExponentialSmoothing(sales, trend='add', seasonal=None)
holt_fit = holt_model.fit(optimized=True)
holt_pred = holt_fit.fittedvalues

plt.figure(figsize=(8,4))
plt.plot(sales, label='Original')
plt.plot(holt_pred, color='purple', label='Holt Trend')
plt.legend()
plt.title('Holt Method for Trend')
plt.show()
No description has been provided for this image
# Holt-Winters: handles both trend and seasonality
hw_model = ExponentialSmoothing(sales, trend='add', seasonal='add', seasonal_periods=12)
hw_fit = hw_model.fit(optimized=True)
hw_pred = hw_fit.fittedvalues

plt.figure(figsize=(8,4))
plt.plot(sales, label='Original')
plt.plot(hw_pred, color='orange', label='Holt-Winters')
plt.legend()
plt.title('Holt-Winters (Trend + Seasonality)')
plt.show()
No description has been provided for this image

Quick recap: Three layers of smoothing#

  1. Simple: Just smooths, ignores trend and cycles.
  2. Holt: Adds line-up or line-down trends.
  3. Holt-Winters: Adds cycles, like holidays or seasons.

You pick based on what your data looks like.

# Make a forecast for future months
future = hw_fit.forecast(6)
print('Next 6 months sales forecast:')
print(future)
Next 6 months sales forecast:
36    629.885574
37    624.241877
38    630.485507
39    685.765159
40    679.799033
41    732.620065
dtype: float64
# Plot prediction vs. real data for the last points
plt.figure(figsize=(8,4))
plt.plot(sales, label='Actual Sales')
plt.plot(hw_fit.fittedvalues, label='Fitted', color='orange')
plt.plot(range(len(sales), len(sales)+6), future, 'go--', label='Forecast')
plt.legend()
plt.title('Real vs. Fitted vs. Forecasted Sales')
plt.show()
No description has been provided for this image
# Safe access: What if there is missing data?
print(data.isnull().sum())
Month    0
Sales    0
dtype: int64
# Fill missing values with the previous month
data_filled = data.fillna(method='ffill')
print(data_filled.isnull().sum())
Month    0
Sales    0
dtype: int64
# Quick mini-project: Predict sales after user input
user_months = int(input('How many months to predict? (1-12): '))
my_forecast = hw_fit.forecast(user_months)
print('Here is your forecast:')
print(my_forecast)
Here is your forecast:
36    629.885574
37    624.241877
38    630.485507
39    685.765159
40    679.799033
dtype: float64
 
# Troubleshooting: What if fitting fails?
try:
    broken_model = ExponentialSmoothing([], trend='add', seasonal='add', seasonal_periods=12)
    broken_fit = broken_model.fit()
except Exception as e:
    print('Error:', e)
    
Error: Cannot compute initial seasonals using heuristic method with less than two full seasonal cycles in the data.
# Extra tip: You can save your fitted model
import pickle
with open('hw_model.pkl', 'wb') as f:
    pickle.dump(hw_fit, f)
print('Model saved!')
Model saved!
# Challenge: Try your own smoothing level
level = float(input('Pick a smoothing level between 0 and 1: '))
user_fit = model.fit(smoothing_level=level, optimized=False)
print('Your smoothed values:')
print(user_fit.fittedvalues.head())
Your smoothed values:
0    266.0000
1    266.0000
2    241.9800
3    230.2040
4    208.0232
dtype: float64
 

Recap: What have you learned?#

  • How to smooth out sales data
  • How to spot trend and seasonality
  • Using Holt-Winters for forecasting
  • Handling missing data
  • Making predictions for real business planning

Practice with other datasets to get better!

Want more? Subscribe for new lessons!#

Try using exponential smoothing on other datasets. Share your charts in the comments below. Subscribe and turn on notifications for more tutorials!

Found this useful?

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