Skip to content

PointReductionForecaster

yohou.point.PointReductionForecaster

Bases: BaseReductionForecaster, BasePointForecaster

Point forecaster using sklearn estimators on tabularized time series.

Converts the time series point forecasting task to a tabular one.

Parameters

Name Type Description Default
estimator BaseEstimator

Point estimator used to fit the tabularized data.

LinearRegression()
reduction_strategy (direct, dir - rec, multi - output)

Strategy for multi-step forecasting.

"direct"
target_transformer BaseActualTransformer or None

Transformer for target preprocessing.

None
actual_transformer BaseActualTransformer or None

Transformer for feature engineering (typically LagTransformer).

None
forecast_transformer BaseForecastTransformer or None

Transformer applied to X_forecast before step columns are derived, so the step columns reaching the estimator are built from transformed values. Must be forecast-kind (vintage-indexed); an actual-kind transformer is rejected. None leaves X_forecast untouched.

None
target_as_feature (transformed, raw)

Whether to include the target variable as a feature for reduction. If "transformed", the transformed target is used. If "raw", the raw target is used. If None, the target is not included as a feature.

"transformed"
panel_strategy ('global', multivariate)

How to handle panel data. See BaseForecaster for details.

"global"
nan_handling (drop, 'pass')

How to handle NaN values in tabularized data. "pass" leaves NaN in place (suitable for estimators that handle NaN natively, such as tree-based models). "drop" removes any training instance where X or y contains NaN before fitting the estimator, and emits a warning with the count of dropped rows. At predict time, returns NaN predictions for any time step whose features contain NaN.

"drop"
n_jobs int or None

Number of jobs to run in parallel for the "direct" strategy (fitting and predicting H independent models). None means 1 unless in a joblib.parallel_backend context. -1 means using all processors. Has no effect for "multi-output" or "dir-rec" strategies.

None
step_feature_alignment (all, matched, cumulative)

Controls which step-indexed feature columns each direct estimator sees. Only the "direct" strategy applies this parameter; a non-default value on any other strategy warns at fit and changes nothing. "multi-output" cannot filter, since one estimator reads a different step column per output and needs them all; "dir-rec" could, but is excluded by a deliberate scope decision.

  • "all": every estimator receives all step columns.
  • "matched": estimator for step h receives only *_step_h.
  • "cumulative": estimator for step h receives *_step_1..h.
"all"
time_weighter BaseWeighter or None

Per-timestep training-sample weighter (e.g. ExponentialDecayWeighter). Its parameters are tunable via search (e.g. time_weighter__half_life). If None, samples are unweighted.

None
vintage_weighter BaseWeighter or None

Per-vintage training-sample weighter. Resolved via direct lookup at observation time (no alignment strategy) and combined multiplicatively with time_weighter. If None, no vintage weighting is applied.

None
sample_weight_alignment (first_step, mean_step, weighted_mean_step, max_weight_step, min_weight_step)

Strategy for converting time_weighter weights to sklearn sample_weight across forecast horizons. Does not apply to vintage_weighter (which uses direct lookup). See BaseReductionForecaster for full per-option semantics.

"first_step"

Examples

>>> import polars as pl
>>> from datetime import datetime
>>> from yohou.point import PointReductionForecaster
>>>
>>> # Create simple time series data
>>> df = pl.DataFrame({
...     "time": pl.datetime_range(
...         start=datetime(2021, 1, 1), end=datetime(2021, 1, 10), interval="1d", eager=True
...     ),
...     "value": [10.0, 12.0, 15.0, 14.0, 16.0, 18.0, 20.0, 19.0, 21.0, 23.0],
... })
>>>
>>> # Split into train/test
>>> train = df[:8]
>>>
>>> # Create and fit forecaster
>>> forecaster = PointReductionForecaster()
>>> _ = forecaster.fit(y=train, forecasting_horizon=1)
>>>
>>> # Generate one-step prediction
>>> y_pred = forecaster.predict(forecasting_horizon=1)
>>> len(y_pred)
1
>>> sorted(y_pred.columns)
['time', 'value', 'vintage_time']

Notes

