openstef-models 4.0.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.
Files changed (161) hide show
  1. openstef_models-4.0.0/.gitignore +146 -0
  2. openstef_models-4.0.0/PKG-INFO +44 -0
  3. openstef_models-4.0.0/README.md +7 -0
  4. openstef_models-4.0.0/pyproject.toml +59 -0
  5. openstef_models-4.0.0/src/openstef_models/__init__.py +13 -0
  6. openstef_models-4.0.0/src/openstef_models/explainability/__init__.py +18 -0
  7. openstef_models-4.0.0/src/openstef_models/explainability/mixins.py +126 -0
  8. openstef_models-4.0.0/src/openstef_models/explainability/plotters/__init__.py +17 -0
  9. openstef_models-4.0.0/src/openstef_models/explainability/plotters/contributions_plotter.py +228 -0
  10. openstef_models-4.0.0/src/openstef_models/explainability/plotters/feature_importance_plotter.py +59 -0
  11. openstef_models-4.0.0/src/openstef_models/integrations/__init__.py +12 -0
  12. openstef_models-4.0.0/src/openstef_models/integrations/joblib/__init__.py +15 -0
  13. openstef_models-4.0.0/src/openstef_models/integrations/joblib/joblib_model_serializer.py +68 -0
  14. openstef_models-4.0.0/src/openstef_models/integrations/mlflow/__init__.py +26 -0
  15. openstef_models-4.0.0/src/openstef_models/integrations/mlflow/mlflow_storage.py +332 -0
  16. openstef_models-4.0.0/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py +383 -0
  17. openstef_models-4.0.0/src/openstef_models/integrations/optuna/__init__.py +23 -0
  18. openstef_models-4.0.0/src/openstef_models/integrations/optuna/tuner.py +354 -0
  19. openstef_models-4.0.0/src/openstef_models/mixins/__init__.py +14 -0
  20. openstef_models-4.0.0/src/openstef_models/mixins/callbacks.py +93 -0
  21. openstef_models-4.0.0/src/openstef_models/mixins/model_serializer.py +72 -0
  22. openstef_models-4.0.0/src/openstef_models/models/__init__.py +18 -0
  23. openstef_models-4.0.0/src/openstef_models/models/component_splitting/__init__.py +17 -0
  24. openstef_models-4.0.0/src/openstef_models/models/component_splitting/component_splitter.py +65 -0
  25. openstef_models-4.0.0/src/openstef_models/models/component_splitting/constant_component_splitter.py +152 -0
  26. openstef_models-4.0.0/src/openstef_models/models/component_splitting/linear_component_splitter.py +212 -0
  27. openstef_models-4.0.0/src/openstef_models/models/component_splitting/linear_component_splitter_model/linear_component_splitter_model.z +0 -0
  28. openstef_models-4.0.0/src/openstef_models/models/component_splitting/linear_component_splitter_model/linear_component_splitter_model.z.license +3 -0
  29. openstef_models-4.0.0/src/openstef_models/models/component_splitting_model.py +119 -0
  30. openstef_models-4.0.0/src/openstef_models/models/forecasting/__init__.py +9 -0
  31. openstef_models-4.0.0/src/openstef_models/models/forecasting/base_case_forecaster.py +177 -0
  32. openstef_models-4.0.0/src/openstef_models/models/forecasting/constant_quantile_forecaster.py +127 -0
  33. openstef_models-4.0.0/src/openstef_models/models/forecasting/flatliner_forecaster.py +111 -0
  34. openstef_models-4.0.0/src/openstef_models/models/forecasting/forecaster.py +140 -0
  35. openstef_models-4.0.0/src/openstef_models/models/forecasting/gblinear_forecaster.py +335 -0
  36. openstef_models-4.0.0/src/openstef_models/models/forecasting/lgbm_forecaster.py +342 -0
  37. openstef_models-4.0.0/src/openstef_models/models/forecasting/lgbmlinear_forecaster.py +344 -0
  38. openstef_models-4.0.0/src/openstef_models/models/forecasting/median_forecaster.py +320 -0
  39. openstef_models-4.0.0/src/openstef_models/models/forecasting/xgboost_forecaster.py +414 -0
  40. openstef_models-4.0.0/src/openstef_models/models/forecasting_model.py +609 -0
  41. openstef_models-4.0.0/src/openstef_models/presets/__init__.py +19 -0
  42. openstef_models-4.0.0/src/openstef_models/presets/forecasting_workflow.py +590 -0
  43. openstef_models-4.0.0/src/openstef_models/testing.py +98 -0
  44. openstef_models-4.0.0/src/openstef_models/transforms/__init__.py +26 -0
  45. openstef_models-4.0.0/src/openstef_models/transforms/energy_domain/__init__.py +14 -0
  46. openstef_models-4.0.0/src/openstef_models/transforms/energy_domain/wind_power_feature_adder.py +135 -0
  47. openstef_models-4.0.0/src/openstef_models/transforms/general/__init__.py +37 -0
  48. openstef_models-4.0.0/src/openstef_models/transforms/general/dimensionality_reducer.py +138 -0
  49. openstef_models-4.0.0/src/openstef_models/transforms/general/empty_feature_remover.py +120 -0
  50. openstef_models-4.0.0/src/openstef_models/transforms/general/flagger.py +97 -0
  51. openstef_models-4.0.0/src/openstef_models/transforms/general/imputer.py +278 -0
  52. openstef_models-4.0.0/src/openstef_models/transforms/general/nan_dropper.py +89 -0
  53. openstef_models-4.0.0/src/openstef_models/transforms/general/outlier_handler.py +178 -0
  54. openstef_models-4.0.0/src/openstef_models/transforms/general/sample_weighter.py +309 -0
  55. openstef_models-4.0.0/src/openstef_models/transforms/general/scaler.py +122 -0
  56. openstef_models-4.0.0/src/openstef_models/transforms/general/selector.py +83 -0
  57. openstef_models-4.0.0/src/openstef_models/transforms/general/shifter.py +130 -0
  58. openstef_models-4.0.0/src/openstef_models/transforms/postprocessing/__init__.py +16 -0
  59. openstef_models-4.0.0/src/openstef_models/transforms/postprocessing/confidence_interval_applicator.py +232 -0
  60. openstef_models-4.0.0/src/openstef_models/transforms/postprocessing/isotonic_quantile_calibrator.py +229 -0
  61. openstef_models-4.0.0/src/openstef_models/transforms/postprocessing/quantile_sorter.py +78 -0
  62. openstef_models-4.0.0/src/openstef_models/transforms/time_domain/__init__.py +29 -0
  63. openstef_models-4.0.0/src/openstef_models/transforms/time_domain/cyclic_features_adder.py +171 -0
  64. openstef_models-4.0.0/src/openstef_models/transforms/time_domain/datetime_features_adder.py +111 -0
  65. openstef_models-4.0.0/src/openstef_models/transforms/time_domain/holiday_features_adder.py +168 -0
  66. openstef_models-4.0.0/src/openstef_models/transforms/time_domain/lags_adder.py +388 -0
  67. openstef_models-4.0.0/src/openstef_models/transforms/time_domain/rolling_aggregates_adder.py +170 -0
  68. openstef_models-4.0.0/src/openstef_models/transforms/time_domain/versioned_lags_adder.py +139 -0
  69. openstef_models-4.0.0/src/openstef_models/transforms/validation/__init__.py +16 -0
  70. openstef_models-4.0.0/src/openstef_models/transforms/validation/completeness_checker.py +123 -0
  71. openstef_models-4.0.0/src/openstef_models/transforms/validation/flatline_checker.py +152 -0
  72. openstef_models-4.0.0/src/openstef_models/transforms/validation/input_consistency_checker.py +67 -0
  73. openstef_models-4.0.0/src/openstef_models/transforms/weather_domain/__init__.py +20 -0
  74. openstef_models-4.0.0/src/openstef_models/transforms/weather_domain/atmosphere_derived_features_adder.py +205 -0
  75. openstef_models-4.0.0/src/openstef_models/transforms/weather_domain/daylight_feature_adder.py +74 -0
  76. openstef_models-4.0.0/src/openstef_models/transforms/weather_domain/radiation_derived_features_adder.py +165 -0
  77. openstef_models-4.0.0/src/openstef_models/utils/__init__.py +9 -0
  78. openstef_models-4.0.0/src/openstef_models/utils/data_split.py +346 -0
  79. openstef_models-4.0.0/src/openstef_models/utils/evaluation_functions.py +30 -0
  80. openstef_models-4.0.0/src/openstef_models/utils/feature_selection.py +225 -0
  81. openstef_models-4.0.0/src/openstef_models/utils/loss_functions.py +256 -0
  82. openstef_models-4.0.0/src/openstef_models/utils/multi_quantile_regressor.py +157 -0
  83. openstef_models-4.0.0/src/openstef_models/utils/xgboost.py +45 -0
  84. openstef_models-4.0.0/src/openstef_models/workflows/__init__.py +16 -0
  85. openstef_models-4.0.0/src/openstef_models/workflows/callbacks/__init__.py +13 -0
  86. openstef_models-4.0.0/src/openstef_models/workflows/callbacks/data_save.py +108 -0
  87. openstef_models-4.0.0/src/openstef_models/workflows/callbacks/model_performance_callback.py +92 -0
  88. openstef_models-4.0.0/src/openstef_models/workflows/custom_component_split_workflow.py +154 -0
  89. openstef_models-4.0.0/src/openstef_models/workflows/custom_forecasting_workflow.py +206 -0
  90. openstef_models-4.0.0/tests/__init__.py +3 -0
  91. openstef_models-4.0.0/tests/integration/__init__.py +0 -0
  92. openstef_models-4.0.0/tests/integration/test_integration.py +17 -0
  93. openstef_models-4.0.0/tests/unit/__init__.py +0 -0
  94. openstef_models-4.0.0/tests/unit/explainability/__init__.py +0 -0
  95. openstef_models-4.0.0/tests/unit/explainability/plotters/__init__.py +0 -0
  96. openstef_models-4.0.0/tests/unit/explainability/plotters/test_contributions_plotter.py +219 -0
  97. openstef_models-4.0.0/tests/unit/integrations/__init__.py +5 -0
  98. openstef_models-4.0.0/tests/unit/integrations/joblib/__init__.py +5 -0
  99. openstef_models-4.0.0/tests/unit/integrations/joblib/test_joblib_model_serializer.py +36 -0
  100. openstef_models-4.0.0/tests/unit/integrations/mlflow/__init__.py +3 -0
  101. openstef_models-4.0.0/tests/unit/integrations/mlflow/test_mlflow_storage.py +192 -0
  102. openstef_models-4.0.0/tests/unit/integrations/mlflow/test_mlflow_storage_callback.py +291 -0
  103. openstef_models-4.0.0/tests/unit/integrations/optuna/__init__.py +0 -0
  104. openstef_models-4.0.0/tests/unit/integrations/optuna/test_tuner.py +304 -0
  105. openstef_models-4.0.0/tests/unit/models/__init__.py +0 -0
  106. openstef_models-4.0.0/tests/unit/models/component_splitting/__init__.py +3 -0
  107. openstef_models-4.0.0/tests/unit/models/component_splitting/test_constant_component_splitter.py +144 -0
  108. openstef_models-4.0.0/tests/unit/models/component_splitting/test_linear_component_splitter.py +92 -0
  109. openstef_models-4.0.0/tests/unit/models/forecasting/__init__.py +0 -0
  110. openstef_models-4.0.0/tests/unit/models/forecasting/conftest.py +59 -0
  111. openstef_models-4.0.0/tests/unit/models/forecasting/test_base_case_forecaster.py +264 -0
  112. openstef_models-4.0.0/tests/unit/models/forecasting/test_constant_quantile_forecaster.py +109 -0
  113. openstef_models-4.0.0/tests/unit/models/forecasting/test_flatliner_forecaster.py +66 -0
  114. openstef_models-4.0.0/tests/unit/models/forecasting/test_gblinear_forecaster.py +159 -0
  115. openstef_models-4.0.0/tests/unit/models/forecasting/test_lgbm_forecaster.py +173 -0
  116. openstef_models-4.0.0/tests/unit/models/forecasting/test_lgbmlinear_forecaster.py +147 -0
  117. openstef_models-4.0.0/tests/unit/models/forecasting/test_median_forecaster.py +460 -0
  118. openstef_models-4.0.0/tests/unit/models/forecasting/test_xgboost_forecaster.py +194 -0
  119. openstef_models-4.0.0/tests/unit/models/test_forecasting_model.py +301 -0
  120. openstef_models-4.0.0/tests/unit/test_example.py +7 -0
  121. openstef_models-4.0.0/tests/unit/transforms/__init__.py +0 -0
  122. openstef_models-4.0.0/tests/unit/transforms/energy_domain/__init__.py +0 -0
  123. openstef_models-4.0.0/tests/unit/transforms/energy_domain/test_wind_power_feature_adder.py +118 -0
  124. openstef_models-4.0.0/tests/unit/transforms/general/__init__.py +0 -0
  125. openstef_models-4.0.0/tests/unit/transforms/general/test_dimensionality_reducer.py +115 -0
  126. openstef_models-4.0.0/tests/unit/transforms/general/test_empty_feature_remover.py +148 -0
  127. openstef_models-4.0.0/tests/unit/transforms/general/test_flagger.py +61 -0
  128. openstef_models-4.0.0/tests/unit/transforms/general/test_imputer.py +396 -0
  129. openstef_models-4.0.0/tests/unit/transforms/general/test_nan_dropper.py +48 -0
  130. openstef_models-4.0.0/tests/unit/transforms/general/test_outlier_handler.py +234 -0
  131. openstef_models-4.0.0/tests/unit/transforms/general/test_sample_weighter.py +280 -0
  132. openstef_models-4.0.0/tests/unit/transforms/general/test_scaler.py +129 -0
  133. openstef_models-4.0.0/tests/unit/transforms/general/test_selector.py +82 -0
  134. openstef_models-4.0.0/tests/unit/transforms/general/test_shifter.py +174 -0
  135. openstef_models-4.0.0/tests/unit/transforms/postprocessing/__init__.py +5 -0
  136. openstef_models-4.0.0/tests/unit/transforms/postprocessing/test_confidence_interval_applicator.py +203 -0
  137. openstef_models-4.0.0/tests/unit/transforms/postprocessing/test_isotonic_quantile_calibrator.py +200 -0
  138. openstef_models-4.0.0/tests/unit/transforms/postprocessing/test_quantile_sorter.py +48 -0
  139. openstef_models-4.0.0/tests/unit/transforms/time_domain/__init__.py +0 -0
  140. openstef_models-4.0.0/tests/unit/transforms/time_domain/test_cyclic_features_adder.py +117 -0
  141. openstef_models-4.0.0/tests/unit/transforms/time_domain/test_datetime_features_adder.py +118 -0
  142. openstef_models-4.0.0/tests/unit/transforms/time_domain/test_holiday_features_adder.py +155 -0
  143. openstef_models-4.0.0/tests/unit/transforms/time_domain/test_lags_adder.py +535 -0
  144. openstef_models-4.0.0/tests/unit/transforms/time_domain/test_rolling_aggregates_adder.py +242 -0
  145. openstef_models-4.0.0/tests/unit/transforms/time_domain/test_versioned_lags_adder.py +225 -0
  146. openstef_models-4.0.0/tests/unit/transforms/validation/__init__.py +0 -0
  147. openstef_models-4.0.0/tests/unit/transforms/validation/test_completeness_checker.py +169 -0
  148. openstef_models-4.0.0/tests/unit/transforms/validation/test_flatline_checker.py +149 -0
  149. openstef_models-4.0.0/tests/unit/transforms/validation/test_input_consistency_checker.py +81 -0
  150. openstef_models-4.0.0/tests/unit/transforms/weather_domain/__init__.py +0 -0
  151. openstef_models-4.0.0/tests/unit/transforms/weather_domain/test_atmosphere_derived_features_adder.py +113 -0
  152. openstef_models-4.0.0/tests/unit/transforms/weather_domain/test_daylight_feature_adder.py +102 -0
  153. openstef_models-4.0.0/tests/unit/transforms/weather_domain/test_radiation_derived_featuers_adder.py +343 -0
  154. openstef_models-4.0.0/tests/unit/utils/__init__.py +3 -0
  155. openstef_models-4.0.0/tests/unit/utils/test_data_split.py +162 -0
  156. openstef_models-4.0.0/tests/unit/utils/test_feature_selection.py +125 -0
  157. openstef_models-4.0.0/tests/unit/utils/test_loss_functions.py +416 -0
  158. openstef_models-4.0.0/tests/unit/utils/test_multi_quantile_regressor.py +118 -0
  159. openstef_models-4.0.0/tests/unit/workflows/__init__.py +0 -0
  160. openstef_models-4.0.0/tests/unit/workflows/callbacks/__init__.py +0 -0
  161. openstef_models-4.0.0/tests/unit/workflows/callbacks/test_model_performance_callback.py +83 -0
