Contents

"""
Time series forecasting with TabICL on electricity load
=======================================================
"""
'\nTime series forecasting with TabICL on electricity load\n=======================================================\n'
%pip install -q https://pypi.anaconda.org/ogrisel/simple/polars/1.24.0/polars-1.24.0-cp39-abi3-emscripten_3_1_58_wasm32.whl
%pip install -q skrub altair holidays plotly nbformat
%pip install "tabicl[forecast]"
ERROR: polars-1.24.0-cp39-abi3-emscripten_3_1_58_wasm32.whl is not a supported wheel on this platform.

Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: tabicl[forecast] in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (2.2.0)
Requirement already satisfied: einops>=0.7 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (0.8.2)
Requirement already satisfied: huggingface-hub in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (1.32.0)
Requirement already satisfied: numpy in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (2.4.6)
Requirement already satisfied: psutil in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (7.2.2)
Requirement already satisfied: scikit-learn>=1.3.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (1.9.1)
Requirement already satisfied: scipy in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (1.15.2)
Requirement already satisfied: torch>=2.2 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (2.12.0)
Requirement already satisfied: tqdm>=4.64.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (4.70.1)
Requirement already satisfied: matplotlib>=3.10.3 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (3.11.2)
Requirement already satisfied: pandas>=2.1.2 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (2.3.3)
Requirement already satisfied: statsmodels>=0.14.5 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from tabicl[forecast]) (0.15.0)
Requirement already satisfied: contourpy>=1.0.1 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from matplotlib>=3.10.3->tabicl[forecast]) (1.4.0)
Requirement already satisfied: cycler>=0.10 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from matplotlib>=3.10.3->tabicl[forecast]) (0.12.1)
Requirement already satisfied: fonttools>=4.28.2 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from matplotlib>=3.10.3->tabicl[forecast]) (4.66.1)
Requirement already satisfied: kiwisolver>=1.3.1 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from matplotlib>=3.10.3->tabicl[forecast]) (1.5.1)
Requirement already satisfied: packaging>=20.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from matplotlib>=3.10.3->tabicl[forecast]) (26.3)
Requirement already satisfied: pillow>=9 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from matplotlib>=3.10.3->tabicl[forecast]) (12.3.0)
Requirement already satisfied: pyparsing>=3 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from matplotlib>=3.10.3->tabicl[forecast]) (3.3.3)
Requirement already satisfied: python-dateutil>=2.7 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from matplotlib>=3.10.3->tabicl[forecast]) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from pandas>=2.1.2->tabicl[forecast]) (2026.5)
Requirement already satisfied: tzdata>=2022.7 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from pandas>=2.1.2->tabicl[forecast]) (2026.5)
Requirement already satisfied: six>=1.5 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from python-dateutil>=2.7->matplotlib>=3.10.3->tabicl[forecast]) (1.17.0)
Requirement already satisfied: joblib>=1.4.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from scikit-learn>=1.3.0->tabicl[forecast]) (1.6.0)
Requirement already satisfied: narwhals>=2.0.1 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from scikit-learn>=1.3.0->tabicl[forecast]) (2.27.0)
Requirement already satisfied: threadpoolctl>=3.5.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from scikit-learn>=1.3.0->tabicl[forecast]) (3.7.0)
Requirement already satisfied: cloudpickle>=3.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from joblib>=1.4.0->scikit-learn>=1.3.0->tabicl[forecast]) (3.0.0)
Requirement already satisfied: patsy>=0.5.6 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from statsmodels>=0.14.5->tabicl[forecast]) (1.0.3)
Requirement already satisfied: formulaic>=1.1.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from statsmodels>=0.14.5->tabicl[forecast]) (1.2.2)
Requirement already satisfied: interface-meta>=1.2.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from formulaic>=1.1.0->statsmodels>=0.14.5->tabicl[forecast]) (1.3.0)
Requirement already satisfied: typing-extensions>=4.2.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from formulaic>=1.1.0->statsmodels>=0.14.5->tabicl[forecast]) (4.16.0)
Requirement already satisfied: wrapt>=1.17.0rc1 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from formulaic>=1.1.0->statsmodels>=0.14.5->tabicl[forecast]) (2.5.0)
Requirement already satisfied: filelock in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from torch>=2.2->tabicl[forecast]) (4.0.12)
Requirement already satisfied: sympy>=1.13.3 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from torch>=2.2->tabicl[forecast]) (1.14.0)
Requirement already satisfied: networkx>=2.5.1 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from torch>=2.2->tabicl[forecast]) (3.7)
Requirement already satisfied: jinja2 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from torch>=2.2->tabicl[forecast]) (3.1.6)
Requirement already satisfied: fsspec>=0.8.5 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from torch>=2.2->tabicl[forecast]) (2026.9.0)
Requirement already satisfied: mpmath<1.5,>=1.1.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.2->tabicl[forecast]) (1.4.1)
Requirement already satisfied: click<9.0.0,>=8.4.2 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from huggingface-hub->tabicl[forecast]) (8.5.0)
Requirement already satisfied: hf-xet<2.0.0,>=1.5.2 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from huggingface-hub->tabicl[forecast]) (1.5.2)
Requirement already satisfied: httpx<1,>=0.23.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from huggingface-hub->tabicl[forecast]) (0.28.1)
Requirement already satisfied: pyyaml>=5.1 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from huggingface-hub->tabicl[forecast]) (6.0.3)
Requirement already satisfied: anyio in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from httpx<1,>=0.23.0->huggingface-hub->tabicl[forecast]) (4.15.1)
Requirement already satisfied: certifi in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from httpx<1,>=0.23.0->huggingface-hub->tabicl[forecast]) (2026.7.22)
Requirement already satisfied: httpcore==1.* in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from httpx<1,>=0.23.0->huggingface-hub->tabicl[forecast]) (1.0.9)
Requirement already satisfied: idna in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from httpx<1,>=0.23.0->huggingface-hub->tabicl[forecast]) (3.20)
Requirement already satisfied: h11>=0.16 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->huggingface-hub->tabicl[forecast]) (0.16.0)
Requirement already satisfied: MarkupSafe>=2.0 in /home/runner/work/forecasting/forecasting/.pixi/envs/default/lib/python3.13/site-packages (from jinja2->torch>=2.2->tabicl[forecast]) (3.0.4)
Note: you may need to restart the kernel to use updated packages.
import functools
import importlib
import re
import warnings
from datetime import UTC, datetime, timedelta

