views_evaluation 0.1.0__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.
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.1
2
+ Name: views_evaluation
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: Xiaolong Sun
6
+ Author-email: xiaolong.sun@pcr.uu.se
7
+ Requires-Python: >=3.11,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Requires-Dist: numpy (>=1.25.2,<2.0.0)
11
+ Requires-Dist: pandas (>=1.5.3,<2.0.0)
12
+ Requires-Dist: properscoring (>=0.1.0,<0.2.0)
13
+ Requires-Dist: scikit-learn (>=1.2.2,<2.0.0)
14
+ Description-Content-Type: text/markdown
15
+
16
+ ![GitHub License](https://img.shields.io/github/license/views-platform/views-evaluation)
17
+ ![GitHub branch check runs](https://img.shields.io/github/check-runs/views-platform/views-evaluation/main)
18
+ ![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/views-platform/views-evaluation)
19
+ ![GitHub Release](https://img.shields.io/github/v/release/views-platform/views-evaluation)
20
+
21
+ <div style="width: 100%; max-width: 1500px; height: 400px; overflow: hidden; position: relative;">
22
+ <img src="https://pbs.twimg.com/profile_banners/1237000633896652800/1717069203/1500x500" alt="VIEWS Twitter Header" style="position: absolute; top: -50px; width: 100%; height: auto;">
23
+ </div>
24
+
25
+ # **VIEWS Evaluation** ๐Ÿ“Š
26
+
27
+ > **Part of the [VIEWS Platform](https://github.com/views-platform) ecosystem for large-scale conflict forecasting.**
28
+
29
+ ## ๐Ÿ“š **Table of Contents**
30
+
31
+ 1. [Overview](#overview)
32
+ 2. [Role in the VIEWS Pipeline](#role-in-the-views-pipeline)
33
+ 3. [Features](#features)
34
+ 4. [Installation](#installation)
35
+ 5. [Architecture](#architecture)
36
+ 6. [Project Structure](#project-structure)
37
+ 7. [Contributing](#contributing)
38
+ 8. [License](#license)
39
+ 9. [Acknowledgements](#acknowledgements)
40
+
41
+ ---
42
+
43
+ ## ๐Ÿง  **Overview**
44
+
45
+ The **VIEWS Evaluation** repository provides a standardized framework for **assessing time-series forecasting models** used in the **VIEWS conflict prediction pipeline**. It ensures consistent, robust, and interpretable evaluations through **metrics tailored to conflict-related data**, which often exhibit **right-skewness and zero-inflation**.
46
+
47
+ ---
48
+
49
+ ## ๐ŸŒ **Role in the VIEWS Pipeline**
50
+
51
+ VIEWS Evaluation ensures **forecasting accuracy and model robustness** as the **official evaluation component** of the VIEWS ecosystem.
52
+
53
+ ### **Pipeline Integration:**
54
+ 1. **Model Predictions** โ†’
55
+ 2. **Evaluation Metrics Processing** โ†’
56
+ 3. **Metrics Computation (via MetricsManager)** โ†’
57
+ 4. **Final Performance Reports**
58
+
59
+ ### **Integration with Other Repositories:**
60
+ - **[views-pipeline-core](https://github.com/views-platform/views-pipeline-core):** Supplies preprocessed data for evaluation.
61
+ - **[views-models](https://github.com/views-platform/views-models):** Provides trained models to be assessed.
62
+ - **[views-stepshifter](https://github.com/views-platform/views-stepshifter):** Evaluates **time-shifted forecasting models**.
63
+ - **[views-hydranet](https://github.com/views-platform/views-hydranet):** Supports **spatiotemporal deep learning model evaluations**.
64
+
65
+ ---
66
+
67
+ ## โœจ **Features**
68
+
69
+ ### **1. EvaluationMetrics**
70
+ A **data class** for managing and storing evaluation metrics for time-series forecasting models.
71
+
72
+ ๐Ÿ”น **Key Capabilities:**
73
+ - **Handles conflict-specific data distributions**, including **skewness and zero-inflation**.
74
+ - **Three evaluation schemas**:
75
+ 1. **Time-series-wise**: Evaluates long-term forecasting behavior.
76
+ 2. **Step-wise**: Assesses performance at each forecasting step.
77
+ 3. **Month-wise**: Measures forecast accuracy on a rolling monthly basis.
78
+ - **Transforms evaluation metrics into structured DataFrames** for analysis.
79
+
80
+ ๐Ÿ“– More details in the **[Evaluation Metrics Workshop Notes](https://www.notion.so/Notes-37de5410f8b547de8e03dddeb70193a6)**.
81
+
82
+ ---
83
+
84
+ ### **2. MetricsManager**
85
+ A **centralized evaluation engine** for computing metrics on time-series forecasts.
86
+
87
+ ๐Ÿ”น **Key Capabilities:**
88
+ - **Customizable metric lists** allow for flexible evaluation.
89
+ - **Ensures metric consistency** by warning about unrecognized metrics.
90
+ - **Implements all three evaluation schemas** (time-series, step-wise, month-wise).
91
+ - **Batch processing** for multiple models and forecasting targets.
92
+
93
+ ๐Ÿ“– More details in **[schema.MD](https://github.com/prio-data/views_pipeline/blob/eval_docs/documentation/evaluation/schema.MD)**.
94
+
95
+ ---
96
+
97
+ ### **3. Roadmap & Upcoming Features** ๐Ÿšง
98
+ โœ… **Planned Enhancements:**
99
+ - **Multi-target evaluation** (e.g., assessing multiple dependent variables simultaneously).
100
+ - **Expanding metric calculations** beyond RMSLE, CRPS, and AP.
101
+ - **New visualization tools** for better interpretability of evaluation reports.
102
+
103
+ ---
104
+
105
+ ## โš™๏ธ **Installation**
106
+
107
+ ### **Prerequisites**
108
+ - Python **>= 3.11**
109
+
110
+ ---
111
+
112
+ ## ๐Ÿ— **Architecture**
113
+
114
+ ### **1. Evaluation Metrics Framework**
115
+ - **Handles forecasting evaluation** across **multiple models, levels of analysis, and forecasting windows**.
116
+ - Converts model outputs into **standardized evaluation reports**.
117
+
118
+ ### **2. Metrics Computation Pipeline**
119
+ 1. **Input**: Predictions from models in standardized DataFrames.
120
+ 2. **Processing**: Calculation of relevant evaluation metrics.
121
+ 3. **Output**: Performance scores for comparison across models.
122
+
123
+ ### **3. Error Handling & Standardization**
124
+ - **Ensures conformity to VIEWS evaluation standards**.
125
+ - **Warns about unrecognized or incorrectly formatted metrics**.
126
+
127
+ ---
128
+
129
+ ## ๐Ÿ—‚ **Project Structure**
130
+
131
+ ```plaintext
132
+ views-evaluation/
133
+ โ”œโ”€โ”€ README.md # Documentation
134
+ โ”œโ”€โ”€ .github/workflows/ # CI/CD pipelines
135
+ โ”œโ”€โ”€ tests/ # Unit tests
136
+ โ”œโ”€โ”€ views_evaluation/ # Main source code
137
+ โ”‚ โ”œโ”€โ”€ evaluation/
138
+ โ”‚ โ”‚ โ”œโ”€โ”€ metrics.py
139
+ โ”‚ โ”œโ”€โ”€ __init__.py # Package initialization
140
+ โ”œโ”€โ”€ .gitignore # Git ignore rules
141
+ โ”œโ”€โ”€ pyproject.toml # Poetry project file
142
+ โ”œโ”€โ”€ poetry.lock # Dependency lock file
143
+ ```
144
+
145
+ ---
146
+
147
+ ## ๐Ÿค **Contributing**
148
+
149
+ We welcome contributions! Please follow the **[VIEWS Contribution Guidelines](https://github.com/views-platform/docs)**.
150
+
151
+ ---
152
+
153
+ ## ๐Ÿ“œ **License**
154
+
155
+ This project is licensed under the [LICENSE](/LICENSE) file.
156
+
157
+ ---
158
+
159
+ ## ๐Ÿ’ฌ **Acknowledgements**
160
+
161
+ <p align="center">
162
+ <img src="https://raw.githubusercontent.com/views-platform/docs/main/images/views_funders.png" alt="Views Funders" width="80%">
163
+ </p>
164
+
165
+ Special thanks to the **VIEWS MD&D Team** for their collaboration and support.
166
+
167
+
@@ -0,0 +1,151 @@
1
+ ![GitHub License](https://img.shields.io/github/license/views-platform/views-evaluation)
2
+ ![GitHub branch check runs](https://img.shields.io/github/check-runs/views-platform/views-evaluation/main)
3
+ ![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/views-platform/views-evaluation)
4
+ ![GitHub Release](https://img.shields.io/github/v/release/views-platform/views-evaluation)
5
+
6
+ <div style="width: 100%; max-width: 1500px; height: 400px; overflow: hidden; position: relative;">
7
+ <img src="https://pbs.twimg.com/profile_banners/1237000633896652800/1717069203/1500x500" alt="VIEWS Twitter Header" style="position: absolute; top: -50px; width: 100%; height: auto;">
8
+ </div>
9
+
10
+ # **VIEWS Evaluation** ๐Ÿ“Š
11
+
12
+ > **Part of the [VIEWS Platform](https://github.com/views-platform) ecosystem for large-scale conflict forecasting.**
13
+
14
+ ## ๐Ÿ“š **Table of Contents**
15
+
16
+ 1. [Overview](#overview)
17
+ 2. [Role in the VIEWS Pipeline](#role-in-the-views-pipeline)
18
+ 3. [Features](#features)
19
+ 4. [Installation](#installation)
20
+ 5. [Architecture](#architecture)
21
+ 6. [Project Structure](#project-structure)
22
+ 7. [Contributing](#contributing)
23
+ 8. [License](#license)
24
+ 9. [Acknowledgements](#acknowledgements)
25
+
26
+ ---
27
+
28
+ ## ๐Ÿง  **Overview**
29
+
30
+ The **VIEWS Evaluation** repository provides a standardized framework for **assessing time-series forecasting models** used in the **VIEWS conflict prediction pipeline**. It ensures consistent, robust, and interpretable evaluations through **metrics tailored to conflict-related data**, which often exhibit **right-skewness and zero-inflation**.
31
+
32
+ ---
33
+
34
+ ## ๐ŸŒ **Role in the VIEWS Pipeline**
35
+
36
+ VIEWS Evaluation ensures **forecasting accuracy and model robustness** as the **official evaluation component** of the VIEWS ecosystem.
37
+
38
+ ### **Pipeline Integration:**
39
+ 1. **Model Predictions** โ†’
40
+ 2. **Evaluation Metrics Processing** โ†’
41
+ 3. **Metrics Computation (via MetricsManager)** โ†’
42
+ 4. **Final Performance Reports**
43
+
44
+ ### **Integration with Other Repositories:**
45
+ - **[views-pipeline-core](https://github.com/views-platform/views-pipeline-core):** Supplies preprocessed data for evaluation.
46
+ - **[views-models](https://github.com/views-platform/views-models):** Provides trained models to be assessed.
47
+ - **[views-stepshifter](https://github.com/views-platform/views-stepshifter):** Evaluates **time-shifted forecasting models**.
48
+ - **[views-hydranet](https://github.com/views-platform/views-hydranet):** Supports **spatiotemporal deep learning model evaluations**.
49
+
50
+ ---
51
+
52
+ ## โœจ **Features**
53
+
54
+ ### **1. EvaluationMetrics**
55
+ A **data class** for managing and storing evaluation metrics for time-series forecasting models.
56
+
57
+ ๐Ÿ”น **Key Capabilities:**
58
+ - **Handles conflict-specific data distributions**, including **skewness and zero-inflation**.
59
+ - **Three evaluation schemas**:
60
+ 1. **Time-series-wise**: Evaluates long-term forecasting behavior.
61
+ 2. **Step-wise**: Assesses performance at each forecasting step.
62
+ 3. **Month-wise**: Measures forecast accuracy on a rolling monthly basis.
63
+ - **Transforms evaluation metrics into structured DataFrames** for analysis.
64
+
65
+ ๐Ÿ“– More details in the **[Evaluation Metrics Workshop Notes](https://www.notion.so/Notes-37de5410f8b547de8e03dddeb70193a6)**.
66
+
67
+ ---
68
+
69
+ ### **2. MetricsManager**
70
+ A **centralized evaluation engine** for computing metrics on time-series forecasts.
71
+
72
+ ๐Ÿ”น **Key Capabilities:**
73
+ - **Customizable metric lists** allow for flexible evaluation.
74
+ - **Ensures metric consistency** by warning about unrecognized metrics.
75
+ - **Implements all three evaluation schemas** (time-series, step-wise, month-wise).
76
+ - **Batch processing** for multiple models and forecasting targets.
77
+
78
+ ๐Ÿ“– More details in **[schema.MD](https://github.com/prio-data/views_pipeline/blob/eval_docs/documentation/evaluation/schema.MD)**.
79
+
80
+ ---
81
+
82
+ ### **3. Roadmap & Upcoming Features** ๐Ÿšง
83
+ โœ… **Planned Enhancements:**
84
+ - **Multi-target evaluation** (e.g., assessing multiple dependent variables simultaneously).
85
+ - **Expanding metric calculations** beyond RMSLE, CRPS, and AP.
86
+ - **New visualization tools** for better interpretability of evaluation reports.
87
+
88
+ ---
89
+
90
+ ## โš™๏ธ **Installation**
91
+
92
+ ### **Prerequisites**
93
+ - Python **>= 3.11**
94
+
95
+ ---
96
+
97
+ ## ๐Ÿ— **Architecture**
98
+
99
+ ### **1. Evaluation Metrics Framework**
100
+ - **Handles forecasting evaluation** across **multiple models, levels of analysis, and forecasting windows**.
101
+ - Converts model outputs into **standardized evaluation reports**.
102
+
103
+ ### **2. Metrics Computation Pipeline**
104
+ 1. **Input**: Predictions from models in standardized DataFrames.
105
+ 2. **Processing**: Calculation of relevant evaluation metrics.
106
+ 3. **Output**: Performance scores for comparison across models.
107
+
108
+ ### **3. Error Handling & Standardization**
109
+ - **Ensures conformity to VIEWS evaluation standards**.
110
+ - **Warns about unrecognized or incorrectly formatted metrics**.
111
+
112
+ ---
113
+
114
+ ## ๐Ÿ—‚ **Project Structure**
115
+
116
+ ```plaintext
117
+ views-evaluation/
118
+ โ”œโ”€โ”€ README.md # Documentation
119
+ โ”œโ”€โ”€ .github/workflows/ # CI/CD pipelines
120
+ โ”œโ”€โ”€ tests/ # Unit tests
121
+ โ”œโ”€โ”€ views_evaluation/ # Main source code
122
+ โ”‚ โ”œโ”€โ”€ evaluation/
123
+ โ”‚ โ”‚ โ”œโ”€โ”€ metrics.py
124
+ โ”‚ โ”œโ”€โ”€ __init__.py # Package initialization
125
+ โ”œโ”€โ”€ .gitignore # Git ignore rules
126
+ โ”œโ”€โ”€ pyproject.toml # Poetry project file
127
+ โ”œโ”€โ”€ poetry.lock # Dependency lock file
128
+ ```
129
+
130
+ ---
131
+
132
+ ## ๐Ÿค **Contributing**
133
+
134
+ We welcome contributions! Please follow the **[VIEWS Contribution Guidelines](https://github.com/views-platform/docs)**.
135
+
136
+ ---
137
+
138
+ ## ๐Ÿ“œ **License**
139
+
140
+ This project is licensed under the [LICENSE](/LICENSE) file.
141
+
142
+ ---
143
+
144
+ ## ๐Ÿ’ฌ **Acknowledgements**
145
+
146
+ <p align="center">
147
+ <img src="https://raw.githubusercontent.com/views-platform/docs/main/images/views_funders.png" alt="Views Funders" width="80%">
148
+ </p>
149
+
150
+ Special thanks to the **VIEWS MD&D Team** for their collaboration and support.
151
+
@@ -0,0 +1,22 @@
1
+ [tool.poetry]
2
+ name = "views_evaluation"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = [
6
+ "Xiaolong Sun <xiaolong.sun@pcr.uu.se>",
7
+ "Borbรกla Farkas <borbala.farkas@pcr.uu.se>",
8
+ "Dylan Pinheiro <dylpin@prio.org>"
9
+ ]
10
+ readme = "README.md"
11
+
12
+ [tool.poetry.dependencies]
13
+ python = "^3.11"
14
+ scikit-learn = "^1.2.2"
15
+ pandas = "^1.5.3"
16
+ numpy = "^1.25.2"
17
+ properscoring = "^0.1.0"
18
+
19
+
20
+ [build-system]
21
+ requires = ["poetry-core"]
22
+ build-backend = "poetry.core.masonry.api"
File without changes
@@ -0,0 +1,375 @@
1
+ from typing import List, Dict, Tuple, Optional
2
+ from dataclasses import dataclass
3
+ import logging
4
+ import pandas as pd
5
+ import numpy as np
6
+ import properscoring as ps
7
+ from statistics import mean, stdev, median
8
+ from sklearn.metrics import root_mean_squared_error, root_mean_squared_log_error, average_precision_score
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ # ============================================================ Metrics Dataclass ============================================================
14
+
15
+ @dataclass
16
+ class EvaluationMetrics:
17
+ """
18
+ A data class for storing and managing evaluation metrics for time series forecasting models.
19
+
20
+ Attributes:
21
+ RMSLE (Optional[float]): Root Mean Squared Logarithmic Error.
22
+ CRPS (Optional[float]): Continuous Ranked Probability Score.
23
+ AP (Optional[float]): Average Precision.
24
+ Brier (Optional[float]): Brier Score.
25
+ Jeffreys (Optional[float]): Jeffreys Divergence.
26
+ Coverage (Optional[float]): Coverage (Histograms).
27
+ EMD (Optional[float]): Earth Mover Distance.
28
+ SD (Optional[float]): Sinkhorn Distance.
29
+ pEMDiv (Optional[float]): pseudo-Earth Mover Divergence.
30
+ Pearson (Optional[float]): Pearson Correlation.
31
+ Variogram (Optional[float]): Variogram.
32
+ """
33
+
34
+ RMSLE: Optional[float] = None
35
+ CRPS: Optional[float] = None
36
+ AP: Optional[float] = None
37
+ Brier: Optional[float] = None
38
+ Jeffreys: Optional[float] = None
39
+ Coverage: Optional[float] = None
40
+ EMD: Optional[float] = None
41
+ SD: Optional[float] = None
42
+ pEMDiv: Optional[float] = None
43
+ Pearson: Optional[float] = None
44
+ Variogram: Optional[float] = None
45
+
46
+ @classmethod
47
+ def make_time_series_wise_evaluation_dict(cls, time_series_length: int =12) -> dict:
48
+ """
49
+ Generates a dictionary of EvaluationMetrics instances for a specified number of time series.
50
+
51
+ This method facilitates the batch creation of metric containers for multiple time series, initializing them with None.
52
+
53
+ Args:
54
+ time_series_length (int): The number of time series for which to generate evaluation metrics. Defaults to 12.
55
+
56
+ Returns:
57
+ dict: A dictionary where each key is a step label (e.g., 'ts01', 'ts02', ...) and each value is an instance of EvaluationMetrics.
58
+
59
+ Example:
60
+ >>> from utils_evaluation_metrics import EvaluationMetrics
61
+ >>> evaluation_dict = EvaluationMetrics.make_evaluation_dict(time_series_length=12)
62
+ >>> evaluation_dict['ts01'].MSE = sklearn.metrics.mean_squared_error(ts01_y_true, ts01_y_pred)
63
+ >>> evaluation_dict['ts02'].MSE = sklearn.metrics.mean_squared_error(ts02_y_true, ts02_y_pred)
64
+ >>> ...
65
+
66
+ """
67
+ return {f"ts{str(i).zfill(2)}": cls() for i in range(0, time_series_length)}
68
+
69
+ @classmethod
70
+ def make_step_wise_evaluation_dict(cls, steps: int =36) -> dict:
71
+ """
72
+ Generates a dictionary of EvaluationMetrics instances for a specified number of steps.
73
+
74
+ This method facilitates the batch creation of metric containers for multiple steps, initializing them with None.
75
+
76
+ Args:
77
+ steps (int): The number of forecasting steps for which to generate evaluation metrics. Defaults to 36.
78
+
79
+ Returns:
80
+ dict: A dictionary where each key is a step label (e.g., 'step01', 'step02', ...) and each value is an instance of EvaluationMetrics.
81
+
82
+ Example:
83
+ >>> from utils_evaluation_metrics import EvaluationMetrics
84
+ >>> evaluation_dict = EvaluationMetrics.make_evaluation_dict(steps=36)
85
+ >>> evaluation_dict['step01'].MSE = sklearn.metrics.mean_squared_error(step01_y_true, step01_y_pred)
86
+ >>> evaluation_dict['step02'].MSE = sklearn.metrics.mean_squared_error(step02_y_true, step02_y_pred)
87
+ >>> ...
88
+
89
+ """
90
+ return {f"step{str(i).zfill(2)}": cls() for i in range(1, steps + 1)}
91
+
92
+ @classmethod
93
+ def make_month_wise_evaluation_dict(cls, month_start: int, month_end: int) -> dict:
94
+ """
95
+ Generates a dictionary of EvaluationMetrics instances for a specified range of months.
96
+
97
+ This method facilitates the batch creation of metric containers for multiple months, initializing them with None.
98
+
99
+ Args:
100
+ month_start (int): The first month for which to generate evaluation metrics.
101
+ month_end (int): The last month for which to generate evaluation metrics.
102
+
103
+ Returns:
104
+ dict: A dictionary where each key is a step label (e.g., 'month501', 'month502', ...) and each value is an instance of EvaluationMetrics.
105
+
106
+ Example:
107
+ >>> from utils_evaluation_metrics import EvaluationMetrics
108
+ >>> evaluation_dict = EvaluationMetrics.make_evaluation_dict(month_start=501, month_end=548)
109
+ >>> evaluation_dict['month501'].MSE = sklearn.metrics.mean_squared_error(month501_y_true, month501_y_pred)
110
+ >>> evaluation_dict['month502'].MSE = sklearn.metrics.mean_squared_error(month502_y_true, month502_y_pred)
111
+ >>> ...
112
+
113
+ """
114
+ return {f"month{str(i)}": cls() for i in range(month_start, month_end + 1)}
115
+
116
+ @staticmethod
117
+ def evaluation_dict_to_dataframe(evaluation_dict: dict) -> pd.DataFrame:
118
+ """
119
+ Converts a dictionary of EvaluationMetrics instances into a pandas DataFrame for easy analysis.
120
+
121
+ This static method transforms a structured dictionary of evaluation metrics into a DataFrame, where each row corresponds to a forecasting step and columns represent different metrics.
122
+
123
+ Args:
124
+ evaluation_dict (dict): A dictionary of EvaluationMetrics instances, typically generated by the make_evaluation_dict class method.
125
+
126
+ Returns:
127
+ pd.DataFrame: A pandas DataFrame where each row indexes a forecasting step and columns correspond to the various metrics stored in EvaluationMetrics.
128
+
129
+ Example:
130
+ >>> evaluation_df = EvaluationMetrics.evaluation_dict_to_dataframe(evaluation_dict)
131
+
132
+ """
133
+ df = pd.DataFrame.from_dict(evaluation_dict, orient='index')
134
+ return df.loc[:, df.notna().any()]
135
+
136
+
137
+ # ============================================================ Metrics Manager ============================================================
138
+
139
+ class MetricsManager:
140
+ """
141
+ A class for calculating metrics on time series predictions
142
+ Refer to https://github.com/prio-data/views_pipeline/blob/eval_docs/documentation/evaluation/schema.MD for more details on three evaluation schemas.
143
+ """
144
+
145
+ def __init__(self, metrics_list):
146
+ """
147
+ Initialize the manager with a list of metric names to calculate.
148
+
149
+ Args:
150
+ metrics_list (List[str]): A list of metric names to evaluate.
151
+ """
152
+
153
+ self.metrics_list = metrics_list
154
+ self.metric_functions = {
155
+ "RMSLE": self._calculate_rmsle,
156
+ "CRPS": self._calculate_crps,
157
+ "AP": self._calculate_ap,
158
+ "Brier": self._calculate_brier,
159
+ "Jeffreys": self._calculate_jeffreys,
160
+ "Coverage": self._calculate_coverage,
161
+ "EMD": self._calculate_emd,
162
+ "SD": self._calculate_sd,
163
+ "pEMDiv": self._calculate_pEMDiv,
164
+ "Pearson": self._calculate_pearson,
165
+ "Variogram": self._calculate_variogram,
166
+ }
167
+
168
+ @staticmethod
169
+ def _calculate_rmsle(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
170
+ return (
171
+ root_mean_squared_error(matched_actual, matched_pred)
172
+ if target.startswith("ln")
173
+ else root_mean_squared_log_error(matched_actual, matched_pred)
174
+ )
175
+
176
+ @staticmethod
177
+ def _calculate_crps(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
178
+ return ps.crps_ensemble(matched_actual, matched_pred).mean()
179
+
180
+ @staticmethod
181
+ def _calculate_ap(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str, threshold=0.01) -> float:
182
+ """
183
+ Calculate Average Precision (AP) for binary predictions with a threshold.
184
+ """
185
+ matched_pred_binary = (matched_pred >= threshold).astype(int)
186
+ matched_actual_binary = (matched_actual > 0).astype(int)
187
+ return average_precision_score(matched_actual_binary, matched_pred_binary)
188
+
189
+ @staticmethod
190
+ def _calculate_brier(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
191
+ pass
192
+
193
+ @staticmethod
194
+ def _calculate_jeffreys(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
195
+ pass
196
+
197
+ @staticmethod
198
+ def _calculate_coverage(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
199
+ pass
200
+
201
+ @staticmethod
202
+ def _calculate_emd(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
203
+ pass
204
+
205
+ @staticmethod
206
+ def _calculate_sd(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
207
+ pass
208
+
209
+ @staticmethod
210
+ def _calculate_pEMDiv(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
211
+ pass
212
+
213
+ @staticmethod
214
+ def _calculate_pearson(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
215
+ pass
216
+
217
+ @staticmethod
218
+ def _calculate_variogram(matched_actual: pd.DataFrame, matched_pred: pd.DataFrame, target: str) -> float:
219
+ pass
220
+
221
+ @staticmethod
222
+ def _match_actual_pred(actual: pd.DataFrame, pred: pd.DataFrame, target: str) -> Tuple[pd.DataFrame, pd.DataFrame]:
223
+ """
224
+ Matches the actual and predicted DataFrames based on the index and target column.
225
+
226
+ Parameters:
227
+ - actual: pd.DataFrame with a MultiIndex (e.g., month, level).
228
+ - pred: pd.DataFrame with a MultiIndex that may contain duplicated indices.
229
+ - target: str, the target column in actual.
230
+
231
+ Returns:
232
+ - matched_actual: pd.DataFrame aligned with pred.
233
+ - matched_pred: pd.DataFrame aligned with actual.
234
+ """
235
+ if target not in actual.columns:
236
+ raise ValueError(f"Target column '{target}' not found in actual DataFrame.")
237
+
238
+ actual_target = actual[[target]]
239
+ aligned_actual, aligned_pred = actual_target.align(pred, join="inner")
240
+ matched_actual = aligned_actual.reindex(index=aligned_pred.index)
241
+ matched_actual[[target]] = actual_target
242
+
243
+ return matched_actual.sort_index(), pred.sort_index()
244
+
245
+ @staticmethod
246
+ def _split_dfs_by_step(dfs: list) -> list:
247
+ """ยจ
248
+ This function splits a list of DataFrames into a dictionary of DataFrames by step, where the key is the step.
249
+ For example, assume df0 has month_id from 100 to 102, df1 has month_id from 101 to 103, and df2 has month_id from 102 to 104.
250
+ This function returns three dataframes, with the first dataframe having month_id 100 from df0, month_id 101 from df1, and month_id 102 from df2.
251
+
252
+ Args:
253
+ dfs (list): List of DataFrames with overlapping time ranges.
254
+
255
+ Returns:
256
+ dict (list): A list of DataFrames where each contains one unique month_id from each input DataFrame.
257
+ """
258
+ time_id = dfs[0].index.names[0]
259
+ all_month_ids = [df.index.get_level_values(0).unique() for df in dfs]
260
+
261
+ grouped_month_ids = list(zip(*all_month_ids))
262
+
263
+ result_dfs = []
264
+ for i, group in enumerate(grouped_month_ids):
265
+ step = i + 1
266
+ combined = pd.concat(
267
+ [df.loc[month_id] for df, month_id in zip(dfs, group)],
268
+ keys=group,
269
+ names=[time_id]
270
+ )
271
+ result_dfs.append(combined)
272
+
273
+ return result_dfs
274
+
275
+ def step_wise_evaluation(
276
+ self, actual: pd.DataFrame, predictions: List[pd.DataFrame], target: str, steps: List[int]
277
+ ) -> pd.DataFrame:
278
+ """
279
+ Evaluates the predictions step-wise and calculates the specified metrics.
280
+
281
+ Args:
282
+ actual (pd.DataFrame): The actual values.
283
+ predictions (List[pd.DataFrame]): A list of DataFrames containing the predictions.
284
+ target (str): The target column in the actual DataFrame.
285
+ steps (List[int]): The steps to evaluate.
286
+
287
+ Returns:
288
+ pd.DataFrame: A DataFrame containing the evaluation metrics.
289
+ """
290
+ evaluation_dict = EvaluationMetrics.make_step_wise_evaluation_dict(steps=max(steps))
291
+ step_metrics = {}
292
+
293
+ result_dfs = MetricsManager._split_dfs_by_step(predictions)
294
+
295
+ for metric in self.metrics_list:
296
+ if metric in self.metric_functions:
297
+ for i, pred in enumerate(result_dfs):
298
+ step = i + 1
299
+ matched_actual, matched_pred = MetricsManager._match_actual_pred(actual, pred, target)
300
+ evaluation_dict[f"step{str(step).zfill(2)}"].__setattr__(
301
+ metric, self.metric_functions[metric](matched_actual, matched_pred, target)
302
+ )
303
+ else:
304
+ logger.warning(f"Metric {metric} is not a default metric, skipping...")
305
+
306
+ return evaluation_dict, EvaluationMetrics.evaluation_dict_to_dataframe(evaluation_dict)
307
+
308
+ def time_series_wise_evaluation(
309
+ self, actual: pd.DataFrame, predictions: List[pd.DataFrame], target: str
310
+ ) -> pd.DataFrame:
311
+ """
312
+ Evaluates the predictions time series-wise and calculates the specified metrics.
313
+
314
+ Args:
315
+ actual (pd.DataFrame): The actual values.
316
+ predictions (List[pd.DataFrame]): A list of DataFrames containing the predictions.
317
+ target (str): The target column in the actual DataFrame.
318
+
319
+ Returns:
320
+ pd.DataFrame: A DataFrame containing the evaluation metrics.
321
+ """
322
+ evaluation_dict = EvaluationMetrics.make_time_series_wise_evaluation_dict(len(predictions))
323
+
324
+ for metric in self.metrics_list:
325
+ if metric in self.metric_functions:
326
+ for i, pred in enumerate(predictions):
327
+ matched_actual, matched_pred = MetricsManager._match_actual_pred(actual, pred, target)
328
+ evaluation_dict[f"ts{str(i).zfill(2)}"].__setattr__(
329
+ metric, self.metric_functions[metric](matched_actual, matched_pred, target)
330
+ )
331
+ else:
332
+ logger.warning(f"Metric {metric} is not a default metric, skipping...")
333
+
334
+ return evaluation_dict, EvaluationMetrics.evaluation_dict_to_dataframe(evaluation_dict)
335
+
336
+ def month_wise_evaluation(
337
+ self, actual: pd.DataFrame, predictions: List[pd.DataFrame], target: str
338
+ ) -> pd.DataFrame:
339
+ """
340
+ Evaluates the predictions month-wise and calculates the specified metrics.
341
+
342
+ Args:
343
+ actual (pd.DataFrame): The actual values.
344
+ predictions (List[pd.DataFrame]): A list of DataFrames containing the predictions.
345
+ target (str): The target column in the actual DataFrame.
346
+
347
+ Returns:
348
+ pd.DataFrame: A DataFrame containing the evaluation metrics.
349
+ """
350
+ pred_concat = pd.concat(predictions)
351
+ pred_concat_target = pred_concat.columns[0]
352
+ month_range = pred_concat.index.get_level_values(0).unique()
353
+ month_start = month_range.min()
354
+ month_end = month_range.max()
355
+ evaluation_dict = EvaluationMetrics.make_month_wise_evaluation_dict(month_start, month_end)
356
+
357
+ matched_actual, matched_pred = MetricsManager._match_actual_pred(actual, pred_concat, target)
358
+ matched_concat = pd.merge(matched_actual, matched_pred, left_index=True, right_index=True)
359
+
360
+ for metric in self.metrics_list:
361
+ if metric in self.metric_functions:
362
+ metric_by_month = matched_concat.groupby(level=matched_concat.index.names[0]).apply(
363
+ lambda df: self.metric_functions[metric](
364
+ df[[target]], df[[pred_concat_target]], target
365
+ )
366
+ )
367
+
368
+ for month in month_range:
369
+ evaluation_dict[f"month{str(month)}"].__setattr__(metric, metric_by_month.loc[month])
370
+ else:
371
+ logger.warning(f"Metric {metric} is not a default metric, skipping...")
372
+
373
+ return evaluation_dict, EvaluationMetrics.evaluation_dict_to_dataframe(evaluation_dict)
374
+
375
+