From eea97371b166e423739745d9dacb79ed7d072594 Mon Sep 17 00:00:00 2001
From: Le Roux Erwan <erwan.le-roux@irstea.fr>
Date: Tue, 7 May 2019 18:08:58 +0200
Subject: [PATCH] [SCM][NON STATIONARITY] add non stationary trend object. to
 create specific plot for this topic

---
 .../main_study_visualizer.py                  | 11 ++-
 .../non_stationary_trends.py                  | 79 +++++++++++++++++++
 .../study_visualization/study_visualizer.py   | 32 ++++++--
 .../margin_model/linear_margin_model.py       |  2 +-
 .../parametric_margin_function.py             |  1 -
 .../extreme_models/result_from_fit.py         | 19 ++++-
 .../test_estimator/test_margin_estimators.py  |  1 +
 7 files changed, 135 insertions(+), 10 deletions(-)
 create mode 100644 experiment/meteo_france_SCM_study/visualization/study_visualization/non_stationary_trends.py

diff --git a/experiment/meteo_france_SCM_study/visualization/study_visualization/main_study_visualizer.py b/experiment/meteo_france_SCM_study/visualization/study_visualization/main_study_visualizer.py
index 19a01c23..17ab7a99 100644
--- a/experiment/meteo_france_SCM_study/visualization/study_visualization/main_study_visualizer.py
+++ b/experiment/meteo_france_SCM_study/visualization/study_visualization/main_study_visualizer.py
@@ -95,9 +95,18 @@ def complete_analysis(only_first_one=False):
                 study_visualizer.visualize_linear_margin_fit()
 
 
+def trend_analysis():
+    save_to_file = False
+    only_first_one = True
+    for study_class in [CrocusDepth, SafranSnowfall, SafranRainfall, SafranTemperature][1:2]:
+        for study in study_iterator(study_class, only_first_one=only_first_one):
+            study_visualizer = StudyVisualizer(study, save_to_file=save_to_file)
+            study_visualizer.visualize_temporal_trend_relevance(complete_analysis=False)
+
 if __name__ == '__main__':
     # annual_mean_vizu_compare_durand_study(safran=True, take_mean_value=True, altitude=2100)
-    normal_visualization(temporal_non_stationarity=True)
+    # normal_visualization(temporal_non_stationarity=True)
+    trend_analysis()
     # max_stable_process_vizu_compare_gaume_study(altitude=1800, nb_days=1)
     # extended_visualization()
     # complete_analysis()