import numpy as np
import plotly.graph_objects as go
import polars as pl
import skrub
from tabicl import TabICLRegressor

import tutorial_helpers

importlib.reload(tutorial_helpers)
from feature_engineering_lib import feature_engineering_outputs
from prediction_intervals_lib import (
    concat_horizons,
    cross_val_predict,
    neg_mape,
    neg_mape_scorer,
    pinball,
    pinball_scorer,
    plot_predictions,
)
from single_horizon_prediction_lib import TimeSeriesSplitter
from tutorial_helpers import plot_lorenz_curve, plot_reliability_diagram

warnings.filterwarnings("ignore", category=UserWarning, module="pkg_resources")


def tabicl_quantiles_to_df(prediction, quantiles, mode=skrub.eval_mode()):
    if mode == "fit":
        return prediction
    return pl.DataFrame(prediction, schema=[f"q_{q}" for q in quantiles])


def limit_train_size(
    df,
    size=tutorial_helpers.TABICL_TRAIN_SIZE,  # 9000 by default, 1500 in CI
    mode=skrub.eval_mode(),
):
    if mode in ("fit", "fit_transform", "preview"):
        return df.tail(size)
    return df


def sanitize_tabicl_matrix(X):
    X = np.asarray(X, dtype=float)
    if X.ndim == 1:
        X = X.reshape(-1, 1)
    X = np.nan_to_num(X, nan=0.0, posinf=0.0, neginf=0.0)
    if not np.isfinite(X).all():
        X = np.where(np.isfinite(X), X, 0.0)
    return X


def make_multi_horizon_pred_tabicl(features, y, quantiles):
    quantiles_to_predict = skrub.as_data_op(quantiles).skb.set_name("quantiles")
    predictor = TabICLRegressor(n_estimators=1)
    predict_kwargs = {"output_type": "quantiles", "alphas": quantiles_to_predict}
    predictions = {
        h: feat.skb.drop(["prediction_time", "target_time"])
        .skb.apply(skrub.ToFloat())
        .to_numpy()
        .skb.apply_func(sanitize_tabicl_matrix)
        .skb.apply(predictor, y=y[f"{h}h"], predict_kwargs=predict_kwargs)
        .skb.apply_func(tabicl_quantiles_to_df, quantiles_to_predict)
        .skb.set_name(f"pred_{h}h")
        for h, feat in features.items()
    }
    return skrub.deferred(concat_horizons)(predictions)


def concat_X_y_predictions(X_test, y_test, prediction):
    return pl.concat(
        [
            X_test,
            y_test,
            prediction.rename("pred_{}".format),
        ],
        how="horizontal",
    )
TIME_HORIZONS = (1, 12, 24)
QUANTILES = (0.05, 0.5, 0.95)

features, y = feature_engineering_outputs(TIME_HORIZONS, TimeSeriesSplitter())
features = {h: features[h].skb.apply_func(limit_train_size) for h in TIME_HORIZONS}
y = y.skb.apply_func(limit_train_size)

pred = (
    make_multi_horizon_pred_tabicl(
        features,
        y,
        quantiles=QUANTILES,
    )
    .skb.with_scoring(functools.partial(neg_mape_scorer, quantile_regression=True))
    .skb.with_scoring(pinball_scorer)
)

