Skip to content

AbsoluteResidual

yohou.metrics.AbsoluteResidual

Bases: Residual

Absolute residual conformity scorer using unsigned prediction errors.

Computes conformity scores as the absolute difference between true and predicted values:

\[s = |y - \hat{y}|\]

The absolute residuals produce symmetric prediction intervals, where the lower and upper bounds are equidistant from the point prediction.

Parameters

Name Type Description Default
groups list of str, dict of str to float, or None

Panel group filter (list) or filter with weights (dict). If None, all panel groups are included with equal weight.

None
components list of str, dict of str to float, or None

Component filter (list) or filter with weights (dict). If None, all components are included with equal weight.

None
time_weighter BaseWeighter or None

Weighter applied along the time axis (observed timestamps). If None, all timestamps contribute equally.

None
step_weighter BaseWeighter or None

Weighter applied along the forecasting-step axis. If None, all forecasting steps contribute equally.

None
vintage_weighter BaseWeighter or None

Weighter applied along the vintage-time axis. If None, all vintages contribute equally.

None

See Also

Examples

>>> import polars as pl
>>> from datetime import date
>>> from yohou.metrics.conformity import AbsoluteResidual
>>> scorer = AbsoluteResidual().fit(
...     pl.DataFrame({"time": [date(2020, 1, 1), date(2020, 1, 2)], "y": [1.0, 2.0]})
... )
>>> y_truth = pl.DataFrame({"time": [date(2020, 1, 3), date(2020, 1, 4)], "y": [3.0, 5.0]})
>>> y_pred = pl.DataFrame({"time": [date(2020, 1, 3), date(2020, 1, 4)], "y": [2.5, 6.0]})
>>> scores = scorer.score(y_truth, y_pred)
>>> scores.drop("time").to_series().to_list()
[0.5, 1.0]

Source Code

Source code in src/yohou/metrics/conformity.py
class AbsoluteResidual(Residual):
    r"""Absolute residual conformity scorer using unsigned prediction errors.

    Computes conformity scores as the absolute difference between true
    and predicted values:

    $$s = |y - \hat{y}|$$

    The absolute residuals produce **symmetric** prediction intervals,
    where the lower and upper bounds are equidistant from the point
    prediction.

    Parameters
    ----------
    groups : list of str, dict of str to float, or None, default=None
        Panel group filter (list) or filter with weights (dict). If None,
        all panel groups are included with equal weight.
    components : list of str, dict of str to float, or None, default=None
        Component filter (list) or filter with weights (dict). If None,
        all components are included with equal weight.
    time_weighter : BaseWeighter or None, default=None
        Weighter applied along the time axis (observed timestamps). If None,
        all timestamps contribute equally.
    step_weighter : BaseWeighter or None, default=None
        Weighter applied along the forecasting-step axis. If None, all
        forecasting steps contribute equally.
    vintage_weighter : BaseWeighter or None, default=None
        Weighter applied along the vintage-time axis. If None, all vintages
        contribute equally.

    See Also
    --------
    - [`Residual`][yohou.metrics.conformity.Residual] : Asymmetric variant using signed residuals.
    - [`AbsoluteGammaResidual`][yohou.metrics.conformity.AbsoluteGammaResidual] : Scale-independent symmetric variant.

    Examples
    --------
    >>> import polars as pl
    >>> from datetime import date
    >>> from yohou.metrics.conformity import AbsoluteResidual
    >>> scorer = AbsoluteResidual().fit(
    ...     pl.DataFrame({"time": [date(2020, 1, 1), date(2020, 1, 2)], "y": [1.0, 2.0]})
    ... )
    >>> y_truth = pl.DataFrame({"time": [date(2020, 1, 3), date(2020, 1, 4)], "y": [3.0, 5.0]})
    >>> y_pred = pl.DataFrame({"time": [date(2020, 1, 3), date(2020, 1, 4)], "y": [2.5, 6.0]})
    >>> scores = scorer.score(y_truth, y_pred)
    >>> scores.drop("time").to_series().to_list()
    [0.5, 1.0]

    """

    def __sklearn_tags__(self):
        """Get the tags for this estimator."""
        tags = super().__sklearn_tags__()
        assert tags.scorer_tags is not None
        tags.scorer_tags.symmetric = True
        return tags

    def score(self, y_truth: pl.DataFrame, y_pred: pl.DataFrame, /, **score_params) -> pl.DataFrame:
        """Compute absolute residual conformity scores.

        Parameters
        ----------
        y_truth : pl.DataFrame
            True target values.

        y_pred : pl.DataFrame
            Predicted values.

        Returns
        -------
        pl.DataFrame
            Conformity scores (|y_truth - y_pred|) with "time" column preserved.

        """
        check_is_fitted(self, ["_is_fitted"])

        # Filter out scorer from score_params to avoid conflict with explicit scorer=self
        score_params_filtered = {k: v for k, v in score_params.items() if k != "scorer"}

        # Validate and align (time dropped, returned as context)
        y_truth, y_pred, context = validate_scorer_data(
            self,
            y_truth,
            y_pred,
            **score_params_filtered,
        )

        # Compute scores and reconstruct with time
        scores_values = (y_truth - y_pred).select(pl.all().abs())
        scores = pl.DataFrame({"time": context.time_values}).hstack(scores_values)

        return scores

    def inverse_score(
        self, y_pred: pl.DataFrame, conformity_scores: pl.DataFrame, coverage_rate: float
    ) -> pl.DataFrame:
        """Construct symmetric prediction intervals from absolute conformity scores.

        Parameters
        ----------
        y_pred : pl.DataFrame
            Point predictions, optionally with "time" column.

        conformity_scores : pl.DataFrame
            Absolute conformity scores from calibration set, optionally with "time" column.

        coverage_rate : float
            Desired coverage probability.

        Returns
        -------
        pl.DataFrame
            Symmetric prediction intervals.

        """
        check_is_fitted(self, ["_is_fitted"])

        # Validate and align inputs (time dropped, returned as context for reconstruction)
        y_pred, conformity_scores, context = validate_scorer_data(
            self, y_true=None, y_pred=y_pred, scores=conformity_scores, inverse=True
        )

        # Compute symmetric intervals
        quantile = self._compute_symmetric_quantiles(conformity_scores, coverage_rate)
        lower_bound, upper_bound = y_pred - quantile, y_pred + quantile

        y_pred_interval = self._format_y_pred_interval(lower_bound, upper_bound, coverage_rate)

        # Add time column back
        y_pred_interval = pl.DataFrame({"time": context.time_values}).hstack(y_pred_interval)

        return y_pred_interval

