machine learning
vector autoregression
time series analysis
scikit-learn
predictive modeling

Vector autoregressive model fitting with scikit-learn

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A vector autoregressive model predicts several time series together by using previous values from all series as inputs. Scikit-learn does not ship a dedicated VAR class, but you can model the same idea by converting lagged history into a supervised learning matrix. This pattern is useful when you want scikit-learn pipelines, model selection APIs, and deployment-friendly estimators. This approach is especially useful when you want one scikit-learn deployment path for both classical regression baselines and richer ensemble models.

Reframe VAR as a Multi-Output Regression Problem

For a VAR of lag order p, each training row uses p previous time steps and predicts the current step. If there are k variables, each feature row has p * k values, and each target row has k values.

python
1import numpy as np
2
3
4def make_var_dataset(y: np.ndarray, lag: int):
5    X, Y = [], []
6    for t in range(lag, len(y)):
7        X.append(y[t - lag:t].reshape(-1))
8        Y.append(y[t])
9    return np.asarray(X), np.asarray(Y)
10
11
12series = np.array([
13    [10.0, 100.0],
14    [11.0, 101.0],
15    [13.0, 100.5],
16    [12.0, 102.0],
17    [14.0, 103.0],
18    [15.0, 104.0],
19    [16.0, 105.0],
20])
21
22X, Y = make_var_dataset(series, lag=2)
23print(X.shape, Y.shape)

This transformation is the core step. The rest of the workflow looks like a normal supervised learning project.

Start with a Linear Baseline

Classical VAR is linear, so linear regression or ridge regression is a strong baseline.

python
1from sklearn.linear_model import Ridge
2from sklearn.metrics import mean_absolute_error
3
4split = int(len(X) * 0.8)
5X_train, X_test = X[:split], X[split:]
6Y_train, Y_test = Y[:split], Y[split:]
7
8model = Ridge(alpha=1.0)
9model.fit(X_train, Y_train)
10
11pred = model.predict(X_test)
12print("MAE total:", mean_absolute_error(Y_test, pred))
13print("MAE var0:", mean_absolute_error(Y_test[:, 0], pred[:, 0]))
14print("MAE var1:", mean_absolute_error(Y_test[:, 1], pred[:, 1]))

Always report per-variable error, not only one aggregate number. One target can degrade while overall error still looks acceptable.

Use Time-Aware Validation

Random shuffle splits leak temporal structure. Use chronological splits or walk-forward validation.

python
1from sklearn.model_selection import TimeSeriesSplit
2from sklearn.base import clone
3
4cv = TimeSeriesSplit(n_splits=4)
5base = Ridge(alpha=0.5)
6
7fold_mae = []
8for train_idx, val_idx in cv.split(X):
9    m = clone(base)
10    m.fit(X[train_idx], Y[train_idx])
11    p = m.predict(X[val_idx])
12    fold_mae.append(mean_absolute_error(Y[val_idx], p))
13
14print("Fold MAE:", fold_mae)
15print("Mean MAE:", float(np.mean(fold_mae)))

This gives a more realistic estimate of future forecasting behavior.

Add Nonlinear Models Carefully

If residuals show nonlinear structure, try tree-based or boosted models through multi-output wrappers.

python
1from sklearn.multioutput import MultiOutputRegressor
2from sklearn.ensemble import RandomForestRegressor
3
4rf = MultiOutputRegressor(
5    RandomForestRegressor(n_estimators=200, random_state=42)
6)
7rf.fit(X_train, Y_train)
8rf_pred = rf.predict(X_test)
9print("RF MAE:", mean_absolute_error(Y_test, rf_pred))

Nonlinear models can improve fit, but overfitting risk rises fast with small time-series datasets. Keep linear baseline as a reference point.

Multi-Step Forecasting Pattern

One-step-ahead prediction is easiest, but many applications need horizon forecasts. A standard approach is recursive forecasting where each predicted step becomes part of the next input window.

python
1def recursive_forecast(model, last_window, steps, n_vars):
2    window = last_window.copy()  # shape: (lag, n_vars)
3    out = []
4    for _ in range(steps):
5        x = window.reshape(1, -1)
6        yhat = model.predict(x)[0]
7        out.append(yhat)
8        window = np.vstack([window[1:], yhat])
9    return np.asarray(out)
10
11future = recursive_forecast(model, series[-2:], steps=3, n_vars=2)
12print(future)

Track error by forecast horizon because accuracy usually drops as horizon grows.

Feature Engineering That Helps VAR-Style Models

Useful additions include:

  • seasonal lags, such as 24 for hourly data
  • differences or percent changes to stabilize trend
  • calendar features such as day index
  • exogenous regressors such as promotions or weather

When adding engineered features, ensure the same feature generation logic runs during training and inference.

Common Pitfalls

A common mistake is leakage from future values when constructing lag windows. Build features from strictly earlier timestamps only.

Another issue is random cross-validation on temporal data. This inflates metrics and leads to disappointing production performance.

A third issue is tuning complex models before validating a linear baseline. Many datasets are well explained by simple lagged linear structure.

Teams also forget to evaluate each target separately. Multi-output metrics can hide weak performance on the variable that matters most to the business.

Summary

  • VAR can be implemented in scikit-learn by reframing sequence history as supervised multi-output regression.
  • Lag feature construction is the essential step and must avoid time leakage.
  • Use chronological validation or walk-forward folds, never random shuffle splits.
  • Keep a linear baseline and add nonlinear estimators only when residual patterns justify it.
  • Evaluate one-step and multi-step behavior separately, with per-target error reporting.

Course illustration
Course illustration

All Rights Reserved.