cv_predictions_tabicl = cross_val_predict(
    pred,
    environment={"start": "2023-01-01", "end": "2025-05-31"},
)
2025-02-01T00:00:00+00:00
{'neg_mape__average__q_0.05': -0.06916509808064368, 'neg_mape__1h__q_0.05': -0.05586851483330389, 'neg_mape__12h__q_0.05': -0.07099659301685442, 'neg_mape__24h__q_0.05': -0.08063018639177273, 'neg_mape__average__q_0.5': -0.07995331261149095, 'neg_mape__1h__q_0.5': -0.041166832118307246, 'neg_mape__12h__q_0.5': -0.08901421964050539, 'neg_mape__24h__q_0.5': -0.10967888607566023, 'neg_mape__average': -0.07995331261149095, 'neg_mape__1h': -0.041166832118307246, 'neg_mape__12h': -0.08901421964050539, 'neg_mape__24h': -0.10967888607566023, 'neg_mape__average__q_0.95': -0.2187418494209595, 'neg_mape__1h__q_0.95': -0.14495105565391037, 'neg_mape__12h__q_0.95': -0.22147277087785752, 'neg_mape__24h__q_0.95': -0.2898017217311107, 'd2_pinball_score__average__q_0.05': 0.6037598983598957, 'd2_pinball_score__1h__q_0.05': 0.6362510577947884, 'd2_pinball_score__12h__q_0.05': 0.4903481849929787, 'd2_pinball_score__24h__q_0.05': 0.426672003562944, 'd2_pinball_score__average__q_0.5': 0.5215958274067369, 'd2_pinball_score__1h__q_0.5': 0.7520014799074068, 'd2_pinball_score__12h__q_0.5': 0.46487240858188994, 'd2_pinball_score__24h__q_0.5': 0.3479135937309139, 'd2_pinball_score__average__q_0.95': 0.45553721085074445, 'd2_pinball_score__1h__q_0.95': 0.10793103534472748, 'd2_pinball_score__12h__q_0.95': -0.3737828068945248, 'd2_pinball_score__24h__q_0.95': -0.809701036815186}
2025-05-01T00:00:00+00:00
{'neg_mape__average__q_0.05': -0.08552548370691616, 'neg_mape__1h__q_0.05': -0.04813589246142939, 'neg_mape__12h__q_0.05': -0.0959788924399876, 'neg_mape__24h__q_0.05': -0.11246166621933151, 'neg_mape__average__q_0.5': -0.04582253975428424, 'neg_mape__1h__q_0.5': -0.027849276379172616, 'neg_mape__12h__q_0.5': -0.049297158694908576, 'neg_mape__24h__q_0.5': -0.06032118418877153, 'neg_mape__average': -0.04582253975428424, 'neg_mape__1h': -0.027849276379172616, 'neg_mape__12h': -0.049297158694908576, 'neg_mape__24h': -0.06032118418877153, 'neg_mape__average__q_0.95': -0.1625111325130444, 'neg_mape__1h__q_0.95': -0.10995786779347295, 'neg_mape__12h__q_0.95': -0.15997138585731227, 'neg_mape__24h__q_0.95': -0.21760414388834795, 'd2_pinball_score__average__q_0.05': 0.5796819547864691, 'd2_pinball_score__1h__q_0.05': 0.5095011838707564, 'd2_pinball_score__12h__q_0.05': -0.01920764077559345, 'd2_pinball_score__24h__q_0.05': -0.2027122832002788, 'd2_pinball_score__average__q_0.5': 0.5553156768692457, 'd2_pinball_score__1h__q_0.5': 0.7331006530319139, 'd2_pinball_score__12h__q_0.5': 0.5194426981306723, 'd2_pinball_score__24h__q_0.5': 0.41340367944515044, 'd2_pinball_score__average__q_0.95': 0.17417960902083185, 'd2_pinball_score__1h__q_0.95': -0.07004253674201033, 'd2_pinball_score__12h__q_0.95': -0.6028332108458012, 'd2_pinball_score__24h__q_0.95': -1.187983312818036}
plot_predictions(cv_predictions_tabicl[0], horizons=(12,), start="2023-01-01").show()
plot_reliability_diagram(
    cv_predictions_tabicl[0],
    1,
    forecast_quantile=0.50,
).interactive().properties(title="TabICL reliability diagram for quantile 0.50")
plot_reliability_diagram(
    cv_predictions_tabicl[0],
    1,
    forecast_quantile=0.05,
).interactive().properties(title="TabICL reliability diagram for quantile 0.05")
plot_reliability_diagram(
    cv_predictions_tabicl[0],
    1,
    forecast_quantile=0.95,
).interactive().properties(title="TabICL reliability diagram for quantile 0.95")
plot_lorenz_curve(
    cv_predictions_tabicl[0],
    1,
    quantile=0.50,
).interactive().properties(title="TabICL Lorenz curve for quantile 0.50")
plot_lorenz_curve(
    cv_predictions_tabicl[0],
    1,
    quantile=0.05,
).interactive().properties(title="TabICL Lorenz curve for quantile 0.05")
plot_lorenz_curve(
    cv_predictions_tabicl[0],
    1,
    quantile=0.95,
).interactive().properties(title="TabICL Lorenz curve for quantile 0.95")