From 6d9c99ad989507aaf02240adbd0cb98ef5c11d60 Mon Sep 17 00:00:00 2001 From: Le Roux Erwan <erwan.le-roux@irstea.fr> Date: Fri, 12 Feb 2021 17:55:17 +0100 Subject: [PATCH] [projections] add new folder for the work with the ensembles. --- .../__init__.py | 0 .../ensemble_fit/abstract_ensemble_fit.py | 0 .../ensemble_fit/independent_ensemble_fit.py | 0 .../ensemble_fit/together_ensemble_fit.py | 0 ...ation_temporal_for_projections_ensemble.py | 120 ++++ .../visualizer_for_projection_ensemble.py | 548 ++++++++++++++++++ 6 files changed, 668 insertions(+) create mode 100644 projects/elevation_temporal_model_for_projections/__init__.py create mode 100644 projects/elevation_temporal_model_for_projections/ensemble_fit/abstract_ensemble_fit.py create mode 100644 projects/elevation_temporal_model_for_projections/ensemble_fit/independent_ensemble_fit.py create mode 100644 projects/elevation_temporal_model_for_projections/ensemble_fit/together_ensemble_fit.py create mode 100644 projects/elevation_temporal_model_for_projections/main_elevation_temporal_for_projections_ensemble.py create mode 100644 projects/elevation_temporal_model_for_projections/visualizer_for_projection_ensemble.py diff --git a/projects/elevation_temporal_model_for_projections/__init__.py b/projects/elevation_temporal_model_for_projections/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/elevation_temporal_model_for_projections/ensemble_fit/abstract_ensemble_fit.py b/projects/elevation_temporal_model_for_projections/ensemble_fit/abstract_ensemble_fit.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/elevation_temporal_model_for_projections/ensemble_fit/independent_ensemble_fit.py b/projects/elevation_temporal_model_for_projections/ensemble_fit/independent_ensemble_fit.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/elevation_temporal_model_for_projections/ensemble_fit/together_ensemble_fit.py b/projects/elevation_temporal_model_for_projections/ensemble_fit/together_ensemble_fit.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/elevation_temporal_model_for_projections/main_elevation_temporal_for_projections_ensemble.py b/projects/elevation_temporal_model_for_projections/main_elevation_temporal_for_projections_ensemble.py new file mode 100644 index 00000000..01fa3a47 --- /dev/null +++ b/projects/elevation_temporal_model_for_projections/main_elevation_temporal_for_projections_ensemble.py @@ -0,0 +1,120 @@ +import datetime +import time +from typing import List + +import matplotlib + +matplotlib.use('Agg') + +from extreme_data.meteo_france_data.scm_models_data.safran.safran_max_snowf import SafranSnowfall2019, \ + SafranSnowfall2020 +from projects.altitude_spatial_model.altitudes_fit.plots.plot_histogram_altitude_studies import \ + plot_shoe_plot_changes_against_altitude, plot_histogram_all_trends_against_altitudes, \ + plot_shoe_plot_ratio_interval_size_against_altitude, plot_histogram_all_models_against_altitudes + +from extreme_fit.model.result_from_model_fit.result_from_extremes.abstract_extract_eurocode_return_level import \ + AbstractExtractEurocodeReturnLevel + +import matplotlib as mpl + +from extreme_fit.model.utils import set_seed_for_test +from projects.altitude_spatial_model.altitudes_fit.plots.plot_coherence_curves import plot_coherence_curves + +mpl.rcParams['text.usetex'] = True +mpl.rcParams['text.latex.preamble'] = [r'\usepackage{amsmath}'] + +from projects.altitude_spatial_model.altitudes_fit.utils_altitude_studies_visualizer import load_visualizer_list + +from projects.altitude_spatial_model.altitudes_fit.one_fold_analysis.altitude_group import altitudes_for_groups +from projects.altitude_spatial_model.altitudes_fit.one_fold_analysis.plot_total_aic import plot_individual_aic + +from extreme_data.meteo_france_data.scm_models_data.safran.safran import SafranSnowfall1Day, SafranSnowfall3Days, \ + SafranSnowfall5Days, SafranSnowfall7Days, SafranDateFirstSnowfall, SafranPrecipitation1Day, \ + SafranPrecipitation3Days, SafranSnowfallCenterOnDay1dayMeanRate, SafranSnowfallNotCenterOnDay1day +from extreme_data.meteo_france_data.scm_models_data.utils import Season + + +def main(): + study_classes = [SafranSnowfall1Day + , SafranSnowfall3Days, + SafranSnowfall5Days, SafranSnowfall7Days][:1] + seasons = [Season.annual, Season.winter, Season.spring, Season.automn][:1] + + set_seed_for_test() + model_must_pass_the_test = False + AbstractExtractEurocodeReturnLevel.ALPHA_CONFIDENCE_INTERVAL_UNCERTAINTY = 0.2 + + fast = False + if fast is None: + massif_names = None + AbstractExtractEurocodeReturnLevel.NB_BOOTSTRAP = 10 + altitudes_list = altitudes_for_groups[2:3] + elif fast: + AbstractExtractEurocodeReturnLevel.NB_BOOTSTRAP = 10 + massif_names = ['Vanoise', 'Haute-Maurienne', 'Vercors'][:1] + altitudes_list = altitudes_for_groups[1:2] + else: + massif_names = None + altitudes_list = altitudes_for_groups[:] + + start = time.time() + main_loop(altitudes_list, massif_names, seasons, study_classes, model_must_pass_the_test) + end = time.time() + duration = str(datetime.timedelta(seconds=end - start)) + print('Total duration', duration) + + +def main_loop(altitudes_list, massif_names, seasons, study_classes, model_must_pass_the_test): + assert isinstance(altitudes_list, List) + assert isinstance(altitudes_list[0], List) + for season in seasons: + for study_class in study_classes: + print('Inner loop', season, study_class) + visualizer_list = load_visualizer_list(season, study_class, altitudes_list, massif_names, + model_must_pass_the_test + ) + plot_visualizers(massif_names, visualizer_list) + for visualizer in visualizer_list: + plot_visualizer(massif_names, visualizer) + del visualizer_list + time.sleep(2) + + +def plot_visualizers(massif_names, visualizer_list): + # plot_histogram_all_models_against_altitudes(massif_names, visualizer_list) + plot_histogram_all_trends_against_altitudes(massif_names, visualizer_list) + # plot_shoe_plot_ratio_interval_size_against_altitude(massif_names, visualizer_list) + for relative in [True, False]: + plot_shoe_plot_changes_against_altitude(massif_names, visualizer_list, relative=relative) + # plot_coherence_curves(massif_names, visualizer_list) + # plot_coherence_curves(['Vanoise'], visualizer_list) + pass + + +def plot_visualizer(massif_names, visualizer): + # Plot time series + # visualizer.studies.plot_maxima_time_series(massif_names) + # visualizer.studies.plot_maxima_time_series(['Vanoise']) + + # Plot the results for the model that minimizes the individual aic + plot_individual_aic(visualizer) + + # Plot the results for the model that minimizes the total aic + # plot_total_aic(model_classes, visualizer) + pass + +def plots(visualizer: AltitudesStudiesVisualizerForNonStationaryModels): + # visualizer.plot_shape_map() + visualizer.plot_moments() + # visualizer.plot_qqplots() + # for std in [True, False]: + # visualizer.studies.plot_mean_maxima_against_altitude(std=std) + + +def plot_individual_aic(visualizer): + OneFoldFit.best_estimator_minimizes_total_aic = False + plots(visualizer) + + +if __name__ == '__main__': + main() diff --git a/projects/elevation_temporal_model_for_projections/visualizer_for_projection_ensemble.py b/projects/elevation_temporal_model_for_projections/visualizer_for_projection_ensemble.py new file mode 100644 index 00000000..29ad2130 --- /dev/null +++ b/projects/elevation_temporal_model_for_projections/visualizer_for_projection_ensemble.py @@ -0,0 +1,548 @@ +from collections import Counter +from math import ceil, floor +from typing import List, Dict + +import matplotlib +import matplotlib.pyplot as plt + +import numpy as np +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.create_shifted_cmap import get_shifted_map, \ + get_colors, ticks_values_and_labels_for_percentages, get_half_colormap, ticks_values_and_labels_for_positive_value, \ + get_inverse_colormap, get_cmap_with_inverted_blue_and_green_channels, remove_the_extreme_colors +from extreme_data.meteo_france_data.scm_models_data.visualization.main_study_visualizer import \ + SCM_STUDY_CLASS_TO_ABBREVIATION, ALL_ALTITUDES_WITHOUT_NAN +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 extreme_fit.distribution.gev.gev_params import GevParams +from extreme_fit.function.margin_function.abstract_margin_function import AbstractMarginFunction +from extreme_fit.function.param_function.linear_coef import LinearCoef +from extreme_fit.model.margin_model.polynomial_margin_model.spatio_temporal_polynomial_model import \ + AbstractSpatioTemporalPolynomialModel +from extreme_fit.model.margin_model.utils import MarginFitMethod +from projects.altitude_spatial_model.altitudes_fit.altitudes_studies import AltitudesStudies +from projects.altitude_spatial_model.altitudes_fit.one_fold_analysis.altitude_group import \ + get_altitude_group_from_altitudes, HighAltitudeGroup, VeyHighAltitudeGroup, MidAltitudeGroup +from projects.altitude_spatial_model.altitudes_fit.one_fold_analysis.one_fold_fit import \ + OneFoldFit +from spatio_temporal_dataset.coordinates.abstract_coordinates import AbstractCoordinates +from spatio_temporal_dataset.dataset.abstract_dataset import AbstractDataset + + +class VisualizerForProjectionEnsemble(StudyVisualizer): + + def __init__(self, studies: AltitudesStudies, + model_classes: List[AbstractSpatioTemporalPolynomialModel], + show=False, + massif_names=None, + fit_method=MarginFitMethod.extremes_fevd_mle, + temporal_covariate_for_fit=None, + display_only_model_that_pass_anderson_test=True, + confidence_interval_based_on_delta_method=False + ): + super().__init__(studies.study, show=show, save_to_file=not show) + self.studies = studies + self.non_stationary_models = model_classes + self.fit_method = fit_method + self.temporal_covariate_for_fit = temporal_covariate_for_fit + self.display_only_model_that_pass_test = display_only_model_that_pass_anderson_test + self.massif_names = massif_names if massif_names is not None else self.study.all_massif_names() + self.massif_name_to_massif_id = {m: i for i, m in enumerate(self.massif_names)} + self.altitude_group = get_altitude_group_from_altitudes(self.studies.altitudes) + self.confidence_interval_based_on_delta_method = confidence_interval_based_on_delta_method + # Load one fold fit + self.massif_name_to_massif_altitudes = {} + self._massif_name_to_one_fold_fit = {} + for massif_name in self.massif_names: + # Load valid massif altitudes + massif_altitudes = self.get_massif_altitudes(massif_name) + if self.load_condition(massif_altitudes): + # Save the massif altitudes only for those who pass the condition + self.massif_name_to_massif_altitudes[massif_name] = massif_altitudes + # Load dataset + dataset = studies.spatio_temporal_dataset(massif_name=massif_name, massif_altitudes=massif_altitudes) + old_fold_fit = OneFoldFit(massif_name, dataset, model_classes, self.fit_method, + self.temporal_covariate_for_fit, + type(self.altitude_group), + self.display_only_model_that_pass_test, + self.confidence_interval_based_on_delta_method) + self._massif_name_to_one_fold_fit[massif_name] = old_fold_fit + # Print number of massif without any validated fit + massifs_without_any_validated_fit = [massif_name + for massif_name, old_fold_fit in self._massif_name_to_one_fold_fit.items() + if not old_fold_fit.has_at_least_one_valid_model] + print('Not validated:', len(massifs_without_any_validated_fit), massifs_without_any_validated_fit) + # Cache + self._method_name_and_order_to_massif_name_to_value = {} + self._method_name_and_order_to_max_abs = {} + self._max_abs_for_shape = None + + moment_names = ['moment', 'changes_of_moment', 'relative_changes_of_moment'][:] + orders = [1, 2, None][2:] + + def get_massif_altitudes(self, massif_name): + valid_altitudes = [altitude for altitude, study in self.studies.altitude_to_study.items() + if massif_name in study.study_massif_names] + massif_altitudes = [] + for altitude in valid_altitudes: + study = self.studies.altitude_to_study[altitude] + annual_maxima = study.massif_name_to_annual_maxima[massif_name] + percentage_of_non_zeros = 100 * np.count_nonzero(annual_maxima) / len(annual_maxima) + if percentage_of_non_zeros > 90: + massif_altitudes.append(altitude) + # else: + # print(massif_name, altitude, percentage_of_non_zeros) + return massif_altitudes + + def load_condition(self, massif_altitudes): + # At least two altitudes for the estimated + # reference_altitude_is_in_altitudes = (self.altitude_group.reference_altitude in massif_altitudes) + at_least_two_altitudes = (len(massif_altitudes) >= 2) + # return reference_altitude_is_in_altitudes and at_least_two_altitudes + return at_least_two_altitudes + + @property + def massif_name_to_one_fold_fit(self) -> Dict[str, OneFoldFit]: + return {massif_name: old_fold_fit for massif_name, old_fold_fit in self._massif_name_to_one_fold_fit.items() + if not self.display_only_model_that_pass_test + or old_fold_fit.has_at_least_one_valid_model} + + def plot_moments(self): + for method_name in self.moment_names[:2]: + for order in self.orders: + # self.plot_against_years(method_name, order) + self.plot_map_moment(method_name, order) + + def method_name_and_order_to_max_abs(self, method_name, order): + c = (method_name, order) + if c not in self._method_name_and_order_to_max_abs: + return None + else: + return self._method_name_and_order_to_max_abs[c] + + def method_name_and_order_to_d(self, method_name, order): + c = (method_name, order) + if c not in self._method_name_and_order_to_massif_name_to_value: + # Compute values + massif_name_to_value = {} + for massif_name, one_fold_fit in self.massif_name_to_one_fold_fit.items(): + value = \ + one_fold_fit.__getattribute__(method_name)([self.altitude_group.reference_altitude], order=order)[0] + massif_name_to_value[massif_name] = value + # Remove values + if any([np.isinf(v) for v in massif_name_to_value.values()]): + print("shape to large > 0.5, thus removing std that are infinite") + massif_name_to_value = {m: v for m, v in massif_name_to_value.items() + if not np.isinf(v)} + # Store it + self._method_name_and_order_to_massif_name_to_value[c] = massif_name_to_value + return self._method_name_and_order_to_massif_name_to_value[c] + + def ratio_groups(self): + return [self.ratio_uncertainty_interval_size(altitude, 2019) for altitude in self.studies.altitudes] + + def ratio_uncertainty_interval_size(self, altitude, year): + study = self.studies.altitude_to_study[altitude] + massif_name_to_interval = study.massif_name_to_stationary_gev_params_and_confidence(OneFoldFit.quantile_level, + self.confidence_interval_based_on_delta_method)[ + 1] + massif_names_with_pointwise_interval = set(massif_name_to_interval) + valid_massif_names = set(self.massif_name_to_one_fold_fit.keys()) + intersection_massif_names = valid_massif_names.intersection(massif_names_with_pointwise_interval) + ratios = [] + for massif_name in intersection_massif_names: + one_fold_fit = self.massif_name_to_one_fold_fit[massif_name] + new_interval_size = one_fold_fit.best_confidence_interval(altitude, year).interval_size + old_interval_size = massif_name_to_interval[massif_name].interval_size + ratio = new_interval_size / old_interval_size + ratios.append(ratio) + return ratios + + def plot_map_moment(self, method_name, order): + massif_name_to_value = self.method_name_and_order_to_d(method_name, order) + # Plot settings + moment = ' '.join(method_name.split('_')) + str_for_last_year = ' in 2019' + moment = moment.replace('moment', '{}{}'.format(OneFoldFit.get_moment_str(order=order), str_for_last_year)) + plot_name = '{}{} '.format(OneFoldFit.folder_for_plots, moment) + + + if 'change' in method_name: + plot_name = plot_name.replace(str_for_last_year, '') + plot_name += ' between {} and {}'.format(2019 - OneFoldFit.nb_years, 2019) + if 'relative' not in method_name: + # Put the relative score as text on the plot for the change. + massif_name_to_text = {m: ('+' if v > 0 else '') + str(int(v)) + '\%' for m, v in + self.method_name_and_order_to_d(self.moment_names[2], order).items()} + + parenthesis = self.study.variable_unit if 'relative' not in method_name else '\%' + ylabel = '{} ({})'.format(plot_name, parenthesis) + + max_abs_change = self.method_name_and_order_to_max_abs(method_name, order) + add_colorbar = self.add_colorbar + + is_return_level_plot = (self.moment_names.index(method_name) == 0) and (order is None) + if is_return_level_plot: + + cmap = plt.cm.Spectral + cmap = remove_the_extreme_colors(cmap, epsilon=0.25) + cmap = get_inverse_colormap(cmap) + add_colorbar = True + max_abs_change = None + massif_name_to_text = {m: round(v) for m, v in massif_name_to_value.items()} + graduation = self.altitude_group.graduation_for_return_level + fontsize_label = 17 + else: + # cmap = plt.cm.RdYlGn + cmap = [plt.cm.coolwarm, plt.cm.bwr, plt.cm.seismic][1] + # cmap = get_inverse_colormap(cmap) + # cmap = get_cmap_with_inverted_blue_and_green_channels(cmap) + cmap = remove_the_extreme_colors(cmap) + graduation = 10 + fontsize_label = 10 + + negative_and_positive_values = self.moment_names.index(method_name) > 0 + # Plot the map + + self.plot_map(cmap=cmap, graduation=graduation, + label=ylabel, massif_name_to_value=massif_name_to_value, + plot_name=plot_name, add_x_label=True, + negative_and_positive_values=negative_and_positive_values, + altitude=self.altitude_group.reference_altitude, + add_colorbar=add_colorbar, + max_abs_change=max_abs_change, + massif_name_to_text=massif_name_to_text, + xlabel=self.altitude_group.xlabel, + fontsize_label=fontsize_label, + ) + + + @property + def add_colorbar(self): + # return isinstance(self.altitude_group, (VeyHighAltitudeGroup)) + return isinstance(self.altitude_group, (VeyHighAltitudeGroup, MidAltitudeGroup)) + + def plot_against_years(self, method_name, order): + ax = plt.gca() + min_altitude, *_, max_altitude = self.studies.altitudes + altitudes_plot = np.linspace(min_altitude, max_altitude, num=50) + for massif_name, one_fold_fit in self.massif_name_to_one_fold_fit.items(): + massif_altitudes = self.studies.massif_name_to_altitudes[massif_name] + ind = (min(massif_altitudes) <= altitudes_plot) & (altitudes_plot <= max(massif_altitudes)) + massif_altitudes_plot = altitudes_plot[ind] + values = one_fold_fit.__getattribute__(method_name)(massif_altitudes_plot, order=order) + massif_id = self.massif_name_to_massif_id[massif_name] + plot_against_altitude(massif_altitudes_plot, ax, massif_id, massif_name, values) + # Plot settings + ax.legend(prop={'size': 7}, ncol=3) + moment = ' '.join(method_name.split('_')) + moment = moment.replace('moment', '{} in 2019'.format(OneFoldFit.get_moment_str(order=order))) + plot_name = '{}Model {} annual maxima of {}'.format(OneFoldFit.folder_for_plots, moment, + SCM_STUDY_CLASS_TO_ABBREVIATION[self.studies.study_class]) + ax.set_ylabel('{} ({})'.format(plot_name, self.study.variable_unit), fontsize=15) + ax.set_xlabel('altitudes', fontsize=15) + ax.tick_params(axis='both', which='major', labelsize=13) + self.studies.show_or_save_to_file(plot_name=plot_name, show=self.show, no_title=True) + ax.clear() + + # def plot_abstract_fast(self, massif_name_to_value, label, graduation=10.0, cmap=plt.cm.coolwarm, add_x_label=True, + # negative_and_positive_values=True, massif_name_to_text=None): + # plot_name = '{}{}'.format(OneFoldFit.folder_for_plots, label) + # self.plot_map(cmap, self.fit_method, graduation, label, massif_name_to_value, plot_name, add_x_label, + # negative_and_positive_values, + # massif_name_to_text) + + @property + def massif_name_to_shape(self): + return {massif_name: one_fold_fit.best_shape + for massif_name, one_fold_fit in self.massif_name_to_one_fold_fit.items()} + + @property + def massif_name_to_best_name(self): + return {massif_name: one_fold_fit.best_name + for massif_name, one_fold_fit in self.massif_name_to_one_fold_fit.items()} + + def plot_best_coef_maps(self): + for param_name in GevParams.PARAM_NAMES: + coordinate_names = [AbstractCoordinates.COORDINATE_X, AbstractCoordinates.COORDINATE_T] + dim_to_coordinate_name = dict(zip([0, 1], coordinate_names)) + for dim in [0, 1, (0, 1)]: + coordinate_name = LinearCoef.coefficient_name(dim, dim_to_coordinate_name) + for degree in range(4): + coef_name = ' '.join([param_name + coordinate_name + str(degree)]) + massif_name_to_best_coef = {} + for massif_name, one_fold_fit in self.massif_name_to_one_fold_fit.items(): + best_coef = one_fold_fit.best_coef(param_name, dim, degree) + if best_coef is not None: + massif_name_to_best_coef[massif_name] = best_coef + + if len(massif_name_to_best_coef) > 0: + for evaluate_coordinate in [False, True][:1]: + if evaluate_coordinate: + coef_name += 'evaluated at coordinates' + for massif_name in massif_name_to_best_coef.keys(): + if AbstractCoordinates.COORDINATE_X in coordinate_name: + massif_name_to_best_coef[massif_name] *= np.power(1000, degree) + if AbstractCoordinates.COORDINATE_T in coordinate_name: + massif_name_to_best_coef[massif_name] *= np.power(2019, degree) + self.plot_best_coef_map(coef_name.replace('_', ''), massif_name_to_best_coef) + + def plot_best_coef_map(self, coef_name, massif_name_to_best_coef): + values = list(massif_name_to_best_coef.values()) + graduation = (max(values) - min(values)) / 6 + print(coef_name, graduation, max(values), min(values)) + negative_and_positive_values = (max(values) > 0) and (min(values) < 0) + raise NotImplementedError + self.plot_map(massif_name_to_value=massif_name_to_best_coef, + label='{}Coef/{} plot for {} {}'.format(OneFoldFit.folder_for_plots, + coef_name, + SCM_STUDY_CLASS_TO_ABBREVIATION[ + type(self.study)], + self.study.variable_unit), + add_x_label=False, graduation=graduation, massif_name_to_text=self.massif_name_to_best_name, + negative_and_positive_values=negative_and_positive_values) + + def plot_shape_map(self): + + label = 'Shape parameter in 2019 (no unit)' + max_abs_change = self._max_abs_for_shape + 0.05 + self.plot_map(massif_name_to_value=self.massif_name_to_shape, + label=label, + plot_name=label, + fontsize_label=15, + add_x_label=True, graduation=0.1, + massif_name_to_text=self.massif_name_to_best_name, + cmap=matplotlib.cm.get_cmap('BrBG_r'), + altitude=self.altitude_group.reference_altitude, + add_colorbar=self.add_colorbar, + max_abs_change=max_abs_change, + xlabel=self.altitude_group.xlabel, + ) + + def plot_altitude_for_the_peak(self): + pass + + def plot_year_for_the_peak(self, plot_mean=True): + t_list = self.study.ordered_years + return_period = 50 + for massif_name, one_fold_fit in self.massif_name_to_one_fold_fit.items(): + ax = plt.gca() + # One plot for each altitude + altitudes = np.arange(500, min(3000, max(self.studies.altitudes)), 500) + for altitude in altitudes: + i = 0 + while self.studies.altitudes[i] < altitude: + i += 1 + nearest_altitude = self.studies.altitudes[i] + nearest_study = self.studies.altitude_to_study[nearest_altitude] + if massif_name in nearest_study.study_massif_names: + y_list = [] + for t in t_list: + coordinate = np.array([altitude, t]) + gev_params = one_fold_fit.best_function_from_fit.get_params(coordinate, is_transformed=False) + if plot_mean: + y = gev_params.mean + else: + y = gev_params.return_level(return_period=return_period) + y_list.append(y) + label = '{} m'.format(altitude) + ax.plot(t_list, y_list, label=label) + ax.legend() + # Modify the limits of the y axis + lim_down, lim_up = ax.get_ylim() + ax_lim = (0, lim_up) + ax.set_ylim(ax_lim) + ax.set_xlabel('Year') + if plot_mean: + ylabel = 'Mean {} maxima'.format(self.study.season_name) + else: + ylabel = '{}-year return level'.format(return_period) + ax.set_ylabel('{} of {} in {} ({})'.format(ylabel, SCM_STUDY_CLASS_TO_ABBREVIATION[type(self.study)], + massif_name.replace('_', ' '), self.study.variable_unit)) + peak_year_folder = 'Peak year ' + ylabel + plot_name = '{}{}/Peak year for {}'.format(OneFoldFit.folder_for_plots, peak_year_folder, + massif_name.replace('_', '')) + self.studies.show_or_save_to_file(plot_name=plot_name, show=self.show, no_title=True, tight_layout=True) + plt.close() + + # Plots "altitude switch" and "peak year" + + @property + def massif_name_to_is_decreasing_parabol(self): + # For the test we only activate the Mont-Blanc massif + d = {massif_name: False for massif_name in self.massif_name_to_one_fold_fit.keys()} + if max(self.study.ordered_years) < 2030: + for massif_name in ['Vanoise', 'Aravis', 'Beaufortain', 'Chablais']: + d[massif_name] = True + return d + + @property + def massif_name_to_altitudes_switch_and_peak_years(self): + return {massif_name: self.compute_couple_peak_year_and_altitude_switch(massif_name) + for massif_name, is_decreasing_parabol in self.massif_name_to_is_decreasing_parabol.items() + if is_decreasing_parabol} + + def compute_couple_peak_year_and_altitude_switch(self, massif_name): + # Get the altitude limits + altitudes = self.study.massif_name_to_altitudes[massif_name] + # use a step of 100m for instance + step = 10 + altitudes = list(np.arange(min(altitudes), max(altitudes) + step, step)) + # Get all the correspond peak years + margin_function = self.massif_name_to_one_fold_fit[massif_name].best_function_from_fit + peak_years = [] + year_left = 1900 + switch_altitudes = [] + for altitude in altitudes: + year_left = self.compute_peak_year(margin_function, altitude, year_left) + if year_left > 2020: + break + peak_years.append(year_left) + switch_altitudes.append(altitude) + print(switch_altitudes) + print(peak_years) + return switch_altitudes, peak_years + + def compute_peak_year(self, margin_function: AbstractMarginFunction, altitude, year_left): + year_right = year_left + 0.1 + mean_left = margin_function.get_params(np.array([altitude, year_left])).mean + mean_right = margin_function.get_params(np.array([altitude, year_right])).mean + print(year_left, year_right, mean_left, mean_right) + if mean_right < mean_left: + return year_left + else: + return self.compute_peak_year(margin_function, altitude, year_right) + + def plot_peak_year_against_altitude(self): + ax = plt.gca() + for massif_name, (altitudes, peak_years) in self.massif_name_to_altitudes_switch_and_peak_years.items(): + ax.plot(altitudes, peak_years, label=massif_name) + ax.legend() + ax.set_xlabel('Altitude') + ax.set_ylabel('Peak years') + plot_name = 'Peak Years' + self.studies.show_or_save_to_file(plot_name=plot_name, show=self.show) + plt.close() + + def plot_altitude_switch_against_peak_year(self): + ax = plt.gca() + for massif_name, (altitudes, peak_years) in self.massif_name_to_altitudes_switch_and_peak_years.items(): + ax.plot(peak_years, altitudes, label=massif_name) + ax.legend() + ax.set_xlabel('Peak years') + ax.set_ylabel('Altitude') + plot_name = 'Switch altitude' + self.studies.show_or_save_to_file(plot_name=plot_name, show=self.show) + plt.close() + + def all_trends(self, massif_names): + """return percents which contain decrease, significant decrease, increase, significant increase percentages""" + valid_massif_names = self.get_valid_names(massif_names) + + nb_valid_massif_names = len(valid_massif_names) + nbs = np.zeros(4) + for one_fold in [one_fold for m, one_fold in self.massif_name_to_one_fold_fit.items() + if m in valid_massif_names]: + # Compute nb of non stationary models + if one_fold.change_in_return_level_for_reference_altitude == 0: + continue + # Compute nbs + idx = 0 if one_fold.change_in_return_level_for_reference_altitude < 0 else 2 + nbs[idx] += 1 + if one_fold.is_significant: + nbs[idx + 1] += 1 + + percents = 100 * nbs / nb_valid_massif_names + return [nb_valid_massif_names] + list(percents) + + def all_changes(self, massif_names, relative=False): + """return percents which contain decrease, significant decrease, increase, significant increase percentages""" + valid_massif_names = self.get_valid_names(massif_names) + changes = [] + non_stationary_changes = [] + non_stationary_significant_changes = [] + for one_fold in [one_fold for m, one_fold in self.massif_name_to_one_fold_fit.items() + if m in valid_massif_names]: + # Compute changes + if relative: + change = one_fold.relative_change_in_return_level_for_reference_altitude + else: + change = one_fold.change_in_return_level_for_reference_altitude + changes.append(change) + if change != 0: + non_stationary_changes.append(change) + if one_fold.is_significant: + non_stationary_significant_changes.append(change) + + moment = 'relative mean' if relative else 'Mean' + print('{} for {}m'.format(moment, self.altitude_group.reference_altitude), np.mean(changes)) + return changes, non_stationary_changes, non_stationary_significant_changes + + def get_valid_names(self, massif_names): + valid_massif_names = set(self.massif_name_to_one_fold_fit.keys()) + if massif_names is not None: + valid_massif_names = valid_massif_names.intersection(set(massif_names)) + return valid_massif_names + + def model_name_to_percentages(self, massif_names, only_significant=False): + valid_massif_names = self.get_valid_names(massif_names) + nb_valid_massif_names = len(valid_massif_names) + best_names = [one_fold_fit.best_estimator.margin_model.name_str + for m, one_fold_fit in self.massif_name_to_one_fold_fit.items() + if m in valid_massif_names and (not only_significant or one_fold_fit.is_significant)] + counter = Counter(best_names) + d = {name: 100 * c / nb_valid_massif_names for name, c in counter.items()} + # Add 0 for the name not present + for name in self.model_names: + if name not in d: + d[name] = 0 + return d + + @property + def model_names(self): + massif_name = list(self.massif_name_to_one_fold_fit.keys())[0] + return self.massif_name_to_one_fold_fit[massif_name].model_names + + def plot_qqplots(self): + for massif_name, one_fold_fit in self.massif_name_to_one_fold_fit.items(): + ax = plt.gca() + altitudes = self.massif_name_to_massif_altitudes[massif_name] + massif_name_corrected = massif_name.replace('_', ' ') + + all_quantiles = [] + + for altitude in self.studies.altitudes: + coordinate_for_filter = (altitude, None) + unconstrained_empirical_quantiles = one_fold_fit.best_estimator.sorted_empirical_standard_gumbel_quantiles(coordinate_for_filter=coordinate_for_filter) + n = len(unconstrained_empirical_quantiles) + if n > 0: + assert n == 61 + standard_gumbel_quantiles = one_fold_fit.standard_gumbel_quantiles(n=n) + ax.plot(standard_gumbel_quantiles, unconstrained_empirical_quantiles, linestyle='None', + label='{} m'.format(altitude), marker='o') + + all_quantiles.extend(standard_gumbel_quantiles) + all_quantiles.extend(unconstrained_empirical_quantiles) + + size_label = 20 + ax.set_xlabel("Theoretical quantile", fontsize=size_label) + ax.set_ylabel("Empirical quantile", fontsize=size_label) + + epsilon = 0.1 + ax_lim = [min(all_quantiles) - epsilon, max(all_quantiles) + epsilon] + ax.set_xlim(ax_lim) + ax.set_ylim(ax_lim) + + ax.plot(ax_lim, ax_lim, color='k') + ticks = [i for i in range(ceil(ax_lim[0]), floor(ax_lim[1]) + 1)] + ax.set_xticks(ticks) + ax.set_yticks(ticks) + labelsize = 15 + ax.tick_params(labelsize=labelsize) + plot_name = 'qqplot/{}'.format(massif_name_corrected) + handles, labels = ax.get_legend_handles_labels() + ax.legend(handles[::-1], labels[::-1], prop={'size': labelsize}) + self.studies.show_or_save_to_file(plot_name=plot_name, show=self.show, no_title=True) + plt.close() -- GitLab