Reduction strategies:

  • Multi-output: A single model predicts all H horizon steps simultaneously. Simple and fast, but assumes the same model structure is appropriate for every step.
  • Direct: H independent models, one per horizon step. Each model specialises in its own step, avoiding error accumulation from recursive prediction but ignoring inter-step dependencies.
  • Dir-Rec (direct-recursive hybrid): H models are fitted sequentially. Model h predicts step h using the original features augmented with in-sample predictions from models 1 to h-1. This combines the specialised per-step training of the direct strategy with inter-step information flow.

For direct and dir-rec strategies, estimator_ becomes a list[BaseEstimator] of length H (one per horizon step) instead of a single estimator.

All strategies can be applied recursively for multi-step forecasting beyond the fit horizon by specifying a larger forecasting horizon during prediction, unless X_forecast was provided at fit time, in which case predict raises a ValueError; use ForecastedFeatureForecaster for that case.

See Also

Source Code

Source code in src/yohou/point/reduction.py
class PointReductionForecaster(BaseReductionForecaster, BasePointForecaster):
    """Point forecaster using sklearn estimators on tabularized time series.

    Converts the time series point forecasting task to a tabular one.

    Parameters
    ----------
    estimator : BaseEstimator, default=LinearRegression()
        Point estimator used to fit the tabularized data.
    reduction_strategy : {"direct", "dir-rec", "multi-output"}, default="multi-output"
        Strategy for multi-step forecasting.
    target_transformer : BaseActualTransformer or None, default=None
        Transformer for target preprocessing.
    actual_transformer : BaseActualTransformer or None, default=None
        Transformer for feature engineering (typically LagTransformer).
    forecast_transformer : BaseForecastTransformer or None, default=None
        Transformer applied to ``X_forecast`` before step columns are derived,
        so the step columns reaching the estimator are built from transformed
        values. Must be forecast-kind (vintage-indexed); an actual-kind
        transformer is rejected. ``None`` leaves ``X_forecast`` untouched.
    target_as_feature : {"transformed", "raw"} or None, default="transformed"
        Whether to include the target variable as a feature for reduction.
        If ``"transformed"``, the transformed target is used. If ``"raw"``,
        the raw target is used. If ``None``, the target is not included as
        a feature.
    panel_strategy : {"global", "multivariate"}, default="global"
        How to handle panel data. See `BaseForecaster` for details.
    nan_handling : {"drop", "pass"}, default="pass"
        How to handle NaN values in tabularized data.
        ``"pass"`` leaves NaN in place (suitable for estimators that
        handle NaN natively, such as tree-based models). ``"drop"``
        removes any training instance where X or y contains NaN before
        fitting the estimator, and emits a warning with the count of
        dropped rows. At predict time, returns NaN predictions for any
        time step whose features contain NaN.
    n_jobs : int or None, default=None
        Number of jobs to run in parallel for the ``"direct"`` strategy
        (fitting and predicting H independent models). ``None`` means 1
        unless in a ``joblib.parallel_backend`` context. ``-1`` means
        using all processors. Has no effect for ``"multi-output"`` or
        ``"dir-rec"`` strategies.
    step_feature_alignment : {"all", "matched", "cumulative"}, default="all"
        Controls which step-indexed feature columns each direct estimator
        sees. Only the ``"direct"`` strategy applies this parameter; a
        non-default value on any other strategy warns at fit and changes
        nothing. ``"multi-output"`` cannot filter, since one estimator reads a
        different step column per output and needs them all; ``"dir-rec"``
        could, but is excluded by a deliberate scope decision.

        - ``"all"``: every estimator receives all step columns.
        - ``"matched"``: estimator for step h receives only ``*_step_h``.
        - ``"cumulative"``: estimator for step h receives ``*_step_1..h``.

    time_weighter : BaseWeighter or None, default=None
        Per-timestep training-sample weighter (e.g.
        [`ExponentialDecayWeighter`][yohou.weighting.weighters.ExponentialDecayWeighter]).
        Its parameters are tunable via search (e.g.
        ``time_weighter__half_life``). If None, samples are unweighted.
    vintage_weighter : BaseWeighter or None, default=None
        Per-vintage training-sample weighter. Resolved via direct lookup at
        observation time (no alignment strategy) and combined multiplicatively
        with ``time_weighter``. If None, no vintage weighting is applied.
    sample_weight_alignment : {"first_step", "mean_step", "weighted_mean_step", \
"max_weight_step", "min_weight_step"}, default="first_step"
        Strategy for converting ``time_weighter`` weights to sklearn
        ``sample_weight`` across forecast horizons. Does not apply to
        ``vintage_weighter`` (which uses direct lookup). See
        [`BaseReductionForecaster`][yohou.base.reduction.BaseReductionForecaster]
        for full per-option semantics.

    Examples
    --------
    >>> import polars as pl
    >>> from datetime import datetime
    >>> from yohou.point import PointReductionForecaster
    >>>
    >>> # Create simple time series data
    >>> df = pl.DataFrame({
    ...     "time": pl.datetime_range(
    ...         start=datetime(2021, 1, 1), end=datetime(2021, 1, 10), interval="1d", eager=True
    ...     ),
    ...     "value": [10.0, 12.0, 15.0, 14.0, 16.0, 18.0, 20.0, 19.0, 21.0, 23.0],
    ... })
    >>>
    >>> # Split into train/test
    >>> train = df[:8]
    >>>
    >>> # Create and fit forecaster
    >>> forecaster = PointReductionForecaster()
    >>> _ = forecaster.fit(y=train, forecasting_horizon=1)
    >>>
    >>> # Generate one-step prediction
    >>> y_pred = forecaster.predict(forecasting_horizon=1)
    >>> len(y_pred)
    1
    >>> sorted(y_pred.columns)
    ['time', 'value', 'vintage_time']

    Notes
    -----
    Reduction strategies:

    - **Multi-output**: A single model predicts all H horizon steps
      simultaneously. Simple and fast, but assumes the same model
      structure is appropriate for every step.
    - **Direct**: H independent models, one per horizon step. Each
      model specialises in its own step, avoiding error accumulation
      from recursive prediction but ignoring inter-step dependencies.
    - **Dir-Rec** (direct-recursive hybrid): H models are fitted
      sequentially. Model h predicts step h using the original features
      augmented with in-sample predictions from models 1 to h-1. This
      combines the specialised per-step training of the direct
      strategy with inter-step information flow.

    For direct and dir-rec strategies, ``estimator_`` becomes a
    ``list[BaseEstimator]`` of length H (one per horizon step) instead
    of a single estimator.

    All strategies can be applied recursively for multi-step forecasting
    beyond the fit horizon by specifying a larger forecasting horizon
    during prediction, unless ``X_forecast`` was provided at fit time, in
    which case ``predict`` raises a ``ValueError``; use
    [`ForecastedFeatureForecaster`][yohou.compose.ForecastedFeatureForecaster]
    for that case.

    See Also
    --------
    - [`BaseReductionForecaster`][yohou.base.reduction.BaseReductionForecaster] : Base class for reduction forecasters.
    - [`LagTransformer`][yohou.preprocessing.window.LagTransformer] : Create lagged features for reduction strategies.

    """

    _parameter_constraints: dict = {
        **BaseReductionForecaster._parameter_constraints,
        **BasePointForecaster._parameter_constraints,
        "estimator": [HasMethods(["fit", "predict"])],
        "reduction_strategy": [StrOptions({"direct", "dir-rec", "multi-output"})],
    }

    _supports_panel = True

    def __init__(
        self,
        estimator: BaseEstimator = LinearRegression(),
        *,
        reduction_strategy: Literal["direct", "dir-rec", "multi-output"] = "multi-output",
        target_transformer: BaseActualTransformer | None = None,
        actual_transformer: BaseActualTransformer | None = None,
        forecast_transformer: BaseForecastTransformer | None = None,
        target_as_feature: Literal["transformed", "raw"] | None = "transformed",
        step_feature_alignment: Literal["all", "matched", "cumulative"] = "all",
        nan_handling: Literal["drop", "pass"] = "pass",
        n_jobs: int | None = None,
        panel_strategy: Literal["global", "multivariate"] = "global",
        time_weighter: BaseWeighter | None = None,
        vintage_weighter: BaseWeighter | None = None,
        sample_weight_alignment: str = "first_step",
    ) -> None:
        BaseReductionForecaster.__init__(
            self,
            estimator=estimator,
            reduction_strategy=reduction_strategy,
            target_as_feature=target_as_feature,
            target_transformer=target_transformer,
            actual_transformer=actual_transformer,
            forecast_transformer=forecast_transformer,
            step_feature_alignment=step_feature_alignment,
            nan_handling=nan_handling,
            n_jobs=n_jobs,
            panel_strategy=panel_strategy,
            time_weighter=time_weighter,
            vintage_weighter=vintage_weighter,
            sample_weight_alignment=sample_weight_alignment,
        )

    @_fit_context(prefer_skip_nested_validation=True)
    def fit(
        self,
        y: pl.DataFrame,
        X_actual: pl.DataFrame | None = None,
        forecasting_horizon: StrictInt = 1,
        X_future: pl.DataFrame | None = None,
        X_forecast: pl.DataFrame | None = None,
        **params,
    ) -> "PointReductionForecaster":
        """Fit the forecaster to historical data.

        Tabularizes the time series and fits the wrapped sklearn estimator.

        Parameters
        ----------
        y : pl.DataFrame
            Target time series with a ``"time"`` column (datetime) and one
            or more numeric value columns.
        X_actual : pl.DataFrame or None, default=None
            Actual feature observations with a ``"time"`` column aligned
            with ``y``. Processed by the actual transformer to produce
            lags, rolling statistics, and other derived features. If
            ``None``, only target-derived features are used.
        forecasting_horizon : int, default=1
            Number of time steps to forecast into the future.
        X_future : pl.DataFrame or None, default=None
            Known future features with a ``"time"`` column. Deterministic
            values available for past and future dates. Bypasses the
            actual transformer.
        X_forecast : pl.DataFrame or None, default=None
            External forecasts with ``"vintage_time"`` and ``"time"``
            columns. Bypasses the actual transformer. When provided,
            recursive prediction (``forecasting_horizon > fit_forecasting_horizon_``
            at predict time) is not supported and raises a ``ValueError``;
            use ``ForecastedFeatureForecaster`` for that use case.
        **params : dict
            Metadata to route to nested estimators.

        Returns
        -------
        self
            The fitted forecaster instance.

        Raises
        ------
        ValueError
            If ``forecasting_horizon`` < 1, or if ``y`` / ``X_actual`` have
            invalid structure (e.g., missing ``"time"`` column, or
            mismatched panel groups).

        """
        forecasting_horizon = self._validate_fit_params(forecasting_horizon)
        self._warn_inapplicable_step_alignment()

        y_t, X_t = self._pre_fit(
            y=y,
            X_actual=X_actual,
            forecasting_horizon=forecasting_horizon,
            X_future=X_future,
            X_forecast=X_forecast,
        )

        self.estimator_ = self._estimator_fit_one(
            y_t,
            X_t,
            forecasting_horizon,
            estimator_fit_params=params,
        )

        return self

    def _predict_one(
        self,
        groups: list[str],
        **params,
    ) -> pl.DataFrame:
        """Predict ``fit_forecasting_horizon_`` steps from the observation horizon.

        Parameters
        ----------
        groups : list of str
            Panel group names to predict for.
        **params : dict
            Metadata to route to nested estimators.

        Returns
        -------
        pl.DataFrame
            Predicted time series.

        """
        y_pred = self._estimator_predict_one(self.estimator_, groups=groups)
        y_pred = self._add_time_columns(y_pred)

        return y_pred

