Mathew K Analytics

Lesson 27 · Python For Time Series

Creating Lag and Rolling Features for Time Series Analysis in Python

Welcome! Today we will explore how to create lag and rolling features using Python. These tools help us understand how past values affect the present in…

⬇ 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
 

Rolling and Lag Features in Time Series with Python#

Welcome! Today we will explore how to create lag and rolling features using Python.

These tools help us understand how past values affect the present in time series data.

Ready? Let's get started!

# Start by ignoring warnings for a smoother experience
import warnings
warnings.filterwarnings("ignore")

What Are Lag and Rolling Features?#

A lag feature helps you look back at previous time steps - like asking, "What was the value last week?"

A rolling feature, also known as moving average, helps you smooth out data by averaging over several past values.

These are useful for forecasting and spotting patterns!

# Data setup: Load the monthly shampoo sales dataset
import pandas as pd
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/shampoo.csv"
df = pd.read_csv(url)
df.columns = ["Month", "Sales"]
print("Data shape:", df.shape)
display(df.head())
Data shape: (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
# Plot the time series to see the sales over time
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 4))
plt.plot(df["Month"], df["Sales"], marker="o")
plt.title("Monthly Shampoo Sales")
plt.xlabel("Month")
plt.ylabel("Sales")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
No description has been provided for this image

Why Lag Features?#

Imagine you run a store and want to predict next month's sales.

It helps to know what you sold last month and the month before.

Lag features capture this history - making patterns clear for forecasting models.

# Creating a lag feature: sales from the previous month
df["Sales_Lag1"] = df["Sales"].shift(1)
display(df.head())
Month Sales Sales_Lag1
0 1-01 266.0 NaN
1 1-02 145.9 266.0
2 1-03 183.1 145.9
3 1-04 119.3 183.1
4 1-05 180.3 119.3
# Lag features for two and three months ago
df["Sales_Lag2"] = df["Sales"].shift(2)
df["Sales_Lag3"] = df["Sales"].shift(3)
display(df.head())
Month Sales Sales_Lag1 Sales_Lag2 Sales_Lag3
0 1-01 266.0 NaN NaN NaN
1 1-02 145.9 266.0 NaN NaN
2 1-03 183.1 145.9 266.0 NaN
3 1-04 119.3 183.1 145.9 266.0
4 1-05 180.3 119.3 183.1 145.9

What Is a Rolling Mean?#

A rolling mean takes an average of sales for a small window, like 3 months at a time.

This helps us see the main trend by removing quick ups and downs.

# Make a rolling average over 3 months
df["Rolling_Mean_3"] = df["Sales"].rolling(window=3).mean()
display(df.head(10))
Month Sales Sales_Lag1 Sales_Lag2 Sales_Lag3 Rolling_Mean_3
0 1-01 266.0 NaN NaN NaN NaN
1 1-02 145.9 266.0 NaN NaN NaN
2 1-03 183.1 145.9 266.0 NaN 198.333333
3 1-04 119.3 183.1 145.9 266.0 149.433333
4 1-05 180.3 119.3 183.1 145.9 160.900000
5 1-06 168.5 180.3 119.3 183.1 156.033333
6 1-07 231.8 168.5 180.3 119.3 193.533333
7 1-08 224.5 231.8 168.5 180.3 208.266667
8 1-09 192.8 224.5 231.8 168.5 216.366667
9 1-10 122.9 192.8 224.5 231.8 180.066667
# Compare sales, lag, and rolling mean on a plot
plt.figure(figsize=(10, 5))
plt.plot(df["Month"], df["Sales"], label="Actual Sales", marker="o")
plt.plot(df["Month"], df["Sales_Lag1"], label="Lag 1 Month", linestyle="--")
plt.plot(df["Month"], df["Rolling_Mean_3"], label="3-Month Avg", linewidth=3, alpha=0.7)
plt.xlabel("Month")
plt.ylabel("Sales")
plt.title("Sales, Lag, and Rolling Average")
plt.legend()
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
No description has been provided for this image
# Identify missing values caused by lagging and rolling
print(df.isnull().sum())
Month             0
Sales             0
Sales_Lag1        1
Sales_Lag2        2
Sales_Lag3        3
Rolling_Mean_3    2
dtype: int64
# Drop rows with missing values for modeling
df_clean = df.dropna().reset_index(drop=True)
print(df_clean.head())
  Month  Sales  Sales_Lag1  Sales_Lag2  Sales_Lag3  Rolling_Mean_3
