Skip to content

FourierSeasonalityForecaster

yohou.stationarity.seasonality.FourierSeasonalityForecaster

Bases: _BaseSeasonalityForecaster

Forecast using Fourier series representation of seasonality.

Represents seasonality using Fourier series with specified harmonics, fitted via ElasticNet regression. More flexible than pattern-based methods and can handle non-integer seasonality.

Parameters

Name Type Description Default
seasonality float

Seasonal period length (can be non-integer, e.g., 365.25 for yearly).

required
harmonics list of int

List of Fourier harmonics to use (e.g., [1, 2, 3] uses first 3 harmonics).

[1]
estimator RegressorMixin

Regression model used to fit Fourier coefficients.

ElasticNet()
target_transformer BaseTransformer

Transformer for target variable.

None
panel_strategy ('global', multivariate)

How to handle panel data. See BaseForecaster for details.

"global"

Attributes

Name Type Description
estimator_ Pipeline or dict[str, Pipeline]

Fitted sklearn Pipeline with a fourier feature transformer and the provided a clone of the estimator model.

harmonics_ list of int

Effective list of harmonics used for Fourier features.

Examples

>>> import polars as pl
>>> import numpy as np
>>> from datetime import datetime
>>> from yohou.stationarity import FourierSeasonalityForecaster
>>>
>>> # Create time series with sinusoidal seasonality
>>> time_range = pl.datetime_range(
...     start=datetime(2020, 1, 1), end=datetime(2020, 2, 29), interval="1d", eager=True
... )
>>> y = pl.DataFrame({
...     "time": time_range,
...     "value": [np.sin(2 * np.pi * i / 12) for i in range(len(time_range))],
... })
>>>
>>> # Fit Fourier seasonality forecaster
>>> forecaster = FourierSeasonalityForecaster(seasonality=12, harmonics=[1, 2, 3])
>>> forecaster.fit(y, forecasting_horizon=30)
FourierSeasonalityForecaster(...)
>>>
>>> # Forecast next 30 days
>>> y_pred = forecaster.predict(forecasting_horizon=30)

See Also

Notes

  • Handles non-integer seasonality (e.g., 365.25 days/year)
  • Produces smooth seasonal curves
  • Can represent multiple seasonalities by using more harmonics
  • Unlike pattern-based methods, representation is continuous and differentiable

Source Code

