Search⌘ K
AI Features

Stationarity and Differencing

Explore the fundamentals of stationarity in time series data by learning to recognize non-stationary behavior through rolling statistics and the Augmented Dickey-Fuller test. Understand how differencing and variance-stabilizing transforms like log1p can help prepare data for reliable forecasting models. This lesson guides you to diagnose and apply appropriate transformations to achieve stable, interpretable time series for analysis.

Daily active users can rise from 8,000 in early January to 14,000 in late February, so yesterday is no longer a stable baseline for tomorrow. A stationary series has behavior that stays broadly stable over time. In practical terms, its average level and spread do not drift, and the relationship between two observations depends on how far apart they are, not on their calendar dates.

Many classical forecasting models assume this stable behavior. When the series keeps climbing, a pattern learned from days 1 through 30 may no longer describe days 61 through 90. Decomposition can reveal trend and seasonality; stationarity checks tell us whether the remaining behavior is stable enough for a model that relies on past patterns.

Inspecting a shifting baseline

A random walk with drift adds a small upward movement plus random variation at each step. Its level can wander far from where it started, so its rolling average changes over time. White noise is random variation around a fixed average, making it a useful contrast case.

The code builds both series with a fixed random seed. A rolling mean calculates the average within a moving window, such as the most recent 20 days. A rolling standard deviation calculates the spread within that same window. These summaries do not prove stationarity, but they make changing level and spread visible before we use a formal test.

Python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
n_periods = 200
rolling_window = 20
drift = 0.05
steps = np.random.normal(loc=0.0, scale=1.0, size=n_periods)
random_walk_with_drift = np.cumsum(drift + steps)
white_noise = np.random.normal(loc=0.0, scale=1.0, size=n_periods)
df = pd.DataFrame(
{
"random_walk_with_drift": random_walk_with_drift,
"white_noise": white_noise,
}
)
rolling_mean = df.rolling(window=rolling_window).mean()
rolling_std = df.rolling(window=rolling_window).std()
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(14, 8), sharex=True)
ax = axes[0, 0]
ax.plot(df.index, df["random_walk_with_drift"], label="Random walk (drift)", linewidth=1)
ax.plot(df.index, rolling_mean["random_walk_with_drift"], label=f"Rolling mean ({rolling_window})", linewidth=2)
ax.set_title("Random Walk with Drift: Level + Rolling Mean")
ax.set_ylabel("Value")
ax.legend(loc="best")
ax = axes[1, 0]
ax.plot(df.index, rolling_std["random_walk_with_drift"], label=f"Rolling std ({rolling_window})", linewidth=2)
ax.set_title("Random Walk with Drift: Rolling Standard Deviation")
ax.set_xlabel("Time")
ax.set_ylabel("Rolling std")
ax.legend(loc="best")
ax = axes[0, 1]
ax.plot(df.index, df["white_noise"], label="White noise", linewidth=1)
ax.plot(df.index, rolling_mean["white_noise"], label=f"Rolling mean ({rolling_window})", linewidth=2)
ax.set_title("White Noise: Level + Rolling Mean")
ax.legend(loc="best")
ax = axes[1, 1]
ax.plot(df.index, rolling_std["white_noise"], label=f"Rolling std ({rolling_window})", linewidth=2)
ax.set_title("White Noise: Rolling Standard Deviation")
ax.set_xlabel("Time")
ax.legend(loc="best")
plt.tight_layout()
plt.savefig("output/stationarity_and_differencing.png")
plt.show()
  • Lines 10–14: The random walk is constructed by cumulatively summing Gaussian steps and adding a constant drift to each step, while the white-noise series is drawn independently each period. This contrast is what makes rolling mean ...