diff --git a/experiment/meteo_france_SCM_study/visualization/study_visualization/non_stationary_trends.py b/experiment/meteo_france_SCM_study/visualization/study_visualization/non_stationary_trends.py
new file mode 100644
index 00000000..89fe4351
--- /dev/null
+++ b/experiment/meteo_france_SCM_study/visualization/study_visualization/non_stationary_trends.py
@@ -0,0 +1,79 @@
+from extreme_estimator.estimator.abstract_estimator import AbstractEstimator
+from scipy.stats import chi2
+from extreme_estimator.estimator.full_estimator.abstract_full_estimator import \
+    FullEstimatorInASingleStepWithSmoothMargin
+from extreme_estimator.estimator.margin_estimator.abstract_margin_estimator import LinearMarginEstimator
+from extreme_estimator.extreme_models.margin_model.linear_margin_model import \
+    LinearAllParametersTwoFirstCoordinatesMarginModel, LinearAllTwoStatialCoordinatesLocationLinearMarginModel, \
+    LinearStationaryMarginModel, LinearNonStationaryLocationMarginModel
+from spatio_temporal_dataset.dataset.abstract_dataset import AbstractDataset
+
+
+class AbstractNonStationaryTrendTest(object):
+    RESULT_ATTRIBUTE_METRIC = 'deviance'
+
+    def __init__(self, dataset: AbstractDataset, estimator_class,
+                 stationary_margin_model_class, non_stationary_margin_model_class):
+        self.dataset = dataset
+        self.estimator_class = estimator_class
+        self.stationary_margin_model_class = stationary_margin_model_class
+        self.non_stationary_margin_model_class = non_stationary_margin_model_class
+
+    def load_estimator(self, margin_model) -> AbstractEstimator:
+        return self.estimator_class(self.dataset, margin_model)
+
+    def get_metric(self, margin_model_class, starting_point):
+        margin_model = margin_model_class(coordinates=self.dataset.coordinates, starting_point=starting_point)
+        estimator = self.load_estimator(margin_model)   # type: AbstractEstimator
+        estimator.fit()
+        metric = estimator.result_from_fit.__getattribute__(self.RESULT_ATTRIBUTE_METRIC)
+        assert isinstance(metric, float)
+        return metric
+
+    def visualize(self, ax, complete_analysis=True):
+        # Define the year_min and year_max for the starting point
+        if complete_analysis:
+            year_min, year_max, step = 1960, 1990, 1
+        else:
+            year_min, year_max, step = 1960, 1990, 10
+        # Fit the stationary model
+        stationary_metric = self.get_metric(self.stationary_margin_model_class, starting_point=None)
+        # Fit the non stationary model
+        years = list(range(year_min, year_max + 1, step))
+        non_stationary_metrics = [self.get_metric(self.non_stationary_margin_model_class, year) for year in years]
+        difference = [m - stationary_metric for m in non_stationary_metrics]
+        # Plot some lines
+        ax.axhline(y=0, xmin=year_min, xmax=year_max)
+        # Significative line
+        significative_deviance = chi2.ppf(q=0.95, df=1)
+        ax.axhline(y=significative_deviance, xmin=year_min, xmax=year_max)
+        # todo: plot the line corresponding to the significance 0.05
+        ax.plot(years, difference, 'ro-')
+
+
+class IndependenceLocationTrendTest(AbstractNonStationaryTrendTest):
+
+    def __init__(self, dataset, coordinate_idx):
+        pass
+
+
+class ConditionalIndedendenceLocationTrendTest(AbstractNonStationaryTrendTest):
+
+    def __init__(self, dataset):
+        super().__init__(dataset=dataset,
+                         estimator_class=LinearMarginEstimator,
+                         stationary_margin_model_class=LinearStationaryMarginModel,
+                         non_stationary_margin_model_class=LinearNonStationaryLocationMarginModel)
+
+
+class MaxStableLocationTrendTest(AbstractNonStationaryTrendTest):
+
+    def __init__(self, dataset, max_stable_model):
+        super().__init__(dataset=dataset,
+                         estimator_class=FullEstimatorInASingleStepWithSmoothMargin,
+                         stationary_margin_model_class=LinearStationaryMarginModel,
+                         non_stationary_margin_model_class=LinearNonStationaryLocationMarginModel)
+        self.max_stable_model = max_stable_model
+
+    def load_estimator(self, margin_model) -> AbstractEstimator:
+        return self.estimator_class(self.dataset, margin_model, self.max_stable_model)
diff --git a/experiment/meteo_france_SCM_study/visualization/study_visualization/study_visualizer.py b/experiment/meteo_france_SCM_study/visualization/study_visualization/study_visualizer.py
index 94e46bcd..ea9d5b5c 100644
--- a/experiment/meteo_france_SCM_study/visualization/study_visualization/study_visualizer.py
+++ b/experiment/meteo_france_SCM_study/visualization/study_visualization/study_visualizer.py
@@ -9,13 +9,15 @@ import pandas as pd
 import seaborn as sns
 
 from experiment.meteo_france_SCM_study.abstract_study import AbstractStudy
+from experiment.meteo_france_SCM_study.visualization.study_visualization.non_stationary_trends import \
+    ConditionalIndedendenceLocationTrendTest, MaxStableLocationTrendTest
 from experiment.meteo_france_SCM_study.visualization.utils import create_adjusted_axes
 from experiment.utils import average_smoothing_with_sliding_window
 from extreme_estimator.estimator.full_estimator.abstract_full_estimator import \
     FullEstimatorInASingleStepWithSmoothMargin
 from extreme_estimator.estimator.margin_estimator.abstract_margin_estimator import LinearMarginEstimator
 from extreme_estimator.extreme_models.margin_model.linear_margin_model import LinearAllParametersAllDimsMarginModel, \