@@ -0,0 +1,146 @@
1
+ # SPDX-FileCopyrightText: 2017-2025 Contributors to the OpenSTEF project <openstef@lfenergy.org> # noqa E501>
2
+ # SPDX-License-Identifier: MPL-2.0
3
+
4
+ # Core
5
+ config.user.yaml
6
+ git-template
7
+ .DS_Store
8
+ tmp
9
+
10
+ # Python bytecode
11
+ __pycache__/
12
+ *.py[cod]
13
+ *$py.class
14
+
15
+ # C extensions
16
+ *.so
17
+
18
+ # Packaging and build artifacts
19
+ .Python
20
+ build/
21
+ dist/
22
+ downloads/
23
+ wheels/
24
+ share/python-wheels/
25
+ sdist/
26
+ .eggs/
27
+ *.egg-info/
28
+ *.egg
29
+ .installed.cfg
30
+ MANIFEST
31
+
32
+ # uv
33
+ .uv/
34
+
35
+ # Ruff
36
+ .ruff_cache/
37
+
38
+ # Pyright
39
+ .pyright/
40
+ # pyright-report/
41
+
42
+ # Test, coverage, tox
43
+ .pytest_cache/
44
+ .coverage
45
+ .coverage.*
46
+ htmlcov/
47
+ .cover/
48
+ cover/
49
+ pytest-report.xml
50
+ .tox/
51
+ .nox/
52
+ coverage.xml
53
+
54
+ # Hypothesis
55
+ .hypothesis/
56
+
57
+ # Cython
58
+ cython_debug/
59
+
60
+ # Type checkers (misc)
61
+ .mypy_cache/
62
+ .dmypy.json
63
+ dmypy.json
64
+ .pytype/
65
+ .pyre/
66
+
67
+ # Sphinx
68
+ docs/_build/
69
+ docs/source/api/generated/
70
+ docs/source/tutorials/
71
+ docs/source/benchmarks/
72
+ docs/source/user_guide/**/quick_start_tutorial.py
73
+ docs/source/user_guide/**/feature_engineering_tutorial.py
74
+ docs/source/user_guide/**/datasets_tutorial.py
75
+ docs/source/user_guide/**/backtesting_tutorial.py
76
+
77
+ # docs/_doctrees/
78
+ # docs/_static_gen/
79
+
80
+ # Editors: JetBrains
81
+ .idea/
82
+
83
+ # Editors: VS Code (allow shared configs)
84
+ .vscode/*
85
+
86
+ # Jupyter / IPython
87
+ .ipynb_checkpoints
88
+ profile_default/
89
+ ipython_config.py
90
+
91
+ # Spyder / Rope
92
+ .spyderproject
93
+ .spyproject
94
+ .ropeproject
95
+
96
+ # Environments
97
+ .env
98
+ .venv
99
+ env/
100
+ venv/
101
+ ENV/
102
+ env.bak/
103
+ venv.bak/
104
+
105
+ # PEP 582
106
+ __pypackages__/
107
+
108
+ # web frameworks / services
109
+ *.sqlite
110
+ *.sqlite3
111
+ *.log
112
+ instance/
113
+ .webassets-cache
114
+ celerybeat-schedule
115
+ celerybeat.pid
116
+ *.sage.py
117
+ tmp/
118
+
119
+ # PyInstaller
120
+ *.manifest
121
+ *.spec
122
+
123
+ # Project outputs
124
+ output/
125
+ prof/
126
+ certificates/
127
+
128
+ # Output artifacts
129
+ *.html
130
+ *.pkl
131
+
132
+ # Benchmark outputs
133
+ benchmark_results*/
134
+
135
+ # Local dataset files
136
+ liander_dataset/
137
+
138
+ # Mlflow
139
+ /mlflow
140
+ /mlflow_artifacts_local
141
+
142
+ .github/instructions
143
+
144
+ # Jupyter notebook cache (myst-nb execution outputs)
145
+ .jupyter_cache/
146
+ docs/build.zip
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: openstef-models
3
+ Version: 4.0.0
4
+ Summary: Core models for OpenSTEF
5
+ Project-URL: Documentation, https://openstef.github.io/openstef/index.html
6
+ Project-URL: Homepage, https://lfenergy.org/projects/openstef/
7
+ Project-URL: Issues, https://github.com/OpenSTEF/openstef/issues
8
+ Project-URL: Repository, https://github.com/OpenSTEF/openstef
9
+ Author-email: "Alliander N.V" <openstef@lfenergy.org>
10
+ License-Expression: MPL-2.0
11
+ Keywords: energy,forecasting,machinelearning
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Requires-Python: <4.0,>=3.12
19
+ Requires-Dist: holidays>=0.79
20
+ Requires-Dist: mlflow-skinny<4,>=3
21
+ Requires-Dist: openstef-beam<5,>=4.0.0.dev0
22
+ Requires-Dist: openstef-core<5,>=4.0.0.dev0
23
+ Requires-Dist: pvlib>=0.13
24
+ Requires-Dist: pycountry>=24.6.1
25
+ Requires-Dist: scikit-learn<1.8,>=1.7.1
26
+ Requires-Dist: scipy<2,>=1.16.3
27
+ Provides-Extra: lgbm
28
+ Requires-Dist: lightgbm>=4.6; extra == 'lgbm'
29
+ Provides-Extra: tuning
30
+ Requires-Dist: optuna>=4.7; extra == 'tuning'
31
+ Provides-Extra: xgb-cpu
32
+ Requires-Dist: xgboost-cpu<4,>=3; (sys_platform == 'linux' or sys_platform == 'win32') and extra == 'xgb-cpu'
33
+ Requires-Dist: xgboost<4,>=3; (sys_platform == 'darwin') and extra == 'xgb-cpu'
34
+ Provides-Extra: xgb-gpu
35
+ Requires-Dist: xgboost<4,>=3; extra == 'xgb-gpu'
36
+ Description-Content-Type: text/markdown
37
+
38
+ <!--
39
+ SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
40
+
41
+ SPDX-License-Identifier: MPL-2.0
42
+ -->
43
+
44
+ # openstef-model
@@ -0,0 +1,7 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
3
+
4
+ SPDX-License-Identifier: MPL-2.0
5
+ -->
6
+
7
+ # openstef-model
@@ -0,0 +1,59 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ [build-system]
6
+ build-backend = "hatchling.build"
7
+
8
+ requires = [ "hatchling" ]
9
+
10
+ [project]
11
+ name = "openstef-models"
12
+ version = "4.0.0"
13
+ description = "Core models for OpenSTEF"
14
+ readme = "README.md"
15
+ keywords = [ "energy", "forecasting", "machinelearning" ]
16
+ license = "MPL-2.0"
17
+ authors = [
18
+ { name = "Alliander N.V", email = "openstef@lfenergy.org" },
19
+ ]
20
+ requires-python = ">=3.12,<4.0"
21
+ classifiers = [
22
+ "Development Status :: 5 - Production/Stable",
23
+ "Intended Audience :: Developers",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ ]
29
+
30
+ dependencies = [
31
+ "holidays>=0.79",
32
+ "mlflow-skinny>=3,<4",
33
+ "openstef-beam>=4.0.0.dev0,<5",
34
+ "openstef-core>=4.0.0.dev0,<5",
35
+ "pvlib>=0.13",
36
+ "pycountry>=24.6.1",
37
+ "scikit-learn>=1.7.1,<1.8",
38
+ "scipy>=1.16.3,<2",
39
+ ]
40
+
41
+ optional-dependencies.lgbm = [
42
+ "lightgbm>=4.6",
43
+ ]
44
+
45
+ optional-dependencies.tuning = [ "optuna>=4.7" ]
46
+
47
+ optional-dependencies.xgb-cpu = [
48
+ "xgboost>=3,<4; sys_platform=='darwin'",
49
+ "xgboost-cpu>=3,<4; sys_platform=='linux' or sys_platform=='win32'",
50
+ ]
51
+
52
+ optional-dependencies.xgb-gpu = [ "xgboost>=3,<4" ]
53
+ urls.Documentation = "https://openstef.github.io/openstef/index.html"
54
+ urls.Homepage = "https://lfenergy.org/projects/openstef/"
55
+ urls.Issues = "https://github.com/OpenSTEF/openstef/issues"
56
+ urls.Repository = "https://github.com/OpenSTEF/openstef"
57
+
58
+ [tool.hatch.build.targets.wheel]
59
+ packages = [ "src/openstef_models" ]
@@ -0,0 +1,13 @@
1
+ # SPDX-FileCopyrightText: 2017-2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ """Core models for OpenSTEF."""
5
+
6
+ import logging
7
+
8
+ # Set up logging configuration
9
+ root_logger = logging.getLogger(name=__name__)
10
+ if not root_logger.handlers:
11
+ root_logger.addHandler(logging.NullHandler())
12
+
13
+ __all__ = []
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Explainability utilities for OpenSTEF.
6
+
7
+ Tools for feature importance, attribution and model interpretation.
8
+ """
9
+
10
+ from .mixins import ContributionsMixin, ExplainableForecaster
11
+ from .plotters import ContributionsPlotter, FeatureImportancePlotter
12
+
13
+ __all__ = [
14
+ "ContributionsMixin",
15
+ "ContributionsPlotter",
16
+ "ExplainableForecaster",
17
+ "FeatureImportancePlotter",
18
+ ]
@@ -0,0 +1,126 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Mixins for adding explainability features to forecasting models.
6
+
7
+ Provides base classes that enable models to expose feature importance scores
8
+ and generate visualization plots.
9
+ """
10
+
11
+ from abc import ABC, abstractmethod
12
+ from typing import Any, Literal
13
+
14
+ import pandas as pd
15
+ import plotly.graph_objects as go
16
+
17
+ from openstef_core.datasets import ForecastInputDataset, TimeSeriesDataset
18
+ from openstef_core.types import Q, Quantile
19
+ from openstef_models.explainability.plotters.contributions_plotter import ContributionsPlotter
20
+ from openstef_models.explainability.plotters.feature_importance_plotter import FeatureImportancePlotter
21
+
22
+
23
+ class ExplainableForecaster(ABC):
24
+ """Mixin for forecasters that can explain feature importance.
25
+
26
+ Provides a standardized interface for accessing and visualizing feature
27
+ importance scores across different forecasting models.
28
+ """
29
+
30
+ @property
31
+ @abstractmethod
32
+ def feature_importances(self) -> pd.DataFrame:
33
+ """Get feature importance scores for this model.
34
+
35
+ Returns DataFrame with feature names as index and quantiles as columns.
36
+ Each quantile represents the importance distribution across multiple
37
+ model training runs or folds.
38
+
39
+ Returns:
40
+ DataFrame with feature names as index and quantile columns.
41
+ Values represent normalized importance scores summing to 1.0.
42
+
43
+ Note:
44
+ The returned DataFrame must have feature names as index and quantile
45
+ columns in format 'quantile_PXX' (e.g., 'quantile_P50', 'quantile_P95').
46
+ All quantile values must be between 0 and 1.
47
+ """
48
+ raise NotImplementedError
49
+
50
+ def plot_feature_importances(self, quantile: Quantile = Q(0.5)) -> go.Figure:
51
+ """Create interactive treemap visualization of feature importances.
52
+
53
+ Args:
54
+ quantile: Which quantile of importance scores to display.
55
+ Defaults to median (0.5).
56
+
57
+ Returns:
58
+ Plotly Figure containing treemap with feature importance scores.
59
+ Color intensity indicates relative importance of each feature.
60
+ """
61
+ return FeatureImportancePlotter().plot(scores=self.feature_importances, quantile=quantile)
62
+
63
+
64
+ class ContributionsMixin(ABC):
65
+ """Mixin for forecasters that can explain per-sample feature contributions.
66
+
67
+ Unlike ``ExplainableForecaster`` which provides aggregate feature importance,
68
+ this mixin provides per-sample decomposition of predictions — i.e., how
69
+ much each feature contributed to the prediction for each individual sample.
70
+
71
+ For tree-based models (XGBoost), this corresponds to SHAP TreeExplainer values.
72
+ For linear models (GBLinear), this is the coefficient x feature value decomposition.
73
+ For ensembles, this shows each base model's contribution weight.
74
+ """
75
+
76
+ @abstractmethod
77
+ def predict_contributions(self, data: ForecastInputDataset) -> TimeSeriesDataset:
78
+ """Compute per-sample feature contributions for the given input data.
79
+
80
+ Returns a TimeSeriesDataset where columns are feature names (or model
81
+ names for ensemble contributions) and rows correspond to the same time
82
+ index as the input. Values represent the additive contribution of each
83
+ feature to the prediction at that timestep.
84
+
85
+ Args:
86
+ data: Preprocessed input data (same format as ``predict()`` takes).
87
+
88
+ Returns:
89
+ TimeSeriesDataset with feature contributions. Columns are features,
90
+ rows are timesteps. A ``bias`` column may be included for the
91
+ model intercept/base value.
92
+ """
93
+
94
+ def plot_contributions(
95
+ self,
96
+ data: ForecastInputDataset,
97
+ kind: Literal["heatmap", "waterfall", "bar"] = "heatmap",
98
+ **kwargs: Any,
99
+ ) -> go.Figure:
100
+ """Plot per-sample feature contributions.
101
+
102
+ Calls ``predict_contributions()`` and visualizes the result using the
103
+ requested chart type.
104
+
105
+ Args:
106
+ data: Preprocessed input data.
107
+ kind: Chart type — ``"heatmap"``, ``"waterfall"``, or ``"bar"``.
108
+ **kwargs: Forwarded to the corresponding plotter method
109
+ (e.g. ``top_n``, ``timestep``).
110
+
111
+ Returns:
112
+ Plotly Figure.
113
+
114
+ Raises:
115
+ ValueError: If *kind* is not one of the supported chart types.
116
+ """
117
+ contributions = self.predict_contributions(data)
118
+ plotters = {
119
+ "heatmap": ContributionsPlotter.plot_heatmap,
120
+ "waterfall": ContributionsPlotter.plot_waterfall,
121
+ "bar": ContributionsPlotter.plot_bar,
122
+ }
123
+ if kind not in plotters:
124
+ msg = f"Unknown plot kind {kind!r}. Choose from {list(plotters)}"
125
+ raise ValueError(msg)
126
+ return plotters[kind](contributions=contributions, **kwargs)
@@ -0,0 +1,17 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Visualization tools for model explainability.
6
+
7
+ Provides plotters for creating interactive visualizations of feature importance
8
+ scores and other model explanation outputs.
9
+ """
10
+
11
+ from .contributions_plotter import ContributionsPlotter
12
+ from .feature_importance_plotter import FeatureImportancePlotter
13
+
14
+ __all__ = [
15
+ "ContributionsPlotter",
16
+ "FeatureImportancePlotter",
17
+ ]
@@ -0,0 +1,228 @@
1
+ # SPDX-FileCopyrightText: 2026 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Visualizations for per-sample feature contributions (SHAP values)."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ import plotly.graph_objects as go
12
+ from plotly.subplots import make_subplots # pyright: ignore[reportUnknownVariableType]
13
+
14
+ from openstef_core.datasets import TimeSeriesDataset # noqa: TC001 # runtime needed for pyright
15
+
16
+ if TYPE_CHECKING:
17
+ import pandas as pd
18
+
19
+
20
+ class ContributionsPlotter:
21
+ """Visualizations for per-timestep feature contributions."""
22
+
23
+ @staticmethod
24
+ def plot_heatmap(
25
+ contributions: TimeSeriesDataset,
26
+ top_n: int = 10,
27
+ target_column: str = "load",
28
+ bias_column: str = "bias",
29
+ *,
30
+ show_prediction: bool = True,
31
+ ) -> go.Figure:
32
+ """Create an interactive heatmap of feature contributions over time.
33
+
34
+ X-axis is the prediction datetime, Y-axis shows feature names ranked by mean absolute contribution
35
+ (most important at top). Color ranges from blue (negative) through white (zero) to red (positive).
36
+ When ``show_prediction`` is True a line plot of the model prediction (sum of contributions + bias)
37
+ is shown above the heatmap.
38
+
39
+ Args:
40
+ contributions: Output of ``predict_contributions()``.
41
+ top_n: Number of top features to show (ranked by mean absolute contribution).
42
+ target_column: Name of the target column to exclude. Default "load".
43
+ bias_column: Name of the bias column. Default "bias".
44
+ show_prediction: If True, add a prediction line subplot above the heatmap. Default True.
45
+
46
+ Returns:
47
+ Plotly Figure with a diverging heatmap centered at zero (and optional prediction line).
48
+ """
49
+ bias = contributions.data[bias_column] if bias_column in contributions.data.columns else None
50
+ cols_to_drop = [c for c in [target_column, bias_column] if c in contributions.data.columns]
51
+ df = contributions.data.drop(columns=cols_to_drop)
52
+ ranked: list[str] = df.abs().mean().sort_values(ascending=False).head(top_n).index.tolist()
53
+
54
+ # Most-important feature at top of Y-axis
55
+ y_labels = list(reversed(ranked))
56
+
57
+ heatmap = go.Heatmap(
58
+ z=df[y_labels].T.values,
59
+ x=df.index,
60
+ y=y_labels,
61
+ colorscale="RdBu_r",
62
+ zmid=0,
63
+ colorbar={"title": "Contribution"},
64
+ showlegend=False,
65
+ )
66
+
67
+ if show_prediction:
68
+ prediction = df.sum(axis=1)
69
+ if bias is not None:
70
+ prediction += bias
71
+
72
+ fig = make_subplots(
73
+ rows=2,
74
+ cols=1,
75
+ shared_xaxes=True,
76
+ row_heights=[0.2, 0.8],
77
+ vertical_spacing=0.03,
78
+ )
79
+
80
+ fig.add_trace( # pyright: ignore[reportUnknownMemberType]
81
+ go.Scatter(
82
+ x=df.index,
83
+ y=prediction,
84
+ mode="lines",
85
+ name="Prediction",
86
+ line={"color": "black", "width": 1.5},
87
+ showlegend=False,
88
+ ),
89
+ row=1,
90
+ col=1,
91
+ )
92
+ fig.add_trace(heatmap, row=2, col=1) # pyright: ignore[reportUnknownMemberType]
93
+
94
+ fig.update_layout( # pyright: ignore[reportUnknownMemberType]
95
+ yaxis_title="Prediction",
96
+ yaxis2_title="Feature",
97
+ xaxis2_title="Time",
98
+ margin={"t": 30, "r": 10, "b": 40, "l": 120},
99
+ )
100
+ else:
101
+ fig = go.Figure(
102
+ data=heatmap,
103
+ layout={
104
+ "xaxis_title": "Time",
105
+ "yaxis_title": "Feature",
106
+ "margin": {"t": 30, "r": 10, "b": 40, "l": 120},
107
+ },
108
+ )
109
+
110
+ return fig
111
+
112
+ @staticmethod
113
+ def plot_waterfall(
114
+ contributions: TimeSeriesDataset,
115
+ timestep: int = 0,
116
+ top_n: int = 10,
117
+ target_column: str = "load",
118
+ bias_column: str = "bias",
119
+ ) -> go.Figure:
120
+ """Create a waterfall chart decomposing a single timestep's prediction.
121
+
122
+ Shows how the bias (base value) is pushed up or down by each feature's
123
+ contribution to arrive at the final prediction.
124
+
125
+ Args:
126
+ contributions: Output of ``predict_contributions()``.
127
+ timestep: Row index (0-based) of the timestep to explain.
128
+ top_n: Number of top features to show. Remaining features are
129
+ aggregated into an "other" bar.
130
+ target_column: Name of the target column to exclude. Default "load".
131
+ bias_column: Name of the bias column used as base value. Default "bias".
132
+
133
+ Returns:
134
+ Plotly Figure with waterfall chart.
135
+ """
136
+ bias = contributions.data[bias_column] if bias_column in contributions.data.columns else None
137
+ cols_to_drop = [c for c in [target_column, bias_column] if c in contributions.data.columns]
138
+ df = contributions.data.drop(columns=cols_to_drop)
139
+ row = df.iloc[timestep]
140
+ base_value = float(bias.iloc[timestep]) if bias is not None else 0.0
141
+
142
+ # Rank by |contribution| for this specific timestep
143
+ abs_sorted = row.abs().sort_values(ascending=False)
144
+ top = abs_sorted.head(top_n).index.tolist()
145
+ remaining = [c for c in abs_sorted.index if c not in top]
146
+
147
+ names: list[str] = [bias_column]
148
+ values: list[float] = [base_value]
149
+ measures: list[str] = ["absolute"]
150
+
151
+ for feat in top:
152
+ names.append(feat)
153
+ values.append(float(row[feat])) # pyright: ignore[reportArgumentType]
154
+ measures.append("relative")
155
+
156
+ if len(remaining) > 0:
157
+ other_sum = float(row[remaining].sum())
158
+ names.append(f"other ({len(remaining)})")
159
+ values.append(other_sum)
160
+ measures.append("relative")
161
+
162
+ names.append("Prediction")
163
+ values.append(base_value + float(row.sum()))
164
+ measures.append("total")
165
+
166
+ timestamp = contributions.data.index[timestep]
167
+ return go.Figure(
168
+ go.Waterfall(
169
+ x=names,
170
+ y=values,
171
+ measure=measures,
172
+ connector={"line": {"color": "grey", "width": 0.5}},
173
+ increasing={"marker": {"color": "#ff4136"}},
174
+ decreasing={"marker": {"color": "#0074d9"}},
175
+ totals={"marker": {"color": "#2ecc40"}},
176
+ textposition="outside",
177
+ text=[f"{v:+.4f}" if m == "relative" else f"{v:.4f}" for v, m in zip(values, measures, strict=True)],
178
+ ),
179
+ layout={
180
+ "title": f"Contributions at {timestamp}",
181
+ "yaxis_title": "Contribution",
182
+ "margin": {"t": 50, "r": 10, "b": 40, "l": 60},
183
+ "showlegend": False,
184
+ },
185
+ )
186
+
187
+ @staticmethod
188
+ def plot_bar(
189
+ contributions: TimeSeriesDataset,
190
+ top_n: int = 10,
191
+ target_column: str = "load",
192
+ bias_column: str = "bias",
193
+ ) -> go.Figure:
194
+ """Create a horizontal bar chart of mean absolute contributions per feature.
195
+
196
+ Features are ranked from most to least important (top to bottom).
197
+
198
+ Args:
199
+ contributions: Output of ``predict_contributions()``.
200
+ top_n: Number of top features to show.
201
+ target_column: Name of the target column to exclude. Default "load".
202
+ bias_column: Name of the bias column to exclude. Default "bias".
203
+
204
+ Returns:
205
+ Plotly Figure with horizontal bar chart.
206
+ """
207
+ cols_to_drop = [c for c in [target_column, bias_column] if c in contributions.data.columns]
208
+ df = contributions.data.drop(columns=cols_to_drop)
209
+ mean_abs: pd.Series = df.abs().mean().sort_values(ascending=False).head(top_n)
210
+
211
+ # Reverse for plotly (bottom-to-top rendering)
212
+ mean_abs = mean_abs.iloc[::-1]
213
+
214
+ return go.Figure(
215
+ go.Bar(
216
+ x=mean_abs.values, # pyright: ignore[reportArgumentType]
217
+ y=mean_abs.index.tolist(),
218
+ orientation="h",
219
+ marker_color="#1f77b4",
220
+ hovertemplate="<b>%{y}</b><br>mean |SHAP|: %{x:.4f}<extra></extra>",
221
+ ),
222
+ layout={
223
+ "xaxis_title": "mean |SHAP value|",
224
+ "yaxis_title": "Feature",
225
+ "margin": {"t": 30, "r": 10, "b": 40, "l": 120},
226
+ "showlegend": False,
227
+ },
228
+ )