Methods

__sklearn_tags__()

Get the tags for this estimator.

Source Code
Source code in src/yohou/metrics/conformity.py
def __sklearn_tags__(self):
    """Get the tags for this estimator."""
    tags = super().__sklearn_tags__()
    assert tags.scorer_tags is not None
    tags.scorer_tags.symmetric = True
    return tags

score(y_truth, y_pred, /, **score_params)

Compute absolute residual conformity scores.

Parameters
Name Type Description Default
y_truth DataFrame

True target values.

required
y_pred DataFrame

Predicted values.

required
Returns
Type Description
DataFrame

Conformity scores (|y_truth - y_pred|) with "time" column preserved.

Source Code
Source code in src/yohou/metrics/conformity.py
def score(self, y_truth: pl.DataFrame, y_pred: pl.DataFrame, /, **score_params) -> pl.DataFrame:
    """Compute absolute residual conformity scores.

    Parameters
    ----------
    y_truth : pl.DataFrame
        True target values.

    y_pred : pl.DataFrame
        Predicted values.

    Returns
    -------
    pl.DataFrame
        Conformity scores (|y_truth - y_pred|) with "time" column preserved.

    """
    check_is_fitted(self, ["_is_fitted"])

    # Filter out scorer from score_params to avoid conflict with explicit scorer=self
    score_params_filtered = {k: v for k, v in score_params.items() if k != "scorer"}

    # Validate and align (time dropped, returned as context)
    y_truth, y_pred, context = validate_scorer_data(
        self,
        y_truth,
        y_pred,
        **score_params_filtered,
    )

    # Compute scores and reconstruct with time
    scores_values = (y_truth - y_pred).select(pl.all().abs())
    scores = pl.DataFrame({"time": context.time_values}).hstack(scores_values)

    return scores

inverse_score(y_pred, conformity_scores, coverage_rate)

Construct symmetric prediction intervals from absolute conformity scores.

Parameters
Name Type Description Default
y_pred DataFrame

Point predictions, optionally with "time" column.

required
conformity_scores DataFrame

Absolute conformity scores from calibration set, optionally with "time" column.

required
coverage_rate float

Desired coverage probability.

required
Returns
Type Description
DataFrame

Symmetric prediction intervals.

Source Code
Source code in src/yohou/metrics/conformity.py
def inverse_score(
    self, y_pred: pl.DataFrame, conformity_scores: pl.DataFrame, coverage_rate: float
) -> pl.DataFrame:
    """Construct symmetric prediction intervals from absolute conformity scores.

    Parameters
    ----------
    y_pred : pl.DataFrame
        Point predictions, optionally with "time" column.

    conformity_scores : pl.DataFrame
        Absolute conformity scores from calibration set, optionally with "time" column.

    coverage_rate : float
        Desired coverage probability.

    Returns
    -------
    pl.DataFrame
        Symmetric prediction intervals.

    """
    check_is_fitted(self, ["_is_fitted"])

    # Validate and align inputs (time dropped, returned as context for reconstruction)
    y_pred, conformity_scores, context = validate_scorer_data(
        self, y_true=None, y_pred=y_pred, scores=conformity_scores, inverse=True
    )

    # Compute symmetric intervals
    quantile = self._compute_symmetric_quantiles(conformity_scores, coverage_rate)
    lower_bound, upper_bound = y_pred - quantile, y_pred + quantile

    y_pred_interval = self._format_y_pred_interval(lower_bound, upper_bound, coverage_rate)

    # Add time column back
    y_pred_interval = pl.DataFrame({"time": context.time_values}).hstack(y_pred_interval)

    return y_pred_interval

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

  • How to Search Interval Forecaster Hyperparameters


    Tune interval forecaster parameters directly with interval metrics in GridSearchCV, including mixed point+interval multimetric search.

    View · Open in marimo

  • How to Use Conformity Scorers


    Compare Residual, AbsoluteResidual, GammaResidual, and AbsoluteGammaResidual conformity scorers with coverage/width analysis and DistanceSimilarity interaction.

    View · Open in marimo

  • How to Use Distance-Based Similarity for Intervals


    Adaptive prediction intervals via similarity-weighted conformal prediction using DistanceSimilarity with configurable distance metrics and bandwidths.

    View · Open in marimo