Skip to content

SeasonalDifferencing

yohou.stationarity.transformers.SeasonalDifferencing

Bases: BaseTransformer

Seasonal differencing time series transformer.

Computes the difference between each value and its value at a seasonal lag:

\[\Delta_s y_t = y_t - y_{t-s}\]

where \(s\) is the seasonality. This removes seasonal patterns and is a common stationarization technique.

Parameters

Name Type Description Default
seasonality int >= 1

Seasonality for the differencing.

1

Attributes

Name Type Description
n_features_in_ int

Number of features seen during fit.

feature_names_in_ list of str

Names of features seen during fit.

Notes

This transformer is stateful with observation_horizon = seasonality. The first seasonality rows are dropped in the output since they lack sufficient history for differencing.

References

[1] Hyndman, R.J., & Athanasopoulos, G. (2021). "Forecasting: principles and practice," 3rd edition, OTexts: Melbourne, Australia. OTexts.com/fpp3. Chapter 9.1.

Examples

>>> import polars as pl
>>> from datetime import datetime
>>> from yohou.stationarity import SeasonalDifferencing
>>> X = pl.DataFrame({
...     "time": [datetime(2020, 1, i) for i in range(1, 8)],
...     "value": [10.0, 12.0, 15.0, 13.0, 11.0, 14.0, 16.0],
... })
>>> # First-order differencing (seasonality=1)
>>> transformer = SeasonalDifferencing(seasonality=1)
>>> transformer.fit(X)
SeasonalDifferencing(...)
>>> X_diff = transformer.transform(X)
>>> len(X_diff)  # One row dropped
6

See Also

Source Code

Show/Hide source
class SeasonalDifferencing(BaseTransformer):
    r"""Seasonal differencing time series transformer.

    Computes the difference between each value and its value at a seasonal
    lag:

    $$\Delta_s y_t = y_t - y_{t-s}$$

    where $s$ is the ``seasonality``. This removes seasonal patterns and is
    a common stationarization technique.

    Parameters
    ----------
    seasonality : int >= 1, default=1
        Seasonality for the differencing.

    Attributes
    ----------
    n_features_in_ : int
        Number of features seen during fit.
    feature_names_in_ : list of str
        Names of features seen during fit.

    Notes
    -----
    This transformer is stateful with ``observation_horizon = seasonality``.
    The first ``seasonality`` rows are dropped in the output since they lack
    sufficient history for differencing.

    References
    ----------
    [1] Hyndman, R.J., & Athanasopoulos, G. (2021). "Forecasting:
        principles and practice," 3rd edition, OTexts: Melbourne, Australia.
        OTexts.com/fpp3. Chapter 9.1.

    Examples
    --------
    >>> import polars as pl
    >>> from datetime import datetime
    >>> from yohou.stationarity import SeasonalDifferencing

    >>> X = pl.DataFrame({
    ...     "time": [datetime(2020, 1, i) for i in range(1, 8)],
    ...     "value": [10.0, 12.0, 15.0, 13.0, 11.0, 14.0, 16.0],
    ... })

    >>> # First-order differencing (seasonality=1)
    >>> transformer = SeasonalDifferencing(seasonality=1)
    >>> transformer.fit(X)  # doctest: +ELLIPSIS
    SeasonalDifferencing(...)
    >>> X_diff = transformer.transform(X)
    >>> len(X_diff)  # One row dropped
    6

    See Also
    --------
    - [`SeasonalLogDifferencing`][yohou.stationarity.transformers.SeasonalLogDifferencing] : Log transform followed by seasonal differencing.
    - [`SeasonalReturn`][yohou.stationarity.transformers.SeasonalReturn] : Compute seasonal returns ((x_t - x_{t-s}) / x_{t-s}).

    """

    _parameter_constraints: dict = {
        "seasonality": [Interval(numbers.Integral, 1, None, closed="left")],
    }

    _tags = {"stateful": True, "invertible": True}

    def __init__(self, seasonality: StrictInt = 1):
        self.seasonality = seasonality

    @property
    def observation_horizon(self) -> int:  # noqa: D102
        """Return the number of past observations needed."""
        return self.seasonality

    def _transform(self, X: pl.DataFrame) -> pl.DataFrame:
        """Transform the input time series."""
        time = X.select(cs.by_name("time"))[self.seasonality :]
        X_t = X.select(~cs.by_name("time")).select(pl.all().diff(self.seasonality))[self.seasonality :]
        feature_names = self.get_feature_names_out()
        X_t = X_t.rename(dict(zip(X_t.columns, feature_names, strict=False)))
        X_t = pl.concat([time, X_t], how="horizontal")

        return X_t

    def _inverse_transform(self, X_t: pl.DataFrame, X_p: pl.DataFrame | None = None) -> pl.DataFrame:
        """Inverse-transform the time series."""
        X_t, X_p = validate_transformer_data(
            self,
            X=X_t,
            reset=False,
            inverse=True,
            X_p=X_p,
            observation_horizon=self.observation_horizon,
            stateful=True,
        )

        time = X_t.select(cs.by_name("time"))
        X_t.columns = X_p.columns
        X = pl.concat([X_p, X_t])

        # Get the columns and their dtypes (excluding "time")
        X_no_time = X.select(~cs.by_name("time"))
        cols_and_dtypes = list(zip(X_no_time.columns, X_no_time.dtypes, strict=False))

        def inverse_diff_col(series: pl.Series) -> pl.Series:
            """Reverse seasonal differencing for a single series."""
            # Convert to numpy for in-place mutation
            arr = series.to_numpy().copy()
            for i in range(len(X_p), len(arr)):
                arr[i] += arr[i - self.seasonality]
            return pl.Series(arr)

        X = X_no_time.with_columns([
            pl.col(col).map_batches(inverse_diff_col, return_dtype=dtype) for col, dtype in cols_and_dtypes
        ])[len(X_p) :]
        X.columns = self.feature_names_in_
        X = pl.concat([time, X], how="horizontal")

        return X

    def get_feature_names_out(self, input_features: list[str] | None = None) -> list[str]:
        """Get output feature names for transformation.

        Parameters
        ----------
        input_features : array-like of str or None, default=None
            Column names of the input features.  If ``None``, uses the
            feature names seen during ``fit``.

        Returns
        -------
        list of str
            Output feature names after transformation.

        """
        input_features = _check_feature_names_in(self, input_features)
        feature_names = [panel_aware_prefix(col, f"diff_s_{self.seasonality}") for col in input_features]

        return feature_names

