Mathew K Analytics

Lesson 19 · Python For Time Series

Understanding Stationarity and the Augmented Dickey-Fuller Test in Time Series Analysis

Welcome! In this lesson, you will learn how to check if a time series is stationary. You will also try the Augmented Dickey-Fuller (ADF) test in Python. We…

⬇ 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
 

Stationarity and Augmented Dickey-Fuller Test: A Beginner's Guide#

Welcome! In this lesson, you will learn how to check if a time series is stationary. You will also try the Augmented Dickey-Fuller (ADF) test in Python.

We will use real weather data to understand these topics with simple code and friendly steps.

import warnings; warnings.filterwarnings("ignore")

# Data setup: Load daily minimum temperature data
import pandas as pd
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-min-temperatures.csv"
df = pd.read_csv(url, parse_dates=["Date"])
print("Data shape:", df.shape)
df.head()
Data shape: (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
# Quick data check: Check for missing values
df.isnull().sum()
Date    0
Temp    0
dtype: int64
# Basic plot: Line chart of daily temperatures
import matplotlib.pyplot as plt
plt.figure(figsize=(10,4))
plt.plot(df["Date"], df["Temp"])
plt.title("Daily Minimum Temperatures")
plt.ylabel("Temperature (Celsius)")
plt.xlabel("Date")
plt.show()
No description has been provided for this image

What is Stationarity?#

A stationary time series stays stable over time. Mean and variance do not change as you move along. If trends or seasonality exist, the data is probably not stationary. Why does this matter? Many forecasting methods only work with stationary series.

# Quick mean and variance check
window = 365  # About a year
df["rolling_mean"] = df["Temp"].rolling(window).mean()
df["rolling_var"] = df["Temp"].rolling(window).var()
plt.figure(figsize=(10,4))
plt.plot(df["Date"], df["Temp"], label="Original")
plt.plot(df["Date"], df["rolling_mean"], label="Rolling Mean")
plt.plot(df["Date"], df["rolling_var"], label="Rolling Variance")
plt.legend()
plt.title("Rolling Mean and Variance")
plt.show()
No description has been provided for this image

The Augmented Dickey-Fuller (ADF) Test#

The ADF test is a standard way to check for stationarity. It gives a test statistic and a p-value. A low p-value (usually below 0.05) means the data is probably stationary. You do not have to do the math yourself; Python can handle it!

# Run the Augmented Dickey-Fuller test
from statsmodels.tsa.stattools import adfuller
result = adfuller(df["Temp"])
print("ADF Statistic:", result[0])
print("p-value:", result[1])
for key, value in result[4].items():
    print("Critical Value ({}): {:.3f}".format(key, value))
    
ADF Statistic: -4.444804924611687
p-value: 0.00024708263003611164
Critical Value (1%): -3.432
Critical Value (5%): -2.862
Critical Value (10%): -2.567
# Print a human-friendly summary
if result[1] < 0.05:
    print("The series is likely stationary (p < 0.05)")
else:
    print("The series is likely not stationary (p >= 0.05)")
    
The series is likely stationary (p < 0.05)

Why would a series not be stationary?#

Non-stationary data often has a trend or seasonality. A trend means values go up or down overall. Seasonality is a regular pattern, like higher temperatures each summer. Next, we will try to remove these effects.

# Differencing to remove trend
df["diff_1"] = df["Temp"].diff()
plt.figure(figsize=(10,4))
plt.plot(df["Date"], df["diff_1"])
plt.title("First Difference of Temperature")
plt.show()
No description has been provided for this image
# Run ADF on differenced data
import numpy as np
diff_result = adfuller(df["diff_1"].dropna())
print("ADF Statistic (1st diff):", diff_result[0])
print("p-value:", diff_result[1])
ADF Statistic (1st diff): -18.028224167992082
p-value: 2.6815618226750238e-30
# Seasonal differencing to remove seasonality effect
df["seasonal_diff"] = df["Temp"].diff(365)
plt.figure(figsize=(10,4))
plt.plot(df["Date"], df["seasonal_diff"])
plt.title("Seasonal Differencing (365 days)")
plt.show()
No description has been provided for this image
# Try ADF again with seasonal differencing
seasonal_result = adfuller(df["seasonal_diff"].dropna())
print("ADF Statistic (seasonal):", seasonal_result[0])
print("p-value:", seasonal_result[1])
ADF Statistic (seasonal): -19.036532147066108
p-value: 0.0

Useful tricks: Log transformation#

A log transformation can help make variance more constant. This is useful especially if peaks and dips grow over time. Let us try it out!

# Apply a log transformation
df["log_temp"] = np.log(df["Temp"] + 1)
plt.figure(figsize=(10,4))
plt.plot(df["Date"], df["log_temp"])
plt.title("Log-Transformed Temperature")
plt.show()
No description has been provided for this image
# Combine: Log transform AND differencing
df["log_diff"] = df["log_temp"].diff()
plt.figure(figsize=(10,4))
plt.plot(df["Date"], df["log_diff"])
plt.title("First Difference of Log-Transformed Data")
plt.show()
No description has been provided for this image
# ADF test after all transformations
transformed_result = adfuller(df["log_diff"].dropna())
print("ADF Statistic (log diff):", transformed_result[0])
print("p-value:", transformed_result[1])
ADF Statistic (log diff): -19.24771525638979
p-value: 0.0
# Mini Project: Ask user for differencing period
period = int(input("Enter differencing period (e.g., 1 for day, 365 for year): "))
df["user_diff"] = df["Temp"].diff(period)
plt.figure(figsize=(10,4))
plt.plot(df["Date"], df["user_diff"], label="User Difference")
plt.title(f"Differencing with Period = {period}")
plt.legend()
plt.show()
No description has been provided for this image
 
# Extra: Try ADF test on user-differenced column
user_result = adfuller(df["user_diff"].dropna())
print("ADF Statistic (Your diff):", user_result[0])
print("p-value:", user_result[1])
ADF Statistic (Your diff): -19.036532147066108
p-value: 0.0

Troubleshooting common problems#

If you see errors, check for missing data or try a smaller period. Remember to drop missing data using .dropna() before running the ADF test. If your p-value does not go low, try both differencing and log transforms together!

# Challenge: Try with your own CSV file
file = input("Paste a CSV URL or path to your own time series: ")
try:
    new_df = pd.read_csv(file, parse_dates=True)
    print(new_df.head())
except Exception as e:
    print("Could not load your file. Please check the path and try again.")
    
     Month  Passengers
0  1949-01         112
1  1949-02         118
2  1949-03         132
3  1949-04         129
4  1949-05         121
 

Recap: What did we learn?#

You learned how to:

  • Load time series data
  • Visualize and check for stationarity
  • Use the Augmented Dickey-Fuller test
  • Transform data for better forecasting
  • Debug problems

Great job! Stationarity checks are now part of your data science toolkit.

Next stepsKeep learning and share!#

Practice stationarity tests on new datasets. Share your results with the community or in the video comments.

Like and subscribe if you found this lesson helpful. Happy coding!

Found this useful?

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