Skip to content

CalendarFeatureTransformer

yohou.preprocessing.CalendarFeatureTransformer

Bases: BaseActualTransformer

Extract calendar-based features from the time column.

Creates new integer feature columns derived from the datetime index, useful for capturing seasonal and calendar effects in reduction forecasters. Output columns are prefixed with cal_.

Each feature is a deterministic function of the timestamp \(t\):

\[f_j(t) = \text{calendar}_{j}(t) \quad \text{for } j \in \{\text{month}, \text{day_of_week}, \ldots\}\]

For example, \(f_{\text{month}}(t) \in \{1, \ldots, 12\}\) and \(f_{\text{is_weekend}}(t) \in \{0, 1\}\).

Parameters

Name Type Description Default
features list of str or None

Calendar features to extract. If None, extracts all features applicable to the detected time interval. Valid options: "year", "month", "week", "day_of_week", "day_of_month", "day_of_year", "hour", "minute", "quarter", "is_weekend", "is_month_start", "is_month_end", "is_quarter_start", "is_quarter_end", "is_year_start", "is_year_end".

None
time_zone str or None

If set to an IANA zone, features are computed from the "time" column converted into that zone, so wall-clock features (cal_hour, cal_day_of_week, ...) are local. The conversion is ephemeral: the output "time" column is returned unchanged, so it stays a valid join key against frames in its original zone. Requires a timezone-aware "time" column. If None (the default), features are read from "time" as-is.

None

Attributes

Name Type Description
applicable_features_ list of str

Calendar features that will be extracted during transform.

Raises

Type Description
ValueError

At fit time if any requested feature name is not a valid calendar feature; if a requested feature is not applicable to the detected time interval (e.g. "hour" on daily data); if any generated cal_* column name conflicts with an existing column in X; or if time_zone is set and the "time" column is not timezone-aware.

See Also

Examples

>>> import polars as pl
>>> from datetime import datetime
>>> time = pl.datetime_range(
...     start=datetime(2020, 1, 1), end=datetime(2020, 3, 1), interval="1d", eager=True
... )
>>> X = pl.DataFrame({"time": time, "value": range(len(time))})
>>> transformer = CalendarFeatureTransformer(features=["month", "day_of_week"])
>>> transformer.fit(X)
CalendarFeatureTransformer(features=['month', 'day_of_week'])
>>> X_t = transformer.transform(X)
>>> "cal_month" in X_t.columns
True

With time_zone set, cal_hour is local while the output "time" stays put:

>>> from datetime import timezone
>>> t = pl.datetime_range(
...     datetime(2026, 7, 1, 17, tzinfo=timezone.utc),
...     datetime(2026, 7, 1, 18, tzinfo=timezone.utc),
...     interval="1h",
...     eager=True,
... )
>>> Xz = pl.DataFrame({"time": t})
>>> out = CalendarFeatureTransformer(features=["hour"], time_zone="America/Chicago").fit_transform(Xz)
>>> out["cal_hour"].to_list()  # UTC 17, 18 -> Central 12, 13
[12, 13]
>>> out["time"].to_list() == Xz["time"].to_list()  # output time unchanged
True

Source Code