-    LinearNonStationaryMarginModel, LinearStationaryMarginModel
+    LinearNonStationaryLocationMarginModel, LinearStationaryMarginModel
 from extreme_estimator.extreme_models.margin_model.margin_function.abstract_margin_function import \
     AbstractMarginFunction
 from extreme_estimator.extreme_models.margin_model.param_function.param_function import AbstractParamFunction
@@ -51,6 +53,8 @@ class StudyVisualizer(object):
         self._coordinates = None
         self._observations = None
 
+        self.default_covariance_function = CovarianceFunction.powexp
+
         # KDE PLOT ARGUMENTS
         self.vertical_kde_plot = vertical_kde_plot
         self.year_for_kde_plot = year_for_kde_plot
@@ -132,6 +136,25 @@ class StudyVisualizer(object):
             'for the year {}'.format(self.year_for_kde_plot)
         self.show_or_save_to_file()
 
+    def visualize_temporal_trend_relevance(self, complete_analysis):
+        self.temporal_non_stationarity = True
+        trend_tests = [ConditionalIndedendenceLocationTrendTest(self.dataset)]
+
+        max_stable_models = load_test_max_stable_models(default_covariance_function=self.default_covariance_function)
+        for max_stable_model in max_stable_models[:1]:
+            trend_tests.append(MaxStableLocationTrendTest(self.dataset, max_stable_model))
+
+        nb_trend_tests = len(trend_tests)
+        fig, axes = plt.subplots(1, nb_trend_tests, figsize=self.figsize)
+        if nb_trend_tests == 1:
+            axes = [axes]
+        fig.subplots_adjust(hspace=self.subplot_space, wspace=self.subplot_space)
+        for ax, trend_test in zip(axes, trend_tests):
+            trend_test.visualize(ax, complete_analysis=complete_analysis)
+
+        self.plot_name = 'trend tests'
+        self.show_or_save_to_file()
+
     def visualize_experimental_law(self, ax, massif_id):
         # Display the experimental law for a given massif
         all_massif_data = self.get_all_massif_data(massif_id)
@@ -248,15 +271,14 @@ class StudyVisualizer(object):
         pass
 
     def visualize_linear_margin_fit(self, only_first_max_stable=False):
-        default_covariance_function = CovarianceFunction.powexp
-        margin_class = LinearNonStationaryMarginModel if self.temporal_non_stationarity else LinearStationaryMarginModel
+        margin_class = LinearNonStationaryLocationMarginModel if self.temporal_non_stationarity else LinearStationaryMarginModel
         plot_name = 'Full Likelihood with Linear marginals and max stable dependency structure'
         plot_name += '\n(with {} covariance structure when a covariance is needed)'.format(
-            str(default_covariance_function).split('.')[-1])
+            str(self.default_covariance_function).split('.')[-1])
         self.plot_name = plot_name
 
         # Load max stable models
-        max_stable_models = load_test_max_stable_models(default_covariance_function=default_covariance_function)
+        max_stable_models = load_test_max_stable_models(default_covariance_function=self.default_covariance_function)
         if only_first_max_stable:
             # Keep only the BrownResnick model
             max_stable_models = max_stable_models[1:2]
