Time Series Analysis
A time series is a sequence of data points indexed in time order — stock prices, weather readings, sensor data, sales figures. Time series analysis extracts patterns to understand the past and forecast the future.
import pandas as pd
import matplotlib.pyplot as plt
# Load a simple time series
df = pd.read_csv('data.csv', parse_dates=['date'], index_col='date')
df['value'].plot(figsize=(12, 4), title='Time Series')
plt.show()
Components of a Time Series
Every time series can be decomposed into four components:
Trend
Long-term increase or decrease. E.g., a company's revenue growing year over year.
Seasonality
Repeating pattern within a fixed period. E.g., ice cream sales peak every summer.
Cyclicality
Long irregular fluctuations not tied to a calendar period. E.g., economic boom-bust cycles.
Residual (Noise)
Random variation left after removing trend, seasonality, and cycles.
from statsmodels.tsa.seasonal import seasonal_decompose
result = seasonal_decompose(df['value'], model='additive', period=12)
result.plot()
plt.tight_layout()
plt.show()
# Plots: original, trend, seasonal, residual
Stationarity
A time series is stationary if its statistical properties (mean, variance, autocorrelation) do not change over time. Most classical forecasting models require stationary data.
The Augmented Dickey-Fuller (ADF) test checks for stationarity — a p-value below 0.05 means the series is stationary.
from statsmodels.tsa.stattools import adfuller
def check_stationarity(series):
result = adfuller(series.dropna())
print(f'ADF Statistic : {result[0]:.4f}')
print(f'p-value : {result[1]:.4f}')
print('Stationary ✓' if result[1] < 0.05 else 'Not stationary — try differencing')
check_stationarity(df['value'])
Making a Series Stationary
If the series is not stationary, differencing (subtracting the previous value) often helps:
# First-order differencing
df['diff1'] = df['value'].diff()
check_stationarity(df['diff1'])
ARIMA
ARIMA(p, d, q) combines three components:
- AR(p) — AutoRegressive: the value depends on its own past p values
- I(d) — Integrated: d rounds of differencing to make the series stationary
- MA(q) — Moving Average: the value depends on the past q forecast errors
from statsmodels.tsa.arima.model import ARIMA
import warnings
warnings.filterwarnings('ignore')
# Fit ARIMA(1,1,1) — a common starting point
model = ARIMA(df['value'], order=(1, 1, 1))
result = model.fit()
print(result.summary())
# Forecast next 12 steps
forecast = result.forecast(steps=12)
print(forecast)
Choosing p, d, q with ACF/PACF
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(df['value'].diff().dropna(), ax=axes[0]) # guides q
plot_pacf(df['value'].diff().dropna(), ax=axes[1]) # guides p
plt.show()
Moving Average Explorer
Adjust the window size to see how SMA (Simple Moving Average) and EMA (Exponential Moving Average) smooth a noisy signal. Switch signal presets to explore different patterns.
Raw Signal
After SMA
Noise Reduction
LSTM Forecasting
Long Short-Term Memory (LSTM) networks are a type of RNN that can learn long-range dependencies in sequential data — making them powerful for time series forecasting when patterns are complex or non-linear.
Prepare Sequences
import numpy as np
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaled = scaler.fit_transform(df[['value']])
def create_sequences(data, window=30):
X, y = [], []
for i in range(len(data) - window):
X.append(data[i:i+window])
y.append(data[i+window])
return np.array(X), np.array(y)
X, y = create_sequences(scaled)
split = int(len(X) * 0.8)
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
Build and Train the LSTM
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
model = Sequential([
LSTM(64, return_sequences=True, input_shape=(30, 1)),
Dropout(0.2),
LSTM(32),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20, batch_size=32,
validation_split=0.1, verbose=1)
# Predict and inverse-transform
preds = scaler.inverse_transform(model.predict(X_test))
Knowledge Check
Test your understanding — pick the best answer, then click Check.
1. What does stationarity mean in a time series?
2. What does the I in ARIMA stand for?
3. Why is differencing applied before fitting ARIMA?