Time Series Forecasting with Python
Introduction. A time series is a sequence of observations of a variable recorded over time 1 . Key
characteristics include a trend (long-term increase or decrease) and seasonality (regular patterns at fixed
frequencies) 2 . An example trend is a systematic growth (or decline) in data (e.g. sales increasing over
years), and seasonality might be monthly or yearly cycles (e.g. retail sales peaking every December). A
stationary series has statistical properties (mean, variance, autocovariance) that do not change over time
3 4 . Non-stationarity arises with trends, seasonality or unit roots (common in financial series).
Autocorrelation (ACF) measures how observations relate to past values, while partial autocorrelation (PACF)
controls for intervening lags 5 6 . For example, in an autoregressive model AR(p), the ACF typically tails
off and the PACF cuts off after lag p, helping identify model order. Another feature is heteroskedasticity (time-
varying volatility), often modeled with ARCH/GARCH methods for financial data 7 .
Model types: Common time-series models include ARIMA/SARIMA (Autoregressive Integrated Moving
Average, handling trends and seasonal differencing 8 ), Exponential Smoothing (level/trend/seasonal
smoothing like Holt–Winters), GARCH (modeling changing variance 7 ), and VAR/VARMAX/VECM (vector
autoregressions for multivariate series). ARIMA(p,d,q) combines AR(p) and MA(q) on differenced data of
order d 9 . Seasonal ARIMA (SARIMA) adds seasonal AR/MA and seasonal differencing. Exponential
smoothing methods (e.g. Holt’s linear trend or Holt–Winters) estimate components with equations like
ℓt = αyt + (1 − α)(ℓt−1 + bt−1 ),
bt = β(ℓt − ℓt−1 ) + (1 − β)bt−1 ,
yt
st = γ + (1 − γ)st−s ,
ℓt
where $\ell_t$, $b_t$, $s_t$ are level, trend and seasonal indices (with smoothing weights $\alpha,\beta,
\gamma$), and $s$ is season length. GARCH models specify
P Q
σt2 =ω+ ∑ αi ϵ2t−i + ∑ βj σt−j
2
,
i=1 j=1
making variance depend on past squared errors (common in finance). Vector Autoregression (VAR)
generalizes AR to vectors:
Yt = ν + A1 Yt−1 + ⋯ + Ap Yt−p + ut ,
with coefficient matrices $A_i$ 10 . VECM (Vector Error Correction) models differences with cointegration
structure 11 .
Various diagnostics (ACF/PACF plots, stationarity tests like ADF/KPSS) are used to analyze series. For
example, the Augmented Dickey–Fuller (ADF) test’s null hypothesis is that the series has a unit root (non-
stationary) 12 , while the KPSS test’s null is that the series is stationary 13 . In practice one often differences
or detrends non-stationary data. Figure 1 illustrates typical ACF/PACF behavior for different patterns:
1
10
Figure 1: Schematic ACF/PACF patterns for AR, MA, and combined ARMA processes 10 .
Section 1: Classical Forecasting with statsmodels
The statsmodels library provides comprehensive time-series modeling tools. Common functions include:
• ARIMA/SARIMA:
from [Link] import ARIMA
model = ARIMA(endog, order=(p,d,q), seasonal_order=(P,D,Q,s))
results = [Link]()
Key parameters: order=(p,d,q) for AR order p, differencing d, MA order q;
seasonal_order=(P,D,Q,s) for seasonal ARIMA with season length s. Input endog is a 1-d
array or pandas Series. fit() uses maximum likelihood (Kalman filter). Output: results
includes summary() , predicted_mean , and prediction/forecast via
[Link](start, end) or [Link](steps) . [Statsmodels docs].
• UnobservedComponents (State Space Structural):
from [Link] import UnobservedComponents
model = UnobservedComponents(endog, level='local level', trend=True,
seasonal=12)
results = [Link]()
Decomposes a series into trend, seasonal, cycle etc. Key args: level , trend , seasonal ,
cycle . Input is a pandas Series. results has similar methods: summary() , predict() , and
filtering/smoothing of components.
• VAR/VARMAX/VECM (Multivariate):
from [Link] import VAR, VARMAX, VECM
model = VAR(endog_df) # endogenous df of multiple series
results = [Link](maxlags=lag)
# or
model = VARMAX(endog_df, order=(p,q))
results = [Link]()
# or for cointegrated data
2
model = VECM(endog_df, k_ar_diff=lag, coint_rank=r)
results = [Link]()
VAR: builds VAR(p) on multiple series (must be stationary or differenced first). fit() returns
lag_order selected by criterion or specified. Forecast via [Link]() 10 . VARMAX
includes optional exogenous inputs and MA terms. VECM handles non-stationary but cointegrated
series; requires specifying cointegration rank. See [34†L179-L183], [36†L605-L614] for VECM
formulation.
• Exponential Smoothing:
from [Link] import ExponentialSmoothing
model = ExponentialSmoothing(endog, trend="add", seasonal="add",
seasonal_periods=s)
results = [Link]()
( trend : “add” or “mul” or None; seasonal : similar; seasonal_periods e.g. 12 for monthly).
Returns fitted level/trend/seasonal components and forecasts via [Link](steps) .
• ACF/PACF Computation:
from [Link] import acf, pacf, adfuller, kpss
acf_values = acf(endog, nlags=lags)
pacf_values = pacf(endog, nlags=lags)
adf_stat = adfuller(endog)
kpss_stat = kpss(endog, regression='c')
• acf , pacf : compute autocorrelations. Useful for identifying ARMA orders via significant lag
cutoffs.
• ADF test ( adfuller ): test for unit root (null: non-stationary) 12 .
• KPSS test ( kpss ): test stationarity (null: stationary) 13 .
• Diagnostic Tests:
from [Link] import acorr_ljungbox, het_breuschpagan
from [Link] import durbin_watson
lb_pvalue = acorr_ljungbox(residuals, lags=lags)[1]
bp_test = het_breuschpagan(residuals, exog)
dw = durbin_watson(residuals)
• Ljung–Box: tests residual autocorrelation (null: no autocorrelation).
3
• Breusch–Pagan: test for heteroskedasticity (null: homoskedastic errors).
• Durbin–Watson: checks first-order autocorrelation in residuals (value ≈2 indicates none).
Each model class has a summary() and diagnostic plots. Below is a schematic pipeline: data ⇒
decompose/difference ⇒ fit model ⇒ forecast. For example, an ARIMA pipeline:
# Example statsmodels pipeline: ARIMA
import pandas as pd
from [Link] import ARIMA
# Load data as pandas Series 'y'
y = [Link](...)
# (Optional) Differencing if non-stationary
y_diff = [Link]().dropna()
# Fit ARIMA
model = ARIMA(y, order=(1,1,1))
res = [Link]()
print([Link]())
# Forecast next 12 periods
forecast = [Link](steps=12)
Section 2: ML-Based Forecasting with scikit-learn
Machine Learning transforms forecasting into regression on lagged features. A time series can be reframed
as supervised data by creating features like $y_{t-1},y_{t-2},\dots$ for each time $t$. For example:
import numpy as np
X = []
y = []
for t in range(p, len(series)):
[Link](series[t-p:t]) # use past p values
[Link](series[t])
X = [Link](X)
y = [Link](y)
In sklearn this can be done via a custom transformer or FunctionTransformer . We also typically scale
features (e.g. StandardScaler ). Time ordering is preserved: for cross-validation, TimeSeriesSplit is used
instead of random K-folds 14 . TimeSeriesSplit yields folds where each training set is a prefix of the series
and the test set follows chronologically 14 , avoiding “peeking” into the future.
Standard regression models (linear, SVR, Random Forest, etc.) or ensembles can be used on these features.
A typical pipeline:
4
from [Link] import Pipeline
from [Link] import StandardScaler, FunctionTransformer
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.linear_model import LinearRegression
# Function to create lag features
def make_lags(data, n_lags=3):
X = []
y = []
for t in range(n_lags, len(data)):
[Link](data[t-n_lags:t])
[Link](data[t])
return [Link](X), [Link](y)
# Prepare data
series = [Link](...) # original time series
X, y = make_lags(series, n_lags=5)
# Build pipeline
pipeline = Pipeline([
('scaler', StandardScaler()),
('model', LinearRegression())
])
tscv = TimeSeriesSplit(n_splits=5)
scores = cross_val_score(pipeline, X, y, cv=tscv,
scoring='neg_mean_squared_error')
print("CV MSE:", -[Link]())
# Fit on full data and forecast
[Link](X, y)
preds = [Link](X[-12:]) # e.g. last 12-step forecast via rolling
Deep-learning models (LSTM, GRU, etc.) can also be used on sequences but require packages like
TensorFlow or PyTorch. For evaluation, use time-based splits (like rolling-window forecasts) and metrics (e.g.
MSE, MAE, MAPE). In short, ML forecasting pipelines often look like: create lag features → train/test split
via TimeSeriesSplit → train model → forecast.
Section 3: Forecasting with Prophet
Prophet (by Facebook/Meta) is an additive regression model designed for business forecasting. It fits
y(t) = g(t) + s(t) + h(t) + ϵt ,
5
where $g(t)$ is a (piecewise) linear or logistic growth trend, $s(t)$ is periodic seasonality (yearly, weekly,
etc.), $h(t)$ are holiday/recurrence effects, and $\epsilon_t$ is noise. Prophet is robust to missing data and
handles outliers. It requires a DataFrame with columns ds (datestamp) and y (metric) 15 16 . For
example, to fit and forecast:
from prophet import Prophet
# Prepare DataFrame df with ds (datetime) and y (value) columns
m = Prophet(growth='linear', yearly_seasonality=True, weekly_seasonality=True,
holidays_prior_scale=10.0, changepoint_prior_scale=0.05)
[Link](df) # expects [Link] (datetimes) and df.y
future = m.make_future_dataframe(periods=12, freq='M')
forecast = [Link](future)
Key parameters: growth (‘linear’ or ‘logistic’), changepoint_prior_scale (flexibility of trend change;
higher = more), seasonality_mode (‘additive’ or ‘multiplicative’), yearly_seasonality ,
weekly_seasonality , plus user-provided holidays (dates of special events). Prophet automatically
detects changepoints in the trend or can take user-specified ones 15 . The output forecast DataFrame
includes predictions and decomposed components ( trend , yearly , weekly , etc.). Visualization:
fig1 = [Link](forecast) # forecast plot
fig2 = m.plot_components(forecast) # trend/seasonality components
Prophet’s simple API means a clear modeling pipeline: format data (ds,y) → instantiate Prophet → fit →
make future dates → predict → visualize 16 .
Final Example: Forecasting a Sample Series
As an illustration, consider a synthetic monthly series with trend and seasonality (50 points). We apply
ARIMA (statsmodels), an sklearn pipeline (linear model on 12 lags), and Prophet. Below is a brief code
outline (plots not shown):
import numpy as np, pandas as pd
from [Link] import ARIMA
from prophet import Prophet
from [Link] import Pipeline
from [Link] import StandardScaler
from sklearn.linear_model import LinearRegression
# Generate sample data (for demonstration)
[Link](0)
t = [Link](50)
y = 50 + 0.5*t + 10*[Link](2*[Link]*t/12) + [Link](scale=2, size=50)
6
dates = pd.date_range('2020-01-01', periods=50, freq='M')
df = [Link]({'ds': dates, 'y': y})
# 1) ARIMA forecasting
arima_model = ARIMA(df['y'], order=(1,1,1))
arima_res = arima_model.fit()
arima_forecast = arima_res.forecast(steps=12)
# 2) Sklearn pipeline (lag-12 linear model)
def make_lags(data, lags=12):
X, y = [], []
for i in range(lags, len(data)):
[Link](data[i-lags:i])
[Link](data[i])
return [Link](X), [Link](y)
X, y_train = make_lags(df['y'].values, lags=12)
pipeline = Pipeline([('scale',StandardScaler()),('lr',LinearRegression())])
[Link](X, y_train)
# rolling forecast: predict next 12 by iteratively appending predictions
last_window = df['y'].values[-12:].tolist()
sk_preds = []
for i in range(12):
pred = [Link]([last_window[-12:]])[0]
sk_preds.append(pred)
last_window.append(pred)
# 3) Prophet forecasting
m = Prophet(yearly_seasonality=True, weekly_seasonality=False)
[Link](df) # df must have ds,y
future = m.make_future_dataframe(periods=12, freq='M')
prophet_forecast = [Link](future)['yhat'][-12:]
All three methods fit the past series and extrapolate 12 months ahead. A comparison of predicted values
(and plots) helps highlight differences: ARIMA may capture trend/seasonality via its structure, the sklearn
model extrapolates based on linear combinations of recent values, and Prophet builds an additive
seasonality+trend curve. In practice, one compares metrics (RMSE, MAE) on held-out points or via rolling
forecasts to judge performance.
Conclusion: This guide has outlined key concepts (stationarity, trend, seasonality), classical statsmodels
functions (ARIMA/SARIMA, VAR, ExponentialSmoothing, tests, diagnostics) and modern approaches with
sklearn and Prophet. The code snippets and references provide a starting point for building full forecasting
pipelines in Python.
Sources: Authoritative references and docs were used for definitions and function usage 3 5 7 2
8 10 15 16 4 14 , among others.
7
1 6 10.2 - Autocorrelation and Time Series Methods | STAT 462
[Link]
2 2.3 Time series patterns | Forecasting: Principles and Practice (2nd ed)
[Link]
3 8.1 Stationarity and differencing | Forecasting: Principles and Practice (2nd ed)
[Link]
4 12 13 Stationarity and detrending (ADF/KPSS) - statsmodels 0.14.6
[Link]
5 Partial autocorrelation function - Wikipedia
[Link]
7 Autoregressive conditional heteroskedasticity - Wikipedia
[Link]
8 9 Autoregressive integrated moving average - Wikipedia
[Link]
10 11 Vector Autoregressions tsa.vector_ar - statsmodels 0.14.6
[Link]
14 TimeSeriesSplit — scikit-learn 1.8.0 documentation
[Link]
15 Prophet | Forecasting at scale.
[Link]
16 Time Series Forecasting With Prophet in Python - [Link]
[Link]