Methods

fit(y, X_actual=None, forecasting_horizon=1, X_future=None, X_forecast=None, **params)

Fit the forecaster to historical data.

Tabularizes the time series and fits the wrapped sklearn estimator.

Parameters
Name Type Description Default
y DataFrame

Target time series with a "time" column (datetime) and one or more numeric value columns.

required
X_actual DataFrame or None

Actual feature observations with a "time" column aligned with y. Processed by the actual transformer to produce lags, rolling statistics, and other derived features. If None, only target-derived features are used.

None
forecasting_horizon int

Number of time steps to forecast into the future.

1
X_future DataFrame or None

Known future features with a "time" column. Deterministic values available for past and future dates. Bypasses the actual transformer.

None
X_forecast DataFrame or None

External forecasts with "vintage_time" and "time" columns. Bypasses the actual transformer. When provided, recursive prediction (forecasting_horizon > fit_forecasting_horizon_ at predict time) is not supported and raises a ValueError; use ForecastedFeatureForecaster for that use case.

None
**params dict

Metadata to route to nested estimators.

{}
Returns
Type Description
self

The fitted forecaster instance.

Raises
Type Description
ValueError

If forecasting_horizon < 1, or if y / X_actual have invalid structure (e.g., missing "time" column, or mismatched panel groups).