0  1-04  119.3       183.1       145.9       266.0      149.433333
1  1-05  180.3       119.3       183.1       145.9      160.900000
2  1-06  168.5       180.3       119.3       183.1      156.033333
3  1-07  231.8       168.5       180.3       119.3      193.533333
4  1-08  224.5       231.8       168.5       180.3      208.266667

Useful Rolling and Lag Variations#

Besides the mean, we can also look at rolling sums, mins, or maxes.

Lag features can use any time gap, not just one month!

# Example: Rolling sum and minimum sales for 3 months
df_clean["Rolling_Sum_3"] = df_clean["Sales"].rolling(window=3).sum()
df_clean["Rolling_Min_3"] = df_clean["Sales"].rolling(window=3).min()
display(df_clean.head())
Month Sales Sales_Lag1 Sales_Lag2 Sales_Lag3 Rolling_Mean_3 Rolling_Sum_3 Rolling_Min_3
0 1-04 119.3 183.1 145.9 266.0 149.433333 NaN NaN
1 1-05 180.3 119.3 183.1 145.9 160.900000 NaN NaN
2 1-06 168.5 180.3 119.3 183.1 156.033333 468.1 119.3
3 1-07 231.8 168.5 180.3 119.3 193.533333 580.6 168.5
4 1-08 224.5 231.8 168.5 180.3 208.266667 624.8 168.5
# Iterating through rows: Print sales and lag for each month
for i, row in df_clean.iterrows():
    print(f"Month: {row['Month']}, Sales: {row['Sales']}, Lag-1: {row['Sales_Lag1']}")
    
Month: 1-04, Sales: 119.3, Lag-1: 183.1
Month: 1-05, Sales: 180.3, Lag-1: 119.3
Month: 1-06, Sales: 168.5, Lag-1: 180.3
Month: 1-07, Sales: 231.8, Lag-1: 168.5
Month: 1-08, Sales: 224.5, Lag-1: 231.8
Month: 1-09, Sales: 192.8, Lag-1: 224.5
Month: 1-10, Sales: 122.9, Lag-1: 192.8
Month: 1-11, Sales: 336.5, Lag-1: 122.9
Month: 1-12, Sales: 185.9, Lag-1: 336.5
Month: 2-01, Sales: 194.3, Lag-1: 185.9
Month: 2-02, Sales: 149.5, Lag-1: 194.3
Month: 2-03, Sales: 210.1, Lag-1: 149.5
Month: 2-04, Sales: 273.3, Lag-1: 210.1
Month: 2-05, Sales: 191.4, Lag-1: 273.3
Month: 2-06, Sales: 287.0, Lag-1: 191.4
Month: 2-07, Sales: 226.0, Lag-1: 287.0
Month: 2-08, Sales: 303.6, Lag-1: 226.0
Month: 2-09, Sales: 289.9, Lag-1: 303.6
Month: 2-10, Sales: 421.6, Lag-1: 289.9
Month: 2-11, Sales: 264.5, Lag-1: 421.6
Month: 2-12, Sales: 342.3, Lag-1: 264.5
Month: 3-01, Sales: 339.7, Lag-1: 342.3
Month: 3-02, Sales: 440.4, Lag-1: 339.7
Month: 3-03, Sales: 315.9, Lag-1: 440.4
Month: 3-04, Sales: 439.3, Lag-1: 315.9
Month: 3-05, Sales: 401.3, Lag-1: 439.3
Month: 3-06, Sales: 437.4, Lag-1: 401.3
Month: 3-07, Sales: 575.5, Lag-1: 437.4
Month: 3-08, Sales: 407.6, Lag-1: 575.5
Month: 3-09, Sales: 682.0, Lag-1: 407.6
Month: 3-10, Sales: 475.3, Lag-1: 682.0
Month: 3-11, Sales: 581.3, Lag-1: 475.3
Month: 3-12, Sales: 646.9, Lag-1: 581.3
# Quick feature creation with a list comprehension for lags 1-3
for l in range(1, 4):
    df_clean[f"Sales_Lag{l}_again"] = df_clean["Sales"].shift(l)
