ForecasterRecursive specialization using CatBoost.
CatBoost is an optional dependency. When it is not installed the wrapper still constructs, but leaves forecaster as None and logs a warning; any subsequent fit/predict call then fails explicitly instead of silently degrading.
The estimator is pinned for bit-level reproducibility and to avoid filesystem side effects: a fixed random_seed, single-threaded summation (thread_count=1) so the result does not depend on the host core count, and allow_writing_files=False so training never writes a catboost_info directory.
Attributes
Name
Type
Description
forecaster
The CatBoost forecaster, or None if CatBoost is not installed.
name
The name of the forecaster ("catboost").
Examples
import numpy as npimport pandas as pdfrom sklearn.linear_model import Ridgefrom spotforecast2_safe.forecaster.recursive import ForecasterRecursivefrom spotforecast2_safe.forecaster.wrappers import ( ForecasterRecursiveCatBoost, ForecasterRecursiveModel,)model = ForecasterRecursiveCatBoost(iteration=0, lags=3)assert model.name =="catboost"assertisinstance(model, ForecasterRecursiveModel)print(f"name: {model.name}")print(f"random_state: {model.random_state}")# When catboost is not installed, substitute a Ridge estimator so the# fit/predict lifecycle can still be demonstrated end-to-end.if model.forecaster isNone: model.forecaster = ForecasterRecursive( estimator=Ridge(random_state=1234), lags=3 )rng = np.random.default_rng(0)y = pd.Series( rng.random(20), index=pd.date_range("2023-01-01", periods=20, freq="h"),)model.fit(y=y)assert model.forecaster.is_fittedpred = model.forecaster.predict(steps=2)assertlen(pred) ==2print(f"is_fitted: {model.forecaster.is_fitted}")print(f"forecast horizon: {len(pred)} steps")
CatBoost not installed. This model will fail during fit/predict.
Fit the forecaster using the recorded best hyperparameters.
After tuning (or manually setting best_params and best_lags), this method loads the data, sets the optimal parameters/lags, and fits the forecaster on the full training + dev set up to end_dev.
Create a model instance with defaults drawn from a config object.
Extracts every __init__ parameter that exists as a config attribute, translating the one known name mismatch (end_train_default → end_dev). Caller-supplied overrides take precedence over config values.
This is a stub. The real implementation (using
``shap.TreeExplainer``) belongs in the sibling package
``spotforecast2``; ``shap`` is not on sf2-safe's allowed
dependency list per ``MODEL_CARD.md``.
If the underlying prediction pipeline fails for any other reason. The original exception is chained via __cause__.
Examples
import osimport shutilimport tempfilefrom pathlib import Pathimport pandas as pdfrom lightgbm import LGBMRegressorfrom spotforecast2_safe.data.fetch_data import get_package_data_homefrom spotforecast2_safe.forecaster.recursive import ForecasterRecursivefrom spotforecast2_safe.forecaster.wrappers import ForecasterRecursiveLGBM# Setup temporary data environmenttmp_dir = tempfile.mkdtemp()os.environ["SPOTFORECAST2_DATA"] = tmp_dirdata_path = Path(tmp_dir) /"interim"data_path.mkdir(parents=True)# Load demo data and rename columns to match expectationsdemo_path = get_package_data_home() /"demo01.csv"df = pd.read_csv(demo_path)df = df.rename(columns={"Time": "Time (UTC)","Actual": "Actual Load","Forecast": "Forecasted Load",})df.to_csv(data_path /"energy_load.csv", index=False)# Initialize model — override forecaster for small demo datamodel = ForecasterRecursiveLGBM(iteration=0, end_dev="2022-01-05 00:00+00:00")model.forecaster = ForecasterRecursive( estimator=LGBMRegressor(n_jobs=-1, verbose=-1, random_state=123456789), lags=12,)result = model.package_prediction(predict_size=24)print("train_actual"in result and"future_pred"in result)# Cleanupshutil.rmtree(tmp_dir)del os.environ["SPOTFORECAST2_DATA"]
╭─────────────────────────────── IgnoredArgumentWarning ───────────────────────────────╮│ The number of bins has been reduced from 10 to 6 due to duplicated edges caused by ││ repeated predicted values. ││││ Category : spotforecast2.exceptions.IgnoredArgumentWarning ││ Location : ││ /Users/bartz/workspace/spotforecast2-safe/src/spotforecast2_safe/preprocessing/_binn ││ er.py:259 ││ Suppress : warnings.simplefilter('ignore', category=IgnoredArgumentWarning) │╰──────────────────────────────────────────────────────────────────────────────────────╯
Directory for the model file. If None, defaults to get_cache_home().
None
Examples
import osimport tempfilefrom spotforecast2_safe.forecaster.wrappers import ForecasterRecursiveModelmodel = ForecasterRecursiveModel(iteration=0, name="test")with tempfile.TemporaryDirectory() as tmpdir: model.save_to_file(model_dir=tmpdir)print(any("test_forecaster_0"in f for f in os.listdir(tmpdir)))
Wrapper-level keys (iteration, name, predict_size, …) are set directly on the model. Keys prefixed with forecaster__ are forwarded to ForecasterRecursive.set_params().
Additional parameter names mapped to their new values. Parameters can target the wrapper (e.g. name="new"), the forecaster (e.g. forecaster__lags=5), or the estimator inside the forecaster (e.g. forecaster__estimator__fit_intercept=False).
In spotforecast2-safe this is a simulated stub that marks the model as tuned without performing an actual Bayesian search. The real implementation (using bayesian_search_forecaster) belongs in the sibling package spotforecast2; sf2-safe deliberately excludes auto-tuning per MODEL_CARD.md.
Examples
from spotforecast2_safe.forecaster.wrappers import ForecasterRecursiveModelmodel = ForecasterRecursiveModel(iteration=0, name="ridge")assertnot model.is_tunedmodel.tune()assert model.is_tunedprint(model.is_tuned)