diff --git a/extreme_estimator/extreme_models/margin_model/linear_margin_model.py b/extreme_estimator/extreme_models/margin_model/linear_margin_model.py
index adbb16d9..14b1b7d9 100644
--- a/extreme_estimator/extreme_models/margin_model/linear_margin_model.py
+++ b/extreme_estimator/extreme_models/margin_model/linear_margin_model.py
@@ -127,5 +127,5 @@ class LinearStationaryMarginModel(LinearAllParametersTwoFirstCoordinatesMarginMo
     pass
 
 
-class LinearNonStationaryMarginModel(LinearAllTwoStatialCoordinatesLocationLinearMarginModel):
+class LinearNonStationaryLocationMarginModel(LinearAllTwoStatialCoordinatesLocationLinearMarginModel):
     pass
diff --git a/extreme_estimator/extreme_models/margin_model/margin_function/parametric_margin_function.py b/extreme_estimator/extreme_models/margin_model/margin_function/parametric_margin_function.py
index 7476aa13..09337e6d 100644
--- a/extreme_estimator/extreme_models/margin_model/margin_function/parametric_margin_function.py
+++ b/extreme_estimator/extreme_models/margin_model/margin_function/parametric_margin_function.py
@@ -62,7 +62,6 @@ class ParametricMarginFunction(IndependentMarginFunction):
         raise NotImplementedError
 
     def get_gev_params(self, coordinate: np.ndarray) -> GevParams:
-        print('here get gev', self.starting_point)
         if self.starting_point is not None:
             # Shift temporal coordinate to enable to model temporal trend with starting point
             assert self.coordinates.has_temporal_coordinates
diff --git a/extreme_estimator/extreme_models/result_from_fit.py b/extreme_estimator/extreme_models/result_from_fit.py
index bc01dd8b..669c2f87 100644
--- a/extreme_estimator/extreme_models/result_from_fit.py
+++ b/extreme_estimator/extreme_models/result_from_fit.py
@@ -1,5 +1,6 @@
 from typing import Dict
 
+import numpy as np
 from rpy2 import robjects
 
 from extreme_estimator.extreme_models.margin_model.param_function.linear_coef import LinearCoef
@@ -9,6 +10,7 @@ from spatio_temporal_dataset.coordinates.abstract_coordinates import AbstractCoo
 
 class ResultFromFit(object):
 
+
     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}
@@ -27,6 +29,14 @@ class ResultFromFit(object):
     def margin_coef_dict(self):
         raise NotImplementedError
 
+    @property
+    def nllh(self):
+        raise NotImplementedError
+
+    @property
+    def deviance(self):
+        raise NotImplementedError
+
 
 class ResultFromIsmev(ResultFromFit):
 
@@ -47,7 +57,8 @@ class ResultFromIsmev(ResultFromFit):
             i += 1
             # Add a potential linear temporal trend
             if gev_param_name in self.gev_param_name_to_dim:
-                temporal_coef_name = LinearCoef.coef_template_str(gev_param_name, AbstractCoordinates.COORDINATE_T).format(1)
+                temporal_coef_name = LinearCoef.coef_template_str(gev_param_name,
+                                                                  AbstractCoordinates.COORDINATE_T).format(1)
                 coef_dict[temporal_coef_name] = mle_values[i]
                 i += 1
         return coef_dict
@@ -58,7 +69,7 @@ class ResultFromIsmev(ResultFromFit):
 
     @property
     def nllh(self):
-        return self.res['nllh']
+        return self.name_to_value['nllh']
 
 
 class ResultFromSpatialExtreme(ResultFromFit):
@@ -68,6 +79,10 @@ class ResultFromSpatialExtreme(ResultFromFit):
     FITTED_VALUES_NAME = 'fitted.values'
     CONVERGENCE_NAME = 'convergence'
 
+    @property
+    def deviance(self):
+        return np.array(self.name_to_value['deviance'])[0]
+
     @property
     def convergence(self) -> str:
         convergence_value = self.name_to_value[self.CONVERGENCE_NAME]
diff --git a/test/test_extreme_estimator/test_estimator/test_margin_estimators.py b/test/test_extreme_estimator/test_estimator/test_margin_estimators.py
index 309d8701..11f2813f 100644
--- a/test/test_extreme_estimator/test_estimator/test_margin_estimators.py
+++ b/test/test_extreme_estimator/test_estimator/test_margin_estimators.py
@@ -30,6 +30,7 @@ class TestSmoothMarginEstimator(unittest.TestCase):
                 # Fit estimator
                 estimator = LinearMarginEstimator(dataset=dataset, margin_model=margin_model)
                 estimator.fit()
+                print(estimator.result_from_fit.name_to_value.keys())
                 # Plot
                 if self.DISPLAY:
                     margin_model.margin_function_sample.visualize_function(show=True)
-- 
GitLab