eb-evaluation 0.1.1__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.
@@ -0,0 +1,82 @@
1
+ """
2
+ Electric Barometer Evaluation Toolkit (eb-evaluation).
3
+
4
+ This package provides **DataFrame-oriented evaluation, diagnostics, and
5
+ model-selection utilities** built around Cost-Weighted Service Loss (CWSL)
6
+ and related readiness metrics.
7
+
8
+ Scope
9
+ -----
10
+ The eb-evaluation package sits *above* ``eb_metrics`` in the Electric Barometer
11
+ architecture:
12
+
13
+ - ``eb_metrics`` defines **metric math** (CWSL, NSL, UD, HR@τ, FRS, etc.)
14
+ - ``eb-evaluation`` provides **tabular orchestration**, grouping logic,
15
+ sensitivity analysis, tolerance calibration, and model selection
16
+
17
+ Primary capabilities include:
18
+
19
+ DataFrame evaluation
20
+ - Single-slice and grouped CWSL computation
21
+ - Hierarchical evaluation across multiple aggregation levels
22
+ - Long-form (tidy) panel outputs for plotting and reporting
23
+ - Entity-aware evaluation with entity-specific cost ratios
24
+ - Cost-ratio (R = cu / co) sensitivity analysis
25
+
26
+ Tolerance (τ) calibration
27
+ - Data-driven τ estimation from historical residuals
28
+ - Global and entity-level τ estimation with governance guards
29
+ - HR@τ computation with automatically selected tolerances
30
+
31
+ Model selection
32
+ - Cost-aware comparison of forecast models
33
+ - Holdout and cross-validated selection by minimum CWSL
34
+ - sklearn-style wrappers for downstream pipelines
35
+
36
+ Design principles
37
+ -----------------
38
+ - **Separation of concerns**: metric definitions live in ``eb_metrics``,
39
+ orchestration and evaluation live here.
40
+ - **Operational alignment**: selection and diagnostics are driven by cost
41
+ and readiness, not symmetric error alone.
42
+ - **Deterministic & explicit**: no hidden heuristics; all behavior is
43
+ controlled via parameters and documented outputs.
44
+
45
+ This package is intended to be used alongside ``eb_metrics`` and
46
+ ``eb-adapters`` as part of the broader Electric Barometer ecosystem.
47
+ """
48
+
49
+ from .dataframe import (
50
+ compute_cwsl_df,
51
+ evaluate_groups_df,
52
+ evaluate_hierarchy_df,
53
+ evaluate_panel_df,
54
+ evaluate_panel_with_entity_R,
55
+ compute_cwsl_sensitivity_df,
56
+ cwsl_sensitivity_df,
57
+ estimate_entity_R_from_balance,
58
+ )
59
+
60
+ from .dataframe.tolerance import (
61
+ hr_at_tau,
62
+ estimate_tau,
63
+ estimate_entity_tau,
64
+ hr_auto_tau,
65
+ TauEstimate,
66
+ )
67
+
68
+ __all__ = [
69
+ "compute_cwsl_df",
70
+ "evaluate_groups_df",
71
+ "evaluate_hierarchy_df",
72
+ "evaluate_panel_df",
73
+ "evaluate_panel_with_entity_R",
74
+ "compute_cwsl_sensitivity_df",
75
+ "cwsl_sensitivity_df",
76
+ "estimate_entity_R_from_balance",
77
+ "hr_at_tau",
78
+ "estimate_tau",
79
+ "estimate_entity_tau",
80
+ "hr_auto_tau",
81
+ "TauEstimate",
82
+ ]
@@ -0,0 +1,25 @@
1
+ """
2
+ Adjustment utilities for Electric Barometer evaluation.
3
+
4
+ The `eb_evaluation.adjustment` package contains the **Readiness Adjustment Layer (RAL)**,
5
+ a lightweight post-processing component that converts a baseline statistical forecast into an
6
+ operationally conservative *readiness forecast* via a learned multiplicative uplift.
7
+
8
+ Key ideas
9
+ ---------
10
+ - **Metrics live in** `eb_metrics.metrics` (definitions only).
11
+ - **Adjustments live here** (evaluation / selection utilities that *consume* metrics).
12
+ - RAL learns an uplift by grid-searching multipliers and selecting the value that minimizes
13
+ **Cost-Weighted Service Loss (CWSL)** on historical data.
14
+ - Uplifts can be learned globally or per-segment (with a global fallback for unseen segments).
15
+
16
+ Public API
17
+ ----------
18
+ - `ReadinessAdjustmentLayer`
19
+ """
20
+
21
+ from .readiness_adjustment import ReadinessAdjustmentLayer
22
+
23
+ __all__ = [
24
+ "ReadinessAdjustmentLayer",
25
+ ]
@@ -0,0 +1,168 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Internal utilities for the Readiness Adjustment Layer.
5
+
6
+ This module contains small, focused helpers used by the Readiness Adjustment Layer (RAL)
7
+ implementation. These utilities are **not** part of the public API and may change without
8
+ notice.
9
+
10
+ The intent is to keep the main algorithm (and its public surface area) in
11
+ `eb_evaluation.adjustment.readiness_adjustment` clean and readable.
12
+ """
13
+
14
+ from collections.abc import Callable, Sequence
15
+ from typing import Any, Union, overload
16
+
17
+ import numpy as np
18
+ import pandas as pd
19
+
20
+
21
+ ArrayLike = Union[np.ndarray, Sequence[float], pd.Series, pd.DataFrame]
22
+
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Array validation utilities
26
+ # ---------------------------------------------------------------------------
27
+ def validate_numeric_array(arr: ArrayLike, name: str = "array") -> np.ndarray:
28
+ """Validate and coerce an input to a finite float NumPy array.
29
+
30
+ This helper is intentionally strict: it ensures the array is not scalar and that
31
+ all values are finite. This is appropriate for evaluation utilities where silent
32
+ propagation of NaNs can lead to misleading metric comparisons.
33
+
34
+ Parameters
35
+ ----------
36
+ arr
37
+ Input array-like object. Common inputs include lists, NumPy arrays, pandas Series,
38
+ or a single-column DataFrame.
39
+ name
40
+ Name used in error messages to make debugging easier.
41
+
42
+ Returns
43
+ -------
44
+ numpy.ndarray
45
+ A NumPy array of dtype ``float64``. The returned array may be 1D or 2D depending on
46
+ the input.
47
+
48
+ Raises
49
+ ------
50
+ ValueError
51
+ If ``arr`` is scalar (0-dimensional) or contains NaN/infinite values.
52
+
53
+ Notes
54
+ -----
55
+ - If you pass a DataFrame, its underlying NumPy representation is used (i.e., you are
56
+ responsible for selecting appropriate columns before calling this helper).
57
+ - This helper does *not* drop missing values. If you need filtering behavior, perform
58
+ it upstream and call this only once the array should be clean.
59
+ """
60
+ if isinstance(arr, (pd.Series, pd.DataFrame)):
61
+ arr = arr.to_numpy()
62
+
63
+ out = np.asarray(arr, dtype=float)
64
+
65
+ if out.ndim == 0:
66
+ raise ValueError(f"{name} must be an array-like (not a scalar).")
67
+
68
+ if not np.isfinite(out).all():
69
+ raise ValueError(f"{name} contains NaN or infinite values.")
70
+
71
+ return out
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Safe statistical helpers
76
+ # ---------------------------------------------------------------------------
77
+ def safe_mean(values: np.ndarray) -> float:
78
+ """Compute a mean with a defined result for empty inputs.
79
+
80
+ Parameters
81
+ ----------
82
+ values
83
+ Numeric array. Typically a 1D array of values after filtering.
84
+
85
+ Returns
86
+ -------
87
+ float
88
+ The arithmetic mean of ``values``. If ``values`` is empty (``values.size == 0``),
89
+ returns ``0.0``.
90
+
91
+ Notes
92
+ -----
93
+ This is mainly used in group-level computations where a group may end up empty after
94
+ filtering invalid rows. Returning ``0.0`` is a pragmatic default for diagnostics; it
95
+ should not be used as a substitute for input validation in the primary metric pathway.
96
+ """
97
+ values = np.asarray(values, dtype=float)
98
+ if values.size == 0:
99
+ return 0.0
100
+ return float(np.mean(values))
101
+
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # Groupby helpers
105
+ # ---------------------------------------------------------------------------
106
+ def groupby_apply_values(
107
+ df: pd.DataFrame,
108
+ group_cols: Union[str, Sequence[str]],
109
+ value_col: str,
110
+ func: Callable[[np.ndarray], float],
111
+ ) -> pd.DataFrame:
112
+ """Apply a numeric reducer to a column, grouped by one or more keys.
113
+
114
+ This helper groups ``df`` by ``group_cols`` and applies ``func`` to the values of
115
+ ``value_col`` for each group. The function is called with a **validated**, finite
116
+ float array.
117
+
118
+ Parameters
119
+ ----------
120
+ df
121
+ Input DataFrame containing grouping keys and the numeric value column.
122
+ group_cols
123
+ Column name or sequence of column names to group by.
124
+ value_col
125
+ Name of the column whose values are passed to ``func``.
126
+ func
127
+ Reducer function taking a 1D NumPy array and returning a scalar (float).
128
+
129
+ Returns
130
+ -------
131
+ pandas.DataFrame
132
+ A tidy DataFrame with columns:
133
+
134
+ - ``group_cols`` (one column per grouping key)
135
+ - ``f"{value_col}_agg"`` (the aggregated scalar result)
136
+
137
+ Raises
138
+ ------
139
+ KeyError
140
+ If ``value_col`` or any ``group_cols`` are missing from ``df``.
141
+ ValueError
142
+ If group values contain NaN/infinite values (via :func:`validate_numeric_array`).
143
+
144
+ Examples
145
+ --------
146
+ >>> out = groupby_apply_values(df, ["cluster", "daypart"], "uplift", np.mean)
147
+ >>> out.columns
148
+ Index(['cluster', 'daypart', 'uplift_agg'], dtype='object')
149
+ """
150
+ if isinstance(group_cols, str):
151
+ group_cols_seq: list[str] = [group_cols]
152
+ else:
153
+ group_cols_seq = list(group_cols)
154
+
155
+ missing = [c for c in [*group_cols_seq, value_col] if c not in df.columns]
156
+ if missing:
157
+ raise KeyError(f"Missing required columns: {missing}")
158
+
159
+ agg_col = f"{value_col}_agg"
160
+
161
+ grouped = (
162
+ df.groupby(group_cols_seq, dropna=False)[value_col]
163
+ .apply(lambda s: func(validate_numeric_array(s.to_numpy(), name=value_col)))
164
+ .reset_index()
165
+ .rename(columns={value_col: agg_col})
166
+ )
167
+
168
+ return grouped