diff --git a/extreme_estimator/estimator/abstract_estimator.py b/extreme_estimator/estimator/abstract_estimator.py index 3cd9220683c5c2c5d6a69ac3fa1d5ca9bd5eeb19..0b938d36253cfd0966cfcd0ac9cdffc77ce0a38f 100644 --- a/extreme_estimator/estimator/abstract_estimator.py +++ b/extreme_estimator/estimator/abstract_estimator.py @@ -1,5 +1,6 @@ import time +from extreme_estimator.extreme_models.result_from_fit import ResultFromFit from extreme_estimator.extreme_models.margin_model.margin_function.abstract_margin_function import \ AbstractMarginFunction from extreme_estimator.extreme_models.margin_model.margin_function.linear_margin_function import LinearMarginFunction @@ -18,7 +19,7 @@ class AbstractEstimator(object): def __init__(self, dataset: AbstractDataset): self.dataset = dataset # type: AbstractDataset self.additional_information = dict() - self._params_fitted = None + self._result_from_fit = None # type: ResultFromFit self._margin_function_fitted = None self._max_stable_model_fitted = None @@ -34,9 +35,9 @@ class AbstractEstimator(object): self.additional_information[self.DURATION] = int((te - ts) * 1000) @property - def params_fitted(self): + def fitted_values(self): assert self.is_fitted - return self._params_fitted + return self._result_from_fit.fitted_values # @property # def max_stable_fitted(self) -> AbstractMarginFunction: @@ -57,7 +58,7 @@ class AbstractEstimator(object): @property def is_fitted(self): - return self._params_fitted is not None + return self._result_from_fit is not None @property def train_split(self): diff --git a/extreme_estimator/estimator/full_estimator/abstract_full_estimator.py b/extreme_estimator/estimator/full_estimator/abstract_full_estimator.py index 70259c5237572b5aab842ad488891882516aff13..16652bcece59d58a8b5b599485c51e53e61e80d5 100644 --- a/extreme_estimator/estimator/full_estimator/abstract_full_estimator.py +++ b/extreme_estimator/estimator/full_estimator/abstract_full_estimator.py @@ -55,7 +55,7 @@ class FullEstimatorInASingleStepWithSmoothMargin(AbstractFullEstimator): def _fit(self): # Estimate both the margin and the max-stable structure - self._params_fitted = self.max_stable_model.fitmaxstab( + self._result_from_fit = self.max_stable_model.fitmaxstab( maxima_gev=self.dataset.maxima_gev(split=self.train_split), df_coordinates=self.dataset.df_coordinates(split=self.train_split), fit_marge=True, @@ -63,7 +63,7 @@ class FullEstimatorInASingleStepWithSmoothMargin(AbstractFullEstimator): margin_start_dict=self.linear_margin_function_to_fit.coef_dict ) # Create the fitted margin function - self.extract_fitted_models_from_fitted_params(self.linear_margin_function_to_fit, self._params_fitted) + self.extract_fitted_models_from_fitted_params(self.linear_margin_function_to_fit, self.fitted_values) class PointwiseAndThenUnitaryMsp(AbstractFullEstimator): diff --git a/extreme_estimator/estimator/margin_estimator/abstract_margin_estimator.py b/extreme_estimator/estimator/margin_estimator/abstract_margin_estimator.py index d79fcec2e475ab3af8f594fb242ceb366a32fa3b..f9b369e7085604aa4479e6a8569d7f3a826db8fe 100644 --- a/extreme_estimator/estimator/margin_estimator/abstract_margin_estimator.py +++ b/extreme_estimator/estimator/margin_estimator/abstract_margin_estimator.py @@ -37,8 +37,8 @@ class SmoothMarginEstimator(AbstractMarginEstimator): maxima_gev = self.dataset.maxima_gev(split=self.train_split) df_coordinates_spatial = self.dataset.coordinates.df_spatial_coordinates(self.train_split) df_coordinates_temporal = self.dataset.coordinates.df_temporal_coordinates(self.train_split) - self._params_fitted = self.margin_model.fitmargin_from_maxima_gev(maxima_gev=maxima_gev, - df_coordinates_spatial=df_coordinates_spatial, - df_coordinates_temporal=df_coordinates_temporal) - self.extract_fitted_models_from_fitted_params(self.margin_model.margin_function_start_fit, self._params_fitted) + self._result_from_fit = self.margin_model.fitmargin_from_maxima_gev(maxima_gev=maxima_gev, + df_coordinates_spatial=df_coordinates_spatial, + df_coordinates_temporal=df_coordinates_temporal) + self.extract_fitted_models_from_fitted_params(self.margin_model.margin_function_start_fit, self.fitted_values) assert isinstance(self.margin_function_fitted, AbstractMarginFunction) diff --git a/extreme_estimator/estimator/max_stable_estimator/abstract_max_stable_estimator.py b/extreme_estimator/estimator/max_stable_estimator/abstract_max_stable_estimator.py index 9fe8d2e8cf4d7ca1bb468bbf945aaf919c5ecc8a..9016613e718e26c6a25939c815cb674067f7398b 100644 --- a/extreme_estimator/estimator/max_stable_estimator/abstract_max_stable_estimator.py +++ b/extreme_estimator/estimator/max_stable_estimator/abstract_max_stable_estimator.py @@ -18,9 +18,10 @@ class MaxStableEstimator(AbstractMaxStableEstimator): def _fit(self): assert self.dataset.maxima_frech(split=self.train_split) is not None - self.max_stable_params_fitted = self.max_stable_model.fitmaxstab( + self._result_from_fit = self.max_stable_model.fitmaxstab( maxima_frech=self.dataset.maxima_frech(split=self.train_split), df_coordinates=self.dataset.df_coordinates(split=self.train_split)) + self.max_stable_params_fitted = self.fitted_values def _error(self, true_max_stable_params: dict): absolute_errors = {param_name: np.abs(param_true_value - self.max_stable_params_fitted[param_name]) diff --git a/extreme_estimator/extreme_models/margin_model/smooth_margin_model.py b/extreme_estimator/extreme_models/margin_model/smooth_margin_model.py index 5622ad5f3ce0c44192c0564d5e0d7d3ab5c32834..0fa0e34b4d756cf378ba73143c724ee6efc79b7e 100644 --- a/extreme_estimator/extreme_models/margin_model/smooth_margin_model.py +++ b/extreme_estimator/extreme_models/margin_model/smooth_margin_model.py @@ -1,12 +1,11 @@ -from typing import Dict - import numpy as np import pandas as pd +from extreme_estimator.extreme_models.result_from_fit import ResultFromFit from extreme_estimator.extreme_models.margin_model.abstract_margin_model import AbstractMarginModel from extreme_estimator.extreme_models.margin_model.margin_function.linear_margin_function import LinearMarginFunction from extreme_estimator.extreme_models.margin_model.param_function.linear_coef import LinearCoef -from extreme_estimator.extreme_models.utils import safe_run_r_estimator, r, retrieve_fitted_values, get_coord, \ +from extreme_estimator.extreme_models.utils import safe_run_r_estimator, r, get_coord, \ get_margin_formula from extreme_estimator.margin_fits.gev.gev_params import GevParams @@ -59,7 +58,7 @@ class LinearMarginModel(AbstractMarginModel): return cls(coordinates, params_sample=params, params_start_fit=params) def fitmargin_from_maxima_gev(self, maxima_gev: np.ndarray, df_coordinates_spatial: pd.DataFrame, - df_coordinates_temporal: pd.DataFrame) -> Dict[str, float]: + df_coordinates_temporal: pd.DataFrame) -> ResultFromFit: # The reshaping on the line below is only valid if we have a single observation per spatio-temporal point if maxima_gev.shape[1] == 1: maxima_gev = maxima_gev.reshape([len(df_coordinates_temporal), len(df_coordinates_spatial)]) @@ -75,9 +74,8 @@ class LinearMarginModel(AbstractMarginModel): coef_dict = self.margin_function_start_fit.coef_dict fit_params['start'] = r.list(**coef_dict) - res = safe_run_r_estimator(function=r.fitspatgev, use_start=self.use_start_value, data=data, - covariables=covariables, **fit_params) - return retrieve_fitted_values(res) + return safe_run_r_estimator(function=r.fitspatgev, use_start=self.use_start_value, data=data, + covariables=covariables, **fit_params) class ConstantMarginModel(LinearMarginModel): diff --git a/extreme_estimator/extreme_models/max_stable_model/abstract_max_stable_model.py b/extreme_estimator/extreme_models/max_stable_model/abstract_max_stable_model.py index 78ae219a174b9cd846fbc218196066789af4723d..231a46a85ed11f0fef1c8cf0d0bf86b0f2d35ed4 100644 --- a/extreme_estimator/extreme_models/max_stable_model/abstract_max_stable_model.py +++ b/extreme_estimator/extreme_models/max_stable_model/abstract_max_stable_model.py @@ -2,10 +2,10 @@ from enum import Enum import numpy as np import pandas as pd -import rpy2.robjects as robjects from extreme_estimator.extreme_models.abstract_model import AbstractModel -from extreme_estimator.extreme_models.utils import r, safe_run_r_estimator, retrieve_fitted_values, get_coord, \ +from extreme_estimator.extreme_models.result_from_fit import ResultFromFit +from extreme_estimator.extreme_models.utils import r, safe_run_r_estimator, get_coord, \ get_margin_formula from spatio_temporal_dataset.coordinates.abstract_coordinates import AbstractCoordinates @@ -21,8 +21,7 @@ class AbstractMaxStableModel(AbstractModel): return {'cov.mod': self.cov_mod} def fitmaxstab(self, df_coordinates: pd.DataFrame, maxima_frech: np.ndarray = None, maxima_gev: np.ndarray = None, - fit_marge=False, - fit_marge_form_dict=None, margin_start_dict=None) -> dict: + fit_marge=False, fit_marge_form_dict=None, margin_start_dict=None) -> ResultFromFit: assert isinstance(df_coordinates, pd.DataFrame) if fit_marge: assert fit_marge_form_dict is not None @@ -63,8 +62,7 @@ class AbstractMaxStableModel(AbstractModel): fit_params['fit.marge'] = fit_marge # Run the fitmaxstab in R - res = safe_run_r_estimator(function=r.fitmaxstab, use_start=self.use_start_value, data=data, coord=coord, **fit_params) - return retrieve_fitted_values(res) + return safe_run_r_estimator(function=r.fitmaxstab, use_start=self.use_start_value, data=data, coord=coord, **fit_params) def rmaxstab(self, nb_obs: int, coordinates_values: np.ndarray) -> np.ndarray: """ diff --git a/extreme_estimator/extreme_models/result_from_fit.py b/extreme_estimator/extreme_models/result_from_fit.py new file mode 100644 index 0000000000000000000000000000000000000000..b8a8d7d18378e5af351dd3f683886b3939c89451 --- /dev/null +++ b/extreme_estimator/extreme_models/result_from_fit.py @@ -0,0 +1,31 @@ +from typing import Dict + +from rpy2 import robjects + + +class ResultFromFit(object): + """ + Handler from any result with the result of a fit functions from the package Spatial Extreme + """ + FITTED_VALUES_NAME = 'fitted.values' + CONVERGENCE_NAME = 'convergence' + + def __init__(self, result_from_fit: robjects.ListVector) -> None: + if hasattr(result_from_fit, 'names'): + self.name_to_value = {name: result_from_fit.rx2(name) for name in result_from_fit.names} + else: + self.name_to_value = {} + + @property + def names(self): + return self.name_to_value.keys() + + @property + def convergence(self): + convergence_value = self.name_to_value[self.CONVERGENCE_NAME] + return convergence_value + + @property + def fitted_values(self) -> Dict[str, float]: + fitted_values = self.name_to_value[self.FITTED_VALUES_NAME] + return {key: fitted_values.rx2(key)[0] for key in fitted_values.names} diff --git a/extreme_estimator/extreme_models/utils.py b/extreme_estimator/extreme_models/utils.py index d404a9940a10f474171a1f4954255e66a716725d..057dfdfb6663d2973800c3717088ccf556f7d965 100644 --- a/extreme_estimator/extreme_models/utils.py +++ b/extreme_estimator/extreme_models/utils.py @@ -16,6 +16,8 @@ from rpy2.rinterface._rinterface import RRuntimeError from rpy2.robjects import numpy2ri from rpy2.robjects import pandas2ri +from extreme_estimator.extreme_models.result_from_fit import ResultFromFit + r = ro.R() numpy2ri.activate() pandas2ri.activate() @@ -41,7 +43,7 @@ class WarningMaximumAbsoluteValueTooHigh(Warning): pass -def safe_run_r_estimator(function, data, use_start=False, threshold_max_abs_value=100, **parameters): +def safe_run_r_estimator(function, data, use_start=False, threshold_max_abs_value=100, **parameters) -> ResultFromFit: # Raise warning if the maximum absolute value is above a threshold assert isinstance(data, np.ndarray) maximum_absolute_value = np.max(np.abs(data)) @@ -54,6 +56,7 @@ def safe_run_r_estimator(function, data, use_start=False, threshold_max_abs_valu # First run without using start value # Then if it crashes, use start value run_successful = False + res = None while not run_successful: current_parameter = parameters.copy() if not use_start and 'start' in current_parameter: @@ -70,15 +73,7 @@ def safe_run_r_estimator(function, data, use_start=False, threshold_max_abs_valu if isinstance(e, RRuntimeWarning): print(e.__repr__()) print('WARNING') - return res - - -def retrieve_fitted_values(res: robjects.ListVector) -> Dict[str, float]: - # todo: maybe if the convergence was not successful I could try other starting point several times - # Retrieve the resulting fitted values - fitted_values = res.rx2('fitted.values') - fitted_values = {key: fitted_values.rx2(key)[0] for key in fitted_values.names} - return fitted_values + return ResultFromFit(res) def get_coord(df_coordinates: pd.DataFrame): diff --git a/test/test_unitary/test_fitmaxstab/test_fitmaxstab_with_margin.py b/test/test_unitary/test_fitmaxstab/test_fitmaxstab_with_margin.py index 4246cb2785aed58bb110fcc817e26836c4db8a81..a4e29995136747183cfb9ac6a30b2d8be7e3c9b0 100644 --- a/test/test_unitary/test_fitmaxstab/test_fitmaxstab_with_margin.py +++ b/test/test_unitary/test_fitmaxstab/test_fitmaxstab_with_margin.py @@ -31,7 +31,7 @@ class TestMaxStableFitWithConstantMargin(TestUnitaryAbstract): full_estimator = FullEstimatorInASingleStepWithSmoothMargin(dataset, margin_model, max_stable_model) full_estimator.fit() - return full_estimator.params_fitted + return full_estimator.fitted_values def test_max_stable_fit_with_constant_margin(self): self.compare() @@ -59,7 +59,7 @@ class TestMaxStableFitWithLinearMargin(TestUnitaryAbstract): full_estimator = FullEstimatorInASingleStepWithSmoothMargin(dataset, margin_model, max_stable_model) full_estimator.fit() - return full_estimator.params_fitted + return full_estimator.fitted_values def test_max_stable_fit_with_linear_margin(self): self.compare()