From 1ec6c1a3974db3147e6275f9ae24d0326eb0ab31 Mon Sep 17 00:00:00 2001 From: Le Roux Erwan <erwan.le-roux@irstea.fr> Date: Wed, 29 Apr 2020 17:45:42 +0200 Subject: [PATCH] [contrasting] add snowfall_plot.py --- .../visualization/plot_utils.py | 17 ++++- .../visualization/study_visualizer.py | 1 + .../independent_margin_function.py | 8 +++ .../parametric_margin_function.py | 6 +- .../function/param_function/param_function.py | 5 +- extreme_trend/abstract_gev_trend_test.py | 20 ++++++ .../altitudes_fit/altitudes_studies.py | 18 +---- .../main_snowfall_article.py | 7 +- .../snowfall_plot.py | 72 +++++++++++++++++++ .../study_visualizer_for_mean_values.py | 2 - .../validation_plot.py | 5 +- 11 files changed, 134 insertions(+), 27 deletions(-) create mode 100644 projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/snowfall_plot.py diff --git a/extreme_data/meteo_france_data/scm_models_data/visualization/plot_utils.py b/extreme_data/meteo_france_data/scm_models_data/visualization/plot_utils.py index 341b53ce..c16465d2 100644 --- a/extreme_data/meteo_france_data/scm_models_data/visualization/plot_utils.py +++ b/extreme_data/meteo_france_data/scm_models_data/visualization/plot_utils.py @@ -1,10 +1,25 @@ import matplotlib.pyplot as plt +import matplotlib.colors as mcolors from extreme_data.meteo_france_data.scm_models_data.abstract_study import AbstractStudy from extreme_data.meteo_france_data.scm_models_data.visualization.create_shifted_cmap import \ ticks_values_and_labels_for_percentages, get_shifted_map, get_colors +def plot_against_altitude(altitudes, ax, massif_id, massif_name, values): + di = massif_id // 8 + if di == 0: + linestyle = '-' + elif di == 1: + linestyle = 'dotted' + else: + linestyle = '--' + colors = list(mcolors.TABLEAU_COLORS) + colors[-3:-1] = [] # remove gray and olive + color = colors[massif_id % 8] + ax.plot(altitudes, values, color=color, linewidth=2, label=massif_name, linestyle=linestyle) + + def load_plot(cmap, graduation, label, massif_name_to_value, altitude, fit_method): max_abs_change = max([abs(e) for e in massif_name_to_value.values()]) ticks, labels = ticks_values_and_labels_for_percentages(graduation=graduation, max_abs_change=max_abs_change) @@ -33,4 +48,4 @@ def load_plot(cmap, graduation, label, massif_name_to_value, altitude, fit_metho ax.get_xaxis().set_visible(True) ax.set_xticks([]) ax.set_xlabel('Altitude = {}m'.format(altitude), fontsize=15) - ax.set_title('Fit method is {}'.format(fit_method)) \ No newline at end of file + ax.set_title('Fit method is {}'.format(fit_method)) diff --git a/extreme_data/meteo_france_data/scm_models_data/visualization/study_visualizer.py b/extreme_data/meteo_france_data/scm_models_data/visualization/study_visualizer.py index 38481079..ed322e21 100644 --- a/extreme_data/meteo_france_data/scm_models_data/visualization/study_visualizer.py +++ b/extreme_data/meteo_france_data/scm_models_data/visualization/study_visualizer.py @@ -1,4 +1,5 @@ import os +import matplotlib.colors as mcolors import os.path as op from collections import OrderedDict from multiprocessing.pool import Pool diff --git a/extreme_fit/function/margin_function/independent_margin_function.py b/extreme_fit/function/margin_function/independent_margin_function.py index b1d6b54a..79db8d38 100644 --- a/extreme_fit/function/margin_function/independent_margin_function.py +++ b/extreme_fit/function/margin_function/independent_margin_function.py @@ -30,6 +30,14 @@ class IndependentMarginFunction(AbstractMarginFunction): for param_name, param_function in self.param_name_to_param_function.items()} return self.params_class.from_dict(params) + def get_first_derivative_param(self, coordinate: np.ndarray, is_transformed: bool, dim: int = 0): + transformed_coordinate = coordinate if is_transformed else self.transform(coordinate) + return { + param_name: param_function.get_first_derivative_param_value(transformed_coordinate, dim) + for param_name, param_function in self.param_name_to_param_function.items() + } + + diff --git a/extreme_fit/function/margin_function/parametric_margin_function.py b/extreme_fit/function/margin_function/parametric_margin_function.py index ccf2a83d..17e5dcfd 100644 --- a/extreme_fit/function/margin_function/parametric_margin_function.py +++ b/extreme_fit/function/margin_function/parametric_margin_function.py @@ -67,6 +67,10 @@ class ParametricMarginFunction(IndependentMarginFunction): return self.coordinates.temporal_coordinates.transformation.transform_array(np.array([self.starting_point])) def get_params(self, coordinate: np.ndarray, is_transformed: bool = True) -> GevParams: + coordinate = self.shift_coordinates_if_needed(coordinate, is_transformed) + return super().get_params(coordinate, is_transformed=is_transformed) + + def shift_coordinates_if_needed(self, coordinate, is_transformed): if self.starting_point is not None: starting_point = self.transformed_starting_point if is_transformed else self.starting_point # Shift temporal coordinate to enable to model temporal trend with starting point @@ -74,7 +78,7 @@ class ParametricMarginFunction(IndependentMarginFunction): assert 0 <= self.coordinates.idx_temporal_coordinates < len(coordinate) if coordinate[self.coordinates.idx_temporal_coordinates] < starting_point: coordinate[self.coordinates.idx_temporal_coordinates] = starting_point - return super().get_params(coordinate, is_transformed=is_transformed) + return coordinate @classmethod def from_coef_dict(cls, coordinates: AbstractCoordinates, param_name_to_dims: Dict[str, List[int]], diff --git a/extreme_fit/function/param_function/param_function.py b/extreme_fit/function/param_function/param_function.py index 470b1c69..575ff747 100644 --- a/extreme_fit/function/param_function/param_function.py +++ b/extreme_fit/function/param_function/param_function.py @@ -8,7 +8,10 @@ class AbstractParamFunction(object): OUT_OF_BOUNDS_ASSERT = True def get_param_value(self, coordinate: np.ndarray) -> float: - pass + raise NotImplementedError + + def get_first_derivative_param_value(self, coordinate: np.ndarray, dim: int) -> float: + raise NotImplementedError class ConstantParamFunction(AbstractParamFunction): diff --git a/extreme_trend/abstract_gev_trend_test.py b/extreme_trend/abstract_gev_trend_test.py index 3ba90563..eac300d0 100644 --- a/extreme_trend/abstract_gev_trend_test.py +++ b/extreme_trend/abstract_gev_trend_test.py @@ -238,6 +238,13 @@ class AbstractGevTrendTest(object): is_transformed=False) return gev_params_year + def get_unconstrained_time_derivative_gev_params(self, year): + gev_params_year = self.unconstrained_estimator.function_from_fit.get_first_derivative_param( + coordinate=np.array([year]), + is_transformed=False, + dim=0) + return gev_params_year + def linear_extension(self, ax, q, quantiles, sorted_maxima): # Extend the curve linear a bit if the return period 50 is not in the quantiles def compute_slope_intercept(x, y): @@ -387,3 +394,16 @@ class AbstractGevTrendTest(object): average_mean_value = np.mean(mean_values) assert isinstance(average_mean_value, float) return average_mean_value + + # Derivative mean value in some year + + def mean_value(self, year): + return self.get_unconstrained_gev_params(year).mean + + def first_derivative_mean_value(self, year): + param_name_to_value = self.get_unconstrained_time_derivative_gev_params(year) + assert param_name_to_value[GevParams.SHAPE] == 0, 'The formula does not work if is not zero' + gev_params = self.get_unconstrained_gev_params(year) + gev_params.location = param_name_to_value[GevParams.LOC] + gev_params.scale = param_name_to_value[GevParams.SCALE] + return gev_params.mean diff --git a/projects/altitude_spatial_model/altitudes_fit/altitudes_studies.py b/projects/altitude_spatial_model/altitudes_fit/altitudes_studies.py index 284a7622..34a8b7ff 100644 --- a/projects/altitude_spatial_model/altitudes_fit/altitudes_studies.py +++ b/projects/altitude_spatial_model/altitudes_fit/altitudes_studies.py @@ -7,6 +7,7 @@ from cached_property import cached_property from extreme_data.meteo_france_data.scm_models_data.abstract_study import AbstractStudy from extreme_data.meteo_france_data.scm_models_data.visualization.main_study_visualizer import \ SCM_STUDY_CLASS_TO_ABBREVIATION +from extreme_data.meteo_france_data.scm_models_data.visualization.plot_utils import plot_against_altitude from extreme_data.meteo_france_data.scm_models_data.visualization.study_visualizer import StudyVisualizer from spatio_temporal_dataset.coordinates.abstract_coordinates import AbstractCoordinates from spatio_temporal_dataset.coordinates.spatial_coordinates.abstract_spatial_coordinates import \ @@ -18,7 +19,6 @@ from spatio_temporal_dataset.coordinates.temporal_coordinates.generated_temporal from spatio_temporal_dataset.dataset.abstract_dataset import AbstractDataset from spatio_temporal_dataset.spatio_temporal_observations.annual_maxima_observations import AnnualMaxima import matplotlib.pyplot as plt -import matplotlib.colors as mcolors class AltitudesStudies(object): @@ -157,18 +157,4 @@ class AltitudesStudies(object): moment = function(annual_maxima) mean_moment.append(moment) altitudes.append(altitude) - self.plot_against_altitude(altitudes, ax, massif_id, massif_name, mean_moment) - - @staticmethod - def plot_against_altitude(altitudes, ax, massif_id, massif_name, mean_moment): - di = massif_id // 8 - if di == 0: - linestyle = '-' - elif di == 1: - linestyle = 'dotted' - else: - linestyle = '--' - colors = list(mcolors.TABLEAU_COLORS) - colors[-3:-1] = [] # remove gray and olive - color = colors[massif_id % 8] - ax.plot(altitudes, mean_moment, color=color, linewidth=2, label=massif_name, linestyle=linestyle) + plot_against_altitude(altitudes, ax, massif_id, massif_name, mean_moment) diff --git a/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/main_snowfall_article.py b/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/main_snowfall_article.py index a2b9366d..cbc4c2af 100644 --- a/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/main_snowfall_article.py +++ b/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/main_snowfall_article.py @@ -4,6 +4,7 @@ from multiprocessing.pool import Pool import matplotlib as mpl from extreme_data.meteo_france_data.scm_models_data.safran.safran import SafranSnowfall1Day +from projects.contrasting_trends_in_snow_loads.snowfall_versus_time_and_altitude.snowfall_plot import plot_snowfall_mean from projects.contrasting_trends_in_snow_loads.snowfall_versus_time_and_altitude.study_visualizer_for_mean_values import \ StudyVisualizerForMeanValues from projects.contrasting_trends_in_snow_loads.snowfall_versus_time_and_altitude.validation_plot import validation_plot @@ -70,16 +71,18 @@ def intermediate_result(altitudes, massif_names=None, _ = compute_minimized_aic(visualizer) # Plots - validation_plot(altitude_to_visualizer) + # validation_plot(altitude_to_visualizer) + plot_snowfall_mean(altitude_to_visualizer) def major_result(): uncertainty_methods = [ConfidenceIntervalMethodFromExtremes.ci_mle][:] - massif_names = ['Beaufortain', 'Vercors'] + # massif_names = ['Beaufortain', 'Vercors'] massif_names = None study_classes = [SafranSnowfall1Day] model_subsets_for_uncertainty = None altitudes = paper_altitudes + altitudes = [900, 1200, 1500, 1800, 2100, 2400, 2700, 3000] # altitudes = [900, 1200] for study_class in study_classes: diff --git a/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/snowfall_plot.py b/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/snowfall_plot.py new file mode 100644 index 00000000..5d17a958 --- /dev/null +++ b/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/snowfall_plot.py @@ -0,0 +1,72 @@ +from typing import Dict +import matplotlib.pyplot as plt + +import numpy as np +from sklearn.linear_model import LinearRegression + +from extreme_data.meteo_france_data.scm_models_data.visualization.main_study_visualizer import \ + SCM_STUDY_CLASS_TO_ABBREVIATION +from extreme_data.meteo_france_data.scm_models_data.visualization.plot_utils import plot_against_altitude +from projects.contrasting_trends_in_snow_loads.snowfall_versus_time_and_altitude.study_visualizer_for_mean_values import \ + StudyVisualizerForMeanValues + + +def fit_linear_regression(x, y): + X = np.array(x).reshape(-1, 1) + reg = LinearRegression().fit(X, y) + r2_score = reg.score(X, y) + a = reg.coef_ + b = reg.intercept_ + return a, b, r2_score + + +def plot_snowfall_mean(altitude_to_visualizer: Dict[int, StudyVisualizerForMeanValues]): + visualizer = list(altitude_to_visualizer.values())[0] + # Plot the curve for the evolution of the mean + massif_name_to_a, massif_name_to_b, massif_name_to_r2_score = plot_mean(altitude_to_visualizer, derivative=False) + # Plot map with the coefficient a + visualizer.plot_abstract_fast(massif_name_to_a, label='a') + visualizer.plot_abstract_fast(massif_name_to_b, label='b') + visualizer.plot_abstract_fast(massif_name_to_r2_score, label='r2') + + +def plot_mean(altitude_to_visualizer: Dict[int, StudyVisualizerForMeanValues], derivative=False): + massif_name_to_linear_regression_result = {} + + altitudes = list(altitude_to_visualizer.keys()) + visualizers = list(altitude_to_visualizer.values()) + visualizer = visualizers[0] + study = visualizer.study + year = study.year_max + + for massif_id, massif_name in enumerate(visualizer.study.all_massif_names()): + altitudes_massif = [a for a, v in altitude_to_visualizer.items() + if massif_name in v.massif_name_to_trend_test_that_minimized_aic] + if len(altitudes_massif) >= 2: + trend_tests = [altitude_to_visualizer[a].massif_name_to_trend_test_that_minimized_aic[massif_name] + for a in altitudes_massif] + if derivative: + moment = 'time derivative of the mean' + values = [t.first_derivative_mean_value(year=year) for t in trend_tests] + else: + moment = 'mean' + values = [t.mean_value(year=year) for t in trend_tests] + massif_name_to_linear_regression_result[massif_name] = fit_linear_regression(altitudes, values) + plot_values_against_altitudes(altitudes_massif, massif_id, massif_name, moment, study, values, visualizer) + + return [{m: t[i][0] if i == 0 else t[i] + for m, t in massif_name_to_linear_regression_result.items()} for i in range(3)] + + +def plot_values_against_altitudes(altitudes, massif_id, massif_name, moment, study, values, visualizer): + ax = plt.gca() + plot_against_altitude(altitudes=altitudes, ax=ax, massif_id=massif_id, massif_name=massif_name, values=values) + plot_name = '{} annual maxima of {}'.format(moment, SCM_STUDY_CLASS_TO_ABBREVIATION[type(study)]) + ax.set_ylabel('{} ({})'.format(plot_name, study.variable_unit), fontsize=15) + ax.set_xlabel('altitudes', fontsize=15) + lim_down, lim_up = ax.get_ylim() + lim_up += (lim_up - lim_down) / 3 + ax.set_ylim([lim_down, lim_up]) + ax.tick_params(axis='both', which='major', labelsize=13) + visualizer.plot_name = plot_name + visualizer.show_or_save_to_file(dpi=500, add_classic_title=False) diff --git a/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/study_visualizer_for_mean_values.py b/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/study_visualizer_for_mean_values.py index 84dd12be..334ec2ca 100644 --- a/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/study_visualizer_for_mean_values.py +++ b/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/study_visualizer_for_mean_values.py @@ -39,7 +39,6 @@ class StudyVisualizerForMeanValues(StudyVisualizerForNonStationaryTrends): massif_name_to_parameter_value = {} for massif_name, trend_test in self.massif_name_to_trend_test_that_minimized_aic.items(): parameter_value = trend_test.unconstrained_average_mean_value - print(type(parameter_value), parameter_value) massif_name_to_parameter_value[massif_name] = parameter_value return massif_name_to_parameter_value @@ -56,4 +55,3 @@ class StudyVisualizerForMeanValues(StudyVisualizerForNonStationaryTrends): def plot_abstract_fast(self, massif_name_to_value, label, graduation=10.0, cmap=plt.cm.coolwarm): super().plot_abstract(massif_name_to_value, label, label, self.fit_method, graduation, cmap) - diff --git a/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/validation_plot.py b/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/validation_plot.py index 58d27a21..6b630252 100644 --- a/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/validation_plot.py +++ b/projects/contrasting_trends_in_snow_loads/snowfall_versus_time_and_altitude/validation_plot.py @@ -32,7 +32,6 @@ def plot_shoe_relative_differences_distribution(altitude_to_relative_differences ax.set_xlabel('Altitude (m)') ax.legend() ax.grid() - ax.set_ylim([-8, 5]) def plot_relative_difference_map(visualizer: StudyVisualizerForMeanValues): @@ -40,10 +39,8 @@ def plot_relative_difference_map(visualizer: StudyVisualizerForMeanValues): visualizer.plot_abstract_fast(massif_name_to_value=visualizer.massif_name_to_empirical_mean, label='Empirical' + label) - print(visualizer.massif_name_to_parametric_mean) visualizer.plot_abstract_fast(massif_name_to_value=visualizer.massif_name_to_parametric_mean, label='Model' + label) - print(visualizer.massif_name_to_relative_difference_for_mean) visualizer.plot_abstract_fast(massif_name_to_value=visualizer.massif_name_to_relative_difference_for_mean, - label='Relative difference of' + label) + label='Relative difference of' + label, graduation=1) return list(visualizer.massif_name_to_relative_difference_for_mean.values()) -- GitLab