display(df_clean.head())
Month Sales Sales_Lag1 Sales_Lag2 Sales_Lag3 Rolling_Mean_3 Rolling_Sum_3 Rolling_Min_3 Sales_Lag1_again Sales_Lag2_again Sales_Lag3_again
0 1-04 119.3 183.1 145.9 266.0 149.433333 NaN NaN NaN NaN NaN
1 1-05 180.3 119.3 183.1 145.9 160.900000 NaN NaN 119.3 NaN NaN
2 1-06 168.5 180.3 119.3 183.1 156.033333 468.1 119.3 180.3 119.3 NaN
3 1-07 231.8 168.5 180.3 119.3 193.533333 580.6 168.5 168.5 180.3 119.3
4 1-08 224.5 231.8 168.5 180.3 208.266667 624.8 168.5 231.8 168.5 180.3
# Filtering: Find months where sales dropped below rolling mean
below_avg = df_clean[df_clean["Sales"] < df_clean["Rolling_Mean_3"]]
display(below_avg[["Month", "Sales", "Rolling_Mean_3"]].head())
Month Sales Rolling_Mean_3
0 1-04 119.3 149.433333
5 1-09 192.8 216.366667
6 1-10 122.9 180.066667
8 1-12 185.9 215.100000
9 2-01 194.3 238.900000
# Mini project part 1: Predict the next month's sales using last month's sales (very simple!)
df_clean["Pred_Next_Month"] = df_clean["Sales_Lag1"]
df_clean["Actual_Next_Month"] = df_clean["Sales"].shift(-1)
df_proj = df_clean.dropna().reset_index(drop=True)
print(df_proj[["Sales", "Pred_Next_Month", "Actual_Next_Month"]].head())
   Sales  Pred_Next_Month  Actual_Next_Month
0  231.8            168.5              224.5
1  224.5            231.8              192.8
2  192.8            224.5              122.9
3  122.9            192.8              336.5
4  336.5            122.9              185.9
# Mini project part 2: Compute mean absolute error (MAE)
mae = (df_proj["Pred_Next_Month"] - df_proj["Actual_Next_Month"]).abs().mean()
print(f"Mean Absolute Error: {mae:.2f}")
Mean Absolute Error: 69.92
# Troubleshooting: What if you see lots of NaN values?
print(df["Sales_Lag1"].isnull().sum(), "missing values in Sales_Lag1")
1 missing values in Sales_Lag1
# Extra tip: Try input() to create your own lag!
n = int(input("How many months back for lag? Enter a number: "))
df[f"Sales_Lag{n}_input"] = df["Sales"].shift(n)
display(df[["Month", "Sales", f"Sales_Lag{n}_input"]].head(n+3))
Month Sales Sales_Lag4_input
0 1-01 266.0 NaN
1 1-02 145.9 NaN
2 1-03 183.1 NaN
3 1-04 119.3 NaN
4 1-05 180.3 266.0
5 1-06 168.5 145.9
6 1-07 231.8 183.1
 

Challenge Exercise#

Can you create a rolling feature that looks at the last 6 months and grabs the maximum sales?

Hint: Use .rolling(window=6).max()

Try plotting it too!

Recap#

Today you explored lag and rolling features to capture trends and past information.

You used them to make simple predictions and learned how to prepare your data.

Great work!

Thanks and Next Steps#

Want to see more time series tutorials? Subscribe for more lessons and share your favorite time series ideas in the comments!

Happy coding!

Found this useful?

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