openstef-models 4.0.1__tar.gz → 4.1.1.dev0__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.
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/.gitignore +5 -4
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/PKG-INFO +3 -3
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/pyproject.toml +15 -12
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/plotters/contributions_plotter.py +7 -7
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/__init__.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/joblib/joblib_model_serializer.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/mlflow/mlflow_storage.py +8 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py +5 -5
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/optuna/tuner.py +5 -4
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/constant_component_splitter.py +2 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/linear_component_splitter.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/base_case_forecaster.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/constant_quantile_forecaster.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/gblinear_forecaster.py +3 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/lgbm_forecaster.py +5 -3
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/lgbmlinear_forecaster.py +4 -3
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/median_forecaster.py +5 -7
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/xgboost_forecaster.py +3 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting_model.py +75 -6
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/presets/forecasting_workflow.py +6 -4
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/dimensionality_reducer.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/empty_feature_remover.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/imputer.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/nan_dropper.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/sample_weighter.py +1 -25
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/scaler.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/shifter.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/postprocessing/confidence_interval_applicator.py +4 -4
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/postprocessing/isotonic_quantile_calibrator.py +3 -3
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/cyclic_features_adder.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/datetime_features_adder.py +15 -9
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/holiday_features_adder.py +7 -5
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/lags_adder.py +3 -12
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/rolling_aggregates_adder.py +7 -11
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/versioned_lags_adder.py +7 -3
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/validation/flatline_checker.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/weather_domain/atmosphere_derived_features_adder.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/weather_domain/daylight_feature_adder.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/weather_domain/radiation_derived_features_adder.py +5 -5
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/data_split.py +5 -5
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/evaluation_functions.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/feature_selection.py +1 -12
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/loss_functions.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/multi_quantile_regressor.py +4 -4
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/custom_forecasting_workflow.py +61 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/explainability/plotters/test_contributions_plotter.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/mlflow/test_mlflow_storage.py +29 -9
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/mlflow/test_mlflow_storage_callback.py +6 -5
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/optuna/test_tuner.py +11 -9
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_base_case_forecaster.py +20 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_constant_quantile_forecaster.py +26 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_lgbm_forecaster.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_lgbmlinear_forecaster.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_median_forecaster.py +6 -6
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/test_forecasting_model.py +163 -4
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_imputer.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_outlier_handler.py +3 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_scaler.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/postprocessing/test_isotonic_quantile_calibrator.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_datetime_features_adder.py +29 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_rolling_aggregates_adder.py +42 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_versioned_lags_adder.py +53 -43
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/test_data_split.py +1 -1
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/test_multi_quantile_regressor.py +2 -2
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/workflows/callbacks/test_model_performance_callback.py +12 -6
- openstef_models-4.1.1.dev0/tests/unit/workflows/test_custom_forecasting_workflow.py +134 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/README.md +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/mixins.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/plotters/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/plotters/feature_importance_plotter.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/joblib/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/mlflow/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/optuna/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/mixins/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/mixins/callbacks.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/mixins/model_serializer.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/component_splitter.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/linear_component_splitter_model/linear_component_splitter_model.z +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/linear_component_splitter_model/linear_component_splitter_model.z.license +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting_model.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/flatliner_forecaster.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/forecaster.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/presets/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/testing.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/energy_domain/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/energy_domain/wind_power_feature_adder.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/flagger.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/outlier_handler.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/selector.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/postprocessing/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/postprocessing/quantile_sorter.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/validation/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/validation/completeness_checker.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/validation/input_consistency_checker.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/weather_domain/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/xgboost.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/callbacks/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/callbacks/data_save.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/callbacks/model_performance_callback.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/custom_component_split_workflow.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/integration/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/integration/test_integration.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/explainability/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/explainability/plotters/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/joblib/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/joblib/test_joblib_model_serializer.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/mlflow/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/optuna/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/component_splitting/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/component_splitting/test_constant_component_splitter.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/component_splitting/test_linear_component_splitter.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/conftest.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_flatliner_forecaster.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_gblinear_forecaster.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_xgboost_forecaster.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/test_example.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/energy_domain/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/energy_domain/test_wind_power_feature_adder.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_dimensionality_reducer.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_empty_feature_remover.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_flagger.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_nan_dropper.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_sample_weighter.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_selector.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_shifter.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/postprocessing/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/postprocessing/test_confidence_interval_applicator.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/postprocessing/test_quantile_sorter.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_cyclic_features_adder.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_holiday_features_adder.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_lags_adder.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/validation/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/validation/test_completeness_checker.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/validation/test_flatline_checker.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/validation/test_input_consistency_checker.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/weather_domain/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/weather_domain/test_atmosphere_derived_features_adder.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/weather_domain/test_daylight_feature_adder.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/weather_domain/test_radiation_derived_featuers_adder.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/test_feature_selection.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/test_loss_functions.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/workflows/__init__.py +0 -0
- {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/workflows/callbacks/__init__.py +0 -0
|
@@ -35,10 +35,6 @@ MANIFEST
|
|
|
35
35
|
# Ruff
|
|
36
36
|
.ruff_cache/
|
|
37
37
|
|
|
38
|
-
# Pyright
|
|
39
|
-
.pyright/
|
|
40
|
-
# pyright-report/
|
|
41
|
-
|
|
42
38
|
# Test, coverage, tox
|
|
43
39
|
.pytest_cache/
|
|
44
40
|
.coverage
|
|
@@ -69,6 +65,8 @@ docs/_build/
|
|
|
69
65
|
docs/source/api/generated/
|
|
70
66
|
docs/source/tutorials/
|
|
71
67
|
docs/source/benchmarks/
|
|
68
|
+
# Community health files materialized from OpenSTEF/.github at build time
|
|
69
|
+
docs/source/contribute/_community/
|
|
72
70
|
docs/source/user_guide/**/quick_start_tutorial.py
|
|
73
71
|
docs/source/user_guide/**/feature_engineering_tutorial.py
|
|
74
72
|
docs/source/user_guide/**/datasets_tutorial.py
|
|
@@ -135,6 +133,9 @@ benchmark_results*/
|
|
|
135
133
|
# Local dataset files
|
|
136
134
|
liander_dataset/
|
|
137
135
|
|
|
136
|
+
# Deployment example run artifacts (MLflow store, forecasts, dataset, Celery/Airflow state)
|
|
137
|
+
openstef_deployment_runs/
|
|
138
|
+
|
|
138
139
|
# Mlflow
|
|
139
140
|
/mlflow
|
|
140
141
|
/mlflow_artifacts_local
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openstef-models
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.1.1.dev0
|
|
4
4
|
Summary: Core models for OpenSTEF
|
|
5
5
|
Project-URL: Documentation, https://openstef.github.io/openstef/index.html
|
|
6
6
|
Project-URL: Homepage, https://lfenergy.org/projects/openstef/
|
|
@@ -18,8 +18,8 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
18
18
|
Requires-Python: <4.0,>=3.12
|
|
19
19
|
Requires-Dist: holidays>=0.79
|
|
20
20
|
Requires-Dist: mlflow-skinny<4,>=3
|
|
21
|
-
Requires-Dist: openstef-beam<5,>=4
|
|
22
|
-
Requires-Dist: openstef-core<5,>=4
|
|
21
|
+
Requires-Dist: openstef-beam<5,>=4
|
|
22
|
+
Requires-Dist: openstef-core<5,>=4
|
|
23
23
|
Requires-Dist: pvlib>=0.13
|
|
24
24
|
Requires-Dist: pycountry>=24.6.1
|
|
25
25
|
Requires-Dist: scikit-learn<1.8,>=1.7.1
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
|
|
5
4
|
[build-system]
|
|
6
5
|
build-backend = "hatchling.build"
|
|
7
|
-
|
|
8
6
|
requires = [ "hatchling" ]
|
|
9
7
|
|
|
10
8
|
[project]
|
|
11
9
|
name = "openstef-models"
|
|
12
|
-
version = "4.
|
|
10
|
+
version = "4.1.1.dev0"
|
|
13
11
|
description = "Core models for OpenSTEF"
|
|
14
12
|
readme = "README.md"
|
|
15
13
|
keywords = [ "energy", "forecasting", "machinelearning" ]
|
|
@@ -26,34 +24,39 @@ classifiers = [
|
|
|
26
24
|
"Programming Language :: Python :: 3.13",
|
|
27
25
|
"Programming Language :: Python :: 3.14",
|
|
28
26
|
]
|
|
29
|
-
|
|
30
27
|
dependencies = [
|
|
31
28
|
"holidays>=0.79",
|
|
32
29
|
"mlflow-skinny>=3,<4",
|
|
33
|
-
"openstef-beam>=4
|
|
34
|
-
"openstef-core>=4
|
|
30
|
+
"openstef-beam>=4,<5",
|
|
31
|
+
"openstef-core>=4,<5",
|
|
35
32
|
"pvlib>=0.13",
|
|
36
33
|
"pycountry>=24.6.1",
|
|
37
34
|
"scikit-learn>=1.7.1,<1.8",
|
|
38
35
|
"scipy>=1.16.3,<2",
|
|
39
36
|
]
|
|
40
|
-
|
|
41
37
|
optional-dependencies.lgbm = [
|
|
42
38
|
"lightgbm>=4.6",
|
|
43
39
|
]
|
|
44
|
-
|
|
45
40
|
optional-dependencies.tuning = [ "optuna>=4.7" ]
|
|
46
|
-
|
|
47
41
|
optional-dependencies.xgb-cpu = [
|
|
48
42
|
"xgboost>=3,<4; sys_platform=='darwin'",
|
|
49
43
|
"xgboost-cpu>=3,<4; sys_platform=='linux' or sys_platform=='win32'",
|
|
50
44
|
]
|
|
51
|
-
|
|
52
45
|
optional-dependencies.xgb-gpu = [ "xgboost>=3,<4" ]
|
|
53
46
|
urls.Documentation = "https://openstef.github.io/openstef/index.html"
|
|
54
47
|
urls.Homepage = "https://lfenergy.org/projects/openstef/"
|
|
55
48
|
urls.Issues = "https://github.com/OpenSTEF/openstef/issues"
|
|
56
49
|
urls.Repository = "https://github.com/OpenSTEF/openstef"
|
|
57
50
|
|
|
58
|
-
[tool.hatch
|
|
59
|
-
packages = [ "src/openstef_models" ]
|
|
51
|
+
[tool.hatch]
|
|
52
|
+
build.targets.wheel.packages = [ "src/openstef_models" ]
|
|
53
|
+
|
|
54
|
+
[tool.uv]
|
|
55
|
+
# xgb-cpu installs xgboost-cpu (Linux/Windows) and xgb-gpu installs the full
|
|
56
|
+
# xgboost; both provide the same import, so exactly one may be installed.
|
|
57
|
+
conflicts = [
|
|
58
|
+
[
|
|
59
|
+
{ extra = "xgb-cpu" },
|
|
60
|
+
{ extra = "xgb-gpu" },
|
|
61
|
+
],
|
|
62
|
+
]
|
|
@@ -9,9 +9,9 @@ from __future__ import annotations
|
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
11
|
import plotly.graph_objects as go
|
|
12
|
-
from plotly.subplots import make_subplots
|
|
12
|
+
from plotly.subplots import make_subplots
|
|
13
13
|
|
|
14
|
-
from openstef_core.datasets import TimeSeriesDataset # noqa: TC001
|
|
14
|
+
from openstef_core.datasets import TimeSeriesDataset # noqa: TC001
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
import pandas as pd
|
|
@@ -77,7 +77,7 @@ class ContributionsPlotter:
|
|
|
77
77
|
vertical_spacing=0.03,
|
|
78
78
|
)
|
|
79
79
|
|
|
80
|
-
fig.add_trace(
|
|
80
|
+
fig.add_trace(
|
|
81
81
|
go.Scatter(
|
|
82
82
|
x=df.index,
|
|
83
83
|
y=prediction,
|
|
@@ -89,9 +89,9 @@ class ContributionsPlotter:
|
|
|
89
89
|
row=1,
|
|
90
90
|
col=1,
|
|
91
91
|
)
|
|
92
|
-
fig.add_trace(heatmap, row=2, col=1)
|
|
92
|
+
fig.add_trace(heatmap, row=2, col=1)
|
|
93
93
|
|
|
94
|
-
fig.update_layout(
|
|
94
|
+
fig.update_layout(
|
|
95
95
|
yaxis_title="Prediction",
|
|
96
96
|
yaxis2_title="Feature",
|
|
97
97
|
xaxis2_title="Time",
|
|
@@ -150,7 +150,7 @@ class ContributionsPlotter:
|
|
|
150
150
|
|
|
151
151
|
for feat in top:
|
|
152
152
|
names.append(feat)
|
|
153
|
-
values.append(float(row[feat]))
|
|
153
|
+
values.append(float(row[feat]))
|
|
154
154
|
measures.append("relative")
|
|
155
155
|
|
|
156
156
|
if len(remaining) > 0:
|
|
@@ -213,7 +213,7 @@ class ContributionsPlotter:
|
|
|
213
213
|
|
|
214
214
|
return go.Figure(
|
|
215
215
|
go.Bar(
|
|
216
|
-
x=mean_abs.values,
|
|
216
|
+
x=mean_abs.values,
|
|
217
217
|
y=mean_abs.index.tolist(),
|
|
218
218
|
orientation="h",
|
|
219
219
|
marker_color="#1f77b4",
|
{openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/__init__.py
RENAMED
|
@@ -9,4 +9,4 @@ extend OpenSTEF functionality by integrating with external systems such as
|
|
|
9
9
|
monitoring tools, databases, cloud storage, and custom processing pipelines.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
__all__ = ["joblib", "mlflow", "optuna"]
|
|
12
|
+
__all__ = ["joblib", "mlflow", "optuna"]
|
|
@@ -58,11 +58,11 @@ class JoblibModelSerializer(ModelSerializer):
|
|
|
58
58
|
|
|
59
59
|
@override
|
|
60
60
|
def serialize(self, model: object, file: BinaryIO) -> None:
|
|
61
|
-
joblib.dump(model, file)
|
|
61
|
+
joblib.dump(model, file)
|
|
62
62
|
|
|
63
63
|
@override
|
|
64
64
|
def deserialize(self, file: BinaryIO) -> object:
|
|
65
|
-
return joblib.load(file)
|
|
65
|
+
return joblib.load(file)
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
__all__ = ["JoblibModelSerializer"]
|
|
@@ -69,6 +69,13 @@ class MLFlowStorage(BaseConfig):
|
|
|
69
69
|
local_artifacts_path: Path = Field(
|
|
70
70
|
default=Path("./mlflow_artifacts_local"), description="Local path for storing MLflow artifacts before upload."
|
|
71
71
|
)
|
|
72
|
+
artifact_location: str | None = Field(
|
|
73
|
+
default=None,
|
|
74
|
+
description="Artifact storage location for newly created experiments. Defaults to MLflow's "
|
|
75
|
+
"own default (a ``./mlruns`` directory relative to the working directory). Set this to an "
|
|
76
|
+
"absolute URI (e.g. ``file:///abs/path`` or ``s3://bucket/prefix``) to keep artifacts "
|
|
77
|
+
"self-contained — useful with a database tracking backend such as ``sqlite:///...``.",
|
|
78
|
+
)
|
|
72
79
|
experiment_name_prefix: str = Field(default="", description="Prefix for MLflow experiment names.")
|
|
73
80
|
# Artifact subdirectories
|
|
74
81
|
data_path: str = Field(default="data", description="Subdirectory for storing training data artifacts.")
|
|
@@ -119,6 +126,7 @@ class MLFlowStorage(BaseConfig):
|
|
|
119
126
|
if experiment is None:
|
|
120
127
|
experiment_id = self._client.create_experiment(
|
|
121
128
|
name=f"{self.experiment_name_prefix}{model_id}",
|
|
129
|
+
artifact_location=self.artifact_location,
|
|
122
130
|
tags=experiment_tags,
|
|
123
131
|
)
|
|
124
132
|
else:
|
|
@@ -194,7 +194,7 @@ class MLFlowStorageCallback(BaseConfig, ForecastingCallback):
|
|
|
194
194
|
)
|
|
195
195
|
return
|
|
196
196
|
|
|
197
|
-
context.workflow.model = old_model
|
|
197
|
+
context.workflow.model = old_model
|
|
198
198
|
self._logger.info(
|
|
199
199
|
"Loaded model from MLflow run %s for model %s",
|
|
200
200
|
run_id,
|
|
@@ -209,7 +209,7 @@ class MLFlowStorageCallback(BaseConfig, ForecastingCallback):
|
|
|
209
209
|
run_id = cast(str, run.info.run_id)
|
|
210
210
|
|
|
211
211
|
if not self._check_tags_compatible(
|
|
212
|
-
run_tags=run.data.tags,
|
|
212
|
+
run_tags=run.data.tags,
|
|
213
213
|
new_tags=workflow.model.tags,
|
|
214
214
|
run_id=run_id,
|
|
215
215
|
):
|
|
@@ -233,9 +233,9 @@ class MLFlowStorageCallback(BaseConfig, ForecastingCallback):
|
|
|
233
233
|
return
|
|
234
234
|
|
|
235
235
|
if self._check_is_new_model_better(old_metrics=old_metrics, new_metrics=new_metrics):
|
|
236
|
-
workflow.model = new_model
|
|
236
|
+
workflow.model = new_model
|
|
237
237
|
else:
|
|
238
|
-
workflow.model = old_model
|
|
238
|
+
workflow.model = old_model
|
|
239
239
|
self._logger.info(
|
|
240
240
|
"New model did not improve %s metric from previous run %s, reusing old model",
|
|
241
241
|
self.model_selection_metric,
|
|
@@ -250,7 +250,7 @@ class MLFlowStorageCallback(BaseConfig, ForecastingCallback):
|
|
|
250
250
|
continue
|
|
251
251
|
suffix = f"_{name}" if name else ""
|
|
252
252
|
fig = component.plot_feature_importances()
|
|
253
|
-
fig.write_html(data_path / f"feature_importances{suffix}.html")
|
|
253
|
+
fig.write_html(data_path / f"feature_importances{suffix}.html")
|
|
254
254
|
|
|
255
255
|
def _find_run(self, model_id: str, run_name: str | None) -> Run | None:
|
|
256
256
|
"""Find an MLflow run by model_id and optional run_name.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from collections.abc import Callable
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from typing import Any, Literal, NamedTuple, cast
|
|
10
|
+
from typing import Any, Literal, NamedTuple, cast, override
|
|
11
11
|
|
|
12
12
|
from pydantic import ConfigDict, Field, SkipValidation
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ try:
|
|
|
16
16
|
except ImportError as _err:
|
|
17
17
|
from openstef_core.exceptions import MissingExtraError
|
|
18
18
|
|
|
19
|
-
raise MissingExtraError("optuna", "openstef-models
|
|
19
|
+
raise MissingExtraError("optuna", "openstef-models", install_extra="tuning") from _err
|
|
20
20
|
|
|
21
21
|
from openstef_core.base_model import BaseConfig
|
|
22
22
|
from openstef_core.datasets import TimeSeriesDataset
|
|
@@ -65,7 +65,8 @@ class TuningResult[ConfigT: BaseConfig]:
|
|
|
65
65
|
study: optuna.Study
|
|
66
66
|
workflow: CustomForecastingWorkflow
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
@override
|
|
69
|
+
def __repr__(self) -> str: # self-explanatory
|
|
69
70
|
n = len(self.study.best_params)
|
|
70
71
|
return f"TuningResult({n} params tuned)" if n else "TuningResult(no tuning)"
|
|
71
72
|
|
|
@@ -328,7 +329,7 @@ class HyperparameterTuner[ConfigT: BaseConfig](BaseConfig):
|
|
|
328
329
|
best_config = self._reconstruct_best_config(
|
|
329
330
|
config=self.config, model_tuning_info=model_tuning_info, study=study
|
|
330
331
|
)
|
|
331
|
-
return best_config, study #
|
|
332
|
+
return best_config, study # ty: ignore[invalid-return-type] # ConfigT narrowing not expressible
|
|
332
333
|
|
|
333
334
|
def fit_with_tuning(self, *, show_progress_bar: bool = True) -> TuningResult[ConfigT]:
|
|
334
335
|
"""Tune, then fit a final workflow with the best config.
|
|
@@ -14,6 +14,7 @@ components like solar, wind, or other energy sources. No training is required
|
|
|
14
14
|
since ratios are predefined by the user.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
import math
|
|
17
18
|
from typing import Self, override
|
|
18
19
|
|
|
19
20
|
import pandas as pd
|
|
@@ -39,7 +40,7 @@ class ConstantComponentSplitterConfig(ComponentSplitterConfig):
|
|
|
39
40
|
@field_validator("component_ratios")
|
|
40
41
|
@classmethod
|
|
41
42
|
def _validate_component_ratios(cls, value: dict[EnergyComponentType, float]) -> dict[EnergyComponentType, float]:
|
|
42
|
-
if sum(value.values())
|
|
43
|
+
if not math.isclose(sum(value.values()), 1.0):
|
|
43
44
|
raise ValueError("Component ratios must sum to 1.0.")
|
|
44
45
|
|
|
45
46
|
return value
|
|
@@ -101,7 +101,7 @@ class LinearComponentSplitter(ComponentSplitter):
|
|
|
101
101
|
"""
|
|
102
102
|
super().__init__()
|
|
103
103
|
self._config = config
|
|
104
|
-
self._model = joblib.load(self.config.linear_model_path)
|
|
104
|
+
self._model = joblib.load(self.config.linear_model_path)
|
|
105
105
|
|
|
106
106
|
@property
|
|
107
107
|
@override
|
|
@@ -148,7 +148,7 @@ class LinearComponentSplitter(ComponentSplitter):
|
|
|
148
148
|
)
|
|
149
149
|
|
|
150
150
|
# Drop rows with NaN values
|
|
151
|
-
input_df = input_df.dropna()
|
|
151
|
+
input_df = input_df.dropna()
|
|
152
152
|
|
|
153
153
|
if input_df.empty:
|
|
154
154
|
error_msg = "No valid data available for component splitting after dropping NaNs"
|
|
@@ -149,7 +149,7 @@ class BaseCaseForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
149
149
|
# Fill missing values with fallback lag if needed
|
|
150
150
|
if prediction.isna().any():
|
|
151
151
|
prediction_fallback = target_series.shift(freq=self.hyperparams.fallback_lag).reindex(forecast_index)
|
|
152
|
-
prediction = prediction.fillna(prediction_fallback)
|
|
152
|
+
prediction = prediction.fillna(prediction_fallback)
|
|
153
153
|
|
|
154
154
|
return ForecastDataset(
|
|
155
155
|
data=pd.DataFrame(
|
|
@@ -171,7 +171,7 @@ class BaseCaseForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
171
171
|
@override
|
|
172
172
|
def feature_importances(self) -> pd.DataFrame:
|
|
173
173
|
return pd.DataFrame(
|
|
174
|
-
data=
|
|
174
|
+
data=1.0,
|
|
175
175
|
index=["load"],
|
|
176
176
|
columns=[quantile.format() for quantile in self.quantiles],
|
|
177
177
|
)
|
|
@@ -114,7 +114,7 @@ class ConstantQuantileForecaster(Forecaster, ExplainableForecaster, Contribution
|
|
|
114
114
|
@override
|
|
115
115
|
def feature_importances(self) -> pd.DataFrame:
|
|
116
116
|
return pd.DataFrame(
|
|
117
|
-
data=
|
|
117
|
+
data=1.0,
|
|
118
118
|
index=["load"],
|
|
119
119
|
columns=[quantile.format() for quantile in self.quantiles],
|
|
120
120
|
)
|
|
@@ -38,7 +38,7 @@ from openstef_models.utils.xgboost import get_median_shap_contribs
|
|
|
38
38
|
try:
|
|
39
39
|
import xgboost as xgb
|
|
40
40
|
except ImportError as e:
|
|
41
|
-
raise MissingExtraError("xgboost", "openstef-models") from e
|
|
41
|
+
raise MissingExtraError("xgboost", "openstef-models", install_extra="xgb-cpu") from e
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class GBLinearHyperParams(HyperParams):
|
|
@@ -186,6 +186,7 @@ class GBLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
186
186
|
def hparams(self) -> GBLinearHyperParams:
|
|
187
187
|
return self.hyperparams
|
|
188
188
|
|
|
189
|
+
@override
|
|
189
190
|
def model_post_init(self, _context: object, /) -> None:
|
|
190
191
|
"""Initialize the underlying XGBoost gblinear model from configuration."""
|
|
191
192
|
self._gblinear_model = xgb.XGBRegressor(
|
|
@@ -290,6 +291,7 @@ class GBLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
290
291
|
target_column=data.target_column,
|
|
291
292
|
)
|
|
292
293
|
|
|
294
|
+
@override
|
|
293
295
|
def predict_contributions(self, data: ForecastInputDataset) -> TimeSeriesDataset:
|
|
294
296
|
"""Compute SHAP feature contributions for the median quantile.
|
|
295
297
|
|
|
@@ -196,6 +196,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
196
196
|
def hparams(self) -> LGBMHyperParams:
|
|
197
197
|
return self.hyperparams
|
|
198
198
|
|
|
199
|
+
@override
|
|
199
200
|
def model_post_init(self, _context: object, /) -> None:
|
|
200
201
|
"""Initialize the underlying LightGBM model from configuration.
|
|
201
202
|
|
|
@@ -205,7 +206,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
205
206
|
try:
|
|
206
207
|
from lightgbm import LGBMRegressor # noqa: PLC0415
|
|
207
208
|
except ImportError as e:
|
|
208
|
-
raise MissingExtraError("lightgbm", "openstef-models") from e
|
|
209
|
+
raise MissingExtraError("lightgbm", "openstef-models", install_extra="lgbm") from e
|
|
209
210
|
|
|
210
211
|
lgbm_params = {
|
|
211
212
|
# Core parameters
|
|
@@ -234,7 +235,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
self._lgbm_model = MultiQuantileRegressor(
|
|
237
|
-
base_learner=LGBMRegressor,
|
|
238
|
+
base_learner=LGBMRegressor,
|
|
238
239
|
quantile_param="alpha",
|
|
239
240
|
hyperparams=lgbm_params,
|
|
240
241
|
quantiles=[float(q) for q in self.quantiles],
|
|
@@ -294,6 +295,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
294
295
|
target_column=data.target_column,
|
|
295
296
|
)
|
|
296
297
|
|
|
298
|
+
@override
|
|
297
299
|
def predict_contributions(self, data: ForecastInputDataset) -> TimeSeriesDataset:
|
|
298
300
|
"""Compute SHAP feature contributions for the median quantile.
|
|
299
301
|
|
|
@@ -317,7 +319,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
317
319
|
model: LGBMRegressor = self._lgbm_model.models[median_idx] # type: ignore
|
|
318
320
|
|
|
319
321
|
# Get SHAP contributions from median quantile model (includes bias as last column)
|
|
320
|
-
contribs: np.ndarray = model.predict(input_data, pred_contrib=True)
|
|
322
|
+
contribs: np.ndarray = model.predict(input_data, pred_contrib=True)
|
|
321
323
|
|
|
322
324
|
columns = [*input_data.columns, "bias"]
|
|
323
325
|
contribs_df = pd.DataFrame(contribs, index=input_data.index, columns=columns)
|
|
@@ -197,6 +197,7 @@ class LGBMLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin
|
|
|
197
197
|
def hparams(self) -> LGBMLinearHyperParams:
|
|
198
198
|
return self.hyperparams
|
|
199
199
|
|
|
200
|
+
@override
|
|
200
201
|
def model_post_init(self, _context: object, /) -> None:
|
|
201
202
|
"""Initialize the underlying LightGBM linear model from configuration.
|
|
202
203
|
|
|
@@ -206,7 +207,7 @@ class LGBMLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin
|
|
|
206
207
|
try:
|
|
207
208
|
from lightgbm import LGBMRegressor # noqa: PLC0415
|
|
208
209
|
except ImportError as e:
|
|
209
|
-
raise MissingExtraError("lightgbm", "openstef-models") from e
|
|
210
|
+
raise MissingExtraError("lightgbm", "openstef-models", install_extra="lgbm") from e
|
|
210
211
|
|
|
211
212
|
lgbmlinear_params = {
|
|
212
213
|
# Core parameters
|
|
@@ -235,7 +236,7 @@ class LGBMLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin
|
|
|
235
236
|
}
|
|
236
237
|
|
|
237
238
|
self._lgbmlinear_model = MultiQuantileRegressor(
|
|
238
|
-
base_learner=LGBMRegressor,
|
|
239
|
+
base_learner=LGBMRegressor,
|
|
239
240
|
quantile_param="alpha",
|
|
240
241
|
hyperparams=lgbmlinear_params,
|
|
241
242
|
quantiles=[float(q) for q in self.quantiles],
|
|
@@ -319,7 +320,7 @@ class LGBMLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin
|
|
|
319
320
|
model: LGBMRegressor = self._lgbmlinear_model.models[median_idx] # type: ignore
|
|
320
321
|
|
|
321
322
|
# Get SHAP contributions from median quantile model (includes bias as last column)
|
|
322
|
-
contribs: np.ndarray = model.predict(input_data, pred_contrib=True)
|
|
323
|
+
contribs: np.ndarray = model.predict(input_data, pred_contrib=True)
|
|
323
324
|
|
|
324
325
|
columns = [*input_data.columns, "bias"]
|
|
325
326
|
contribs_df = pd.DataFrame(contribs, index=input_data.index, columns=columns)
|
|
@@ -214,13 +214,11 @@ class MedianForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
214
214
|
for time_step in range(lag_array.shape[0]):
|
|
215
215
|
# Get the lag features for the current time step.
|
|
216
216
|
current_lags = lag_array[time_step]
|
|
217
|
-
#
|
|
218
|
-
|
|
219
|
-
# If the median calculation resulted in NaN (e.g., all lags were NaN), skip the autoregression step.
|
|
220
|
-
if not np.isnan(median): # type: ignore
|
|
221
|
-
median = float(median) # type: ignore
|
|
222
|
-
else:
|
|
217
|
+
# If all lags are NaN there is no value to predict, so skip the autoregressive step.
|
|
218
|
+
if np.all(np.isnan(current_lags)):
|
|
223
219
|
continue
|
|
220
|
+
# Calculate the median of the available lag features, ignoring NaNs.
|
|
221
|
+
median = float(np.nanmedian(current_lags))
|
|
224
222
|
|
|
225
223
|
# Store the calculated median in the prediction array.
|
|
226
224
|
prediction[time_step] = median
|
|
@@ -239,7 +237,7 @@ class MedianForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
239
237
|
prediction_df = prediction_df.reindex(input_data.index)
|
|
240
238
|
|
|
241
239
|
return ForecastDataset(
|
|
242
|
-
data=prediction_df.dropna().rename(columns={"median": self.quantiles[0].format()}),
|
|
240
|
+
data=prediction_df.dropna().rename(columns={"median": self.quantiles[0].format()}),
|
|
243
241
|
sample_interval=data.sample_interval,
|
|
244
242
|
forecast_start=data.forecast_start,
|
|
245
243
|
target_column=data.target_column,
|
|
@@ -34,7 +34,7 @@ from openstef_models.utils.xgboost import get_median_shap_contribs
|
|
|
34
34
|
try:
|
|
35
35
|
import xgboost as xgb
|
|
36
36
|
except ImportError as e:
|
|
37
|
-
raise MissingExtraError("xgboost", "openstef-models") from e
|
|
37
|
+
raise MissingExtraError("xgboost", "openstef-models", install_extra="xgb-cpu") from e
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class XGBoostHyperParams(HyperParams):
|
|
@@ -250,6 +250,7 @@ class XGBoostForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
250
250
|
def hparams(self) -> XGBoostHyperParams:
|
|
251
251
|
return self.hyperparams
|
|
252
252
|
|
|
253
|
+
@override
|
|
253
254
|
def model_post_init(self, _context: object, /) -> None:
|
|
254
255
|
"""Initialize the underlying XGBoost model from configuration."""
|
|
255
256
|
self._xgboost_model = xgb.XGBRegressor(
|
|
@@ -364,6 +365,7 @@ class XGBoostForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
364
365
|
target_column=data.target_column,
|
|
365
366
|
)
|
|
366
367
|
|
|
368
|
+
@override
|
|
367
369
|
def predict_contributions(self, data: ForecastInputDataset) -> TimeSeriesDataset:
|
|
368
370
|
"""Compute SHAP feature contributions for the median quantile.
|
|
369
371
|
|
{openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting_model.py
RENAMED
|
@@ -12,6 +12,7 @@ unified interface while providing consistent data transformation and validation.
|
|
|
12
12
|
|
|
13
13
|
import logging
|
|
14
14
|
from abc import abstractmethod
|
|
15
|
+
from collections.abc import Sequence
|
|
15
16
|
from datetime import datetime, timedelta
|
|
16
17
|
from functools import partial
|
|
17
18
|
from typing import Self, cast, override
|
|
@@ -29,7 +30,7 @@ from openstef_core.datasets import (
|
|
|
29
30
|
TimeSeriesDataset,
|
|
30
31
|
)
|
|
31
32
|
from openstef_core.datasets.timeseries_dataset import validate_horizons_present
|
|
32
|
-
from openstef_core.exceptions import InsufficientlyCompleteError, NotFittedError
|
|
33
|
+
from openstef_core.exceptions import InsufficientlyCompleteError, NotFittedError, PredictError
|
|
33
34
|
from openstef_core.mixins import HyperParams, Predictor, TransformPipeline
|
|
34
35
|
from openstef_core.types import LeadTime, Quantile
|
|
35
36
|
from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster
|
|
@@ -137,7 +138,7 @@ class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDatas
|
|
|
137
138
|
"Default of 0 assumes no invalid rows are created by preprocessing.",
|
|
138
139
|
)
|
|
139
140
|
|
|
140
|
-
evaluation_metrics: list[MetricProvider] = Field(
|
|
141
|
+
evaluation_metrics: list[MetricProvider] = Field( # ty: ignore[invalid-assignment]
|
|
141
142
|
default_factory=lambda: [R2Provider(), ObservedProbabilityProvider()],
|
|
142
143
|
description="List of metric providers for evaluating model score.",
|
|
143
144
|
)
|
|
@@ -206,7 +207,7 @@ class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDatas
|
|
|
206
207
|
raise ValueError(msg)
|
|
207
208
|
return self
|
|
208
209
|
|
|
209
|
-
def get_explainable_components(self) -> dict[str, ExplainableForecaster]:
|
|
210
|
+
def get_explainable_components(self) -> dict[str, ExplainableForecaster]:
|
|
210
211
|
"""Return named components that support feature-importance plotting.
|
|
211
212
|
|
|
212
213
|
Keys are used as filename suffixes; an empty key means no suffix.
|
|
@@ -310,6 +311,58 @@ class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDatas
|
|
|
310
311
|
|
|
311
312
|
return self.postprocessing.transform(data=raw_predictions)
|
|
312
313
|
|
|
314
|
+
def predict_batch(
|
|
315
|
+
self,
|
|
316
|
+
data: list[TimeSeriesDataset],
|
|
317
|
+
forecast_start: Sequence[datetime],
|
|
318
|
+
) -> list[ForecastDataset]:
|
|
319
|
+
"""Generate forecasts for a batch of inputs with one model instance.
|
|
320
|
+
|
|
321
|
+
Each item is preprocessed independently (keeping its own ``forecast_start``);
|
|
322
|
+
the preprocessed batch is then forecast together via ``_predict_batch`` and
|
|
323
|
+
each raw forecast is postprocessed.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
data: One ``TimeSeriesDataset`` per location/series to forecast.
|
|
327
|
+
forecast_start: One forecast start per item, in input order. Pass
|
|
328
|
+
``[origin] * len(data)`` to share a single origin across the batch.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
One ``ForecastDataset`` per input, in input order.
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
NotFittedError: If the model has not been fitted.
|
|
335
|
+
ValueError: If ``forecast_start`` length differs from the batch size.
|
|
336
|
+
"""
|
|
337
|
+
if not self.is_fitted:
|
|
338
|
+
raise NotFittedError(type(self).__name__)
|
|
339
|
+
if not data:
|
|
340
|
+
return []
|
|
341
|
+
if len(forecast_start) != len(data):
|
|
342
|
+
msg = f"forecast_start sequence length {len(forecast_start)} != batch size {len(data)}."
|
|
343
|
+
raise ValueError(msg)
|
|
344
|
+
|
|
345
|
+
inputs = [
|
|
346
|
+
self.prepare_input(data=item, forecast_start=start)
|
|
347
|
+
for item, start in zip(data, forecast_start, strict=True)
|
|
348
|
+
]
|
|
349
|
+
raw = self._predict_batch(input_data=inputs)
|
|
350
|
+
return [self.postprocessing.transform(data=forecast) for forecast in raw]
|
|
351
|
+
|
|
352
|
+
def _predict_batch(self, input_data: list[ForecastInputDataset]) -> list[ForecastDataset]:
|
|
353
|
+
"""Generate raw forecasts for a preprocessed batch.
|
|
354
|
+
|
|
355
|
+
The default fallback loops ``_predict`` item by item. Subclasses backed by a
|
|
356
|
+
batch-native forecaster override this to issue a single batched call.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
input_data: One preprocessed ``ForecastInputDataset`` per batch item.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
One raw ``ForecastDataset`` per input, in input order.
|
|
363
|
+
"""
|
|
364
|
+
return [self._predict(input_data=item) for item in input_data]
|
|
365
|
+
|
|
313
366
|
def score(self, data: TimeSeriesDataset) -> SubsetMetric:
|
|
314
367
|
"""Evaluate model performance on the provided dataset.
|
|
315
368
|
|
|
@@ -330,7 +383,7 @@ class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDatas
|
|
|
330
383
|
if prediction.target_series is None:
|
|
331
384
|
raise ValueError("Prediction dataset must contain target series for scoring.")
|
|
332
385
|
|
|
333
|
-
prediction = prediction.pipe_pandas(pd.DataFrame.dropna, subset=[self.target_column])
|
|
386
|
+
prediction = prediction.pipe_pandas(pd.DataFrame.dropna, subset=[self.target_column])
|
|
334
387
|
|
|
335
388
|
pipeline = EvaluationPipeline(
|
|
336
389
|
config=EvaluationConfig(available_ats=[], lead_times=[self.max_horizon]),
|
|
@@ -482,7 +535,7 @@ class ForecastingModel(BaseForecastingModel):
|
|
|
482
535
|
"""
|
|
483
536
|
validate_horizons_present(data, self.forecaster.horizons)
|
|
484
537
|
|
|
485
|
-
target_dropna = partial(pd.DataFrame.dropna, subset=[self.target_column])
|
|
538
|
+
target_dropna = partial(pd.DataFrame.dropna, subset=[self.target_column])
|
|
486
539
|
if data.pipe_pandas(target_dropna).data.empty:
|
|
487
540
|
msg = (
|
|
488
541
|
f"No training data available after dropping NaN targets in column '{self.target_column}'. "
|
|
@@ -542,6 +595,22 @@ class ForecastingModel(BaseForecastingModel):
|
|
|
542
595
|
prediction = self.forecaster.predict(data=input_data)
|
|
543
596
|
return restore_target(dataset=prediction, original_dataset=input_data, target_column=self.target_column)
|
|
544
597
|
|
|
598
|
+
@override
|
|
599
|
+
def _predict_batch(self, input_data: list[ForecastInputDataset]) -> list[ForecastDataset]:
|
|
600
|
+
raw = self.forecaster.predict_batch(data=input_data)
|
|
601
|
+
forecasts: list[ForecastDataset] = []
|
|
602
|
+
for item, forecast in zip(input_data, raw, strict=True):
|
|
603
|
+
if isinstance(forecast, PredictError):
|
|
604
|
+
raise forecast
|
|
605
|
+
forecasts.append(
|
|
606
|
+
restore_target(
|
|
607
|
+
dataset=forecast,
|
|
608
|
+
original_dataset=item,
|
|
609
|
+
target_column=self.target_column,
|
|
610
|
+
)
|
|
611
|
+
)
|
|
612
|
+
return forecasts
|
|
613
|
+
|
|
545
614
|
@override
|
|
546
615
|
def predict_contributions(
|
|
547
616
|
self,
|
|
@@ -580,7 +649,7 @@ def restore_target[T: TimeSeriesDataset](
|
|
|
580
649
|
target_series = original_dataset.select_features([target_column]).select_version().data[target_column]
|
|
581
650
|
|
|
582
651
|
def _transform_restore_target(df: pd.DataFrame) -> pd.DataFrame:
|
|
583
|
-
original_values = df.index.map(target_series) #
|
|
652
|
+
original_values = df.index.map(target_series) # ty: ignore[invalid-argument-type]
|
|
584
653
|
restored = pd.Series(original_values, index=df.index, name=target_column)
|
|
585
654
|
|
|
586
655
|
# Preserve NaN values introduced by the OutlierHandler for the target column.
|