openstef-models 4.2.2__tar.gz → 4.3.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.
- {openstef_models-4.2.2 → openstef_models-4.3.0}/PKG-INFO +2 -2
- {openstef_models-4.2.2 → openstef_models-4.3.0}/pyproject.toml +1 -1
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/mlflow/mlflow_storage.py +44 -3
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/median_forecaster.py +4 -1
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/presets/forecasting_workflow.py +16 -1
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/postprocessing/__init__.py +7 -1
- openstef_models-4.3.0/src/openstef_models/transforms/postprocessing/conformalized_quantile_calibrator.py +115 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/data_split.py +1 -1
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/mlflow/test_mlflow_storage.py +20 -1
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/conftest.py +1 -1
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/test_median_forecaster.py +6 -1
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_dimensionality_reducer.py +1 -1
- openstef_models-4.3.0/tests/unit/transforms/postprocessing/test_conformalized_quantile_calibrator.py +141 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/.gitignore +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/README.md +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/explainability/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/explainability/mixins.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/explainability/plotters/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/explainability/plotters/contributions_plotter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/explainability/plotters/feature_importance_plotter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/joblib/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/joblib/joblib_model_serializer.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/mlflow/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/optuna/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/optuna/tuner.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/mixins/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/mixins/callbacks.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/mixins/model_serializer.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/component_splitting/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/component_splitting/component_splitter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/component_splitting/constant_component_splitter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/component_splitting/linear_component_splitter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/component_splitting/linear_component_splitter_model/linear_component_splitter_model.z +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/component_splitting/linear_component_splitter_model/linear_component_splitter_model.z.license +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/component_splitting_model.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/base_case_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/constant_quantile_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/flatliner_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/gblinear_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/lgbm_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/lgbmlinear_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/xgboost_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting_model.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/presets/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/testing.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/energy_domain/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/energy_domain/wind_power_feature_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/dimensionality_reducer.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/empty_feature_remover.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/flagger.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/imputer.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/nan_dropper.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/outlier_handler.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/sample_weighter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/scaler.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/selector.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/shifter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/postprocessing/confidence_interval_applicator.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/postprocessing/isotonic_quantile_calibrator.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/postprocessing/quantile_sorter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/time_domain/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/time_domain/cyclic_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/time_domain/datetime_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/time_domain/holiday_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/time_domain/lags_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/time_domain/rolling_aggregates_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/time_domain/versioned_lags_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/validation/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/validation/completeness_checker.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/validation/flatline_checker.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/validation/input_consistency_checker.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/weather_domain/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/weather_domain/atmosphere_derived_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/weather_domain/daylight_feature_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/weather_domain/radiation_derived_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/evaluation_functions.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/feature_selection.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/loss_functions.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/multi_quantile_regressor.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/xgboost.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/workflows/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/workflows/callbacks/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/workflows/callbacks/data_save.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/workflows/callbacks/model_performance_callback.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/workflows/custom_component_split_workflow.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/workflows/custom_forecasting_workflow.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/integration/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/integration/test_integration.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/explainability/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/explainability/plotters/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/explainability/plotters/test_contributions_plotter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/joblib/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/joblib/test_joblib_model_serializer.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/mlflow/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/mlflow/test_mlflow_storage_callback.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/optuna/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/optuna/test_tuner.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/component_splitting/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/component_splitting/test_constant_component_splitter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/component_splitting/test_linear_component_splitter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/test_base_case_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/test_constant_quantile_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/test_flatliner_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/test_gblinear_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/test_lgbm_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/test_lgbmlinear_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/forecasting/test_xgboost_forecaster.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/test_forecasting_model.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/test_example.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/energy_domain/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/energy_domain/test_wind_power_feature_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_empty_feature_remover.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_flagger.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_imputer.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_nan_dropper.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_outlier_handler.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_sample_weighter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_scaler.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_selector.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_shifter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/postprocessing/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/postprocessing/test_confidence_interval_applicator.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/postprocessing/test_isotonic_quantile_calibrator.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/postprocessing/test_quantile_sorter.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/test_cyclic_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/test_datetime_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/test_holiday_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/test_lags_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/test_rolling_aggregates_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/test_versioned_lags_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/validation/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/validation/test_completeness_checker.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/validation/test_flatline_checker.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/validation/test_input_consistency_checker.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/weather_domain/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/weather_domain/test_atmosphere_derived_features_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/weather_domain/test_daylight_feature_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/weather_domain/test_radiation_derived_featuers_adder.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/utils/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/utils/test_data_split.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/utils/test_feature_selection.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/utils/test_loss_functions.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/utils/test_multi_quantile_regressor.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/workflows/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/workflows/callbacks/__init__.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/workflows/callbacks/test_model_performance_callback.py +0 -0
- {openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/workflows/test_custom_forecasting_workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: openstef-models
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
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/
|
|
@@ -22,7 +22,7 @@ from urllib.parse import urlparse
|
|
|
22
22
|
from mlflow import MlflowClient
|
|
23
23
|
from mlflow.entities import Metric, Param, Run
|
|
24
24
|
from mlflow.exceptions import MlflowException
|
|
25
|
-
from pydantic import Field, PrivateAttr
|
|
25
|
+
from pydantic import ConfigDict, Field, PrivateAttr
|
|
26
26
|
|
|
27
27
|
from openstef_core.base_model import BaseConfig
|
|
28
28
|
from openstef_core.exceptions import ModelNotFoundError
|
|
@@ -31,7 +31,7 @@ from openstef_models.integrations.joblib import JoblibModelSerializer
|
|
|
31
31
|
from openstef_models.mixins import ModelIdentifier, ModelSerializer
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def normalize_tracking_uri(uri: str) -> str:
|
|
34
|
+
def normalize_tracking_uri(uri: str, exceptions: Sequence[str] = []) -> str:
|
|
35
35
|
r"""Normalize a tracking URI to a file:/// URI when it refers to a local path.
|
|
36
36
|
|
|
37
37
|
MLflow's model registry rejects bare Windows paths (e.g. ``D:\mlflow``) because
|
|
@@ -44,10 +44,13 @@ def normalize_tracking_uri(uri: str) -> str:
|
|
|
44
44
|
|
|
45
45
|
Args:
|
|
46
46
|
uri: Raw tracking URI string, may be a path or a proper URI.
|
|
47
|
+
exceptions: List of URI to treat as urls even if they have no scheme
|
|
47
48
|
|
|
48
49
|
Returns:
|
|
49
50
|
A ``file:///`` URI for local paths, or the original URI for remote schemes.
|
|
50
51
|
"""
|
|
52
|
+
if uri in exceptions:
|
|
53
|
+
return uri
|
|
51
54
|
scheme = urlparse(uri).scheme
|
|
52
55
|
# Empty scheme → relative/absolute POSIX path.
|
|
53
56
|
# Single-letter scheme → Windows drive letter (e.g. "D" from "D:\\...").
|
|
@@ -63,9 +66,33 @@ class MLFlowStorage(BaseConfig):
|
|
|
63
66
|
Handles creation, storage, and retrieval of MLflow runs including models,
|
|
64
67
|
training data, metrics, and hyperparameters. Organizes artifacts locally
|
|
65
68
|
before uploading to MLflow tracking server.
|
|
69
|
+
|
|
70
|
+
``tracking_uri`` configures the Tracking backend that stores experiments and
|
|
71
|
+
runs. ``registry_uri`` optionally configures a separate Model Registry
|
|
72
|
+
backend; if it is omitted, MLflow uses the registry associated with the
|
|
73
|
+
tracking backend. ``artifact_location`` configures the artifact root for
|
|
74
|
+
experiments created by this instance.
|
|
75
|
+
|
|
76
|
+
Typical configurations:
|
|
77
|
+
|
|
78
|
+
tracking_uri="sqlite:///mlflow.db", # Local SQLite tracking backend with separate local artifacts
|
|
79
|
+
tracking_uri="https://mlflow.example.com", # Remote MLflow Tracking Server with S3 artifacts
|
|
80
|
+
tracking_uri="databricks", # Databricks MLflow Tracking Server with DBFS artifacts
|
|
81
|
+
|
|
82
|
+
A caller may provide ``client`` to use an already configured
|
|
83
|
+
:class:`mlflow.MlflowClient`. When supplied, it takes precedence over
|
|
84
|
+
``tracking_uri`` and ``registry_uri`` and is excluded from serialized model
|
|
85
|
+
configuration.
|
|
66
86
|
"""
|
|
67
87
|
|
|
88
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
89
|
+
|
|
68
90
|
tracking_uri: str = Field(default="./mlflow", description="MLflow tracking server URI.")
|
|
91
|
+
registry_uri: str | None = Field(default=None, description="MLflow tracking server URI.")
|
|
92
|
+
|
|
93
|
+
# Excluded from model_dump(), JSON serialization, and config persistence.
|
|
94
|
+
client: MlflowClient | None = Field(default=None, exclude=True, repr=False)
|
|
95
|
+
|
|
69
96
|
local_artifacts_path: Path = Field(
|
|
70
97
|
default=Path("./mlflow_artifacts_local"), description="Local path for storing MLflow artifacts before upload."
|
|
71
98
|
)
|
|
@@ -86,6 +113,11 @@ class MLFlowStorage(BaseConfig):
|
|
|
86
113
|
|
|
87
114
|
model_serializer: ModelSerializer = Field(default_factory=JoblibModelSerializer)
|
|
88
115
|
|
|
116
|
+
uri_exceptions: Sequence[str] = Field(
|
|
117
|
+
default=["databricks"],
|
|
118
|
+
description="List of URI treat as urls even if they have no scheme",
|
|
119
|
+
)
|
|
120
|
+
|
|
89
121
|
_client: MlflowClient = PrivateAttr()
|
|
90
122
|
_logger: logging.Logger = PrivateAttr(default=logging.getLogger(__name__))
|
|
91
123
|
|
|
@@ -95,8 +127,17 @@ class MLFlowStorage(BaseConfig):
|
|
|
95
127
|
# Suppress MLflow's stdout messages (emoji URLs)
|
|
96
128
|
os.environ.setdefault("MLFLOW_SUPPRESS_PRINTING_URL_TO_STDOUT", "true")
|
|
97
129
|
os.environ.setdefault("MLFLOW_ENABLE_ARTIFACTS_PROGRESS_BAR", "false")
|
|
130
|
+
|
|
131
|
+
if self.client is not None:
|
|
132
|
+
self._client = self.client
|
|
133
|
+
return
|
|
134
|
+
|
|
98
135
|
self.tracking_uri = normalize_tracking_uri(self.tracking_uri)
|
|
99
|
-
self.
|
|
136
|
+
self.registry_uri = normalize_tracking_uri(self.registry_uri) if self.registry_uri else None
|
|
137
|
+
self._client = MlflowClient(
|
|
138
|
+
tracking_uri=self.tracking_uri,
|
|
139
|
+
registry_uri=self.registry_uri,
|
|
140
|
+
)
|
|
100
141
|
|
|
101
142
|
def create_run(
|
|
102
143
|
self,
|
|
@@ -200,7 +200,10 @@ class MedianForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
|
|
|
200
200
|
lag_df = input_data.reindex(new_index, fill_value=np.nan)[self._feature_names]
|
|
201
201
|
|
|
202
202
|
# Convert the lag DataFrame to NumPy arrays for faster processing.
|
|
203
|
-
|
|
203
|
+
# Copy explicitly: pandas Copy-on-Write may hand back a read-only view of the
|
|
204
|
+
# DataFrame's underlying block, but the autoregressive loop below fills the
|
|
205
|
+
# array in place (via _fill_diagonal_with_median), so it must own a writable copy.
|
|
206
|
+
lag_array = lag_df.to_numpy(copy=True)
|
|
204
207
|
# Initialize the prediction array with NaNs.
|
|
205
208
|
prediction = np.full(lag_array.shape[0], np.nan)
|
|
206
209
|
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/presets/forecasting_workflow.py
RENAMED
|
@@ -233,6 +233,14 @@ class ForecastingWorkflowConfig(BaseConfig): # PredictionJob
|
|
|
233
233
|
description="Feature selection for which features to replace out-of-range values with NaN. "
|
|
234
234
|
"Defaults to no features (disabled).",
|
|
235
235
|
)
|
|
236
|
+
outlier_strictness_n_std: float = Field(
|
|
237
|
+
default=2.0,
|
|
238
|
+
description=(
|
|
239
|
+
"Number of standard deviations for outlier detection (lower = stricter bounds). "
|
|
240
|
+
"Only used when nan_on_outlier_features is enabled."
|
|
241
|
+
),
|
|
242
|
+
gt=0,
|
|
243
|
+
)
|
|
236
244
|
max_day_lags: int = Field(
|
|
237
245
|
default=14,
|
|
238
246
|
description="Maximum number of days to look back for day-based lags. "
|
|
@@ -412,7 +420,14 @@ def create_forecasting_workflow(
|
|
|
412
420
|
if config.model == "xgboost":
|
|
413
421
|
nan_outlier_handlers = [
|
|
414
422
|
*(
|
|
415
|
-
[
|
|
423
|
+
[
|
|
424
|
+
OutlierHandler(
|
|
425
|
+
mode="standard",
|
|
426
|
+
selection=config.nan_on_outlier_features,
|
|
427
|
+
outlier_action="nan",
|
|
428
|
+
n_std=config.outlier_strictness_n_std,
|
|
429
|
+
)
|
|
430
|
+
]
|
|
416
431
|
if config.nan_on_outlier_features != FeatureSelection.NONE
|
|
417
432
|
else []
|
|
418
433
|
),
|
|
@@ -10,7 +10,13 @@ operate on ForecastDataset objects after the core prediction step.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from openstef_models.transforms.postprocessing.confidence_interval_applicator import ConfidenceIntervalApplicator
|
|
13
|
+
from openstef_models.transforms.postprocessing.conformalized_quantile_calibrator import ConformalizedQuantileCalibrator
|
|
13
14
|
from openstef_models.transforms.postprocessing.isotonic_quantile_calibrator import IsotonicQuantileCalibrator
|
|
14
15
|
from openstef_models.transforms.postprocessing.quantile_sorter import QuantileSorter
|
|
15
16
|
|
|
16
|
-
__all__ = [
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ConfidenceIntervalApplicator",
|
|
19
|
+
"ConformalizedQuantileCalibrator",
|
|
20
|
+
"IsotonicQuantileCalibrator",
|
|
21
|
+
"QuantileSorter",
|
|
22
|
+
]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
"""Asymmetric conformal quantile calibration without external dependencies."""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import override
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
|
12
|
+
|
|
13
|
+
from openstef_core.datasets import ForecastDataset
|
|
14
|
+
from openstef_core.exceptions import NotFittedError
|
|
15
|
+
from openstef_core.mixins import Transform
|
|
16
|
+
from openstef_core.types import Quantile
|
|
17
|
+
|
|
18
|
+
MEDIAN_QUANTILE = Quantile(0.5)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _conformal_quantile(scores: np.ndarray, level: float) -> float:
|
|
22
|
+
"""Return the finite-sample conformal quantile of one-sided scores."""
|
|
23
|
+
n_samples = scores.size
|
|
24
|
+
if n_samples == 0:
|
|
25
|
+
return 0.0
|
|
26
|
+
adjusted_level = min(level * (n_samples + 1) / n_samples, 1.0)
|
|
27
|
+
return float(np.quantile(scores, adjusted_level, method="higher"))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ConformalizedQuantileCalibrator(BaseModel, Transform[ForecastDataset, ForecastDataset]):
|
|
31
|
+
"""Apply asymmetric split-conformal corrections to forecast quantiles.
|
|
32
|
+
|
|
33
|
+
Lower quantiles are corrected using lower-tail scores and upper quantiles
|
|
34
|
+
using upper-tail scores. The median is left unchanged by default.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
quantiles: Quantiles to calibrate. If None, all input quantiles are used.
|
|
38
|
+
conformalize_median: Whether to apply the upper-tail correction to P50.
|
|
39
|
+
min_calibration_samples: Minimum number of valid calibration pairs required
|
|
40
|
+
before fitting a correction for a quantile. Quantiles with fewer valid
|
|
41
|
+
pairs are left unchanged; if all quantiles are skipped, fitting becomes
|
|
42
|
+
a no-op calibrator.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
quantiles: list[Quantile] | None = Field(default=None)
|
|
46
|
+
conformalize_median: bool = Field(default=False)
|
|
47
|
+
min_calibration_samples: int = Field(default=100, ge=1)
|
|
48
|
+
|
|
49
|
+
_corrections: dict[str, float] = PrivateAttr(default_factory=dict)
|
|
50
|
+
_is_fitted: bool = PrivateAttr(default=False)
|
|
51
|
+
_logger: logging.Logger = PrivateAttr(default=logging.getLogger(__name__))
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
@override
|
|
55
|
+
def is_fitted(self) -> bool:
|
|
56
|
+
"""Return whether calibration corrections have been fitted."""
|
|
57
|
+
return self._is_fitted
|
|
58
|
+
|
|
59
|
+
@override
|
|
60
|
+
def fit(self, data: ForecastDataset) -> None:
|
|
61
|
+
"""Estimate one-sided conformal corrections from forecast errors."""
|
|
62
|
+
if data.target_series is None:
|
|
63
|
+
raise ValueError("Input data must contain target series for calibration.")
|
|
64
|
+
|
|
65
|
+
quantiles_to_fit = self.quantiles if self.quantiles is not None else data.quantiles
|
|
66
|
+
if not quantiles_to_fit:
|
|
67
|
+
raise ValueError("No quantiles found to calibrate.")
|
|
68
|
+
|
|
69
|
+
self._is_fitted = False
|
|
70
|
+
actuals = data.target_series.to_numpy()
|
|
71
|
+
self._corrections = {}
|
|
72
|
+
|
|
73
|
+
for quantile in quantiles_to_fit:
|
|
74
|
+
column = quantile.format()
|
|
75
|
+
if column not in data.data.columns:
|
|
76
|
+
continue
|
|
77
|
+
if quantile == MEDIAN_QUANTILE and not self.conformalize_median:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
predictions = data.data[column].to_numpy()
|
|
81
|
+
valid = ~(np.isnan(predictions) | np.isnan(actuals))
|
|
82
|
+
predictions_valid = predictions[valid]
|
|
83
|
+
actuals_valid = actuals[valid]
|
|
84
|
+
if predictions_valid.size < self.min_calibration_samples:
|
|
85
|
+
self._logger.warning(
|
|
86
|
+
"Skipping calibration for quantile %s: not enough data points (found %d, require %d).",
|
|
87
|
+
column,
|
|
88
|
+
predictions_valid.size,
|
|
89
|
+
self.min_calibration_samples,
|
|
90
|
+
)
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if quantile < MEDIAN_QUANTILE:
|
|
94
|
+
scores = predictions_valid - actuals_valid
|
|
95
|
+
self._corrections[column] = -_conformal_quantile(scores, level=1.0 - quantile)
|
|
96
|
+
else:
|
|
97
|
+
scores = actuals_valid - predictions_valid
|
|
98
|
+
self._corrections[column] = _conformal_quantile(scores, level=quantile)
|
|
99
|
+
|
|
100
|
+
self._is_fitted = True
|
|
101
|
+
|
|
102
|
+
@override
|
|
103
|
+
def transform(self, data: ForecastDataset) -> ForecastDataset:
|
|
104
|
+
"""Apply fitted corrections without changing quantile ordering."""
|
|
105
|
+
if not self._is_fitted:
|
|
106
|
+
raise NotFittedError(self.__class__.__name__)
|
|
107
|
+
|
|
108
|
+
predictions = data.data.copy(deep=False)
|
|
109
|
+
for column, correction in self._corrections.items():
|
|
110
|
+
if column in predictions.columns:
|
|
111
|
+
predictions[column] = predictions[column] + correction
|
|
112
|
+
return data._copy_with_data(data=predictions) # noqa: SLF001 - safe transform copy
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
__all__ = ["ConformalizedQuantileCalibrator"]
|
|
@@ -201,7 +201,7 @@ def _get_extreme_days(
|
|
|
201
201
|
raise TypeError("target_series must have a DatetimeIndex.")
|
|
202
202
|
|
|
203
203
|
# Compute daily min and max once
|
|
204
|
-
daily_agg: pd.DataFrame = target_series.resample("1D").agg(["min", "max"])
|
|
204
|
+
daily_agg: pd.DataFrame = target_series.resample("1D").agg(["min", "max"])
|
|
205
205
|
n_days = len(daily_agg)
|
|
206
206
|
n_extremes = max(int(fraction * n_days), 2)
|
|
207
207
|
|
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
from typing import TYPE_CHECKING, cast
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
|
+
from mlflow import MlflowClient
|
|
10
11
|
|
|
11
12
|
from openstef_core.mixins import HyperParams, Stateful
|
|
12
13
|
from openstef_models.integrations.mlflow import MLFlowStorage
|
|
@@ -49,6 +50,21 @@ def model_id() -> str:
|
|
|
49
50
|
return "test_model_123"
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
def test_construct_from_mlflow(tmp_path: Path, model_id: str):
|
|
54
|
+
"""Test that experiment_name_prefix is prepended to experiment names."""
|
|
55
|
+
# Arrange
|
|
56
|
+
mlflow_client = MlflowClient(tracking_uri=f"sqlite:///{tmp_path / 'mlflow.db'}")
|
|
57
|
+
|
|
58
|
+
# Act
|
|
59
|
+
storage = MLFlowStorage(
|
|
60
|
+
tracking_uri=f"sqlite:///{tmp_path / 'mlflow.db'}",
|
|
61
|
+
client=mlflow_client,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Assert
|
|
65
|
+
assert storage._client is mlflow_client
|
|
66
|
+
|
|
67
|
+
|
|
52
68
|
def test_create_run(storage: MLFlowStorage, model_id: str):
|
|
53
69
|
"""Test that create_run converts HyperParams to MLflow parameters."""
|
|
54
70
|
# Arrange
|
|
@@ -194,12 +210,15 @@ def test_search_run__returns_matching_run(storage: MLFlowStorage, model_id: str)
|
|
|
194
210
|
pytest.param("https://mlflow.example.com:5000", "https://mlflow.example.com:5000", id="https-port"),
|
|
195
211
|
pytest.param("sqlite:///mlflow.db", "sqlite:///mlflow.db", id="sqlite"),
|
|
196
212
|
pytest.param("postgresql://user:pass@host/db", "postgresql://user:pass@host/db", id="postgresql"),
|
|
213
|
+
pytest.param("databricks", "databricks", id="databricks"),
|
|
197
214
|
],
|
|
198
215
|
)
|
|
199
216
|
def test_normalize_tracking_uri(uri: str, expected: str):
|
|
200
217
|
"""Local paths become file:/// URIs; remote/database URIs pass through unchanged."""
|
|
201
218
|
# Act
|
|
202
|
-
result = normalize_tracking_uri(
|
|
219
|
+
result = normalize_tracking_uri(
|
|
220
|
+
uri, exceptions=["databricks"]
|
|
221
|
+
) # databricks is a special case that should not be normalized
|
|
203
222
|
|
|
204
223
|
# Assert
|
|
205
224
|
if expected == "file:///":
|
|
@@ -30,7 +30,7 @@ def sample_forecast_input_dataset() -> ForecastInputDataset:
|
|
|
30
30
|
"feature2": feature_2,
|
|
31
31
|
"feature3": feature_3,
|
|
32
32
|
},
|
|
33
|
-
index=pd.date_range(start=start_date, periods=num_samples, freq="
|
|
33
|
+
index=pd.date_range(start=start_date, periods=num_samples, freq="1D"),
|
|
34
34
|
),
|
|
35
35
|
sample_interval=timedelta(days=1),
|
|
36
36
|
target_column="load",
|
|
@@ -158,7 +158,12 @@ def test_median_handles_all_missing_data():
|
|
|
158
158
|
{
|
|
159
159
|
"quantile_P50": [],
|
|
160
160
|
},
|
|
161
|
-
index
|
|
161
|
+
# Slice `index` rather than building a bare `DatetimeIndex([], freq="h")` so the
|
|
162
|
+
# (empty) expected index shares its dtype resolution with the one produced by the
|
|
163
|
+
# real prediction pipeline (pandas infers datetime64 resolution from context, and
|
|
164
|
+
# an index with no data to infer from does not necessarily match one derived from
|
|
165
|
+
# actual timestamps).
|
|
166
|
+
index=index[:0],
|
|
162
167
|
),
|
|
163
168
|
sample_interval=training_input_data.sample_interval,
|
|
164
169
|
forecast_start=training_input_data.forecast_start,
|
|
@@ -33,7 +33,7 @@ def sample_forecast_input_dataset() -> TimeSeriesDataset:
|
|
|
33
33
|
"feature2": feature_2,
|
|
34
34
|
"feature3": feature_3,
|
|
35
35
|
},
|
|
36
|
-
index=pd.date_range(start=start_date, periods=num_samples, freq="
|
|
36
|
+
index=pd.date_range(start=start_date, periods=num_samples, freq="1D"),
|
|
37
37
|
),
|
|
38
38
|
sample_interval=timedelta(days=1),
|
|
39
39
|
)
|
openstef_models-4.3.0/tests/unit/transforms/postprocessing/test_conformalized_quantile_calibrator.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Contributors to the OpenSTEF project <openstef@lfenergy.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from openstef_core.datasets import ForecastDataset
|
|
10
|
+
from openstef_core.types import Quantile
|
|
11
|
+
from openstef_models.transforms.postprocessing import ConformalizedQuantileCalibrator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _dataset(predictions: np.ndarray, actuals: np.ndarray) -> ForecastDataset:
|
|
15
|
+
"""Build a forecast dataset with P10, P50, and P90 columns."""
|
|
16
|
+
return ForecastDataset(
|
|
17
|
+
data=pd.DataFrame(
|
|
18
|
+
{
|
|
19
|
+
"quantile_P10": predictions[:, 0],
|
|
20
|
+
"quantile_P50": predictions[:, 1],
|
|
21
|
+
"quantile_P90": predictions[:, 2],
|
|
22
|
+
"load": actuals,
|
|
23
|
+
},
|
|
24
|
+
index=pd.date_range("2025-01-01", periods=len(actuals), freq="h"),
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_conformalized_calibrator_applies_reference_tail_corrections() -> None:
|
|
30
|
+
"""Test asymmetric lower and upper corrections while preserving the median."""
|
|
31
|
+
predictions = np.column_stack([np.zeros(100), np.full(100, 5.0), np.full(100, 10.0)])
|
|
32
|
+
actuals = np.concatenate([np.full(90, 1.0), np.full(10, 20.0)])
|
|
33
|
+
calibration = _dataset(predictions, actuals)
|
|
34
|
+
forecast = _dataset(predictions[:2], np.full(2, 5.0))
|
|
35
|
+
|
|
36
|
+
calibrator = ConformalizedQuantileCalibrator(quantiles=[Quantile(0.1), Quantile(0.5), Quantile(0.9)])
|
|
37
|
+
calibrator.fit(calibration)
|
|
38
|
+
result = calibrator.transform(forecast)
|
|
39
|
+
|
|
40
|
+
np.testing.assert_allclose(result.data["quantile_P10"], [1.0, 1.0])
|
|
41
|
+
np.testing.assert_allclose(result.data["quantile_P50"], [5.0, 5.0])
|
|
42
|
+
np.testing.assert_allclose(result.data["quantile_P90"], [20.0, 20.0])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_conformalized_calibrator_can_calibrate_the_median_when_enabled() -> None:
|
|
46
|
+
"""Test that median calibration is explicitly opt-in."""
|
|
47
|
+
predictions = np.column_stack([np.zeros(100), np.zeros(100), np.full(100, 10.0)])
|
|
48
|
+
actuals = np.full(100, 5.0)
|
|
49
|
+
calibration = _dataset(predictions, actuals)
|
|
50
|
+
forecast = _dataset(predictions[:1], np.full(1, 5.0))
|
|
51
|
+
|
|
52
|
+
calibrator = ConformalizedQuantileCalibrator(
|
|
53
|
+
quantiles=[Quantile(0.1), Quantile(0.5), Quantile(0.9)],
|
|
54
|
+
conformalize_median=True,
|
|
55
|
+
)
|
|
56
|
+
calibrator.fit(calibration)
|
|
57
|
+
result = calibrator.transform(forecast)
|
|
58
|
+
|
|
59
|
+
np.testing.assert_allclose(result.data["quantile_P50"], [5.0])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_conformalized_calibrator_skips_short_calibration_windows() -> None:
|
|
63
|
+
"""Test that too few calibration samples leave forecasts unchanged."""
|
|
64
|
+
predictions = np.column_stack([np.zeros(2), np.full(2, 5.0), np.full(2, 10.0)])
|
|
65
|
+
actuals = np.full(2, 20.0)
|
|
66
|
+
calibration = _dataset(predictions, actuals)
|
|
67
|
+
forecast = _dataset(predictions[:1], np.full(1, 5.0))
|
|
68
|
+
|
|
69
|
+
calibrator = ConformalizedQuantileCalibrator(
|
|
70
|
+
quantiles=[Quantile(0.1), Quantile(0.5), Quantile(0.9)],
|
|
71
|
+
min_calibration_samples=3,
|
|
72
|
+
)
|
|
73
|
+
calibrator.fit(calibration)
|
|
74
|
+
result = calibrator.transform(forecast)
|
|
75
|
+
|
|
76
|
+
np.testing.assert_allclose(result.data["quantile_P10"], [0.0])
|
|
77
|
+
np.testing.assert_allclose(result.data["quantile_P90"], [10.0])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_conformalized_calibrator_skips_only_sparse_quantiles(caplog: pytest.LogCaptureFixture) -> None:
|
|
81
|
+
"""Test that sparse quantiles are skipped while sufficiently populated ones calibrate."""
|
|
82
|
+
predictions = np.column_stack(
|
|
83
|
+
[
|
|
84
|
+
np.concatenate([np.zeros(100), [np.nan]]),
|
|
85
|
+
np.full(101, 5.0),
|
|
86
|
+
np.full(101, 10.0),
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
actuals = np.full(101, 20.0)
|
|
90
|
+
calibration = _dataset(predictions, actuals)
|
|
91
|
+
forecast = _dataset(np.array([[0.0, 5.0, 10.0]]), np.array([5.0]))
|
|
92
|
+
|
|
93
|
+
calibrator = ConformalizedQuantileCalibrator(
|
|
94
|
+
quantiles=[Quantile(0.1), Quantile(0.5), Quantile(0.9)],
|
|
95
|
+
min_calibration_samples=101,
|
|
96
|
+
)
|
|
97
|
+
calibrator.fit(calibration)
|
|
98
|
+
result = calibrator.transform(forecast)
|
|
99
|
+
|
|
100
|
+
np.testing.assert_allclose(result.data["quantile_P10"], [0.0])
|
|
101
|
+
np.testing.assert_allclose(result.data["quantile_P90"], [20.0])
|
|
102
|
+
assert "Skipping calibration for quantile quantile_P10" in caplog.text
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_conformalized_calibrator_skips_quantiles_missing_from_data() -> None:
|
|
106
|
+
"""Test that configured quantiles absent from the data are skipped."""
|
|
107
|
+
calibration = ForecastDataset(
|
|
108
|
+
data=pd.DataFrame(
|
|
109
|
+
{
|
|
110
|
+
"quantile_P90": np.full(2, 10.0),
|
|
111
|
+
"load": np.full(2, 20.0),
|
|
112
|
+
},
|
|
113
|
+
index=pd.date_range("2025-01-01", periods=2, freq="h"),
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
calibrator = ConformalizedQuantileCalibrator(
|
|
117
|
+
quantiles=[Quantile(0.1), Quantile(0.9)],
|
|
118
|
+
min_calibration_samples=1,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
calibrator.fit(calibration)
|
|
122
|
+
result = calibrator.transform(calibration)
|
|
123
|
+
|
|
124
|
+
np.testing.assert_allclose(result.data["quantile_P90"], [20.0, 20.0])
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_conformalized_calibrator_does_not_sort_quantiles() -> None:
|
|
128
|
+
"""Test that ordering remains the responsibility of a downstream sorter."""
|
|
129
|
+
predictions = np.column_stack([np.full(2, 10.0), np.zeros(2), np.full(2, 5.0)])
|
|
130
|
+
actuals = np.full(2, 5.0)
|
|
131
|
+
calibration = _dataset(predictions, actuals)
|
|
132
|
+
forecast = _dataset(predictions[:1], np.full(1, 5.0))
|
|
133
|
+
|
|
134
|
+
calibrator = ConformalizedQuantileCalibrator(
|
|
135
|
+
quantiles=[Quantile(0.1), Quantile(0.5), Quantile(0.9)],
|
|
136
|
+
min_calibration_samples=1,
|
|
137
|
+
)
|
|
138
|
+
calibrator.fit(calibration)
|
|
139
|
+
result = calibrator.transform(forecast)
|
|
140
|
+
|
|
141
|
+
np.testing.assert_allclose(result.data.filter(like="quantile_").to_numpy(), [[5.0, 0.0, 5.0]])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/explainability/__init__.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/explainability/mixins.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/__init__.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/joblib/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/mlflow/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/optuna/__init__.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/integrations/optuna/tuner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/mixins/model_serializer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting/forecaster.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/models/forecasting_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/flagger.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/imputer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/scaler.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/selector.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/transforms/general/shifter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/evaluation_functions.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/utils/feature_selection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/workflows/callbacks/__init__.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/src/openstef_models/workflows/callbacks/data_save.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/explainability/plotters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/integrations/optuna/test_tuner.py
RENAMED
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/models/component_splitting/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/energy_domain/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_flagger.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_imputer.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_nan_dropper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_scaler.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_selector.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/general/test_shifter.py
RENAMED
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/postprocessing/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/time_domain/test_lags_adder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/validation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/transforms/weather_domain/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openstef_models-4.2.2 → openstef_models-4.3.0}/tests/unit/utils/test_multi_quantile_regressor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|