eb-evaluation 0.1.1__tar.gz

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.
Files changed (32) hide show
  1. eb_evaluation-0.1.1/LICENSE +28 -0
  2. eb_evaluation-0.1.1/PKG-INFO +169 -0
  3. eb_evaluation-0.1.1/README.md +148 -0
  4. eb_evaluation-0.1.1/pyproject.toml +40 -0
  5. eb_evaluation-0.1.1/setup.cfg +4 -0
  6. eb_evaluation-0.1.1/src/eb_evaluation/__init__.py +82 -0
  7. eb_evaluation-0.1.1/src/eb_evaluation/adjustment/__init__.py +25 -0
  8. eb_evaluation-0.1.1/src/eb_evaluation/adjustment/_utils.py +168 -0
  9. eb_evaluation-0.1.1/src/eb_evaluation/adjustment/readiness_adjustment.py +652 -0
  10. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/__init__.py +40 -0
  11. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/cost_ratio.py +224 -0
  12. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/entity.py +250 -0
  13. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/group.py +216 -0
  14. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/hierarchy.py +198 -0
  15. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/panel.py +141 -0
  16. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/sensitivity.py +192 -0
  17. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/single.py +110 -0
  18. eb_evaluation-0.1.1/src/eb_evaluation/dataframe/tolerance.py +674 -0
  19. eb_evaluation-0.1.1/src/eb_evaluation/features/__init__.py +23 -0
  20. eb_evaluation-0.1.1/src/eb_evaluation/features/feature_engineer.py +371 -0
  21. eb_evaluation-0.1.1/src/eb_evaluation/model_selection/__init__.py +43 -0
  22. eb_evaluation-0.1.1/src/eb_evaluation/model_selection/auto_engine.py +299 -0
  23. eb_evaluation-0.1.1/src/eb_evaluation/model_selection/compare.py +419 -0
  24. eb_evaluation-0.1.1/src/eb_evaluation/model_selection/cwsl_regressor.py +428 -0
  25. eb_evaluation-0.1.1/src/eb_evaluation/model_selection/electric_barometer.py +499 -0
  26. eb_evaluation-0.1.1/src/eb_evaluation/utils/__init__.py +29 -0
  27. eb_evaluation-0.1.1/src/eb_evaluation/utils/validation.py +100 -0
  28. eb_evaluation-0.1.1/src/eb_evaluation.egg-info/PKG-INFO +169 -0
  29. eb_evaluation-0.1.1/src/eb_evaluation.egg-info/SOURCES.txt +30 -0
  30. eb_evaluation-0.1.1/src/eb_evaluation.egg-info/dependency_links.txt +1 -0
  31. eb_evaluation-0.1.1/src/eb_evaluation.egg-info/requires.txt +7 -0
  32. eb_evaluation-0.1.1/src/eb_evaluation.egg-info/top_level.txt +1 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Kyle Corrie
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: eb-evaluation
3
+ Version: 0.1.1
4
+ Summary: Electric Barometer: DataFrame-based evaluation utilities for CWSL and related metrics.
5
+ Author-email: "Kyle Corrie (Economistician)" <kcorrie@economistician.com>
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/Economistician/eb-evaluation
8
+ Project-URL: Repository, https://github.com/Economistician/eb-evaluation
9
+ Project-URL: Issues, https://github.com/Economistician/eb-evaluation/issues
10
+ Project-URL: Documentation, https://github.com/Economistician/eb-docs
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy>=1.24
15
+ Requires-Dist: pandas>=2.0
16
+ Requires-Dist: eb-metrics>=0.1.1
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0; extra == "dev"
19
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
20
+ Dynamic: license-file
21
+
22
+ # Electric Barometer Evaluation (`eb-evaluation`)
23
+
24
+ ![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)
25
+ ![Python Versions](https://img.shields.io/badge/Python-3.10%2B-blue)
26
+ [![Docs](https://img.shields.io/badge/docs-electric--barometer-blue)](https://economistician.github.io/eb-docs/)
27
+ ![Project Status](https://img.shields.io/badge/Status-Alpha-yellow)
28
+
29
+ This repository contains the **evaluation and orchestration layer** of the
30
+ *Electric Barometer* ecosystem.
31
+
32
+ `eb-evaluation` sits above core metric implementations (`eb-metrics`) and
33
+ provides structured tools for applying Electric Barometer concepts to
34
+ real-world forecasting workflows, including readiness adjustment, model
35
+ comparison, sensitivity analysis, and dataframe-based evaluation.
36
+
37
+ Conceptual definitions and theoretical framing for the evaluation logic are
38
+ maintained in the companion research repository:
39
+ **`eb-papers`**.
40
+
41
+ ---
42
+
43
+ ## Naming convention
44
+
45
+ Electric Barometer packages follow standard Python packaging conventions:
46
+
47
+ - **Distribution names** (used with `pip install`) use hyphens
48
+ e.g. `pip install eb-evaluation`
49
+ - **Python import paths** use underscores
50
+ e.g. `import eb_evaluation`
51
+
52
+ This distinction is intentional and consistent across the Electric Barometer
53
+ ecosystem.
54
+
55
+ ---
56
+
57
+ ## Role Within Electric Barometer
58
+
59
+ Within the Electric Barometer ecosystem:
60
+
61
+ - **`eb-papers`** defines *concepts, frameworks, and meaning*
62
+ - **`eb-metrics`** implements *individual metrics*
63
+ - **`eb-evaluation`** orchestrates *how metrics are applied, combined, and interpreted*
64
+
65
+ This repository focuses on *evaluation logic*, not raw metric computation.
66
+
67
+ ---
68
+
69
+ ## What This Library Provides
70
+
71
+ - **Readiness adjustment logic** for modifying evaluation outputs based on
72
+ operational readiness signals
73
+ - **Model selection and comparison utilities** grounded in asymmetric loss and
74
+ readiness-aware metrics
75
+ - **Sensitivity and tolerance analysis** for cost ratios and service thresholds
76
+ - **DataFrame-oriented evaluation tools** for entity-level and time-based analysis
77
+ - **Feature engineering utilities** to support evaluation pipelines
78
+
79
+ ---
80
+
81
+ ## Scope
82
+
83
+ This repository focuses on **evaluation workflows and orchestration**, not
84
+ low-level metric definitions.
85
+
86
+ **In scope:**
87
+ - Applying EB metrics to datasets and model outputs
88
+ - Combining metrics into readiness-aware evaluation artifacts
89
+ - Model comparison and selection logic
90
+ - Sensitivity analysis and tolerance handling
91
+
92
+ **Out of scope:**
93
+ - Metric definitions and loss formulations (see `eb-metrics`)
94
+ - Conceptual frameworks and theory (see `eb-papers`)
95
+ - Model training or forecasting algorithms
96
+
97
+ ---
98
+
99
+ ## Installation
100
+
101
+ Once published, the package will be installable via PyPI:
102
+
103
+ ```bash
104
+ pip install eb-evaluation
105
+ ```
106
+
107
+ For development or local use:
108
+
109
+ ```bash
110
+ pip install -e .
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Package Structure
116
+
117
+ The repository follows a modern Python package layout:
118
+
119
+ ```text
120
+ eb-evaluation/
121
+ ├── src/eb_evaluation/
122
+ │ ├── adjustment/ # Readiness and evaluation adjustments
123
+ │ ├── dataframe/ # DataFrame-based evaluation utilities
124
+ │ ├── features/ # Feature engineering helpers
125
+ │ ├── model_selection/ # Model comparison and selection logic
126
+ │ └── utils/ # Shared validation and helpers
127
+
128
+ ├── tests/ # Unit tests mirroring package structure
129
+ ├── pyproject.toml # Build and dependency configuration
130
+ ├── README.md # Project documentation
131
+ └── LICENSE # BSD-3-Clause license
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Relationship to Other EB Repositories
137
+
138
+ - `eb-papers`
139
+ Source of truth for conceptual definitions and evaluation philosophy.
140
+
141
+ - `eb-metrics`
142
+ Provides the metric implementations used during evaluation.
143
+
144
+ - `eb-evaluation`
145
+ Orchestrates evaluation workflows using adapted models.
146
+
147
+ - `eb-adapters`
148
+ Ensures heterogeneous models can be evaluated consistently.
149
+
150
+ When discrepancies arise, conceptual intent in `eb-papers` should be treated as authoritative.
151
+
152
+ ---
153
+
154
+ ## Development and Testing
155
+
156
+ Tests are located under the `tests/` directory and mirror the package structure.
157
+
158
+ To run the test suite:
159
+
160
+ ```bash
161
+ pytest
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Status
167
+
168
+ This package is under active development.
169
+ Public APIs may evolve prior to the first stable release.
@@ -0,0 +1,148 @@
1
+ # Electric Barometer Evaluation (`eb-evaluation`)
2
+
3
+ ![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)
4
+ ![Python Versions](https://img.shields.io/badge/Python-3.10%2B-blue)
5
+ [![Docs](https://img.shields.io/badge/docs-electric--barometer-blue)](https://economistician.github.io/eb-docs/)
6
+ ![Project Status](https://img.shields.io/badge/Status-Alpha-yellow)
7
+
8
+ This repository contains the **evaluation and orchestration layer** of the
9
+ *Electric Barometer* ecosystem.
10
+
11
+ `eb-evaluation` sits above core metric implementations (`eb-metrics`) and
12
+ provides structured tools for applying Electric Barometer concepts to
13
+ real-world forecasting workflows, including readiness adjustment, model
14
+ comparison, sensitivity analysis, and dataframe-based evaluation.
15
+
16
+ Conceptual definitions and theoretical framing for the evaluation logic are
17
+ maintained in the companion research repository:
18
+ **`eb-papers`**.
19
+
20
+ ---
21
+
22
+ ## Naming convention
23
+
24
+ Electric Barometer packages follow standard Python packaging conventions:
25
+
26
+ - **Distribution names** (used with `pip install`) use hyphens
27
+ e.g. `pip install eb-evaluation`
28
+ - **Python import paths** use underscores
29
+ e.g. `import eb_evaluation`
30
+
31
+ This distinction is intentional and consistent across the Electric Barometer
32
+ ecosystem.
33
+
34
+ ---
35
+
36
+ ## Role Within Electric Barometer
37
+
38
+ Within the Electric Barometer ecosystem:
39
+
40
+ - **`eb-papers`** defines *concepts, frameworks, and meaning*
41
+ - **`eb-metrics`** implements *individual metrics*
42
+ - **`eb-evaluation`** orchestrates *how metrics are applied, combined, and interpreted*
43
+
44
+ This repository focuses on *evaluation logic*, not raw metric computation.
45
+
46
+ ---
47
+
48
+ ## What This Library Provides
49
+
50
+ - **Readiness adjustment logic** for modifying evaluation outputs based on
51
+ operational readiness signals
52
+ - **Model selection and comparison utilities** grounded in asymmetric loss and
53
+ readiness-aware metrics
54
+ - **Sensitivity and tolerance analysis** for cost ratios and service thresholds
55
+ - **DataFrame-oriented evaluation tools** for entity-level and time-based analysis
56
+ - **Feature engineering utilities** to support evaluation pipelines
57
+
58
+ ---
59
+
60
+ ## Scope
61
+
62
+ This repository focuses on **evaluation workflows and orchestration**, not
63
+ low-level metric definitions.
64
+
65
+ **In scope:**
66
+ - Applying EB metrics to datasets and model outputs
67
+ - Combining metrics into readiness-aware evaluation artifacts
68
+ - Model comparison and selection logic
69
+ - Sensitivity analysis and tolerance handling
70
+
71
+ **Out of scope:**
72
+ - Metric definitions and loss formulations (see `eb-metrics`)
73
+ - Conceptual frameworks and theory (see `eb-papers`)
74
+ - Model training or forecasting algorithms
75
+
76
+ ---
77
+
78
+ ## Installation
79
+
80
+ Once published, the package will be installable via PyPI:
81
+
82
+ ```bash
83
+ pip install eb-evaluation
84
+ ```
85
+
86
+ For development or local use:
87
+
88
+ ```bash
89
+ pip install -e .
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Package Structure
95
+
96
+ The repository follows a modern Python package layout:
97
+
98
+ ```text
99
+ eb-evaluation/
100
+ ├── src/eb_evaluation/
101
+ │ ├── adjustment/ # Readiness and evaluation adjustments
102
+ │ ├── dataframe/ # DataFrame-based evaluation utilities
103
+ │ ├── features/ # Feature engineering helpers
104
+ │ ├── model_selection/ # Model comparison and selection logic
105
+ │ └── utils/ # Shared validation and helpers
106
+
107
+ ├── tests/ # Unit tests mirroring package structure
108
+ ├── pyproject.toml # Build and dependency configuration
109
+ ├── README.md # Project documentation
110
+ └── LICENSE # BSD-3-Clause license
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Relationship to Other EB Repositories
116
+
117
+ - `eb-papers`
118
+ Source of truth for conceptual definitions and evaluation philosophy.
119
+
120
+ - `eb-metrics`
121
+ Provides the metric implementations used during evaluation.
122
+
123
+ - `eb-evaluation`
124
+ Orchestrates evaluation workflows using adapted models.
125
+
126
+ - `eb-adapters`
127
+ Ensures heterogeneous models can be evaluated consistently.
128
+
129
+ When discrepancies arise, conceptual intent in `eb-papers` should be treated as authoritative.
130
+
131
+ ---
132
+
133
+ ## Development and Testing
134
+
135
+ Tests are located under the `tests/` directory and mirror the package structure.
136
+
137
+ To run the test suite:
138
+
139
+ ```bash
140
+ pytest
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Status
146
+
147
+ This package is under active development.
148
+ Public APIs may evolve prior to the first stable release.
@@ -0,0 +1,40 @@
1
+ [project]
2
+ name = "eb-evaluation"
3
+ version = "0.1.1"
4
+ description = "Electric Barometer: DataFrame-based evaluation utilities for CWSL and related metrics."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { text = "BSD-3-Clause" }
8
+
9
+ authors = [
10
+ { name = "Kyle Corrie (Economistician)", email = "kcorrie@economistician.com" }
11
+ ]
12
+
13
+ dependencies = [
14
+ "numpy>=1.24",
15
+ "pandas>=2.0",
16
+ "eb-metrics>=0.1.1",
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/Economistician/eb-evaluation"
21
+ Repository = "https://github.com/Economistician/eb-evaluation"
22
+ Issues = "https://github.com/Economistician/eb-evaluation/issues"
23
+ Documentation = "https://github.com/Economistician/eb-docs"
24
+
25
+ [project.optional-dependencies]
26
+ dev = [
27
+ "pytest>=8.0",
28
+ "pytest-cov>=5.0",
29
+ ]
30
+
31
+ [build-system]
32
+ requires = ["setuptools>=64", "wheel"]
33
+ build-backend = "setuptools.build_meta"
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["src"]
37
+
38
+ [tool.pytest.ini_options]
39
+ pythonpath = ["src"]
40
+ addopts = "-ra"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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