Source code in src/yohou/preprocessing/calendar.py
class CalendarFeatureTransformer(BaseActualTransformer):
    r"""Extract calendar-based features from the time column.

    Creates new integer feature columns derived from the datetime index,
    useful for capturing seasonal and calendar effects in reduction
    forecasters. Output columns are prefixed with ``cal_``.

    Each feature is a deterministic function of the timestamp $t$:

    $$f_j(t) = \text{calendar}_{j}(t) \quad \text{for } j \in \{\text{month}, \text{day_of_week}, \ldots\}$$

    For example, $f_{\text{month}}(t) \in \{1, \ldots, 12\}$ and
    $f_{\text{is_weekend}}(t) \in \{0, 1\}$.

    Parameters
    ----------
    features : list of str or None, default=None
        Calendar features to extract. If ``None``, extracts all features
        applicable to the detected time interval. Valid options:
        ``"year"``, ``"month"``, ``"week"``, ``"day_of_week"``,
        ``"day_of_month"``, ``"day_of_year"``, ``"hour"``, ``"minute"``,
        ``"quarter"``, ``"is_weekend"``, ``"is_month_start"``,
        ``"is_month_end"``, ``"is_quarter_start"``, ``"is_quarter_end"``,
        ``"is_year_start"``, ``"is_year_end"``.
    time_zone : str or None, default=None
        If set to an IANA zone, features are computed from the ``"time"`` column
        converted into that zone, so wall-clock features (``cal_hour``,
        ``cal_day_of_week``, ...) are local. The conversion is ephemeral: the output
        ``"time"`` column is returned unchanged, so it stays a valid join key against
        frames in its original zone. Requires a timezone-aware ``"time"`` column. If
        ``None`` (the default), features are read from ``"time"`` as-is.

    Attributes
    ----------
    applicable_features_ : list of str
        Calendar features that will be extracted during transform.

    Raises
    ------
    ValueError
        At fit time if any requested feature name is not a valid calendar
        feature; if a requested feature is not applicable to the detected time
        interval (e.g. ``"hour"`` on daily data); if any generated ``cal_*``
        column name conflicts with an existing column in ``X``; or if
        ``time_zone`` is set and the ``"time"`` column is not timezone-aware.

    See Also
    --------
    - [`HolidayFeatureTransformer`][yohou.preprocessing.calendar.HolidayFeatureTransformer] : Binary holiday indicator from user-provided dates.
    - [`DaylightSavingFeatureTransformer`][yohou.preprocessing.calendar.DaylightSavingFeatureTransformer] : Daylight-saving offset and transition-day features.
    - [`FourierFeatureTransformer`][yohou.preprocessing.time_features.FourierFeatureTransformer] : Sin/cos harmonics for cyclical encoding.
    - [`TimeIndexTransformer`][yohou.preprocessing.time_features.TimeIndexTransformer] : Numeric time index for trend features.
    - [`FunctionTransformer`][yohou.preprocessing.function.FunctionTransformer] : Custom function-based transforms.

    Examples
    --------
    >>> import polars as pl
    >>> from datetime import datetime
    >>> time = pl.datetime_range(
    ...     start=datetime(2020, 1, 1), end=datetime(2020, 3, 1), interval="1d", eager=True
    ... )
    >>> X = pl.DataFrame({"time": time, "value": range(len(time))})
    >>> transformer = CalendarFeatureTransformer(features=["month", "day_of_week"])
    >>> transformer.fit(X)
    CalendarFeatureTransformer(features=['month', 'day_of_week'])
    >>> X_t = transformer.transform(X)
    >>> "cal_month" in X_t.columns
    True

    With ``time_zone`` set, ``cal_hour`` is local while the output ``"time"`` stays put:

    >>> from datetime import timezone
    >>> t = pl.datetime_range(
    ...     datetime(2026, 7, 1, 17, tzinfo=timezone.utc),
    ...     datetime(2026, 7, 1, 18, tzinfo=timezone.utc),
    ...     interval="1h",
    ...     eager=True,
    ... )
    >>> Xz = pl.DataFrame({"time": t})
    >>> out = CalendarFeatureTransformer(features=["hour"], time_zone="America/Chicago").fit_transform(Xz)
    >>> out["cal_hour"].to_list()  # UTC 17, 18 -> Central 12, 13
    [12, 13]
    >>> out["time"].to_list() == Xz["time"].to_list()  # output time unchanged
    True

    """

    _parameter_constraints: dict = {
        "features": [list, None],
        "time_zone": [str, None],
    }

    def __init__(
        self,
        features: list[str] | None = None,
        time_zone: str | None = None,
    ):
        self.features = features
        self.time_zone = time_zone

    def _fit(self, X: pl.DataFrame, y: pl.DataFrame | None = None) -> None:
        """Fit the internal model."""
        if self.time_zone is not None:
            dtype = X.schema["time"]
            source_zone = dtype.time_zone if isinstance(dtype, pl.Datetime) else None
            if source_zone is None:
                raise ValueError(
                    "CalendarFeatureTransformer with time_zone set requires a timezone-aware "
                    "'time' column (a zone conversion is undefined without an instant and a "
                    f"source zone); got dtype {dtype}. Localize the source first, e.g. "
                    "dt.replace_time_zone('UTC')."
                )
        if self.features is not None:
            invalid = set(self.features) - set(ALL_FEATURES)
            if invalid:
                raise ValueError(f"Unknown features: {sorted(invalid)}. Valid features: {list(ALL_FEATURES)}")
            inapplicable = [f for f in self.features if not _interval_supports_feature(self.interval_, f)]
            if inapplicable:
                raise ValueError(
                    f"Features {inapplicable} are not applicable to data with "
                    f"interval '{self.interval_}'. These features require "
                    f"sub-daily data."
                )
            self.applicable_features_ = list(self.features)
        else:
            self.applicable_features_ = [f for f in ALL_FEATURES if _interval_supports_feature(self.interval_, f)]

        generated_names = [f"cal_{f}" for f in self.applicable_features_]
        existing = set(X.columns) - {"time"}
        conflicts = set(generated_names) & existing
        if conflicts:
            raise ValueError(
                f"Generated column names {sorted(conflicts)} conflict with "
                f"existing columns in X. Rename input columns or select "
                f"different features."
            )

    def _transform(self, X: pl.DataFrame) -> pl.DataFrame:
        """Extract calendar features from the time column.

        Parameters
        ----------
        X : pl.DataFrame
            Validated input time series.

        Returns
        -------
        pl.DataFrame
            DataFrame with ``"time"`` column and extracted calendar features.

        """
        feature_exprs = [_extract_feature(f, self.time_zone) for f in self.applicable_features_]

        # The output "time" is the original column, never rezoned: only the feature values
        # above are computed in ``time_zone``, so ``time`` stays a valid join key.
        return X.select(pl.col("time"), *feature_exprs)

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

        Parameters
        ----------
        input_features : array-like of str or None, default=None
            Input feature names (unused, for API compatibility).

        Returns
        -------
        list of str
            All non-time output column names.

        """
        check_is_fitted(self, ["applicable_features_"])
        return [f"cal_{f}" for f in self.applicable_features_]

Methods

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

Input feature names (unused, for API compatibility).

None
Returns
Type Description
list of str

All non-time output column names.

Source Code
Source code in src/yohou/preprocessing/calendar.py
def get_feature_names_out(self, input_features=None) -> list[str]:
    """Get output feature names for transformation.

    Parameters
    ----------
    input_features : array-like of str or None, default=None
        Input feature names (unused, for API compatibility).

    Returns
    -------
    list of str
        All non-time output column names.

    """
    check_is_fitted(self, ["applicable_features_"])
    return [f"cal_{f}" for f in self.applicable_features_]

Tutorials

The following example notebooks use this component:

  • How to Add Calendar, Fourier, and Holiday Features


    Enrich your feature matrix with time-derived signals using CalendarFeatureTransformer, FourierFeatureTransformer, and HolidayFeatureTransformer.

    View ยท Open in marimo