Methods

observation_horizon property

Return the number of past observations needed.

get_feature_names_out(input_features=None)

Get output feature names for transformation.

Parameters
Name Type Description Default
input_features array-like of str or None

Column names of the input features. If None, uses the feature names seen during fit.

None
Returns
Type Description
list of str

Output feature names after transformation.

Source Code
Show/Hide source
def get_feature_names_out(self, input_features: list[str] | None = None) -> list[str]:
    """Get output feature names for transformation.

    Parameters
    ----------
    input_features : array-like of str or None, default=None
        Column names of the input features.  If ``None``, uses the
        feature names seen during ``fit``.

    Returns
    -------
    list of str
        Output feature names after transformation.

    """
    input_features = _check_feature_names_in(self, input_features)
    feature_names = [panel_aware_prefix(col, f"diff_s_{self.seasonality}") for col in input_features]

    return feature_names

Tutorials

The following example notebooks use this component:

  • How to Apply Stationarity Transforms


    Data-Features

    Catalogue of variance-stabilising and detrending transforms: LogTransformer, BoxCox, SeasonalDifferencing, SeasonalReturn, and ASinh with inverse verification.

    View · Open in marimo

  • How to Score Multi-Vintage Forecasts


    Evaluation-Search

    Generate multi-vintage predictions with observe_predict, score per step and per vintage, and visualize with heatmap, per-step, and per-vintage plots.

    View · Open in marimo

  • Forecasting Workflow


    Getting-Started

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

    View · Open in marimo

  • Interval Forecasting


    Getting-Started

    Wrap a point forecaster with SplitConformalForecaster to produce 95% prediction intervals with statistical coverage guarantees.

    View · Open in marimo

  • How to Apply Stationarity to Panel Data


    Panel-Data

    Apply per-group stationarity transforms on panel data with SeasonalDifferencing, DecompositionPipeline (polynomial trend + pattern seasonality), and residuals.

    View · Open in marimo

  • Quickstart


    Quickstart

    Comprehensive end-to-end tour of yohou beyond the Getting Started tutorials, covering data loading, baseline forecasting, preprocessing pipelines, decomposition, cross-validation search, and interval prediction.

    View · Open in marimo