Show/Hide source
class FourierSeasonalityForecaster(_BaseSeasonalityForecaster):
    """Forecast using Fourier series representation of seasonality.

    Represents seasonality using Fourier series with specified harmonics,
    fitted via ElasticNet regression. More flexible than pattern-based
    methods and can handle non-integer seasonality.

    Parameters
    ----------
    seasonality : float
        Seasonal period length (can be non-integer, e.g., 365.25 for yearly).
    harmonics : list of int, default=[1]
        List of Fourier harmonics to use (e.g., [1, 2, 3] uses first 3 harmonics).
    estimator : RegressorMixin, default=ElasticNet()
        Regression model used to fit Fourier coefficients.
    target_transformer : BaseTransformer, optional
        Transformer for target variable.
    panel_strategy : {"global", "multivariate"}, default="global"
        How to handle panel data.  See `BaseForecaster` for details.

    Attributes
    ----------
    estimator_ : Pipeline or dict[str, Pipeline]
        Fitted sklearn Pipeline with a fourier feature transformer and the provided
        a clone of the `estimator` model.
    harmonics_ : list of int
        Effective list of harmonics used for Fourier features.

    Examples
    --------
    >>> import polars as pl
    >>> import numpy as np
    >>> from datetime import datetime
    >>> from yohou.stationarity import FourierSeasonalityForecaster
    >>>
    >>> # Create time series with sinusoidal seasonality
    >>> time_range = pl.datetime_range(
    ...     start=datetime(2020, 1, 1), end=datetime(2020, 2, 29), interval="1d", eager=True
    ... )
    >>> y = pl.DataFrame({
    ...     "time": time_range,
    ...     "value": [np.sin(2 * np.pi * i / 12) for i in range(len(time_range))],
    ... })
    >>>
    >>> # Fit Fourier seasonality forecaster
    >>> forecaster = FourierSeasonalityForecaster(seasonality=12, harmonics=[1, 2, 3])
    >>> forecaster.fit(y, forecasting_horizon=30)  # doctest: +ELLIPSIS
    FourierSeasonalityForecaster(...)
    >>>
    >>> # Forecast next 30 days
    >>> y_pred = forecaster.predict(forecasting_horizon=30)

    See Also
    --------
    - [`PatternSeasonalityForecaster`][yohou.stationarity.seasonality.PatternSeasonalityForecaster] : Pattern-based seasonality for discrete cycles.
    - [`PolynomialTrendForecaster`][yohou.stationarity.trend.PolynomialTrendForecaster] : Polynomial trend estimation.
    - [`DecompositionPipeline`][yohou.compose.decomposition_pipeline.DecompositionPipeline] : Combines trend + seasonality + residual forecasters.

    Notes
    -----
    - Handles non-integer seasonality (e.g., 365.25 days/year)
    - Produces smooth seasonal curves
    - Can represent multiple seasonalities by using more harmonics
    - Unlike pattern-based methods, representation is continuous and differentiable

    """

    _parameter_constraints: dict = {
        **_BaseSeasonalityForecaster._parameter_constraints,
        "harmonics": [list, None],
        "alpha": [Interval(numbers.Real, 0, None, closed="left")],
        "l1_ratio": [Interval(numbers.Real, 0, 1, closed="both")],
    }

    def __init__(
        self,
        seasonality: float,
        harmonics: list[int] | None = None,
        estimator: RegressorMixin = ElasticNet(),
        target_transformer=None,
        panel_strategy="global",
    ):
        super().__init__(
            seasonality=int(seasonality),
            target_transformer=target_transformer,
            panel_strategy=panel_strategy,
        )

        self.seasonality = seasonality
        self.harmonics = harmonics
        self.estimator = estimator

    def _build_fourier_features(self, X_time_indices: np.ndarray) -> np.ndarray:
        """Construct Fourier feature matrix.

        Parameters
        ----------
        X_time_indices : np.ndarray
            Time step indices.

        Returns
        -------
        np.ndarray
            Shape (n_samples, 2 * len(harmonics)) with sin/cos features.

        """
        features = []

        for k in self.harmonics_:
            features.append(np.sin(2 * np.pi * k * X_time_indices / self.seasonality))
            features.append(np.cos(2 * np.pi * k * X_time_indices / self.seasonality))
        return np.column_stack(features)

    def _fit(
        self,
        y_t: pl.DataFrame | dict[str, pl.DataFrame],
        X_t: pl.DataFrame | dict[str, pl.DataFrame] | None,
        forecasting_horizon: StrictInt,
    ) -> None:
        """Fit Fourier series model to transformed data.

        Parameters
        ----------
        y_t : pl.DataFrame or dict[str, pl.DataFrame]
            Transformed target time series.
        X_t : pl.DataFrame or dict[str, pl.DataFrame] or None
            Transformed features (unused).
        forecasting_horizon : int
            Number of steps ahead to forecast.

        Raises
        ------
        ValueError
            If harmonics list is empty, contains non-positive integers,
            or exceeds the Nyquist limit.

        """
        # Domain-specific validation: harmonics must be positive and not exceed
        # seasonality/2 (Nyquist limit)

        if self.harmonics is not None and not self.harmonics:
            raise ValueError("harmonics list cannot be empty")

        self.harmonics_ = self.harmonics if self.harmonics else [1]

        if any(h < 1 for h in self.harmonics_):
            raise ValueError("All harmonics must be positive integers")
        max_harmonic = max(self.harmonics_)
        if max_harmonic > self.seasonality / 2:
            raise ValueError(
                f"Maximum harmonic ({max_harmonic}) cannot exceed seasonality/2 "
                f"({self.seasonality / 2:.1f}) due to Nyquist sampling theorem."
            )

        # Validate sufficient data (at least one cycle)
        self._validate_sufficient_data(y_t)

        estimator = Pipeline([
            ("poly_features", FunctionTransformer(func=self._build_fourier_features)),
            ("regressor", clone(self.estimator)),
        ])

        self._fit_estimator(estimator, y_t)

Tutorials

The following example notebooks use this component:

  • Decomposition


    Data-Features

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

    View · Open in marimo

  • How to Tune Fourier Seasonality Terms


    Data-Features

    Explore how Fourier harmonic count affects seasonal fit quality, compare Fourier vs Pattern seasonality, and tune harmonics jointly with GridSearchCV.

    View · Open in marimo

  • How to Handle Short Series


    Data-Features

    Use Fourier seasonality, simple train/test splits, and panel pooling when individual series are too short for standard approaches.

    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