Bases: BaseActualTransformer
Deterministic daylight-saving-time features from the time column.
A series whose local time observes daylight saving crosses two kinds of boundary that a
UTC-indexed frame cannot see directly: the summer/winter clock offset, which shifts the
UTC hour of the local diurnal cycle by one hour, and the two transition days a year,
which are 23- or 25-hour local days with a skipped or repeated hour. Both are
deterministic and known for any future date, so they enter a model as unlagged,
known-future features alongside the calendar terms. Output columns are prefixed with
dst_.
Daylight saving is evaluated in time_zone: the "time" column (which must be
timezone-aware) is converted into that zone, then the offset is read there. The
parameter names the zone whose daylight-saving regime is evaluated, not an assertion
about the input's zone, so a UTC-indexed frame and a Central-indexed frame produce the
same features for the same instants.
For a timestamp \(t\) with local daylight-saving offset \(\delta(t)\) (in minutes, \(0\) in
standard time), and \(\delta_d\) the offset at the local midnight of date \(d\):
\[\text{in\_effect}(t) = \mathbb{1}[\delta(t) \neq 0], \quad
\text{transition\_day}(d) = \mathbb{1}[\delta_d \neq \delta_{d+1}], \quad
\text{transition\_type}(d) = \operatorname{sign}(\delta_{d+1} - \delta_d)\]
Note that dst_transition_day is exactly dst_transition_type != 0; both are
provided because a tree splits on them differently (the binary is a cleaner root split,
the signed carries the spring/fall direction).
| Name |
Type |
Description |
Default |
time_zone
|
str
|
IANA time zone in which daylight saving time is evaluated.
|
"America/Chicago"
|
features
|
list of str or None
|
Which daylight-saving features to emit. If None, emits every default feature
applicable to the detected time interval (["in_effect", "transition_day"]).
Valid options: "in_effect", "transition_day", "transition_type".
|
None
|
| Name |
Type |
Description |
applicable_features_ |
list of str
|
The resolved feature list emitted during transform (after interval gating).
|
| Type |
Description |
ValueError
|
At fit time if the "time" column is not a timezone-aware pl.Datetime (a
timezone-naive datetime or a pl.Date has no well-defined offset); if any
requested feature name is unknown; if "in_effect" is explicitly requested on
data whose detected interval is daily or coarser; or if a generated dst_*
column name conflicts with an existing column in X.
|
>>> from datetime import datetime, timezone
>>> import polars as pl
>>> from yohou.preprocessing import DaylightSavingFeatureTransformer
>>> time = pl.datetime_range(
... datetime(2026, 3, 8, 6, tzinfo=timezone.utc),
... datetime(2026, 3, 8, 10, tzinfo=timezone.utc),
... interval="1h",
... eager=True,
... )
>>> X = pl.DataFrame({"time": time})
>>> DaylightSavingFeatureTransformer().fit_transform(X)["dst_in_effect"].to_list()
[0, 0, 1, 1, 1]
View on GitHub
Source code in src/yohou/preprocessing/calendar.py
| class DaylightSavingFeatureTransformer(BaseActualTransformer):
r"""Deterministic daylight-saving-time features from the time column.
A series whose local time observes daylight saving crosses two kinds of boundary that a
UTC-indexed frame cannot see directly: the summer/winter clock offset, which shifts the
UTC hour of the local diurnal cycle by one hour, and the two transition days a year,
which are 23- or 25-hour local days with a skipped or repeated hour. Both are
deterministic and known for any future date, so they enter a model as unlagged,
known-future features alongside the calendar terms. Output columns are prefixed with
``dst_``.
Daylight saving is evaluated in ``time_zone``: the ``"time"`` column (which must be
timezone-aware) is converted into that zone, then the offset is read there. The
parameter names the zone whose daylight-saving regime is evaluated, not an assertion
about the input's zone, so a UTC-indexed frame and a Central-indexed frame produce the
same features for the same instants.
For a timestamp $t$ with local daylight-saving offset $\delta(t)$ (in minutes, $0$ in
standard time), and $\delta_d$ the offset at the local midnight of date $d$:
$$\text{in\_effect}(t) = \mathbb{1}[\delta(t) \neq 0], \quad
\text{transition\_day}(d) = \mathbb{1}[\delta_d \neq \delta_{d+1}], \quad
\text{transition\_type}(d) = \operatorname{sign}(\delta_{d+1} - \delta_d)$$
Note that ``dst_transition_day`` is exactly ``dst_transition_type != 0``; both are
provided because a tree splits on them differently (the binary is a cleaner root split,
the signed carries the spring/fall direction).
Parameters
----------
time_zone : str, default="America/Chicago"
IANA time zone in which daylight saving time is evaluated.
features : list of str or None, default=None
Which daylight-saving features to emit. If ``None``, emits every default feature
applicable to the detected time interval (``["in_effect", "transition_day"]``).
Valid options: ``"in_effect"``, ``"transition_day"``, ``"transition_type"``.
Attributes
----------
applicable_features_ : list of str
The resolved feature list emitted during ``transform`` (after interval gating).
Raises
------
ValueError
At fit time if the ``"time"`` column is not a timezone-aware ``pl.Datetime`` (a
timezone-naive datetime or a ``pl.Date`` has no well-defined offset); if any
requested feature name is unknown; if ``"in_effect"`` is explicitly requested on
data whose detected interval is daily or coarser; or if a generated ``dst_*``
column name conflicts with an existing column in ``X``.
See Also
--------
- [`CalendarFeatureTransformer`][yohou.preprocessing.calendar.CalendarFeatureTransformer] : Calendar features (month, day of week, etc.).
- [`HolidayFeatureTransformer`][yohou.preprocessing.calendar.HolidayFeatureTransformer] : Binary holiday indicator from user-provided dates.
- [`FourierFeatureTransformer`][yohou.preprocessing.time_features.FourierFeatureTransformer] : Sin/cos harmonics for cyclical encoding.
Examples
--------
>>> from datetime import datetime, timezone
>>> import polars as pl
>>> from yohou.preprocessing import DaylightSavingFeatureTransformer
>>> time = pl.datetime_range(
... datetime(2026, 3, 8, 6, tzinfo=timezone.utc),
... datetime(2026, 3, 8, 10, tzinfo=timezone.utc),
... interval="1h",
... eager=True,
... )
>>> X = pl.DataFrame({"time": time})
>>> DaylightSavingFeatureTransformer().fit_transform(X)["dst_in_effect"].to_list()
[0, 0, 1, 1, 1]
"""
_parameter_constraints: dict = {
"time_zone": [str],
"features": [list, None],
}
def __init__(self, time_zone: str = "America/Chicago", features: list[str] | None = None):
self.time_zone = time_zone
self.features = features
def _fit(self, X: pl.DataFrame, y: pl.DataFrame | None = None) -> None:
"""Validate the input contract, resolve features (with interval gating), and check conflicts."""
dtype = X.schema["time"]
time_zone = dtype.time_zone if isinstance(dtype, pl.Datetime) else None
if time_zone is None:
raise ValueError(
"DaylightSavingFeatureTransformer requires a timezone-aware 'time' column "
"(a daylight-saving offset is undefined without an instant and a zone); "
f"got dtype {dtype}. Localize it first, e.g. dt.replace_time_zone('UTC')."
)
requested = list(self.features) if self.features is not None else list(DST_DEFAULT_FEATURES)
invalid = set(requested) - set(DST_ALL_FEATURES)
if invalid:
raise ValueError(f"Unknown DST features: {sorted(invalid)}. Valid features: {list(DST_ALL_FEATURES)}")
subdaily = _interval_is_subdaily(self.interval_)
gated = [f for f in requested if f in DST_SUBDAILY_FEATURES and not subdaily]
if self.features is not None and gated:
raise ValueError(
f"Features {gated} require sub-daily data, but the detected interval is "
f"'{self.interval_}'. These features need an instant, not just a date."
)
self.applicable_features_ = [f for f in requested if f not in gated]
generated = [f"dst_{f}" for f in self.applicable_features_]
conflicts = set(generated) & (set(X.columns) - {"time"})
if conflicts:
raise ValueError(f"Generated column names {sorted(conflicts)} conflict with existing columns in X.")
def _transform(self, X: pl.DataFrame) -> pl.DataFrame:
"""Emit ``time`` plus the selected ``dst_*`` feature columns."""
exprs = [_extract_dst_feature(f, self.time_zone) for f in self.applicable_features_]
return X.select(pl.col("time"), *exprs)
def get_feature_names_out(self, input_features=None) -> list[str]:
"""Return the selected ``dst_*`` feature-column names.
Parameters
----------
input_features : array-like of str or None, default=None
Input feature names (unused, for API compatibility).
Returns
-------
list of str
Generated daylight-saving feature column names.
"""
check_is_fitted(self, ["applicable_features_"])
return [f"dst_{f}" for f in self.applicable_features_]
|
Return the selected dst_* feature-column names.
| Name |
Type |
Description |
Default |
input_features
|
array-like of str or None
|
Input feature names (unused, for API compatibility).
|
None
|
| Type |
Description |
list of str
|
Generated daylight-saving feature column names.
|
View on GitHub
Source code in src/yohou/preprocessing/calendar.py
| def get_feature_names_out(self, input_features=None) -> list[str]:
"""Return the selected ``dst_*`` feature-column names.
Parameters
----------
input_features : array-like of str or None, default=None
Input feature names (unused, for API compatibility).
Returns
-------
list of str
Generated daylight-saving feature column names.
"""
check_is_fitted(self, ["applicable_features_"])
return [f"dst_{f}" for f in self.applicable_features_]
|