| name | forecasting-single-series |
| description | Forecasts a single time series using ForecasterRecursive or ForecasterDirect. Covers data preparation, model creation, training, prediction, backtesting, and prediction intervals. Use when the user needs to predict future values of one time series.
|
Forecasting a Single Time Series
When to Use
Use this workflow when you have one time series and want to predict its future values.
- ForecasterRecursive: Default choice. Uses its own predictions as inputs for multi-step forecasting. Works with any sklearn-compatible regressor.
- ForecasterDirect: Trains one model per forecast step. Better when the relationship between lags and target changes across the horizon.
Related skills
- Before:
choosing-a-forecaster (decide between Recursive and Direct based on the data)
- Before:
autocorrelation-and-lag-selection (pick the lags argument from ACF/PACF analysis)
- Before:
feature-engineering (assemble the rolling, calendar, and exogenous features)
- After:
hyperparameter-optimization (tune the forecaster once a baseline is trained)
- After:
prediction-intervals (add bootstrap or conformal intervals on top of the point forecasts)
Stop Conditions
Scan before writing code. Each row lists a rule, the symptom when it is broken, and the recovery. Full pitfall catalog: the troubleshooting-common-errors skill.
| Rule | Symptom | Recovery |
|---|
| Set the index frequency before fitting | ValueError: ... must be a DatetimeIndex with frequency | Call data = data.asfreq('h') (or the correct alias) on y and exog |
exog passed to predict() must cover every future step | exog length / index error, or the forecast stops short | Slice exog to the full horizon: one row per step, dates matching the forecast |
Fit with store_in_sample_residuals=True before predict_interval(method='bootstrapping') | No in-sample residuals stored / empty residuals | Refit: forecaster.fit(y=y_train, store_in_sample_residuals=True) |
| Split chronologically, never shuffle | Over-optimistic metrics, leakage | Use a time-ordered split (TimeSeriesFold); do not random-split |
Complete Workflow
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from skforecast.recursive import ForecasterRecursive
from skforecast.preprocessing import RollingFeatures
from skforecast.model_selection import backtesting_forecaster, TimeSeriesFold
data = pd.read_csv('data.csv', index_col='date', parse_dates=True)
data = data.asfreq('h')
end_train = '2023-01-01'
y_train = data.loc[:end_train, 'target']
y_test = data.loc[end_train:, 'target']
rolling_features = RollingFeatures(
stats=['mean', 'std'],
window_sizes=24
)
forecaster = ForecasterRecursive(
estimator=RandomForestRegressor(n_estimators=100, random_state=123),
lags=24,
window_features=rolling_features,
transformer_y=None,
categorical_features='auto',
differentiation=None,
dropna_from_series=False,
)
forecaster.fit(y=y_train)
predictions = forecaster.predict(steps=10)
cv = TimeSeriesFold(
steps=,
initial_train_size=(y_train),
refit=,
fixed_train_size=,
)
metric, predictions_bt = backtesting_forecaster(
forecaster=forecaster,
y=data[],
cv=cv,
metric=,
)
()
forecaster.fit(y=y_train, store_in_sample_residuals=)
predictions_interval = forecaster.predict_interval(
steps=,
interval=[, ],
method=,
n_boot=,
)
With Exogenous Variables
forecaster = ForecasterRecursive(
estimator=RandomForestRegressor(n_estimators=100, random_state=123),
lags=24,
)
forecaster.fit(y=y_train, exog=exog_train)
predictions = forecaster.predict(steps=10, exog=exog_test)
Using ForecasterDirect
from skforecast.direct import ForecasterDirect
forecaster = ForecasterDirect(
estimator=RandomForestRegressor(n_estimators=100, random_state=123),
lags=24,
steps=10,
categorical_features='auto',
)
forecaster.fit(y=y_train, exog=exog_train)
predictions = forecaster.predict(exog=exog_test)
Common Mistakes
- Missing frequency on index: Always call
data.asfreq('h') (or 'D', 'MS', etc.).
- NaN in data: Forecasters reject NaN by default. Use
dropna_from_series=True to drop incomplete rows, or keep dropna_from_series=False (default) with NaN-tolerant estimators (LightGBM, CatBoost, HistGradientBoosting, XGBoost hist). Alternatively, impute missing values first.
- Exog not covering forecast horizon: The exogenous DataFrame for
predict() must have rows for every future step.
- Random train/test split: Time series must be split chronologically, never shuffled.
- Forgetting
store_in_sample_residuals=True: Required before calling predict_interval() with method='bootstrapping' on a standalone forecaster. During backtesting, residuals are computed automatically.