Skip to content

How to Add Calendar and Time Features

This guide shows you how to derive calendar, holiday, Fourier, and trend features from the time column and pass them to a forecaster as deterministic inputs.

Prerequisites

Try it interactively

  • 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

Extract Calendar Features

CalendarFeatureTransformer extracts integer features from timestamps. Pass a list of feature names, or leave features=None to auto-detect all features applicable to your data's frequency:

from yohou.preprocessing import CalendarFeatureTransformer
from yohou.datasets import fetch_electricity_demand

data = fetch_electricity_demand()
y = data.frame

transformer = CalendarFeatureTransformer(features=["month", "day_of_week", "hour"])
X = transformer.fit_transform(y)
# Output columns: cal_month, cal_day_of_week, cal_hour

Available features include "year", "month", "quarter", "week", "day_of_week", "day_of_month", "day_of_year", "hour", "minute", and boolean indicators like "is_weekend", "is_month_start", "is_month_end", "is_quarter_start", "is_quarter_end", "is_year_start", "is_year_end".

Frequency aware

Sub-daily features ("hour", "minute") are only applicable to sub-daily data. When features=None, the transformer inspects the data interval and extracts only what makes sense. After fitting, check transformer.applicable_features_ to see which features were selected.

On a UTC-indexed frame, cal_hour is a UTC hour, so the local daily cycle drifts by an hour across a daylight-saving boundary. Pass time_zone to compute the features from local wall-clock time instead:

# "time" is timezone-aware UTC; features are read in America/Chicago.
transformer = CalendarFeatureTransformer(features=["hour", "day_of_week"], time_zone="America/Chicago")
X = transformer.fit_transform(y)
# cal_hour is now the local hour

The conversion is applied only when extracting feature values: the output time column is returned unchanged, so it stays a valid join key against your target and other feature frames (which are typically UTC-indexed). time_zone requires a timezone-aware time column.

Mark Holidays

HolidayFeatureTransformer creates a binary indicator column and optional proximity features showing distance to the nearest holiday:

from yohou.preprocessing import HolidayFeatureTransformer
import polars as pl
from datetime import date

holidays = pl.DataFrame({
    "date": [date(2024, 1, 1), date(2024, 7, 4), date(2024, 12, 25)]
})

transformer = HolidayFeatureTransformer(
    holidays=holidays,
    days_to_next=True,
    days_since_last=True,
)
X = transformer.fit_transform(y)
# Output columns: holiday_indicator, holiday_days_to_next, holiday_days_since_last

The holidays DataFrame needs a "date" column of Date or Datetime type. Set days_to_next=True or days_since_last=True to add integer columns measuring the distance (in days) to surrounding holidays.

Add Fourier Features

FourierFeatureTransformer captures smooth seasonal patterns with sine/cosine pairs at specified harmonics:

from yohou.preprocessing import FourierFeatureTransformer

transformer = FourierFeatureTransformer(seasonality=24, harmonics=[1, 2, 3])
X = transformer.fit_transform(y)
# Output columns: fourier_24_sin_1, fourier_24_cos_1, fourier_24_sin_2, ...

Set seasonality to the period length in time steps (e.g., 24 for daily cycles in hourly data, 365.25 for yearly cycles in daily data). Use more harmonics for sharper seasonal shapes, fewer for smoother patterns. Harmonics must satisfy the Nyquist limit: each harmonic \(k\) must be at most \(S/2\) where \(S\) is the seasonality.

Flag Daylight-Saving Transitions

DaylightSavingFeatureTransformer captures the two daylight-saving effects a UTC-indexed frame cannot see: the summer/winter clock offset (which shifts the local diurnal cycle by an hour) and the 23- or 25-hour transition days:

from yohou.preprocessing import DaylightSavingFeatureTransformer

# The "time" column must be timezone-aware; time_zone is the zone whose
# daylight-saving regime is evaluated (any input zone is converted into it).
transformer = DaylightSavingFeatureTransformer(time_zone="America/Chicago")
X = transformer.fit_transform(y)
# Output columns: dst_in_effect, dst_transition_day

The time_zone parameter names the zone whose daylight-saving regime is evaluated, not the input's zone. Pass features=["in_effect", "transition_day", "transition_type"] to also emit a signed flag (+1 on a spring-forward date, -1 on a fall-back date). in_effect requires sub-daily data and is dropped on daily-or-coarser frequencies; transition_day/transition_type apply at any frequency. The "time" column must be timezone-aware (a naive datetime or a Date is rejected at fit, since an offset needs both an instant and a zone).

Create a Trend Index

TimeIndexTransformer converts timestamps to a numeric index, useful for capturing linear or polynomial trends:

from yohou.preprocessing import TimeIndexTransformer

transformer = TimeIndexTransformer(normalize=True, degree=2)
X = transformer.fit_transform(y)
# Output columns: time_index, time_index_2

With degree=1 (the default), the transformer produces a single time_index column. Higher degrees add polynomial terms (time_index_2, time_index_3, etc.). Set normalize=True to scale the index to [0, 1] based on the training data length, which improves numerical stability for polynomial features.

Combine Multiple Time Features

Use FeatureUnion to run multiple time feature transformers in parallel and concatenate their outputs, then pass the result as actual_transformer to any forecaster:

from yohou.compose import FeatureUnion
from yohou.preprocessing import (
    CalendarFeatureTransformer,
    FourierFeatureTransformer,
    TimeIndexTransformer,
)
from yohou.point import PointReductionForecaster
from sklearn.linear_model import Ridge

features = FeatureUnion([
    ("calendar", CalendarFeatureTransformer(features=["month", "day_of_week", "hour"])),
    ("fourier", FourierFeatureTransformer(seasonality=24, harmonics=[1, 2, 3])),
    ("trend", TimeIndexTransformer(normalize=True)),
])

forecaster = PointReductionForecaster(
    estimator=Ridge(),
    actual_transformer=features,
)
forecaster.fit(y, forecasting_horizon=24)
predictions = forecaster.predict()

For more complex compositions (sequential chaining, nesting), see How to Compose Feature Pipelines.

actual_transformer vs X_future

When time features are passed via actual_transformer, the forecaster generates them automatically at both fit and predict time. If you generate calendar or Fourier features externally (outside actual_transformer), pass them via X_future at both fit() and predict() time, since they are deterministic and known in advance. See Exogenous Features for details.

See Also