openstef-beam 4.0.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.
- openstef_beam/__init__.py +43 -0
- openstef_beam/analysis/__init__.py +32 -0
- openstef_beam/analysis/analysis_pipeline.py +194 -0
- openstef_beam/analysis/models/__init__.py +24 -0
- openstef_beam/analysis/models/target_metadata.py +97 -0
- openstef_beam/analysis/models/visualization_aggregation.py +61 -0
- openstef_beam/analysis/models/visualization_output.py +93 -0
- openstef_beam/analysis/plots/__init__.py +27 -0
- openstef_beam/analysis/plots/forecast_time_series_plotter.py +683 -0
- openstef_beam/analysis/plots/grouped_target_metric_plotter.py +173 -0
- openstef_beam/analysis/plots/precision_recall_curve_plotter.py +151 -0
- openstef_beam/analysis/plots/quantile_calibration_box_plotter.py +196 -0
- openstef_beam/analysis/plots/quantile_probability_plotter.py +153 -0
- openstef_beam/analysis/plots/summary_table_plotter.py +80 -0
- openstef_beam/analysis/plots/windowed_metric_plotter.py +141 -0
- openstef_beam/analysis/visualizations/__init__.py +33 -0
- openstef_beam/analysis/visualizations/base.py +250 -0
- openstef_beam/analysis/visualizations/grouped_target_metric_visualization.py +351 -0
- openstef_beam/analysis/visualizations/precision_recall_curve_visualization.py +195 -0
- openstef_beam/analysis/visualizations/quantile_calibration_box_visualization.py +240 -0
- openstef_beam/analysis/visualizations/quantile_probability_visualization.py +221 -0
- openstef_beam/analysis/visualizations/summary_table_visualization.py +311 -0
- openstef_beam/analysis/visualizations/timeseries_visualization.py +155 -0
- openstef_beam/analysis/visualizations/windowed_metric_visualization.py +429 -0
- openstef_beam/backtesting/__init__.py +23 -0
- openstef_beam/backtesting/backtest_callback.py +9 -0
- openstef_beam/backtesting/backtest_event.py +57 -0
- openstef_beam/backtesting/backtest_event_generator.py +207 -0
- openstef_beam/backtesting/backtest_forecaster/__init__.py +25 -0
- openstef_beam/backtesting/backtest_forecaster/dummy_forecaster.py +73 -0
- openstef_beam/backtesting/backtest_forecaster/mixins.py +236 -0
- openstef_beam/backtesting/backtest_pipeline.py +301 -0
- openstef_beam/backtesting/restricted_horizon_timeseries.py +72 -0
- openstef_beam/benchmarking/__init__.py +55 -0
- openstef_beam/benchmarking/baselines/__init__.py +17 -0
- openstef_beam/benchmarking/baselines/openstef4.py +243 -0
- openstef_beam/benchmarking/benchmark_comparison_pipeline.py +255 -0
- openstef_beam/benchmarking/benchmark_pipeline.py +420 -0
- openstef_beam/benchmarking/benchmarks/__init__.py +14 -0
- openstef_beam/benchmarking/benchmarks/liander2024.py +239 -0
- openstef_beam/benchmarking/callbacks/__init__.py +10 -0
- openstef_beam/benchmarking/callbacks/base.py +230 -0
- openstef_beam/benchmarking/callbacks/strict_execution_callback.py +39 -0
- openstef_beam/benchmarking/models/__init__.py +9 -0
- openstef_beam/benchmarking/models/benchmark_target.py +116 -0
- openstef_beam/benchmarking/storage/__init__.py +16 -0
- openstef_beam/benchmarking/storage/base.py +254 -0
- openstef_beam/benchmarking/storage/local_storage.py +163 -0
- openstef_beam/benchmarking/storage/s3_storage.py +193 -0
- openstef_beam/benchmarking/target_provider.py +412 -0
- openstef_beam/evaluation/__init__.py +43 -0
- openstef_beam/evaluation/evaluation_pipeline.py +316 -0
- openstef_beam/evaluation/metric_providers.py +753 -0
- openstef_beam/evaluation/models/__init__.py +21 -0
- openstef_beam/evaluation/models/report.py +150 -0
- openstef_beam/evaluation/models/subset.py +100 -0
- openstef_beam/evaluation/models/window.py +132 -0
- openstef_beam/evaluation/window_iterators.py +117 -0
- openstef_beam/metrics/__init__.py +58 -0
- openstef_beam/metrics/metrics_deterministic.py +625 -0
- openstef_beam/metrics/metrics_probabilistic.py +273 -0
- openstef_beam-4.0.0.dist-info/METADATA +39 -0
- openstef_beam-4.0.0.dist-info/RECORD +64 -0
- openstef_beam-4.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2017-2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""OpenSTEF BEAM: Backtesting, Evaluation, Analysis and Metrics framework.
|
|
6
|
+
|
|
7
|
+
BEAM provides all the tools to test energy forecasting models under realistic conditions.
|
|
8
|
+
Instead of simple validation that can mislead, BEAM simulates real-world scenarios using
|
|
9
|
+
versioned data - ensuring models only use information available at prediction time.
|
|
10
|
+
|
|
11
|
+
Key features:
|
|
12
|
+
|
|
13
|
+
**Real-world simulation**: Uses versioned data to prevent data leakage. Models are
|
|
14
|
+
retrained periodically and can only access historical information, just like in production.
|
|
15
|
+
|
|
16
|
+
**Flexible integration**: Plug in your own forecasting models, create custom metrics,
|
|
17
|
+
design specific visualizations, and select relevant lead times for your use case.
|
|
18
|
+
|
|
19
|
+
**Complete workflow**: Backtesting → Evaluation → Analysis → Benchmarking. From testing
|
|
20
|
+
individual models to comparing multiple approaches across different energy targets.
|
|
21
|
+
|
|
22
|
+
**Lead time analysis**: Evaluate how forecast quality changes from 1-hour to 48-hour
|
|
23
|
+
predictions, critical for operational planning in energy systems.
|
|
24
|
+
|
|
25
|
+
Use BEAM to:
|
|
26
|
+
|
|
27
|
+
- Test models under realistic operational constraints
|
|
28
|
+
- Compare forecasting approaches with versioned data integrity
|
|
29
|
+
- Generate flexible reports tailored to your metrics and visualizations
|
|
30
|
+
- Ensure evaluation results match real-world deployment performance
|
|
31
|
+
|
|
32
|
+
BEAM's versioned data approach and flexible architecture make it the reliable choice
|
|
33
|
+
for energy forecasting model validation that translates to production success.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import logging
|
|
37
|
+
|
|
38
|
+
# Set up logging configuration
|
|
39
|
+
root_logger = logging.getLogger(name=__name__)
|
|
40
|
+
if not root_logger.handlers:
|
|
41
|
+
root_logger.addHandler(logging.NullHandler())
|
|
42
|
+
|
|
43
|
+
__all__ = []
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""Turns evaluation results into visualizations and reports for decision making.
|
|
6
|
+
|
|
7
|
+
Numbers and metrics are hard to interpret. This module creates charts, plots, and
|
|
8
|
+
summary reports that help you understand model performance and make decisions about
|
|
9
|
+
which models to deploy. It automatically generates the visualizations most relevant
|
|
10
|
+
for energy forecasting operations.
|
|
11
|
+
|
|
12
|
+
What you get:
|
|
13
|
+
|
|
14
|
+
- Performance charts: See how models compare across different metrics
|
|
15
|
+
- Time series plots: Visualize predictions vs actual load over time
|
|
16
|
+
- Error analysis: Understand when and why models make mistakes
|
|
17
|
+
- Comparison reports: Side-by-side model performance analysis
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from openstef_beam.analysis import plots, visualizations
|
|
21
|
+
from openstef_beam.analysis.analysis_pipeline import AnalysisConfig, AnalysisPipeline
|
|
22
|
+
from openstef_beam.analysis.models import AnalysisOutput, AnalysisScope, VisualizationOutput
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"AnalysisConfig",
|
|
26
|
+
"AnalysisOutput",
|
|
27
|
+
"AnalysisPipeline",
|
|
28
|
+
"AnalysisScope",
|
|
29
|
+
"VisualizationOutput",
|
|
30
|
+
"plots",
|
|
31
|
+
"visualizations",
|
|
32
|
+
]
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""Analysis pipeline for generating visualizations from evaluation reports.
|
|
6
|
+
|
|
7
|
+
This module provides the core pipeline that orchestrates visualization generation
|
|
8
|
+
from evaluation reports at different aggregation levels. It integrates with the
|
|
9
|
+
benchmarking framework to provide consistent analysis outputs across benchmark runs.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
from collections.abc import Sequence
|
|
14
|
+
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
|
|
17
|
+
from openstef_beam.analysis.models import (
|
|
18
|
+
AnalysisAggregation,
|
|
19
|
+
AnalysisOutput,
|
|
20
|
+
AnalysisScope,
|
|
21
|
+
GroupName,
|
|
22
|
+
TargetMetadata,
|
|
23
|
+
VisualizationOutput,
|
|
24
|
+
)
|
|
25
|
+
from openstef_beam.analysis.visualizations import ReportTuple, VisualizationProvider
|
|
26
|
+
from openstef_beam.evaluation import EvaluationReport
|
|
27
|
+
from openstef_beam.evaluation.models import Filtering
|
|
28
|
+
from openstef_core.base_model import BaseConfig
|
|
29
|
+
from openstef_core.utils.itertools import groupby
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AnalysisConfig(BaseConfig):
|
|
33
|
+
"""Configuration for the analytics pipeline."""
|
|
34
|
+
|
|
35
|
+
visualization_providers: list[VisualizationProvider] = Field(
|
|
36
|
+
default=[], description="List of visualization providers to use for generating analysis outputs"
|
|
37
|
+
)
|
|
38
|
+
filterings: list[Filtering] | None = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
description="When set, only include these filterings (e.g. LeadTime, AvailableAt) in analysis. "
|
|
41
|
+
"None means use all filterings found in the evaluation data.",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AnalysisPipeline:
|
|
46
|
+
"""Orchestrates the generation of visualizations from evaluation reports.
|
|
47
|
+
|
|
48
|
+
The pipeline processes evaluation reports at different aggregation levels:
|
|
49
|
+
|
|
50
|
+
- Individual targets: Creates detailed visualizations for single targets
|
|
51
|
+
- Multiple targets: Creates comparative visualizations across target groups
|
|
52
|
+
|
|
53
|
+
It integrates with the benchmarking framework by being called from BenchmarkPipeline
|
|
54
|
+
after evaluation is complete, ensuring consistent visualization generation across
|
|
55
|
+
all benchmark runs.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
config: AnalysisConfig,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Initialize the analysis pipeline with configuration.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
config: Analysis configuration containing visualization providers.
|
|
66
|
+
"""
|
|
67
|
+
super().__init__()
|
|
68
|
+
self.config = config
|
|
69
|
+
|
|
70
|
+
def _group_by_filtering(
|
|
71
|
+
self,
|
|
72
|
+
reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
|
|
73
|
+
) -> dict[Filtering, list[ReportTuple]]:
|
|
74
|
+
"""Group reports by their lead time filtering conditions.
|
|
75
|
+
|
|
76
|
+
Organizes evaluation reports based on their lead time criteria (e.g.,
|
|
77
|
+
1-hour ahead vs 24-hour ahead forecasts), enabling comparison of model
|
|
78
|
+
performance across different forecasting horizons.
|
|
79
|
+
|
|
80
|
+
When ``config.filterings`` is set, only subsets matching those filterings are included.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Dictionary mapping lead time filtering conditions to lists of report tuples.
|
|
84
|
+
"""
|
|
85
|
+
allowed = set(self.config.filterings) if self.config.filterings is not None else None
|
|
86
|
+
return groupby(
|
|
87
|
+
(subset.filtering, (base_metadata.with_filtering(subset.filtering), subset))
|
|
88
|
+
for base_metadata, report in reports
|
|
89
|
+
for subset in report.subset_reports
|
|
90
|
+
if allowed is None or subset.filtering in allowed
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def run_for_subsets(
|
|
94
|
+
self,
|
|
95
|
+
reports: list[ReportTuple],
|
|
96
|
+
aggregation: AnalysisAggregation,
|
|
97
|
+
) -> list[VisualizationOutput]:
|
|
98
|
+
"""Generate visualizations for a set of evaluation reports at a specific aggregation level.
|
|
99
|
+
|
|
100
|
+
Processes the provided evaluation reports through all configured visualization
|
|
101
|
+
providers that support the requested aggregation level. Only providers that
|
|
102
|
+
declare support for the aggregation are used.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
reports: List of (metadata, evaluation_subset_report) tuples to visualize.
|
|
106
|
+
The metadata provides context about the target and run, while the
|
|
107
|
+
evaluation report contains the metrics and predictions to visualize.
|
|
108
|
+
aggregation: The aggregation level determining how reports are grouped
|
|
109
|
+
and compared in visualizations.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
List of visualization outputs from all applicable providers. Empty if
|
|
113
|
+
no providers support the requested aggregation level.
|
|
114
|
+
"""
|
|
115
|
+
return [
|
|
116
|
+
provider.create(reports=reports, aggregation=aggregation)
|
|
117
|
+
for provider in self.config.visualization_providers
|
|
118
|
+
if aggregation in provider.supported_aggregations
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
def run_for_reports(
|
|
122
|
+
self,
|
|
123
|
+
reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
|
|
124
|
+
scope: AnalysisScope,
|
|
125
|
+
) -> AnalysisOutput:
|
|
126
|
+
"""Generate visualizations for evaluation reports within a specific scope.
|
|
127
|
+
|
|
128
|
+
Groups reports by lead time filtering conditions and generates visualizations
|
|
129
|
+
for each group using all configured visualization providers that support the
|
|
130
|
+
scope's aggregation level. This enables comparing model performance across
|
|
131
|
+
different forecasting horizons (e.g., short-term vs long-term predictions).
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
reports: List of (metadata, evaluation_report) tuples to visualize.
|
|
135
|
+
scope: Analysis scope defining aggregation level and context.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Analysis output containing all generated visualizations grouped by
|
|
139
|
+
lead time filtering conditions.
|
|
140
|
+
"""
|
|
141
|
+
grouped = self._group_by_filtering(reports)
|
|
142
|
+
|
|
143
|
+
result: dict[Filtering, list[VisualizationOutput]] = defaultdict(list)
|
|
144
|
+
for filtering, subset_reports in grouped.items():
|
|
145
|
+
visualizations = self.run_for_subsets(
|
|
146
|
+
reports=subset_reports,
|
|
147
|
+
aggregation=scope.aggregation,
|
|
148
|
+
)
|
|
149
|
+
result[filtering].extend(visualizations)
|
|
150
|
+
|
|
151
|
+
return AnalysisOutput(
|
|
152
|
+
scope=scope,
|
|
153
|
+
visualizations=result,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def run_for_groups(
|
|
157
|
+
self,
|
|
158
|
+
reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
|
|
159
|
+
scope: AnalysisScope,
|
|
160
|
+
) -> dict[GroupName, AnalysisOutput]:
|
|
161
|
+
"""Generate visualizations for multiple target groups at a specific aggregation level.
|
|
162
|
+
|
|
163
|
+
This method processes all evaluation reports, grouping them by their target
|
|
164
|
+
group names and generating visualizations for each group.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
reports: List of (metadata, evaluation_report) tuples to visualize.
|
|
168
|
+
The metadata provides context about the target and run, while the
|
|
169
|
+
evaluation report contains the metrics and predictions to visualize.
|
|
170
|
+
scope: The analytics scope defining how reports are grouped and aggregated.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Dictionary mapping group names to their corresponding AnalyticsOutput.
|
|
174
|
+
"""
|
|
175
|
+
reports_by_group: dict[GroupName, list[tuple[TargetMetadata, EvaluationReport]]] = groupby(
|
|
176
|
+
(report[0].group_name, report) for report in reports
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
result: dict[GroupName, AnalysisOutput] = {}
|
|
180
|
+
for group_name, group_reports in reports_by_group.items():
|
|
181
|
+
if not group_reports:
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
result[group_name] = self.run_for_reports(
|
|
185
|
+
reports=group_reports,
|
|
186
|
+
scope=AnalysisScope(
|
|
187
|
+
aggregation=scope.aggregation,
|
|
188
|
+
target_name=scope.target_name,
|
|
189
|
+
run_name=scope.run_name,
|
|
190
|
+
group_name=group_name,
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return result
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""Data models for analysis pipeline components.
|
|
6
|
+
|
|
7
|
+
This package provides data models and type definitions for the analysis pipeline,
|
|
8
|
+
including aggregation types, output structures, and metadata containers.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from openstef_beam.analysis.models.target_metadata import GroupName, RunName, TargetMetadata, TargetName
|
|
12
|
+
from openstef_beam.analysis.models.visualization_aggregation import AnalysisAggregation
|
|
13
|
+
from openstef_beam.analysis.models.visualization_output import AnalysisOutput, AnalysisScope, VisualizationOutput
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"AnalysisAggregation",
|
|
17
|
+
"AnalysisOutput",
|
|
18
|
+
"AnalysisScope",
|
|
19
|
+
"GroupName",
|
|
20
|
+
"RunName",
|
|
21
|
+
"TargetMetadata",
|
|
22
|
+
"TargetName",
|
|
23
|
+
"VisualizationOutput",
|
|
24
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""Target metadata models for analysis pipeline.
|
|
6
|
+
|
|
7
|
+
This module defines the metadata structure that carries target information
|
|
8
|
+
through the analysis pipeline, including grouping and filtering context.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Self
|
|
12
|
+
|
|
13
|
+
from pydantic import Field, model_validator
|
|
14
|
+
|
|
15
|
+
from openstef_beam.evaluation.models import Filtering
|
|
16
|
+
from openstef_core.base_model import BaseModel
|
|
17
|
+
|
|
18
|
+
type TargetName = str
|
|
19
|
+
type GroupName = str
|
|
20
|
+
type RunName = str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TargetMetadata(BaseModel):
|
|
24
|
+
"""Metadata for a forecasting target in the analysis pipeline.
|
|
25
|
+
|
|
26
|
+
Contains essential information about a target including its grouping context
|
|
27
|
+
and lead time filtering criteria. Lead time filtering determines which
|
|
28
|
+
predictions are included based on how far ahead they were made (e.g.,
|
|
29
|
+
1-hour ahead vs 24-hour ahead forecasts).
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValueError: When limit constraints are not met (either 'limit' alone or both
|
|
33
|
+
'upper_limit' and 'lower_limit' must be specified).
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
name: TargetName = Field(description="Name of the target")
|
|
37
|
+
group_name: GroupName = Field(description="Name of the group this target belongs to, used for grouping in reports")
|
|
38
|
+
filtering: Filtering | None = Field(
|
|
39
|
+
description="Lead time filtering criteria - either AvailableAt (data availability time) "
|
|
40
|
+
"or LeadTime (forecast horizon)"
|
|
41
|
+
)
|
|
42
|
+
limit: float | None = Field(default=None, description="Capacity limit of the target in appropriate units")
|
|
43
|
+
upper_limit: float | None = Field(
|
|
44
|
+
default=None, description="Upper capacity limit of the target in appropriate units"
|
|
45
|
+
)
|
|
46
|
+
lower_limit: float | None = Field(
|
|
47
|
+
default=None, description="Lower capacity limit of the target in appropriate units"
|
|
48
|
+
)
|
|
49
|
+
run_name: RunName = Field(description="Name of the run associated with this target")
|
|
50
|
+
|
|
51
|
+
@model_validator(mode="after")
|
|
52
|
+
def validate_limits(self) -> "TargetMetadata":
|
|
53
|
+
"""Validate that either limit or both upper_limit and lower_limit are provided.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The validated TargetMetadata instance.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ValueError: If neither limit nor (upper_limit and lower_limit) are provided,
|
|
60
|
+
or if both limit and (upper_limit or lower_limit) are provided.
|
|
61
|
+
"""
|
|
62
|
+
has_limit = self.limit is not None
|
|
63
|
+
has_upper = self.upper_limit is not None
|
|
64
|
+
has_lower = self.lower_limit is not None
|
|
65
|
+
|
|
66
|
+
if has_limit and (has_upper or has_lower):
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"Cannot specify both 'limit' and 'upper_limit'/'lower_limit'. "
|
|
69
|
+
"Use either 'limit' alone or both 'upper_limit' and 'lower_limit'."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if not has_limit and not (has_upper and has_lower):
|
|
73
|
+
raise ValueError("Must specify either 'limit' or both 'upper_limit' and 'lower_limit'.")
|
|
74
|
+
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
def with_filtering(self, filtering: Filtering) -> Self:
|
|
78
|
+
"""Returns a copy of the target metadata with different lead time filtering applied.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
filtering: New lead time filtering criteria to apply
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
New TargetMetadata instance with updated lead time filtering
|
|
85
|
+
"""
|
|
86
|
+
return type(self)(
|
|
87
|
+
name=self.name,
|
|
88
|
+
group_name=self.group_name,
|
|
89
|
+
filtering=filtering,
|
|
90
|
+
limit=self.limit,
|
|
91
|
+
upper_limit=self.upper_limit,
|
|
92
|
+
lower_limit=self.lower_limit,
|
|
93
|
+
run_name=self.run_name,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
__all__ = ["GroupName", "RunName", "TargetMetadata", "TargetName"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""Analysis aggregation models for visualization grouping.
|
|
6
|
+
|
|
7
|
+
This module defines aggregation levels and scopes that control how evaluation
|
|
8
|
+
reports are grouped and compared in visualizations.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from enum import StrEnum
|
|
12
|
+
|
|
13
|
+
from openstef_beam.analysis.models.target_metadata import GroupName, RunName, TargetName
|
|
14
|
+
from openstef_core.base_model import BaseConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AnalysisAggregation(StrEnum):
|
|
18
|
+
"""Defines the aggregation levels for visualizations.
|
|
19
|
+
|
|
20
|
+
Each aggregation level determines how evaluation reports are grouped and
|
|
21
|
+
compared in visualizations, enabling different analytical perspectives.
|
|
22
|
+
|
|
23
|
+
Members:
|
|
24
|
+
|
|
25
|
+
- NONE ("none"): Single run, single target - individual performance analysis
|
|
26
|
+
- TARGET ("target"): Single run, per target - cross-target comparison (e.g., RMAE per target)
|
|
27
|
+
- GROUP ("group"): Single run, multiple targets - cross-group comparison (e.g., RMAE per group)
|
|
28
|
+
- RUN ("run"): Multiple runs, per target - model comparison on same target for all targets
|
|
29
|
+
- RUN_AND_GROUP ("run_and_group"): Multiple runs, multiple targets - comparison matrix
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
NONE = "none"
|
|
33
|
+
TARGET = "target"
|
|
34
|
+
GROUP = "group"
|
|
35
|
+
RUN_AND_NONE = "run_and_none"
|
|
36
|
+
RUN_AND_TARGET = "run_and_target"
|
|
37
|
+
RUN_AND_GROUP = "run_and_group"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AnalysisScope(BaseConfig):
|
|
41
|
+
"""Defines the scope context for analysis operations.
|
|
42
|
+
|
|
43
|
+
Specifies which targets, groups, and runs are included in an analysis,
|
|
44
|
+
along with the aggregation level for grouping data.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
target_name: TargetName | None = None
|
|
48
|
+
group_name: GroupName | None = None
|
|
49
|
+
run_name: RunName | None = None
|
|
50
|
+
aggregation: AnalysisAggregation
|
|
51
|
+
|
|
52
|
+
def __hash__(self) -> int:
|
|
53
|
+
"""Enable using AnalysisScope as a dictionary key.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Hash value based on all scope fields.
|
|
57
|
+
"""
|
|
58
|
+
return hash((self.target_name, self.group_name, self.run_name, self.aggregation))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
__all__ = ["AnalysisAggregation", "AnalysisScope"]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""Output models for analysis visualizations.
|
|
6
|
+
|
|
7
|
+
This module defines the output structures for generated visualizations,
|
|
8
|
+
including individual visualization outputs and aggregated analysis results.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import plotly.graph_objects as go
|
|
15
|
+
|
|
16
|
+
from openstef_beam.analysis.models.visualization_aggregation import AnalysisScope
|
|
17
|
+
from openstef_beam.evaluation.models import Filtering
|
|
18
|
+
from openstef_core.base_model import BaseModel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VisualizationOutput:
|
|
22
|
+
"""A generated visualization from evaluation data.
|
|
23
|
+
|
|
24
|
+
Represents a single chart, plot, or visual analysis that can be saved as HTML.
|
|
25
|
+
Contains either a Plotly figure object for interactive visualizations or raw
|
|
26
|
+
HTML content for static displays.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
Creating a visualization from a Plotly figure
|
|
30
|
+
|
|
31
|
+
>>> import plotly.graph_objects as go
|
|
32
|
+
>>> fig = go.Figure(data=go.Scatter(x=[1, 2, 3], y=[4, 5, 6]))
|
|
33
|
+
>>> viz = VisualizationOutput("my_chart", figure=fig)
|
|
34
|
+
>>> viz.get_file_name()
|
|
35
|
+
'my_chart.html'
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, name: str, figure: go.Figure | None = None, html: str | None = None):
|
|
39
|
+
"""Initialize visualization output with either a figure or HTML content.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
name: Name identifier for the visualization.
|
|
43
|
+
figure: Plotly figure object, if available.
|
|
44
|
+
html: Raw HTML string content, if available.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
ValueError: If neither figure nor html is provided.
|
|
48
|
+
"""
|
|
49
|
+
if figure is None and html is None:
|
|
50
|
+
raise ValueError("Either a plotly figure or an HTML string must be provided.")
|
|
51
|
+
self.name = name
|
|
52
|
+
self.figure = figure
|
|
53
|
+
self.html = html
|
|
54
|
+
|
|
55
|
+
def write_html(self, file_path: Path, **kwargs: dict[str, Any]) -> None:
|
|
56
|
+
"""Write the plotly figure or HTML string to an HTML file.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ValueError: If neither figure nor HTML content is available.
|
|
60
|
+
"""
|
|
61
|
+
if self.figure is not None:
|
|
62
|
+
self.figure.write_html(file_path, include_plotlyjs="cdn", **kwargs) # type: ignore[reportUnknownMemberType]
|
|
63
|
+
elif self.html is not None:
|
|
64
|
+
file_path.write_text(self.html, encoding="utf-8")
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError("No figure or HTML to write.")
|
|
67
|
+
|
|
68
|
+
def get_file_name(self) -> str:
|
|
69
|
+
"""Return the file name for the visualization."""
|
|
70
|
+
return f"{self.name}.html"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AnalysisOutput(BaseModel):
|
|
74
|
+
"""Container for analysis results from the benchmarking pipeline.
|
|
75
|
+
|
|
76
|
+
Holds all visualizations generated for a specific analysis scope, organized
|
|
77
|
+
by lead time filtering conditions. This allows comparing model performance
|
|
78
|
+
across different forecasting horizons (e.g., 1-hour vs 24-hour ahead predictions).
|
|
79
|
+
|
|
80
|
+
The output structure enables systematic organization of results from benchmark
|
|
81
|
+
runs, making it easy to generate reports that compare multiple models across
|
|
82
|
+
various lead times and targets.
|
|
83
|
+
|
|
84
|
+
Attributes:
|
|
85
|
+
scope: Analysis context defining what was analyzed (targets, runs, aggregation)
|
|
86
|
+
visualizations: Generated charts and plots grouped by lead time filtering
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
scope: AnalysisScope
|
|
90
|
+
visualizations: dict[Filtering, list[VisualizationOutput]]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__all__ = ["AnalysisOutput", "VisualizationOutput"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""Plotting components for generating visualizations from evaluation data.
|
|
6
|
+
|
|
7
|
+
This package provides specialized plotters for different types of analysis
|
|
8
|
+
visualizations, including time series, metrics, and statistical plots.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .forecast_time_series_plotter import ForecastTimeSeriesPlotter
|
|
12
|
+
from .grouped_target_metric_plotter import GroupedTargetMetricPlotter
|
|
13
|
+
from .precision_recall_curve_plotter import PrecisionRecallCurvePlotter
|
|
14
|
+
from .quantile_calibration_box_plotter import QuantileCalibrationBoxPlotter
|
|
15
|
+
from .quantile_probability_plotter import QuantileProbabilityPlotter
|
|
16
|
+
from .summary_table_plotter import SummaryTablePlotter
|
|
17
|
+
from .windowed_metric_plotter import WindowedMetricPlotter
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"ForecastTimeSeriesPlotter",
|
|
21
|
+
"GroupedTargetMetricPlotter",
|
|
22
|
+
"PrecisionRecallCurvePlotter",
|
|
23
|
+
"QuantileCalibrationBoxPlotter",
|
|
24
|
+
"QuantileProbabilityPlotter",
|
|
25
|
+
"SummaryTablePlotter",
|
|
26
|
+
"WindowedMetricPlotter",
|
|
27
|
+
]
|