eb-features 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eb_features/__init__.py +20 -0
- eb_features/panel/__init__.py +42 -0
- eb_features/panel/calendar.py +193 -0
- eb_features/panel/config.py +95 -0
- eb_features/panel/constants.py +75 -0
- eb_features/panel/encoders.py +91 -0
- eb_features/panel/engineering.py +304 -0
- eb_features/panel/lags.py +95 -0
- eb_features/panel/rolling.py +170 -0
- eb_features/panel/validation.py +128 -0
- eb_features-0.1.0.dist-info/METADATA +168 -0
- eb_features-0.1.0.dist-info/RECORD +15 -0
- eb_features-0.1.0.dist-info/WHEEL +5 -0
- eb_features-0.1.0.dist-info/licenses/LICENSE +28 -0
- eb_features-0.1.0.dist-info/top_level.txt +1 -0
eb_features/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Feature engineering utilities for the Electric Barometer ecosystem.
|
|
5
|
+
|
|
6
|
+
This package provides modular, domain-agnostic feature engineering components
|
|
7
|
+
used across forecasting, evaluation, and operational modeling workflows.
|
|
8
|
+
|
|
9
|
+
Subpackages
|
|
10
|
+
-----------
|
|
11
|
+
panel
|
|
12
|
+
Feature engineering utilities for panel (entity × timestamp) time-series data.
|
|
13
|
+
|
|
14
|
+
Notes
|
|
15
|
+
-----
|
|
16
|
+
This package intentionally exposes a small public API. Most functionality is
|
|
17
|
+
accessed through subpackages such as `eb_features.panel`.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
__all__ = []
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Panel time-series feature engineering.
|
|
5
|
+
|
|
6
|
+
This subpackage provides lightweight, frequency-agnostic feature engineering utilities
|
|
7
|
+
for *panel* time-series data, i.e., data indexed by:
|
|
8
|
+
|
|
9
|
+
- an **entity** identifier (store, SKU, customer, sensor, etc.), and
|
|
10
|
+
- a **timestamp** column
|
|
11
|
+
|
|
12
|
+
The primary public interface is:
|
|
13
|
+
|
|
14
|
+
- [`FeatureConfig`][eb_features.panel.engineering.FeatureConfig]: declarative feature configuration
|
|
15
|
+
- [`FeatureEngineer`][eb_features.panel.engineering.FeatureEngineer]: stateless transformer that
|
|
16
|
+
produces a model-ready ``(X, y, feature_names)`` triple from a long-form DataFrame.
|
|
17
|
+
|
|
18
|
+
Design goals
|
|
19
|
+
------------
|
|
20
|
+
- **Stateless**: no fit/transform lifecycle; features are generated deterministically from inputs.
|
|
21
|
+
- **Frequency-agnostic**: lags and rolling windows are expressed in index steps, not wall-clock units.
|
|
22
|
+
- **Leakage-aware**: feature construction is intended to use only information available at or
|
|
23
|
+
before prediction time (depending on configuration and implementation details).
|
|
24
|
+
|
|
25
|
+
Notes
|
|
26
|
+
-----
|
|
27
|
+
This subpackage is intentionally conservative in scope. It focuses on producing features suitable
|
|
28
|
+
for classical supervised learning pipelines (tree models, linear models, shallow neural nets) that
|
|
29
|
+
expect a fixed-width design matrix.
|
|
30
|
+
|
|
31
|
+
See Also
|
|
32
|
+
--------
|
|
33
|
+
- [`eb_features.panel.engineering.FeatureEngineer`][eb_features.panel.engineering.FeatureEngineer]
|
|
34
|
+
- [`eb_features.panel.engineering.FeatureConfig`][eb_features.panel.engineering.FeatureConfig]
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from eb_features.panel.engineering import FeatureConfig, FeatureEngineer
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"FeatureConfig",
|
|
41
|
+
"FeatureEngineer",
|
|
42
|
+
]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Calendar and time-derived features for panel time series.
|
|
5
|
+
|
|
6
|
+
This module provides utilities to derive calendar/time features from a timestamp column.
|
|
7
|
+
It is designed for *panel* time-series data (entity × timestamp) and is typically used
|
|
8
|
+
as part of a broader feature engineering pipeline.
|
|
9
|
+
|
|
10
|
+
Supported base calendar features
|
|
11
|
+
--------------------------------
|
|
12
|
+
Given a timestamp column ``timestamp_col``:
|
|
13
|
+
|
|
14
|
+
- ``"hour"``: Hour of day in ``[0, 23]``
|
|
15
|
+
- ``"dow"``: Day of week in ``[0, 6]`` where Monday=0 (pandas convention)
|
|
16
|
+
- ``"dom"``: Day of month in ``[1, 31]``
|
|
17
|
+
- ``"month"``: Month in ``[1, 12]``
|
|
18
|
+
- ``"is_weekend"``: Weekend indicator (Saturday/Sunday) as ``0/1``
|
|
19
|
+
|
|
20
|
+
Optional cyclical encodings
|
|
21
|
+
---------------------------
|
|
22
|
+
Certain calendar attributes are periodic and can be represented with sine/cosine pairs:
|
|
23
|
+
|
|
24
|
+
- hour (period 24):
|
|
25
|
+
|
|
26
|
+
$$
|
|
27
|
+
\sin\left(2\pi \frac{\mathrm{hour}}{24}\right),\quad
|
|
28
|
+
\cos\left(2\pi \frac{\mathrm{hour}}{24}\right)
|
|
29
|
+
$$
|
|
30
|
+
|
|
31
|
+
- day of week (period 7):
|
|
32
|
+
|
|
33
|
+
$$
|
|
34
|
+
\sin\left(2\pi \frac{\mathrm{dow}}{7}\right),\quad
|
|
35
|
+
\cos\left(2\pi \frac{\mathrm{dow}}{7}\right)
|
|
36
|
+
$$
|
|
37
|
+
|
|
38
|
+
These encodings are added only when:
|
|
39
|
+
- the corresponding base feature (hour or dow) is present, and
|
|
40
|
+
- ``use_cyclical_time=True``.
|
|
41
|
+
|
|
42
|
+
Notes
|
|
43
|
+
-----
|
|
44
|
+
- Feature construction is *stateless*: functions operate only on the provided DataFrame.
|
|
45
|
+
- Time features are derived from ``pandas.to_datetime`` conversion of ``timestamp_col``.
|
|
46
|
+
Timezone-aware timestamps are supported; feature values reflect the timestamp's local
|
|
47
|
+
representation as stored in the column.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from typing import Iterable, List, Sequence, Tuple
|
|
51
|
+
|
|
52
|
+
import numpy as np
|
|
53
|
+
import pandas as pd
|
|
54
|
+
|
|
55
|
+
from eb_features.panel.constants import (
|
|
56
|
+
ALLOWED_CALENDAR_FEATURES,
|
|
57
|
+
DOW_PERIOD,
|
|
58
|
+
HOUR_PERIOD,
|
|
59
|
+
WEEKEND_DAYS,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def add_calendar_features(
|
|
64
|
+
df: pd.DataFrame,
|
|
65
|
+
*,
|
|
66
|
+
timestamp_col: str,
|
|
67
|
+
calendar_features: Sequence[str],
|
|
68
|
+
use_cyclical_time: bool = True,
|
|
69
|
+
) -> Tuple[pd.DataFrame, List[str], List[str]]:
|
|
70
|
+
r"""
|
|
71
|
+
Add calendar/time-derived features to a DataFrame.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
df : pandas.DataFrame
|
|
76
|
+
Input DataFrame containing a timestamp column.
|
|
77
|
+
timestamp_col : str
|
|
78
|
+
Name of the timestamp column.
|
|
79
|
+
calendar_features : Sequence[str]
|
|
80
|
+
Base calendar features to derive. Allowed values are:
|
|
81
|
+
``{"hour", "dow", "dom", "month", "is_weekend"}``.
|
|
82
|
+
use_cyclical_time : bool, default True
|
|
83
|
+
If True, add sine/cosine encodings for hour and/or day-of-week when those
|
|
84
|
+
base features are included.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
df_out : pandas.DataFrame
|
|
89
|
+
Copy of ``df`` with the requested calendar features added as columns.
|
|
90
|
+
feature_cols : list[str]
|
|
91
|
+
Names of all features added by this call, including cyclical encodings (if any).
|
|
92
|
+
calendar_cols : list[str]
|
|
93
|
+
Names of base calendar feature columns added (excludes cyclical encodings).
|
|
94
|
+
|
|
95
|
+
Raises
|
|
96
|
+
------
|
|
97
|
+
KeyError
|
|
98
|
+
If ``timestamp_col`` is not present in ``df``.
|
|
99
|
+
ValueError
|
|
100
|
+
If an unsupported calendar feature is requested.
|
|
101
|
+
|
|
102
|
+
Notes
|
|
103
|
+
-----
|
|
104
|
+
The derived columns are currently named:
|
|
105
|
+
|
|
106
|
+
- ``hour``
|
|
107
|
+
- ``dayofweek`` (for requested ``"dow"``)
|
|
108
|
+
- ``dayofmonth`` (for requested ``"dom"``)
|
|
109
|
+
- ``month``
|
|
110
|
+
- ``is_weekend``
|
|
111
|
+
|
|
112
|
+
This naming is stable and intended to be referenced by downstream steps.
|
|
113
|
+
"""
|
|
114
|
+
if timestamp_col not in df.columns:
|
|
115
|
+
raise KeyError(f"Timestamp column {timestamp_col!r} not found in DataFrame.")
|
|
116
|
+
|
|
117
|
+
_validate_calendar_features(calendar_features)
|
|
118
|
+
|
|
119
|
+
df_out = df.copy()
|
|
120
|
+
ts = pd.to_datetime(df_out[timestamp_col])
|
|
121
|
+
|
|
122
|
+
calendar_cols: List[str] = []
|
|
123
|
+
feature_cols: List[str] = []
|
|
124
|
+
|
|
125
|
+
for name in calendar_features:
|
|
126
|
+
if name == "hour":
|
|
127
|
+
col = "hour"
|
|
128
|
+
df_out[col] = ts.dt.hour.astype("int16")
|
|
129
|
+
calendar_cols.append(col)
|
|
130
|
+
elif name == "dow":
|
|
131
|
+
col = "dayofweek"
|
|
132
|
+
df_out[col] = ts.dt.dayofweek.astype("int16")
|
|
133
|
+
calendar_cols.append(col)
|
|
134
|
+
elif name == "dom":
|
|
135
|
+
col = "dayofmonth"
|
|
136
|
+
df_out[col] = ts.dt.day.astype("int16")
|
|
137
|
+
calendar_cols.append(col)
|
|
138
|
+
elif name == "month":
|
|
139
|
+
col = "month"
|
|
140
|
+
df_out[col] = ts.dt.month.astype("int16")
|
|
141
|
+
calendar_cols.append(col)
|
|
142
|
+
elif name == "is_weekend":
|
|
143
|
+
col = "is_weekend"
|
|
144
|
+
df_out[col] = ts.dt.dayofweek.isin(WEEKEND_DAYS).astype("int8")
|
|
145
|
+
calendar_cols.append(col)
|
|
146
|
+
else: # pragma: no cover
|
|
147
|
+
# Guarded by _validate_calendar_features, but keep a defensive fallback.
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"Unsupported calendar feature {name!r}. Allowed: {sorted(ALLOWED_CALENDAR_FEATURES)}."
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
feature_cols.extend(calendar_cols)
|
|
153
|
+
|
|
154
|
+
# ---------------------------------------------------------------------
|
|
155
|
+
# Optional cyclical encodings
|
|
156
|
+
# ---------------------------------------------------------------------
|
|
157
|
+
if use_cyclical_time:
|
|
158
|
+
if "hour" in calendar_cols:
|
|
159
|
+
hour = df_out["hour"].astype(float)
|
|
160
|
+
df_out["hour_sin"] = np.sin(2.0 * np.pi * hour / float(HOUR_PERIOD))
|
|
161
|
+
df_out["hour_cos"] = np.cos(2.0 * np.pi * hour / float(HOUR_PERIOD))
|
|
162
|
+
feature_cols.extend(["hour_sin", "hour_cos"])
|
|
163
|
+
|
|
164
|
+
if "dayofweek" in calendar_cols:
|
|
165
|
+
dow = df_out["dayofweek"].astype(float)
|
|
166
|
+
df_out["dow_sin"] = np.sin(2.0 * np.pi * dow / float(DOW_PERIOD))
|
|
167
|
+
df_out["dow_cos"] = np.cos(2.0 * np.pi * dow / float(DOW_PERIOD))
|
|
168
|
+
feature_cols.extend(["dow_sin", "dow_cos"])
|
|
169
|
+
|
|
170
|
+
return df_out, feature_cols, calendar_cols
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _validate_calendar_features(calendar_features: Iterable[str]) -> None:
|
|
174
|
+
r"""
|
|
175
|
+
Validate that requested calendar features are supported.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
calendar_features : Iterable[str]
|
|
180
|
+
Candidate calendar feature keys.
|
|
181
|
+
|
|
182
|
+
Raises
|
|
183
|
+
------
|
|
184
|
+
ValueError
|
|
185
|
+
If any feature key is unsupported.
|
|
186
|
+
"""
|
|
187
|
+
requested = list(calendar_features)
|
|
188
|
+
invalid = [f for f in requested if f not in ALLOWED_CALENDAR_FEATURES]
|
|
189
|
+
if invalid:
|
|
190
|
+
raise ValueError(
|
|
191
|
+
"Unsupported calendar feature(s) requested: "
|
|
192
|
+
f"{invalid}. Allowed: {sorted(ALLOWED_CALENDAR_FEATURES)}."
|
|
193
|
+
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Configuration objects for panel time-series feature engineering.
|
|
5
|
+
|
|
6
|
+
This module defines declarative configuration used by the panel feature engineering
|
|
7
|
+
pipeline. The configuration is designed to be:
|
|
8
|
+
|
|
9
|
+
- **frequency-agnostic** (lags/windows are expressed in index steps, not wall-clock time)
|
|
10
|
+
- **stateless** (config describes what to compute; no fitted state is stored)
|
|
11
|
+
- **explicit** (feature families are enabled/disabled via lists/flags)
|
|
12
|
+
|
|
13
|
+
Notes
|
|
14
|
+
-----
|
|
15
|
+
This configuration is consumed by [`FeatureEngineer`][eb_features.panel.engineer.FeatureEngineer]
|
|
16
|
+
and related helper modules (lags, rolling, calendar, encoders).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import Optional, Sequence, Tuple
|
|
21
|
+
|
|
22
|
+
# -------------------------------------------------------------------------
|
|
23
|
+
# Public constants (useful for validation, docs, and IDE discoverability)
|
|
24
|
+
# -------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
#: Allowed rolling statistics for rolling window features.
|
|
27
|
+
ALLOWED_ROLLING_STATS: Tuple[str, ...] = ("mean", "std", "min", "max", "sum", "median")
|
|
28
|
+
|
|
29
|
+
#: Allowed calendar feature keys derived from timestamps.
|
|
30
|
+
ALLOWED_CALENDAR_FEATURES: Tuple[str, ...] = ("hour", "dow", "dom", "month", "is_weekend")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class FeatureConfig:
|
|
35
|
+
r"""
|
|
36
|
+
Configuration for panel time-series feature engineering.
|
|
37
|
+
|
|
38
|
+
The feature engineering pipeline assumes a long-form panel DataFrame with an entity
|
|
39
|
+
identifier column, a timestamp column, and a numeric target column. This configuration
|
|
40
|
+
describes which feature families to generate and which passthrough columns to include.
|
|
41
|
+
|
|
42
|
+
Notes
|
|
43
|
+
-----
|
|
44
|
+
- Lag steps and rolling windows are expressed in **index steps** (rows) at the input
|
|
45
|
+
frequency, not in wall-clock units.
|
|
46
|
+
- Lags and rolling windows are computed **within each entity**.
|
|
47
|
+
- If ``dropna=True``, rows lacking sufficient lag/rolling history are dropped after
|
|
48
|
+
feature construction.
|
|
49
|
+
|
|
50
|
+
Attributes
|
|
51
|
+
----------
|
|
52
|
+
lag_steps : Sequence[int] | None
|
|
53
|
+
Positive lag offsets (in steps) applied to the target. For each ``k`` in ``lag_steps``,
|
|
54
|
+
the feature ``lag_{k}`` is added.
|
|
55
|
+
rolling_windows : Sequence[int] | None
|
|
56
|
+
Positive rolling window lengths (in steps) applied to the target. For each ``w`` in
|
|
57
|
+
``rolling_windows`` and each stat in ``rolling_stats``, the feature
|
|
58
|
+
``roll_{w}_{stat}`` is added.
|
|
59
|
+
rolling_stats : Sequence[str]
|
|
60
|
+
Rolling statistics to compute. Allowed values are:
|
|
61
|
+
``{"mean", "std", "min", "max", "sum", "median"}``.
|
|
62
|
+
calendar_features : Sequence[str]
|
|
63
|
+
Calendar features derived from the timestamp column. Allowed values are:
|
|
64
|
+
``{"hour", "dow", "dom", "month", "is_weekend"}``.
|
|
65
|
+
use_cyclical_time : bool
|
|
66
|
+
If True, add sine/cosine encodings for hour and day-of-week when those base
|
|
67
|
+
calendar columns are present.
|
|
68
|
+
regressor_cols : Sequence[str] | None
|
|
69
|
+
Numeric external regressors to pass through. If None, numeric columns may be
|
|
70
|
+
auto-detected by the calling pipeline (excluding entity/timestamp/target and
|
|
71
|
+
``static_cols``).
|
|
72
|
+
static_cols : Sequence[str] | None
|
|
73
|
+
Entity-level metadata columns already present on the input DataFrame. These are
|
|
74
|
+
passed through directly as features.
|
|
75
|
+
dropna : bool
|
|
76
|
+
If True, drop rows with NaNs in any engineered feature columns (typically caused
|
|
77
|
+
by lags and rolling windows).
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
lag_steps: Optional[Sequence[int]] = field(default_factory=lambda: (1, 2, 24))
|
|
81
|
+
|
|
82
|
+
rolling_windows: Optional[Sequence[int]] = field(default_factory=lambda: (3, 24))
|
|
83
|
+
rolling_stats: Sequence[str] = field(
|
|
84
|
+
default_factory=lambda: ("mean", "std", "min", "max", "sum")
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
calendar_features: Sequence[str] = field(
|
|
88
|
+
default_factory=lambda: ("hour", "dow", "month", "is_weekend")
|
|
89
|
+
)
|
|
90
|
+
use_cyclical_time: bool = True
|
|
91
|
+
|
|
92
|
+
regressor_cols: Optional[Sequence[str]] = None
|
|
93
|
+
static_cols: Optional[Sequence[str]] = None
|
|
94
|
+
|
|
95
|
+
dropna: bool = True
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Shared constants for panel feature engineering.
|
|
5
|
+
|
|
6
|
+
This module centralizes small, stable configuration values used across the
|
|
7
|
+
``eb_features.panel`` subpackage. Keeping these definitions in one place prevents
|
|
8
|
+
validation drift between modules and provides a single reference point for both
|
|
9
|
+
implementation and documentation.
|
|
10
|
+
|
|
11
|
+
Notes
|
|
12
|
+
-----
|
|
13
|
+
- These constants are intentionally minimal and low-churn.
|
|
14
|
+
- They define *allowed values*, *default configurations*, and *calendar parameters*
|
|
15
|
+
used consistently across feature builders.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Final, FrozenSet, Tuple
|
|
19
|
+
|
|
20
|
+
# -----------------------------------------------------------------------------
|
|
21
|
+
# Allowed feature keys
|
|
22
|
+
# -----------------------------------------------------------------------------
|
|
23
|
+
ALLOWED_ROLLING_STATS: FrozenSet[str] = frozenset(
|
|
24
|
+
{"mean", "std", "min", "max", "sum", "median"}
|
|
25
|
+
)
|
|
26
|
+
"""
|
|
27
|
+
Allowed rolling-window summary statistics.
|
|
28
|
+
|
|
29
|
+
Each statistic corresponds to a feature name of the form:
|
|
30
|
+
|
|
31
|
+
$$
|
|
32
|
+
\mathrm{roll\_{w}\_{stat}}(t)
|
|
33
|
+
$$
|
|
34
|
+
|
|
35
|
+
where ``w`` is the window length (in index steps) and ``stat`` is one of the allowed values.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
ALLOWED_CALENDAR_FEATURES: FrozenSet[str] = frozenset(
|
|
40
|
+
{"hour", "dow", "dom", "month", "is_weekend"}
|
|
41
|
+
)
|
|
42
|
+
"""
|
|
43
|
+
Allowed calendar features derived from the timestamp column.
|
|
44
|
+
|
|
45
|
+
Calendar features are added as integer-valued columns and may optionally be accompanied by
|
|
46
|
+
cyclical encodings (sine/cosine) for periodic components.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# -----------------------------------------------------------------------------
|
|
50
|
+
# Default configuration values (used by FeatureConfig)
|
|
51
|
+
# -----------------------------------------------------------------------------
|
|
52
|
+
DEFAULT_LAG_STEPS: Final[Tuple[int, ...]] = (1, 2, 24)
|
|
53
|
+
|
|
54
|
+
DEFAULT_ROLLING_WINDOWS: Final[Tuple[int, ...]] = (3, 24)
|
|
55
|
+
DEFAULT_ROLLING_STATS: Final[Tuple[str, ...]] = ("mean", "std", "min", "max", "sum")
|
|
56
|
+
|
|
57
|
+
DEFAULT_CALENDAR_FEATURES: Final[Tuple[str, ...]] = (
|
|
58
|
+
"hour",
|
|
59
|
+
"dow",
|
|
60
|
+
"month",
|
|
61
|
+
"is_weekend",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# -----------------------------------------------------------------------------
|
|
65
|
+
# Calendar / cyclical encoding parameters
|
|
66
|
+
# -----------------------------------------------------------------------------
|
|
67
|
+
HOUR_PERIOD: Final[int] = 24
|
|
68
|
+
"""Period used for cyclical hour-of-day encodings."""
|
|
69
|
+
|
|
70
|
+
DOW_PERIOD: Final[int] = 7
|
|
71
|
+
"""Period used for cyclical day-of-week encodings."""
|
|
72
|
+
|
|
73
|
+
# pandas dt.dayofweek convention: Monday=0 ... Sunday=6
|
|
74
|
+
WEEKEND_DAYS: Final[Tuple[int, int]] = (5, 6)
|
|
75
|
+
"""Day-of-week values corresponding to Saturday and Sunday."""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Encoding utilities for panel feature engineering.
|
|
5
|
+
|
|
6
|
+
This module provides small, stateless helpers to make feature matrices numeric.
|
|
7
|
+
|
|
8
|
+
Current scope
|
|
9
|
+
-------------
|
|
10
|
+
The panel feature engineering pipeline produces a feature frame that may contain
|
|
11
|
+
a mixture of numeric and non-numeric columns (e.g., entity metadata strings).
|
|
12
|
+
Many downstream estimators expect purely numeric arrays. The helper in this module
|
|
13
|
+
encodes non-numeric columns using pandas categorical codes.
|
|
14
|
+
|
|
15
|
+
Important
|
|
16
|
+
---------
|
|
17
|
+
Categorical codes are stable **only within the provided DataFrame**. Because this
|
|
18
|
+
module is intentionally stateless (no fitted mapping is persisted), codes may differ
|
|
19
|
+
between training and inference if category sets or ordering differ.
|
|
20
|
+
|
|
21
|
+
For production modeling pipelines that require consistent encodings across datasets,
|
|
22
|
+
consider:
|
|
23
|
+
- pre-encoding categoricals upstream (one-hot, target encoding, etc.), or
|
|
24
|
+
- introducing a fitted encoder with persisted category mappings.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from typing import Iterable
|
|
28
|
+
|
|
29
|
+
import pandas as pd
|
|
30
|
+
from pandas.api.types import is_bool_dtype, is_numeric_dtype
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def encode_non_numeric_as_category_codes(
|
|
34
|
+
feature_frame: pd.DataFrame,
|
|
35
|
+
*,
|
|
36
|
+
columns: Iterable[str] | None = None,
|
|
37
|
+
dtype: str = "int32",
|
|
38
|
+
) -> pd.DataFrame:
|
|
39
|
+
"""
|
|
40
|
+
Encode non-numeric feature columns as categorical codes.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
feature_frame : pandas.DataFrame
|
|
45
|
+
Feature DataFrame whose columns will be encoded as needed.
|
|
46
|
+
columns : Iterable[str] | None, default None
|
|
47
|
+
Columns to consider for encoding. If None, all columns are considered.
|
|
48
|
+
dtype : str, default "int32"
|
|
49
|
+
Output dtype for encoded columns. (Booleans are converted to 0/1 and cast
|
|
50
|
+
to this dtype; categorical codes are integers cast to this dtype.)
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
pandas.DataFrame
|
|
55
|
+
Copy of ``feature_frame`` where:
|
|
56
|
+
- boolean columns are converted to {0,1}
|
|
57
|
+
- non-numeric, non-boolean columns are replaced by categorical integer codes
|
|
58
|
+
|
|
59
|
+
Notes
|
|
60
|
+
-----
|
|
61
|
+
- Missing values in non-numeric columns are assigned the code ``-1`` by pandas.
|
|
62
|
+
- Category ordering is made deterministic by sorting observed values by their
|
|
63
|
+
string representation before assigning codes.
|
|
64
|
+
"""
|
|
65
|
+
df = feature_frame.copy()
|
|
66
|
+
selected = list(df.columns) if columns is None else list(columns)
|
|
67
|
+
|
|
68
|
+
missing = [c for c in selected if c not in df.columns]
|
|
69
|
+
if missing:
|
|
70
|
+
raise KeyError(f"Columns not found in feature_frame: {missing}.")
|
|
71
|
+
|
|
72
|
+
for col in selected:
|
|
73
|
+
s = df[col]
|
|
74
|
+
|
|
75
|
+
# Convert booleans to 0/1 (numeric), for a clean numeric feature matrix.
|
|
76
|
+
if is_bool_dtype(s):
|
|
77
|
+
df[col] = s.astype("int8").astype(dtype)
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
# Leave numeric columns unchanged.
|
|
81
|
+
if is_numeric_dtype(s):
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
# Deterministic category ordering for a given input DataFrame.
|
|
85
|
+
observed = s.dropna().unique().tolist()
|
|
86
|
+
categories = sorted(observed, key=lambda x: str(x))
|
|
87
|
+
|
|
88
|
+
cat = pd.Categorical(s, categories=categories, ordered=False)
|
|
89
|
+
df[col] = cat.codes.astype(dtype)
|
|
90
|
+
|
|
91
|
+
return df
|