Source Code
Source code in src/yohou/point/reduction.py
@_fit_context(prefer_skip_nested_validation=True)
def fit(
    self,
    y: pl.DataFrame,
    X_actual: pl.DataFrame | None = None,
    forecasting_horizon: StrictInt = 1,
    X_future: pl.DataFrame | None = None,
    X_forecast: pl.DataFrame | None = None,
    **params,
) -> "PointReductionForecaster":
    """Fit the forecaster to historical data.

    Tabularizes the time series and fits the wrapped sklearn estimator.

    Parameters
    ----------
    y : pl.DataFrame
        Target time series with a ``"time"`` column (datetime) and one
        or more numeric value columns.
    X_actual : pl.DataFrame or None, default=None
        Actual feature observations with a ``"time"`` column aligned
        with ``y``. Processed by the actual transformer to produce
        lags, rolling statistics, and other derived features. If
        ``None``, only target-derived features are used.
    forecasting_horizon : int, default=1
        Number of time steps to forecast into the future.
    X_future : pl.DataFrame or None, default=None
        Known future features with a ``"time"`` column. Deterministic
        values available for past and future dates. Bypasses the
        actual transformer.
    X_forecast : pl.DataFrame or None, default=None
        External forecasts with ``"vintage_time"`` and ``"time"``
        columns. Bypasses the actual transformer. When provided,
        recursive prediction (``forecasting_horizon > fit_forecasting_horizon_``
        at predict time) is not supported and raises a ``ValueError``;
        use ``ForecastedFeatureForecaster`` for that use case.
    **params : dict
        Metadata to route to nested estimators.

    Returns
    -------
    self
        The fitted forecaster instance.

    Raises
    ------
    ValueError
        If ``forecasting_horizon`` < 1, or if ``y`` / ``X_actual`` have
        invalid structure (e.g., missing ``"time"`` column, or
        mismatched panel groups).

    """
    forecasting_horizon = self._validate_fit_params(forecasting_horizon)
    self._warn_inapplicable_step_alignment()

    y_t, X_t = self._pre_fit(
        y=y,
        X_actual=X_actual,
        forecasting_horizon=forecasting_horizon,
        X_future=X_future,
        X_forecast=X_forecast,
    )

    self.estimator_ = self._estimator_fit_one(
        y_t,
        X_t,
        forecasting_horizon,
        estimator_fit_params=params,
    )

    return self

Tutorials

The following example notebooks use this component:

  • Conformal Prediction Intervals


    Build distribution-free prediction intervals with SplitConformalForecaster using calibration holdouts and configurable conformity scoring functions.

    View · Open in marimo

  • Decomposition


    Chain PolynomialTrendForecaster, PatternSeasonalityForecaster, and FourierSeasonalityForecaster inside DecompositionPipeline with component visualisation.

    View · Open in marimo

  • Direct, Recursive, and MIMO Strategies


    Compare direct, recursive, and MIMO reduction strategies across forecasting horizons to understand the trade-offs for your use case.

    View · Open in marimo

  • Exogenous Features (X_actual, X_future, X_forecast)


    Build a forecasting model with actual observations, known-future indicators, and multi-vintage external forecasts on synthetic electricity price data.

    View · Open in marimo

  • Forecast Visualization


    Visualise point forecasts from single and multiple models, decomposition pipeline components, and time weight decay functions with interactive Plotly.

    View · Open in marimo

  • Forecasting Workflow


    Evaluate forecasters with cross-validation, search hyperparameters with GridSearchCV, and inspect residuals to diagnose model weaknesses.

    View · Open in marimo

See all 49 examples in the gallery