Source code for trajplot

# -*- coding: utf-8 -*-
"""
Created on Mon Feb 12 01:46:55 2024

@author: Alexandre Coche Kenshilik
@contact: alexandre.co@hotmail.fr

"""
#%% IMPORTS
from pywtraj.src.dataprocessing import (
    geohydroconvert as ghc,
)

from pywtraj.src.graphics import (
    ncplot as ncp, 
    cmapgenerator as cmg, 
    monitor_memory as mm,
)
import numpy as np
import os
import datetime
import pandas as pd
import re
from matplotlib import cm

try:
    import tracemalloc
except:
    0

#%% CLASS
[docs]class Figure: """ Main data available as Figure attributes ------------------------------------------ +-----------------------------+--------------------------------------------------------------------+ | Main variables | Description | +=============================+====================================================================+ | ``self.model_names_list`` | | List of **model names**. | | | | For example in EXPLORE2 it corresponds to the identifier of the | | | | climatic experiment: 'Model1', 'Model2', 'Model3'..., 'Model17'. | +-----------------------------+--------------------------------------------------------------------+ | ``self.original_data`` | | List of the **pandas.Dataframes** retrieved from the NetCDF data,| | | | for each model (climatic experiments). NetCDF data are converted | | | | into time series by considering the spatial average over the | | | | ``coords`` argument (mask). | +-----------------------------+--------------------------------------------------------------------+ | ``self.relative_ref`` | | Equivalent to ``self.original_data``, but contains the time | | | | series used as **reference** (historic) to compute relative | | | | values (if user-chosen). | +-----------------------------+--------------------------------------------------------------------+ | ``self.rea_data`` | | Equivalent to ``self.original_data``, but contains the | | | | **reanalysis** time series, which are added to the plots in | | | | order to provide a historic reference. | +-----------------------------+--------------------------------------------------------------------+ | ``self.all_res`` | | List of pd.Dataframes **for each period** (according to | | | | ``period_years`` argument), containing timeseries **averaged** | | | | **over a year** (365 days) for each model (climatic experiments) | | | | (one column per model). The year starts on the month defined by | | | | ``annuality`` argument. | +-----------------------------+--------------------------------------------------------------------+ | ``self.graph_res`` | | Results **formated for the plots**. | | | | Either in the form of a list of pd.Dataframes, one for each | | | | period, each pd.Dataframe containing the aggregated result | | | | (*min*, *mean*, *sum*...) from ``self.all_res`` [in case of | | | | ``plot_type = 'temporality'``]. | | | | Or in the form of a single pd.Dataframe containing the | | | | aggregated values for each day [in case of ``plot_type`` is | | | | a metric]. | +-----------------------------+--------------------------------------------------------------------+ --------------------------------------------------------------------------- """ # Verbose prints memory tracking verbose:bool=False # ============================================================================= # # Access the number of Figure instances, also for memory tracking # n_fig:int=0 # ============================================================================= # Static list of plot_type arguments considered as metrics metric_list = ['annual', 'date'] metric_labels = {'annual': ['annual {}', '{} annuelle'], 'date': ['date of {}', 'date de {}'], } agg_labels = {'mean': ['mean', 'moyenne'], 'sum': ['sum', 'somme'], 'max': ['maximum', 'maximum'], 'min': ['minimum', 'minimum'], 'range': ['range', 'amplitude'], 'increase': ['increase', 'augmentation'], 'decrease': ['decrease', 'diminution'], }
[docs] @classmethod def update_verbose(cls, verbose:bool): cls.verbose = verbose
# ============================================================================= # @classmethod # def get_n_fig(cls): # return cls.n_fig # ============================================================================= def __init__(self, var:str, root_folder, scenario:str='RCP 8.5', epsg_data=None, coords='all', epsg_coords=None, rolling_days:int=1, period_years:int=10, annuality='calendar', plot_type:str=None, repres:(None,str)='area', cumul:bool=False, relative:bool=False, language:str='fr', color='scale', plotsize='wide', name:str='', credit:(None,str)='auto', showlegend:bool=True, shadow:(None,str)=None, verbose:bool=None, ): """ Examples -------- :: from watertrajectories_pytools.src.graphics import trajplot as tjp mask = r"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees\16- Territoire Annecy\masque_zone_etude.shp" F = tjp.Figure( var = 'T', root_folder = r"E:\Inputs\Climat", scenario = 'rcp8.5', coords = mask, rolling_days = 30, period_years = 10, annuality = 3, name = 'Annecy', ) for m in mask_dict: for scenario in ['SIM2', 'rcp8.5']: figweb, _, _ = tjp.temporality( var = 'PRETOT', scenario = scenario, root_folder = r"E:\Inputs\Climat", coords = mask_dict[m], name = m, period_years = 10, rolling_days = 30, cumul = False, plot_type = 'temporality', annuality = 3, relative = False, language = 'fr', plotsize = 'wide', verbose = True) figweb, _, _ = tjp.temporality( var = 'DRAIN', scenario = scenario, root_folder = r"E:\Inputs\Climat", coords = mask_dict[m], name = m, period_years = 10, rolling_days = 30, cumul = False, plot_type = 'temporality', annuality = 10, relative = False, language = 'fr', plotsize = 'wide', verbose = True) figweb, _, _ = tjp.temporality( var = 'SWI', scenario = scenario, root_folder = r"E:\Inputs\Climat", coords = mask_dict[m], name = m, period_years = 10, rolling_days = 30, cumul = False, plot_type = 'temporality', annuality = 3, relative = False, language = 'fr', plotsize = 'wide', verbose = True) figweb, _, _ = tjp.temporality( var = 'T', scenario = scenario, root_folder = r"E:\Inputs\Climat", coords = mask_dict[m], name = m, period_years = 10, rolling_days = 30, cumul = False, plot_type = 'temporality', annuality = 10, relative = False, language = 'fr', plotsize = 'wide', verbose = True) Parameters ---------- var : str 'PRETOT' | 'PRENEI' | 'ETP' | 'EVAPC' | 'RUNOFFC' | 'DRAINC' | 'T' | 'SWI' | ... scenario : str 'SIM2' | 'historical' | 'RCP 4.5' | 'RCP 8.5' root_folder : str, path Path to the folder containing climatic data. r"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees\8- Meteo" (on my PC) r"E:\Inputs\Climat" (on the external harddrive) epsg_data : TYPE, optional DESCRIPTION. The default is None. coords : TYPE, optional DESCRIPTION. The default is 'all'. epsg_coords : TYPE, optional DESCRIPTION. The default is None. language : TYPE, optional DESCRIPTION. The default is 'fr'. plotsize : TYPE, optional DESCRIPTION. The default is 'wide'. rolling_days : TYPE, optional DESCRIPTION. The default is 1. period_years : TYPE, optional DESCRIPTION. The default is 10. cumul : TYPE, optional DESCRIPTION. The default is False. plot_type : TYPE DESCRIPTION. repres : {None, "area" or "bar"}, optional, default None Only used when plot_type is a metric. Defines the type of graphical representation of the metric plot. - ``"area"``: curves representing rolling averages - ``"bar"``: rectangles representing period averages name : int Suffix to add in the filename. Especially usefull to indicate the name of the site. credit : str, optional, default 'auto' To display the acknowledgement for data and conception. If ``'auto'``, the standard info about data source and conception author \ will be displayed. To remove all mention of credits, pass ``credit=''``. color : 'scale', 'discrete', <colormap> (str) or list of colors, optional, default 'scale' Colors for the plots (for now, only for *temporality* plots) relative : bool Whether the values should be computed as absolute or relatively to the reference period. showlegend : bool, optional, default True Whether to display the legend or not. shadow : {None, 'last', 'first', 'firstlast', 'lastfirst', 'all'}, optional, default = None Whether to display dialy values in grey shadows, and for which period. annuality : int or str Type of year : 'calendar' | 'meteorological' | 'meteo' | 'hydrological' | 'hydro' 1 | 9 | 10 verbose: bool Whether or not to display memory diagnotics. """ # ============================================================================= # Figure.n_fig = Figure.n_fig+1 # ============================================================================= # ---- Folders self.root_folder = root_folder # ---- Results self.model_names_list = None self.original_data = None self.relative_ref = None self.rea_data = None self.all_res = None self.all_daily = None self.graph_res = None self.graph_daily = None self.year_labels = None self.xaxis_add_label = ['', ''] self.yaxis_add_label = ['', ''] self.agg_rule = None self.is_metric = False # ---- Coords self.epsg_data = epsg_data self.coords = coords self.epsg_coords = epsg_coords # ---- Adjustments of names and folders self.scenario = scenario.casefold().replace('.', '').replace(' ', '') if self.scenario == 'sim2': self.scenario = self.scenario.upper() if var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'evspsblpot', 'etp', 'pet']: self.var = 'ETP' elif var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'prsn', 'prenei', 'neige', 'snow']: self.var = 'PRENEI' elif var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'prtot', 'precip', 'pretot']: self.var = 'PRETOT' elif var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'tas', 't', 'temp']: self.var = 'T' elif var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'swi']: self.var = 'SWI' elif var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'swe', 'resr_neige', 'neige']: self.var = 'SWE' elif var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'drainc', 'drain', 'rec']: self.var = 'DRAINC' elif var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'runoff', 'runoffc', 'runc', 'run']: self.var = 'RUNOFFC' elif var.casefold().replace(' ', '').replace('-', '').replace('adjust', '') in [ 'etr', 'aet', 'evap', 'evapc']: self.var = 'EVAPC' # Projections self.info_by_var = { # var : [UNIT, UNIT_COEFF, DESCRIPTION, VARNAME IN FILENAME, DATA FOLDER, DATA SOURCE] 'PRENEI': ['mm', 1000, ['Snow', 'Neige'], 'prsn', 'DRIAS-Climat\EXPLORE2-Climat2022', 'drias-climat.fr'], # 'PRELIQ': ['mm', 1000, ['Liquid precipitations', 'Précipitations liquides', 'drias-climat.fr']], 'PRETOT': ['mm', 1000, ['Total precipitations', 'Précipitations totales'], 'prtot', 'DRIAS-Climat\EXPLORE2-Climat2022', 'drias-climat.fr'], 'T': ['°C', -273.15, ['Temperature', 'Température'], 'tas', 'DRIAS-Climat\EXPLORE2-Climat2022', 'drias-climat.fr'], 'EVAPC': ['mm', 1000, ['Actual evapotranspiration', 'Evapotranspiration réelle'], 'EVAPC', f'DRIAS-Eau\EXPLORE2-SIM2 2024\{self.var}', 'drias-eau.fr'], 'ETP': ['mm', 1000, ['Potential evapotranspiration (Penman-Monteith)', 'Evapotranspiration potentielle (Penman-Monteith)'], 'evspsblpot', 'DRIAS-Climat\EXPLORE2-Climat2022', 'drias-climat.fr'], 'SWI': ['-', 1, ['Soil Wetness Index', "Indice d'humidité des sols"], 'SWI', f'DRIAS-Eau\EXPLORE2-SIM2 2024\{self.var}', 'drias-eau.fr'], 'SWE': ['mm', 1, ['Snow Water Equivalent', "Equivalent en eau du manteau neigeux"], 'SWE', f'DRIAS-Eau\EXPLORE2-SIM2 2024\{self.var}', 'drias-eau.fr'], 'DRAINC': ['mm', 1000, ['Drainage (recharge)', 'Drainage (recharge)'], 'DRAINC', f'DRIAS-Eau\EXPLORE2-SIM2 2024\{self.var}', 'drias-eau.fr'], 'RUNOFFC': ['mm', 1000, ['Surface runoff', 'Ruissellement'], 'RUNOFFC', f'DRIAS-Eau\EXPLORE2-SIM2 2024\{self.var}', 'drias-eau.fr'], } # Reanalyses historiques self.info_by_var_sim2 = { # var : [UNIT, UNIT_COEFF, DESCRIPTION, VARNAME IN FILENAME, DATA SOURCE] 'PRENEI': ['mm', 1, ['Snow', 'Neige'], 'PRENEI_Q', 'meteo.data.gouv.fr'], 'PRELIQ': ['mm', 1, ['Liquid precipitations', 'Précipitations liquides'], 'PRELIQ_Q', 'meteo.data.gouv.fr'], 'PRETOT': ['mm', 1, ['Total precipitations', 'Précipitations totales'], 'PRETOT_Q', 'meteo.data.gouv.fr'], 'T': ['°C', 0, ['Temperature', 'Température'], 'T_Q', 'meteo.data.gouv.fr'], 'EVAPC': ['mm', 1, ['Actual evapotranspiration', 'Evapotranspiration réelle'], 'EVAP_Q', 'meteo.data.gouv.fr'], 'ETP': ['mm', 1, ['Potential evapotranspiration (Penman-Monteith)', 'Evapotranspiration potentielle (Penman-Monteith)'], 'ETP_Q', 'meteo.data.gouv.fr'], 'SWI': ['-', 1, ["Soil Wetness Index", "Indice d'humidité des sols"], 'SWI_Q', 'meteo.data.gouv.fr'], 'SWE': ['mm', 1, ["Snow Water Equivalent", "Equivalent en eau du manteau neigeux"], 'RESR_NEIGE_Q', 'meteo.data.gouv.fr'], 'DRAINC': ['mm', 1, ['Drainage (recharge)', 'Drainage (recharge)'], 'DRAINC_Q', 'meteo.data.gouv.fr'], 'RUNOFFC': ['mm', 1, ['Surface runoff', 'Ruissellement'], 'RUNC_Q', 'meteo.data.gouv.fr'], } if self.scenario == 'SIM2': # réanalyses historiques self.data_folder = r"SIM2\compressed" self.n_model = 1 self.varname = self.info_by_var_sim2[self.var][3] self.unit_coeff = self.info_by_var_sim2[self.var][1] else: # projections self.data_folder = os.path.join('DRIAS', self.info_by_var[self.var][4], 'lossy_compressed') self.n_model = 17 self.varname = self.info_by_var[self.var][3] self.unit_coeff = self.info_by_var[self.var][1] # DRIAS data on my PC are stored in [m]. But we want to plot them in [mm] # ---- Relative self.relative = relative self.rel_title = ['', ''] if self.relative: self.rel_suf = '_rel' else: self.rel_suf = '' # ---- Representation mode suffix (only for metrics) self.repres_suf = '' # ---- Initialize user inputs self.plot_type = None # other arguments are either optional for all # Figure methods, or they have a default value. self.update( rolling_days=rolling_days, period_years=period_years, annuality=annuality, plot_type=plot_type, repres=repres, cumul=cumul, plotsize=plotsize, color=color, language=language, name=name, credit=credit, showlegend=showlegend, shadow=shadow, ) # ---- Verbose if verbose is not None: self.update_verbose(verbose) # ---- Loading self.load()
[docs] def load(self): r""" Results ------- This method also update the instance attributes all_res and graph_res, which respectively store the whole results and the final results used for the plot. Warning ------- This method can lead to memory size issues. It seems to appear when the garbage collector is not doing its job fast enough in the xarray variable data from C:\ProgramData\Miniconda3\envs\cwatenv\Lib\site-packages\xarray\coding\variables.py. To solve this, you might just need to open this folder on Windows. Examples (pipeline) -------- import os mask_folder = r"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees\15- Territoire Pays Basque" mask_dict = dict() mask_dict['cotier'] = os.path.join(mask_folder, r"Sage-cotier-basque.shp") mask_dict['CAPB'] = os.path.join(mask_folder, r"zone_etude_fusion.shp") mask_dict['Nive-Nivelle'] = os.path.join(mask_folder, r"Nive-Nivelle.shp") for var in ['PRETOT', 'DRAIN', 'SWI', 'T']: for scenario in ['SIM2', 'rcp8.5']: for m in mask_dict: F = tjp.Figure( var = var, root_folder = r"E:\Inputs\Climat", scenario = scenario, coords = mask_dict[m], name = m, rolling_days = 30, period_years = 10, ) F.plot(plot_type = 'annual_sum') F.plot(plot_type = 'annual_sum', period_years = 30) F.plot(plot_type = 'annual_sum', period_years = 1) F.plot(plot_type = 'annual_scatter', agg_rule = 'sum') F.plot(plot_type = '15/11', period_years = 10) F.plot(plot_type = '15/05', period_years = 10) if var == 'DRAIN': F.plot(plot_type = '> 1.5') """ # For memory tracking (initialization) if self.verbose: tracemalloc.start() # Folder folder = os.path.join(self.root_folder, self.data_folder) # monPC print("\n_ Loading...") # ---- Load reference (if required) if self.scenario != 'SIM2': # SIM2 reference (historical reanalysis) rea_folder = os.path.join(self.root_folder, r"SIM2\compressed") print(" . SIM2 reference (historical reanalysis)") rea_pattern = re.compile(f"^{self.info_by_var_sim2[self.var][3]}.*_SIM2_.*") filelist_rea = [ os.path.join(rea_folder, f) for f in os.listdir(rea_folder) if (os.path.isfile(os.path.join(rea_folder, f)) \ & (os.path.splitext(os.path.join(rea_folder, f))[-1] == '.nc') \ & (len(rea_pattern.findall(f)) > 0))] if len(filelist_rea) > 1: print(f"Err: too many detected files: {filelist_rea}") return else: filepath_rea = filelist_rea[0] self.rea_data = ghc.time_series( input_file = filepath_rea, coords = self.coords, epsg_coords = self.epsg_coords, epsg_data = self.epsg_data, ) if self.verbose: # memory monitoring snapshot = tracemalloc.take_snapshot() mm.display_top(snapshot) # ---- Load main dataset model_pattern = re.compile(f"^{self.varname}.*_{self.scenario}_.*") self.model_names_list = [] self.original_data = [] self.relative_ref = [] for i_model in range(1, self.n_model + 1): # range(1, 18): model_name = f'Model{i_model}' print(f" . {model_name}") if self.scenario == 'SIM2': # reanalyses historiques full_folder = folder else: # projections full_folder = os.path.join(folder, model_name) filelist = [ os.path.join(full_folder, f) for f in os.listdir(full_folder) if (os.path.isfile(os.path.join(full_folder, f)) \ & (os.path.splitext(os.path.join(full_folder, f))[-1] == '.nc') \ & (len(model_pattern.findall(f)) > 0))] if len(filelist) > 1: print(f"Err: too many detected files: {filelist}") return elif len(filelist) == 0: print(f"Scenario {self.scenario} is absent for {model_name}") else: filepath = filelist[0] self.model_names_list.append(model_name) df = ghc.time_series( input_file = filepath, coords = self.coords, epsg_coords = self.epsg_coords, epsg_data = self.epsg_data, ) if self.var == 'T': df = df + self.unit_coeff else: df = df * self.unit_coeff self.original_data.append(df) if self.verbose: # memory monitoring snapshot = tracemalloc.take_snapshot() mm.display_top(snapshot) # ---- Load relative values (if required) if self.relative: relative_pattern = re.compile(f"^{self.varname}.*_historical_.*") filelist_rel = [ os.path.join(full_folder, f) for f in os.listdir(full_folder) if (os.path.isfile(os.path.join(full_folder, f)) \ & (os.path.splitext(os.path.join(full_folder, f))[-1] == '.nc') \ & (len(relative_pattern.findall(f)) > 0))] if len(filelist_rel) > 1: print(f"Err: too many detected reference files: {filelist_rel}") return elif len(filelist_rel) == 0: print(f"The historical reference is absent for {model_name}") else: filepath_rel = filelist_rel[0] ref_df = ghc.time_series( input_file = filepath_rel, coords = self.coords, epsg_coords = self.epsg_coords, epsg_data = self.epsg_data, ) if self.var == 'T': ref_df = ref_df + self.unit_coeff else: ref_df = ref_df * self.unit_coeff self.relative_ref.append(ref_df)
[docs] def plot(self, *, rolling_days=None, period_years=None, annuality=None, plot_type=None, plotsize=None, name=None, credit=None, color=None, language=None, showlegend=None, shadow=None, repres=None, cumul=None, ): """ Examples -------- F = tjp.Figure( var = 'DRAINC', root_folder = r"E:\Inputs\Climat", scenario = 'rcp8.5', coords = r"C:\file.shp", rolling_days = 30, annuality = 10, name = 'myCatchment', ) F.plot(plot_type = '> 1.5', period_years = 10) F.plot(plot_type = 'annual_sum') F.plot(plot_type = 'annual_sum', period_years = 30) F.plot(plot_type = 'annual_scatter', period_years = 1) F.plot(plot_type = '15/11', period_years = 10) Parameters ---------- plot_type : str 'temporality' | 'annual_sum' Returns ------- None. """ # ---- Initializations # Updates # -+-+-+- self.update( rolling_days=rolling_days, period_years=period_years, annuality=annuality, cumul=cumul, plot_type=plot_type, repres=repres, language=language, showlegend=showlegend, shadow=shadow, plotsize=plotsize, name=name, credit=credit, color=color) # Other initializations # -+-+-+-+-+-+-+-+-+-+- # Safeguard if self.plot_type is None: print("Err: A plot_type argument is required.") return # Retrieve plot_mode (metric to plot) and agg_rule (aggregation rule, # such as sum, mean, min...) from plot_type (user input) self.plot_mode, self.agg_rule, self.is_logical, self.is_date, \ self.is_month, self.months = self.get_plot_mode(self.plot_type) # If plot_mode is in any of the previous case, it means it is a metric # (and not a keyword like 'temporality' or 'scatter') if (self.plot_mode in self.metric_list) | self.is_logical | self.is_date | self.is_month: self.is_metric = True else: self.is_metric = False # The plot mode is to show scatter plot of annual values if self.plot_mode == 'scatter': if self.agg_rule is None: print("Err: A agg_rule argument is required when plot_type is 'annual_scatter'.") return print(f"aggregation rule = {self.agg_rule}") # Time-0 dataframe, used as a reference for dates # -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- self.df0 = self.original_data[0].copy() # Remove incomplete years # if (df.index[0].month > self.annuality) | (not df.index[0].is_month_start): if self.df0.index[0] > datetime.datetime(year=self.df0.index[0].year, month=self.annuality, day=1): self.df0 = self.df0[slice(f'{self.df0.index[0].year + 1}-{self.annuality}-01', self.df0.index[-1])] else: self.df0 = self.df0[slice(f'{self.df0.index[0].year}-{self.annuality}-01', self.df0.index[-1])] # if (df.index[-1].month < ( # datetime.datetime(year=1900, month=self.annuality, day=1) \ # - pd.DateOffset(months=1) # ).month) | (df.index[-1].day < 31): # if (df.index[-1].month < self.annuality) | (not df.index[-1].is_month_end): if self.df0.index[-1] < datetime.datetime(year=self.df0.index[-1].year, month=self.annuality, day=1) - pd.DateOffset(days=1): self.df0 = self.df0[slice(self.df0.index[0], f'{self.df0.index[-1].year - 1}-{self.annuality}-01')][0:-1] else: self.df0 = self.df0[slice(self.df0.index[0], f'{self.df0.index[-1].year}-{self.annuality}-01')][0:-1] # Define keystones dates self.initialdate = self.df0.index[0] self.finaldate = self.df0.index[-1] if isinstance(self.period_years, int): self.startdate = self.initialdate self.enddate = self.startdate + pd.DateOffset(years = self.period_years, days = -1) elif isinstance(self.period_years, list): period_count = 0 self.startdate = datetime.datetime(year = self.period_years[0][0], month = self.annuality, day = 1) self.enddate = datetime.datetime(year = self.period_years[0][1], month = self.annuality, day = 1) + pd.DateOffset(days = -1) # Initialize empty lists that will store results # -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ self.year_labels = [] # will store the strings "startyear-endyear" self.all_res = [] # will store the final results: range over all models for each period self.all_daily = [] self.all_rea_daily = [] self.graph_res = [] # previous results formated for the graph self.graph_daily = [] self.graph_rea = [] self.graph_rea_daily = [] self.mean_relative_ref = [] self.plot_mode, self.agg_rule, self.is_logical, self.is_date, \ self.is_month, self.months = self.get_plot_mode(self.plot_type) print("\n_ Time processing...") # ---- Data treatment: Relative results (if required) if self.relative: for i in range(0, len(self.original_data)): ref_df = self.relative_ref[i].copy() # Remove incomplete years # ============================================================================= # if (ref_df.index[0].month > self.annuality) | (not ref_df.index[0].is_month_start): # ref_df = ref_df[slice(f'{ref_df.index[0].year + 1}-{self.annuality}-01', ref_df.index[-1])] # else: # ref_df = ref_df[slice(f'{ref_df.index[0].year}-{self.annuality}-01', ref_df.index[-1])] # if (ref_df.index[-1].month < self.annuality) | (not ref_df.index[-1].is_month_end): # ref_df = ref_df[slice(ref_df.index[0], f'{ref_df.index[-1].year - 1}-{self.annuality}-01')][0:-1] # else: # ref_df = ref_df[slice(ref_df.index[0], f'{ref_df.index[-1].year}-{self.annuality}-01')][0:-1] # ============================================================================= # Extract the reference period ref_startdate = datetime.datetime(year=1971, month=self.annuality, day=1) ref_enddate = ref_startdate + (self.enddate - self.startdate) + pd.DateOffset(days = -1) ref_df = ref_df[slice(ref_startdate, ref_enddate)] # Remove 29th February (to avoid unrecognized dates when standardizing yearly timeseries) ref_df = ref_df.iloc[(ref_df.index.month != 2) | (ref_df.index.day != 29)] # Rolling window ref_df = ref_df.rolling(f"{self.rolling_days}D", center = True).mean() # Mean mean_ref_df = ref_df # Compute an average annual time series mean_ref_df = mean_ref_df.groupby([mean_ref_df.index.month, mean_ref_df.index.day]).agg(self.agg_rule) # mean_ref_df.index += pd.DateOffset(years = 1800 - mean_ref_df.index[0].year) # [pd.to_datetime(f"1800-{m}-{d}", format = "%Y-%m-%d") for (m, d) in mean_ref_df.index] mean_ref_df.index = [pd.to_datetime(f"1800-{m}-{d}", format = "%Y-%m-%d") if m > ref_enddate.month else pd.to_datetime(f"1801-{m}-{d}", format = "%Y-%m-%d") for (m, d) in mean_ref_df.index ] # 1800 is used as an arbitrary year mean_ref_df.sort_index(inplace = True) mean_ref_df.attrs = {'period': (self.startdate, self.enddate), 'model': self.model_names_list[i]} # Save & store self.mean_relative_ref.append(mean_ref_df) self.rel_title = [f" relative to {ref_startdate.strftime('%Y-%m')}{ref_enddate.strftime('%Y-%m')}", f" relativement à {ref_startdate.strftime('%Y-%m')}{ref_enddate.strftime('%Y-%m')}"] # ---- Data treatment: reference data (if required) if self.scenario != 'SIM2': # SIM2 reference (historical reanalysis) # Remove incomplete years # if (self.rea_data.index[0].month > annuality) | (not self.rea_data.index[0].is_month_start): if self.rea_data.index[0] > datetime.datetime(year=self.rea_data.index[0].year, month=self.annuality, day=1): rea_df = self.rea_data[slice(f'{self.rea_data.index[0].year + 1}-{self.annuality}-01', self.rea_data.index[-1])] else: rea_df = self.rea_data[slice(f'{self.rea_data.index[0].year}-{self.annuality}-01', self.rea_data.index[-1])] # if (df.index[-1].month < ( # datetime.datetime(year=1900, month=self.annuality, day=1) \ # - pd.DateOffset(months=1) # ).month) | (df.index[-1].day < 31): # if (self.rea_data.index[-1].month < self.annuality) | (not self.rea_data.index[-1].is_month_end): if self.rea_data.index[-1] < datetime.datetime(year=self.rea_data.index[-1].year, month=self.annuality, day=1) - pd.DateOffset(days=1): rea_df = self.rea_data[slice(self.rea_data.index[0], f'{self.rea_data.index[-1].year - 1}-{self.annuality}-01')][0:-1] else: rea_df = self.rea_data[slice(self.rea_data.index[0], f'{self.rea_data.index[-1].year}-{self.annuality}-01')][0:-1] # Remove 29th February (to avoid unrecognized dates when standardizing yearly timeseries) rea_df = rea_df.iloc[(rea_df.index.month != 2) | (rea_df.index.day != 29)] # Rolling window daily_rea_df = rea_df.copy(deep=True) rea_df = rea_df.rolling(f"{self.rolling_days}D", center = True).mean() rea_startdate = [] rea_enddate = [] rea_startdate.append(datetime.datetime(year=1971, month=self.annuality, day=1)) rea_enddate.append(rea_startdate[0] + (self.enddate - self.startdate) + pd.DateOffset(days = -1)) rea_enddate.append(rea_df.index[-1]) rea_startdate.append(rea_enddate[-1] - (self.enddate - self.startdate) + pd.DateOffset(days = -1)) self.rea_labels = [] for k in range(0, len(rea_startdate)): rea_daily = [] # all daily results for one single period # Select the desired period mean_rea_df = rea_df[slice(rea_startdate[k], rea_enddate[k])].copy() daily_mean_rea_df = daily_rea_df[slice(rea_startdate[k], rea_enddate[k])].copy() # Compute an average annual time series mean_rea_df = mean_rea_df.groupby([mean_rea_df.index.month, mean_rea_df.index.day]).agg(self.agg_rule) min_rea_df = daily_mean_rea_df.groupby([daily_mean_rea_df.index.month, daily_mean_rea_df.index.day]).min() max_rea_df = daily_mean_rea_df.groupby([daily_mean_rea_df.index.month, daily_mean_rea_df.index.day]).max() mean_rea_df.index = [pd.to_datetime(f"1800-{m}-{d}", format = "%Y-%m-%d") if m > rea_enddate[k].month else pd.to_datetime(f"1801-{m}-{d}", format = "%Y-%m-%d") for (m, d) in mean_rea_df.index ] min_rea_df.index = [pd.to_datetime(f"1800-{m}-{d}", format = "%Y-%m-%d") if m > self.enddate.month else pd.to_datetime(f"1801-{m}-{d}", format = "%Y-%m-%d") for (m, d) in min_rea_df.index ] max_rea_df.index = [pd.to_datetime(f"1800-{m}-{d}", format = "%Y-%m-%d") if m > self.enddate.month else pd.to_datetime(f"1801-{m}-{d}", format = "%Y-%m-%d") for (m, d) in max_rea_df.index ] # 1800 is used as an arbitrary year mean_rea_df.sort_index(inplace = True) min_rea_df.sort_index(inplace = True) max_rea_df.sort_index(inplace = True) mean_rea_df.attrs = {'period': (rea_startdate[k], rea_enddate[k])} min_rea_df.attrs = {'period': (rea_startdate[k], rea_enddate[k])} max_rea_df.attrs = {'period': (rea_startdate[k], rea_enddate[k])} # Save & store self.graph_rea.append(mean_rea_df) rea_daily.append(min_rea_df) rea_daily.append(max_rea_df) # self.rea_labels.append(f'{rea_startdate[k].strftime("%Y-%m")} ➞ {rea_enddate[k].strftime("%Y-%m")}') self.rea_labels.append(f"{rea_startdate[k].strftime('%m')}/<b>{rea_startdate[k].year}</b> ➞ {rea_enddate[k].strftime('%m')}/<b>{rea_enddate[k].year}</b>") # Convert rea_daily into list of df with 'min' and 'max' columns merge_rea_daily = pd.concat(rea_daily, axis = 1) merge_rea_daily = merge_rea_daily.set_axis( [f"{rea_startdate[k].year}_{rea_enddate[k].year}_min", f"{rea_startdate[k].year}_{rea_enddate[k].year}_max"], axis = 1) self.all_rea_daily.append(merge_rea_daily) # Only for 'temporality' plot_type min_rea_daily = merge_rea_daily.min(axis = 1) min_rea_daily = min_rea_daily.to_frame('min') max_rea_daily = merge_rea_daily.max(axis = 1) max_rea_daily = max_rea_daily.to_frame('max') min_rea_daily = min_rea_daily.merge(max_rea_daily, left_index = True, right_index = True) self.graph_rea_daily.append(min_rea_daily) # ---- Data treatment print("\n_ Formatting...") while self.enddate <= self.finaldate: print(f" . {self.startdate.strftime('%Y-%m')} : {self.enddate.strftime('%Y-%m')}") single_period_res = [] # all-model results for one single period single_period_daily = [] # all-model results for one single period for i in range(0, len(self.original_data)): df = self.original_data[i].copy() # Remove incomplete years # ============================================================================= # # if (df.index[0].month > self.annuality) | (not df.index[0].is_month_start): # if df.index[0] > datetime.datetime(year=df.index[0].year, month=self.annuality, day=1): # df = df[slice(f'{df.index[0].year + 1}-{self.annuality}-01', df.index[-1])] # else: # df = df[slice(f'{df.index[0].year}-{self.annuality}-01', df.index[-1])] # # if (df.index[-1].month < ( # # datetime.datetime(year=1900, month=self.annuality, day=1) \ # # - pd.DateOffset(months=1) # # ).month) | (df.index[-1].day < 31): # # if (df.index[-1].month < self.annuality) | (not df.index[-1].is_month_end): # if df.index[-1] < datetime.datetime(year=df.index[-1].year, month=self.annuality, day=1) - pd.DateOffset(days=1): # df = df[slice(df.index[0], f'{df.index[-1].year - 1}-{self.annuality}-01')][0:-1] # else: # df = df[slice(df.index[0], f'{df.index[-1].year}-{self.annuality}-01')][0:-1] # ============================================================================= df = df[slice(self.initialdate, self.finaldate)] # Remove 29th February (to avoid unrecognized dates when standardizing yearly timeseries) df = df.iloc[(df.index.month != 2) | (df.index.day != 29)] # Rolling window df_daily = df.copy(deep=True) df = df.rolling(f"{self.rolling_days}D", center = True).mean() # df_daily = df.copy(deep=True) # Select the desired period df = df[slice(self.startdate, self.enddate)].copy() df_daily = df_daily[slice(self.startdate, self.enddate)].copy() # Compute an average annual time series mean_df = df.groupby([df.index.month, df.index.day]).agg(self.agg_rule) min_df = df_daily.groupby([df_daily.index.month, df_daily.index.day]).min() max_df = df_daily.groupby([df_daily.index.month, df_daily.index.day]).max() mean_df.index = [pd.to_datetime(f"1800-{m}-{d}", format = "%Y-%m-%d") if m > self.enddate.month else pd.to_datetime(f"1801-{m}-{d}", format = "%Y-%m-%d") for (m, d) in mean_df.index ] min_df.index = [pd.to_datetime(f"1800-{m}-{d}", format = "%Y-%m-%d") if m > self.enddate.month else pd.to_datetime(f"1801-{m}-{d}", format = "%Y-%m-%d") for (m, d) in min_df.index ] max_df.index = [pd.to_datetime(f"1800-{m}-{d}", format = "%Y-%m-%d") if m > self.enddate.month else pd.to_datetime(f"1801-{m}-{d}", format = "%Y-%m-%d") for (m, d) in max_df.index ] # 1800 is used as an arbitrary year mean_df.sort_index(inplace = True) min_df.sort_index(inplace = True) max_df.sort_index(inplace = True) mean_df.attrs = {'period': (self.startdate, self.enddate), 'model': self.model_names_list[i]} min_df.attrs = {'period': (self.startdate, self.enddate), 'model': self.model_names_list[i]} max_df.attrs = {'period': (self.startdate, self.enddate), 'model': self.model_names_list[i]} if self.relative: mean_df = mean_df - self.mean_relative_ref[i] min_df = min_df - self.mean_relative_ref[i] max_df = max_df - self.mean_relative_ref[i] # Save & store single_period_res.append(mean_df) single_period_daily.append(min_df) single_period_daily.append(max_df) # Define the filled area # ======= old version ========================================================= # merge_res = single_period_res[0].copy() # return single_period_res # for r in range(1, len(single_period_res)): # merge_res = merge_res.merge(single_period_res[r], # left_index = True, # right_index = True, # how = 'outer') # ============================================================================= merge_res = pd.concat(single_period_res, axis = 1) merge_res = merge_res.set_axis([f"{self.varname}_{model}" for model in self.model_names_list], axis = 1) merge_daily = pd.concat(single_period_daily, axis = 1) list_min = [f"{self.varname}_{model}_min" for model in self.model_names_list] list_max = [f"{self.varname}_{model}_max" for model in self.model_names_list] list_daily = [None] * len(self.model_names_list) * 2 list_daily[::2] = list_min list_daily[1::2] = list_max merge_daily = merge_daily.set_axis(list_daily, axis = 1) self.all_res.append(merge_res) self.all_daily.append(merge_daily) if self.plot_mode == 'temporality': min_res = merge_res.min(axis = 1) min_res = min_res.to_frame('min') max_res = merge_res.max(axis = 1) max_res = max_res.to_frame('max') min_res = min_res.merge(max_res, left_index = True, right_index = True) # min_res = min_res.set_axis(['min', 'max']) self.graph_res.append(min_res) min_daily = merge_daily.min(axis = 1) min_daily = min_daily.to_frame('min') max_daily = merge_daily.max(axis = 1) max_daily = max_daily.to_frame('max') min_daily = min_daily.merge(max_daily, left_index = True, right_index = True) self.graph_daily.append(min_daily) # self.year_labels.append(f"{self.startdate.strftime('%Y-%m')} ➞ {self.enddate.strftime('%Y-%m')}") self.year_labels.append(f"{self.startdate.strftime('%m')}/<b>{self.startdate.year}</b> ➞ {self.enddate.strftime('%m')}/<b>{self.enddate.year}</b>") elif self.plot_mode == 'scatter': if self.agg_rule == 'mean': agg_res = merge_res.mean(axis = 0) elif self.agg_rule == 'sum': agg_res = merge_res.sum(axis = 0) # agg_res = agg_res.to_frame(f'{startyear}-{endyear}').T # agg_res = agg_res.to_frame(f"{self.startdate.strftime('%Y-%m')} ➞ {self.enddate.strftime('%Y-%m')}").T agg_res = agg_res.to_frame(self.startdate.year).T agg_res.index.name = 'time' # sum_res.set_index(f'{startyear}-{endyear}') if len(self.graph_res) == 0: self.graph_res = agg_res.copy() else: self.graph_res = pd.concat([self.graph_res, agg_res], axis = 0) elif self.is_metric: metric_res = self.metric(merge_res, self.startdate, self.plot_type) # Range min_res = metric_res.min(axis = 1) min_res = min_res.to_frame('min') max_res = metric_res.max(axis = 1) max_res = max_res.to_frame('max') min_res = min_res.merge(max_res, left_index = True, right_index = True) self.graph_res.append(min_res) if self.enddate <= self.finaldate: if isinstance(self.period_years, list): period_count += 1 if period_count < len(self.period_years): self.startdate = datetime.datetime( year = self.period_years[period_count][0], month = self.annuality, day = 1) self.enddate = datetime.datetime( year = self.period_years[period_count][1], month = self.annuality, day = 1) + pd.DateOffset(days = -1) else: # to quit the while loop self.enddate = self.finaldate + pd.DateOffset(years = 1) elif isinstance(self.period_years, int): if self.is_metric: self.startdate += pd.DateOffset(years = 1) self.enddate += pd.DateOffset(years = 1) else: self.startdate += pd.DateOffset(years = self.period_years) self.enddate += pd.DateOffset(years = self.period_years) # Additional treatment for metrics if self.is_metric: # Redefinition of graph_res self.graph_res = pd.concat(self.graph_res) # Additional treatment for relative representation if self.relative: self.graph_res['to_min'] = 0 self.graph_res['to_max'] = 0 self.graph_res.loc[(self.graph_res['min'] > 0), 'to_max'] = self.graph_res.loc[(self.graph_res['min'] > 0).index, 'min'] self.graph_res.loc[(self.graph_res['min'] > 0), 'max'] = self.graph_res.loc[(self.graph_res['min'] > 0).index, 'max'] - self.graph_res.loc[(self.graph_res['min'] > 0).index, 'min'] self.graph_res.loc[(self.graph_res['min'] > 0), 'min'] = 0 self.graph_res.loc[(self.graph_res['max'] < 0), 'to_min'] = self.graph_res.loc[(self.graph_res['min'] > 0).index, 'max'] self.graph_res.loc[(self.graph_res['max'] < 0), 'min'] = self.graph_res.loc[(self.graph_res['max'] > 0).index, 'min'] - self.graph_res.loc[(self.graph_res['max'] > 0).index, 'max'] self.graph_res.loc[(self.graph_res['max'] < 0), 'max'] = 0 # Xaxis title # ============================================================================= # years_before = self.period_years //2 # years_after = self.period_years //2 + self.period_years %2 # self.xaxis_add_label = [f"<br><i>({self.startdate.strftime('%m')}/<b>YYYY-{years_before}</b> ➞ {(self.startdate + pd.DateOffset(years = years_after, days = - 1)).strftime('%m')}/<b>YYYY+{years_after}</b></i>)", # f"<br><i>({self.startdate.strftime('%m')}/<b>AAAA-{years_before}</b> ➞ {(self.startdate + pd.DateOffset(years = years_after, days = - 1)).strftime('%m')}/<b>AAAA+{years_after}</b></i>)"] # self.graph_res.index = self.graph_res.index + pd.DateOffset(years = years_before) # ============================================================================= self.xaxis_add_label = ['', ''] if isinstance(self.period_years, list): self.period_lengths = [ datetime.datetime( year = self.period_years[period_count][1], month = self.annuality, day = 1, ) - datetime.datetime( year = self.period_years[period_count][0], month = self.annuality, day = 1, ) for period_count in range(0, len(self.period_years))] self.graph_res.index = pd.to_datetime( [datetime.datetime( year = self.period_years[period_count][0], month = self.annuality, day = 1, ) + self.period_lengths[period_count]/2 for period_count in range(0, len(self.period_years))] ) elif isinstance(self.period_years, int): self.graph_res.index = self.graph_res.index + pd.DateOffset( years = self.period_years //2, months = self.period_years %2 /2*12) # self.period_lengths defined later, if self.repres == "bar" if isinstance(self.period_years, int): self.layer_labels = [f"<b>Centered roll. mean ({self.period_years} y)</b>", f"<b>Moy. glissante centrée ({self.period_years} ans)</b>"] elif isinstance(self.period_years, list): self.layer_labels = [f"<b>Centered roll. mean ({len(self.period_years)} periods)</b>", f"<b>Moy. glissante centrée ({len(self.period_years)} périodes)</b>"] # ---- layout/export self.layout() # self.apply_layout()
[docs] def layout(self, *, plotsize=None, name=None, credit=None, color=None, language=None, showlegend=None, shadow=None, repres=None, cumul=None): # ---- Update self.update( plotsize=plotsize, name=name, credit=credit, color=color, language=language, showlegend=showlegend, shadow=shadow, repres=repres, cumul=cumul,) # ---- Plotting print("\n_ Plotting...") # mode 1: temporality if self.plot_mode == 'temporality': # Axis titles self.xaxis_add_label = ['', ''] self.yaxis_add_label = ['', ''] # Colormap # ============================================================================= # color_map1 = cmg.discrete('trio', alpha = 1, black = False, # color_format = 'rgba_str', alternate = True) # color_map2 = cmg.discrete('wong', alpha = 1, black = False, # color_format = 'rgba_str', alternate = False) # color_map3 = cmg.discrete('ibm', alpha = 1, black = False, # color_format = 'rgba_str', alternate = False) # # color_map = np.vstack([ # # np.array([0, 0, 0, 1]), # color_map2, # color_map2, # ]) # ============================================================================= # Colors if self.color == 'scale': if not self.relative: color_map = cm.viridis(np.linspace(0, 1, len(self.all_res))) else: color_map = cm.plasma(np.linspace(0, 1, len(self.all_res))) elif self.color == 'discrete': if len(self.graph_res) <= 6: color_map = cmg.discrete(sequence_name = 'ibm', alpha = 1, black = False, alternate = False, color_format = 'float') color_map = color_map[::-1] if len(self.graph_res) == 2: color_map = color_map[[2, 4]] elif len(self.graph_res) == 3: color_map = color_map[[0, 2, 4]] elif len(self.graph_res) == 4: color_map = color_map[[0, 2, 4]] add_color = cmg.discrete(sequence_name = 'wong', alpha = 1, black = False, alternate = False, color_format = 'float') color_map = np.vstack([color_map, add_color]) elif (len(self.graph_res) > 6) & (len(self.graph_res) <= 9): color_map = cmg.discrete(sequence_name = 'wong', alpha = 1, black = False, alternate = False, color_format = 'float') color_map = color_map[::-1] else: color_map = cmg.discrete(sequence_name = 'trio', alpha = 1, black = False, alternate = False, color_format = 'float') elif isinstance(self.color, str): color_map = exec(f"cm.{self.color}(np.linspace(0, 1, len(self.all_res)))") else: color_map = self.color color_map_fill = color_map.copy() if not self.cumul: color_map_fill[:, -1] = 0.5 else: color_map_fill[:, -1] = 0.2 # Line properties lwidth = [1.5]*len(self.all_res) lstyle = ['-']*len(color_map) lstyle2 = lstyle.copy() if self.cumul: lstyle2 = ['dotted']*len(color_map) # Figure self.figweb = None # Grey shades for climatic variability if self.shadow is not None: # First chrological curve if self.shadow in ['all', 'first', 'firstlast', 'lastfirst']: [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_rea_daily[0]['min'], fill = self.cumul_filloption, labels = 'min', linecolors = [0.0, 0.0, 0.0, 1], lwidths = 0.2, lstyles = '-', cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, legendgroup = 2000, showlegend = False, ) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_rea_daily[0]['max'], fill = 'tonexty', labels = ['daily range for', 'gamme journalière pour '][self.lang] + self.rea_labels[0], linecolors = [0.0, 0.0, 0.0, 1], fillcolors = [0.0, 0.0, 0.0, 0.08/len(self.graph_rea_daily)], lwidths = 0.2, lstyles = '-', cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, legendgroup = 2000, showlegend = True, ) # Next curves if self.shadow == 'all': [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_rea_daily[1]['min'], fill = self.cumul_filloption, labels = 'min', linecolors = [0.0, 0.0, 0.0, 1], lwidths = 0.2, lstyles = 'dotted', cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, legendgroup = 3000, showlegend = False, ) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_rea_daily[1]['max'], fill = 'tonexty', labels = ['daily range for', 'gamme journalière pour '][self.lang] + self.rea_labels[1], linecolors = [0.0, 0.0, 0.0, 1], fillcolors = [0.0, 0.0, 0.0, 0.08/len(self.graph_rea_daily)], lwidths = 0.2, lstyles = 'dotted', cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, legendgroup = 3000, showlegend = True, ) for i in range(0, len(self.graph_daily)-1): [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_daily[i]['min'], fill = self.cumul_filloption, labels = 'min', linecolors = color_map[i], lwidths = 0.2, lstyles = lstyle[i], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, legendgroup = 4000 + i, showlegend = False, ) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_daily[i]['max'], fill = 'tonexty', labels = ['daily range for', 'gamme journalière pour '][self.lang] + self.year_labels[i], linecolors = color_map[i], fillcolors = [0.0, 0.0, 0.0, 0.08/len(self.graph_daily)], lwidths = 0.2, lstyles = lstyle2[i], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, legendgroup = 4000 + i, showlegend = True, ) # Last chronological curve if self.shadow in ['all', 'last', 'firstlast', 'lastfirst']: [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_daily[-1]['min'], fill = self.cumul_filloption, labels = 'min', linecolors = color_map[[-1]], lwidths = 0.2, lstyles = lstyle[-1], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = False, ) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_daily[-1]['max'], fill = 'tonexty', labels = ['daily range for', 'gamme journalière pour '][self.lang] + self.year_labels[-1], linecolors = color_map[[-1]], fillcolors = [0.0, 0.0, 0.0, 0.08/len(self.graph_daily)], lwidths = 0.2, lstyles = lstyle2[-1], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = True, ) # =========== zorder property does not work =================================== # self.figweb.update_traces(zorder = 10, selector = {'line':{'width': 1.5}}) # self.figweb.update_traces(zorder = 0, selector = {'line':{'width': 0.2}}) # ============================================================================= # SIM2 reference (historical reanalysis) if self.scenario != 'SIM2': [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_rea[::-1], labels = self.rea_labels[::-1], linecolors = np.array([[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0]]), lwidths = [1.5]*len(self.graph_rea), lstyles = ['-', 'dotted'][::-1], legendgroup = 1000, # legendgrouptitle_text = ['Historical reanalysis', 'Reanalyse historique'][self.lang], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul,) # Main curves for p in range(0, len(self.graph_res)): if self.graph_res[p]['min'].equals(self.graph_res[p]['max']): # Lines will be visible and there will be no shade color_map_line = color_map.copy() fill_opt = None else: # Lines will be set transparent, and shade will be visible color_map_line = color_map.copy() color_map_line[:, -1] = 0 fill_opt = 'tonexty' [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_res[p]['min'], fill = self.cumul_filloption, labels = 'min', linecolors = color_map_line[[p]], lwidths = lwidth[p], lstyles = lstyle[p], legendgroup = p, # legendgrouptitle_text = self.year_labels[p], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = False) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_res[p]['max'], fill = fill_opt, labels = self.year_labels[p], linecolors = color_map_line[[p]], fillcolors = color_map_fill[[p]], lwidths = lwidth[p], lstyles = lstyle2[p], legendgroup = p, # legendgrouptitle_text = self.year_labels[p], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = True) # Axes self.xtickformat = '%b' # '%d/%m' # mode 2: annual_sum elif self.plot_mode == 'scatter': # Axis titles self.xaxis_add_label = [f"<br><i>({self.startdate.strftime('%m')}/<b>YYYY</b> ➞ {self.enddate.strftime('%m')}/<b>YYYY+{self.enddate.year - self.startdate.year}</b></i>)", f"<br><i>({self.startdate.strftime('%m')}/<b>AAAA</b> ➞ {self.enddate.strftime('%m')}/<b>AAAA+{self.enddate.year - self.startdate.year}</b></i>)"] self.yaxis_add_label = ['', ''] # Colormap color_map1 = cmg.discrete('trio', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map2 = cmg.discrete('wong', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map3 = cmg.discrete('ibm', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map = np.vstack([ # np.array([0, 0, 0, 1]), color_map2, color_map2, ]) # color_map = cm.viridis(np.linspace(0, 1, len(self.all_res))) # Line properties lwidth = [1.5]*self.graph_res.shape[1] lstyle = ['-']*len(color_map1) + ['dotted']*len(color_map1) # Figure self.figweb = None [self.fig1, self.ax1, self.figweb] = ncp.plot_time_series( figweb = self.figweb, data = [self.graph_res[[f"{self.varname}_{model}"]] for model in self.model_names_list], labels = self.model_names_list, linecolors = color_map, lwidths = lwidth, lstyles = lstyle, # legendgroup = p, # legendgrouptitle_text = self.year_labels[p], mode = 'markers', markers = dict( size = 12), ) # Axes self.xtickformat = '%Y' # mode 3: metrics elif self.is_metric: # Yaxis title if self.is_logical: self.yaxis_add_label = [f"<br><i>(number of days such as {self.var} {self.plot_mode})</i>", # en f"<br><i>(nombre de jours tels que {self.var} {self.plot_mode})</i>"] # fr elif self.is_date: self.yaxis_add_label = [f"<br><i>(value at the date of {self.plot_mode})</i>", # en f"<br><i>(valeur à la date du {self.plot_mode})</i>"] # fr elif self.is_month: month_name_start = datetime.datetime(year = 1800, month = self.months[0], day = 1).strftime('%B') month_name_end = datetime.datetime(year = 1800, month = self.months[-1], day = 1).strftime('%B') # ============= Future developpment (lang) ==================================== # To improve the month name language, it is advised to install # the babel package, then use: # babel.dates.format_date(datetime.datetime(year = 1800, month = self.months[0], day = 1), "MMMM", locale = language) # ============================================================================= if month_name_start == month_name_end: month_name = month_name_start else: month_name = month_name_start + ' → ' + month_name_end month_label = [month_name + ' {}', # en '{} sur ' + month_name] # fr self.yaxis_add_label = ["<br><i>(" + month_label[0].format(self.agg_labels[self.agg_rule][0]) + ")</i>", # en "<br><i>(" + month_label[1].format(self.agg_labels[self.agg_rule][1]) + ")</i>"] # fr else: # self.yaxis_add_label = [f"<br><i>({m_label}</i>)" for m_label in self.metric_labels[self.plot_mode]] self.yaxis_add_label = ["<br><i>(" + self.metric_labels[self.plot_mode][0].format(self.agg_labels[self.agg_rule][0]) + ")</i>", # en "<br><i>(" + self.metric_labels[self.plot_mode][1].format(self.agg_labels[self.agg_rule][1]) + ")</i>"] # fr # Colormap color_map = cmg.discrete('wong', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map2 = cmg.discrete('wong', alpha = 0.5, black = False, color_format = 'rgba_str', alternate = False) color_map_rel = cmg.discrete('ibm', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) # Line properties lwidth = 1.5 lstyle = '-' # Figure self.figweb = None if self.repres == "area": if not self.relative: [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_res['min'], fill = self.cumul_filloption, labels = 'min', linecolors = color_map[7], lwidths = lwidth, lstyles = lstyle, legendgroup = 1, # legendgrouptitle_text = self.year_labels[p], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = False) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_res['max'], fill = 'tonexty', labels = self.layer_labels[self.lang], linecolors = color_map[7], # fillcolor = color_map2[3], lwidths = lwidth, lstyles = lstyle, legendgroup = 1, # legendgrouptitle_text = self.year_labels[p], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = True) elif self.relative: [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_res['to_min'], fill = 'tozeroy', labels = None, #['to_min'], fillcolors = 'rgba(255, 255, 255, 0)', linecolors = 'rgba(255, 255, 255, 0)', lwidths = lwidth, lstyles = lstyle, legendgroup = 1, cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = False, visible = True) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_res['min'], fill = 'tonexty', labels = self.layer_labels[self.lang] + ' (neg)', # ['min'], # markers = {'color': color_map_rel[4]}, linecolors = color_map[3], lwidths = lwidth, lstyles = lstyle, legendgroup = 1, cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = True,) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_res['to_max'], fill = 'tozeroy', labels = None, # [self.layer_labels[self.lang]], # markers = {'color': 'rgba(255, 255, 255)'}, linecolors = 'rgba(255, 255, 255, 0)', fillcolors = 'rgba(255, 255, 255, 0)', lwidths = lwidth, lstyles = lstyle, legendgroup = 2, cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = False, visible = True) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, data = self.graph_res['max'], fill = 'tonexty', labels = self.layer_labels[self.lang] + ' (pos)', # markers = {'color': color_map_rel[0]}, linecolors = color_map[-1], # fillcolors = color_map2[[3]], lwidths = lwidth, lstyles = lstyle, legendgroup = 2, cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = True) elif self.repres == "bar": # Treatment for periods if isinstance(self.period_years, int): date_list = [] block_centered_date = self.graph_res.index[0] while block_centered_date <= self.finaldate: date_list.append(block_centered_date) block_centered_date += pd.DateOffset(years = self.period_years) date_list = pd.to_datetime(date_list).intersection(self.graph_res.index) graph_res = self.graph_res.loc[date_list, :] self.period_lengths = [self.enddate - self.startdate]*graph_res.shape[0] # nrows else: graph_res = self.graph_res.copy() period_lengths = [p.total_seconds()*1000 for p in self.period_lengths] # ============ already defined ================================================ # elif isinstance(self.period_years, list): # self.period_lengths # ============================================================================= if not self.relative: [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, mode = "bar", data = graph_res['min'], bar_widths = period_lengths, fill = self.cumul_filloption, labels = 'min', fillcolors = "rgba(255, 255, 255, 0)", linecolors = "rgba(255, 255, 255, 0)", lwidths = lwidth, # lstyle = lstyle, legendgroup = 1, # legendgrouptitle_text = self.year_labels[p], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = False, visible = True,) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, mode = "bar", bar_widths = period_lengths, data = graph_res['max'] - graph_res['min'], # fill = 'tonexty', labels = [self.layer_labels[self.lang]], linecolors = color_map[7], fillcolors = color_map2[7], lwidths = lwidth, # lstyles = lstyle, legendgroup = 1, # legendgrouptitle_text = self.year_labels[p], cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = True) elif self.relative: [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, mode = 'bar', data = graph_res['to_min'], bar_widths = period_lengths, labels = None, #['to_min'], # markers = {'color': 'rgba(255, 255, 255)'}, linecolors = 'rgba(255, 255, 255, 0)', fillcolors = 'rgba(255, 255, 255, 0)', lwidths = lwidth, lstyles = lstyle, cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = False, visible = True) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, mode = 'bar', data = graph_res['min'], bar_widths = period_lengths, labels = self.layer_labels[self.lang] + ' (neg)', # ['min'], # markers = {'color': color_map_rel[3]}, fillcolors = color_map2[3], linecolors = color_map[3], lwidths = lwidth, lstyles = lstyle, cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = True) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, mode = 'bar', data = graph_res['to_max'], bar_widths = period_lengths, labels = None, # [self.layer_labels[self.lang]], # markers = {'color': 'rgba(255, 255, 255)'}, # linecolors = color_map_rel[3], linecolors = 'rgba(255, 255, 255, 0)', fillcolors = 'rgba(255, 255, 255, 0)', lwidths = lwidth, lstyles = lstyle, cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = False, visible = True) [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, mode = 'bar', data = graph_res['max'], bar_widths = period_lengths, labels = self.layer_labels[self.lang] + ' (pos)', # markers = {'color': color_map_rel[0]}, # linecolors = color_map_rel[0], fillcolors = color_map2[-1], linecolors = color_map[-1], lwidths = lwidth, lstyles = lstyle, cumul = self.cumul, date_ini_cumul = self.date_ini_cumul, showlegend = True) # Adjust legend on bars self.figweb.update_traces( hoverinfo = None, hovertext = [f"{(graph_res.index[i] - pd.DateOffset(seconds = period_lengths[i]/2/1000)).strftime('%Y-%m')}{(graph_res.index[i] + pd.DateOffset(seconds = period_lengths[i]/2/1000)).strftime('%Y-%m')}" for i in range(0, len(period_lengths))], hovertemplate = "%{hovertext}<br><b>%{y}</b>", ) # ============ Note in case of SIM2 =========================================== # [fig1, ax1, self.figweb] = ncp.plot_time_series(figweb = self.figweb, # data = [self.graph_res['max']], # # fill = 'tonexty', # labels = [self.layer_labels[self.lang]], # linecolors = color_map2[[3]], # # fillcolors = color_map2[[3]], # lwidths = [lwidth], # lstyles = [lstyle], # mode = 'markers', # markers = {"size": 28}, # legendgroup = 1, # # legendgrouptitle_text = self.year_labels[p], # cumul = self.cumul, # date_ini_cumul = self.date_ini_cumul, # showlegend = True) # ============================================================================= # Axes self.xtickformat = '%Y' # ---- Scales # X # ============================================================================= # dates_lim = [results[0].time.iloc[0], # results[0].time.iloc[-1] + pd.Timedelta(days = 3653)] # ============================================================================= # None # ['2004-01-04', '2024-01-05'] # Y # ============================================================================= # ylim = [results[0].val.mean(), results[0].val.mean()] # ============================================================================= if self.plot_mode == 'date': self.ytickformat = '%d/%m' else: self.ytickformat = None if self.scenario == 'SIM2': ylabel = f'{self.info_by_var_sim2[self.var][2][self.lang]} [{self.info_by_var_sim2[self.var][0]}]' else: ylabel = f'{self.info_by_var[self.var][2][self.lang]} [{self.info_by_var[self.var][0]}]' # Adjust language of legends if self.plot_mode == 'temporality': if self.scenario != 'SIM2': self.figweb.update_traces(legendgrouptitle_text = ['Historical reanalysis', 'Reanalyse historique'][self.lang], selector = ({'legendgroup': 1000})) elif self.is_metric: self.figweb.update_traces(name = self.layer_labels[self.lang], selector = ({'fill': 'tonexty'})) # ---- Title title = f"Scenario {self.scenario.upper()}" + self.rel_title[self.lang] # ---- Figure update self.figweb.update_layout(#font_family = 'Open Sans', title = {'font': {'size': self.title_fontsize}, 'text': title, 'xanchor': 'center', 'x': 0.5, }, # annotations = {'xanchor': 'middle', # 'yanchor': 'top', # 'size': 20, # 'text': add_title + "\nK = 5e-6 m/s Poro = 0.1% e = 25 m"}, xaxis = {'title': {'font': {'size': self.axes_fontsize}, 'text': ['Time [d]', 'Temps [j]'][self.lang] + self.xaxis_add_label[self.lang]}, # 'range': dates_lim, 'tickformat': self.xtickformat, }, yaxis = {'title': {'font': {'size': self.axes_fontsize}, 'text': ylabel + self.yaxis_add_label[self.lang]}, # 'text': field_title + ' [m3/s]'}, 'type': 'linear', # 'range': ylim,#_ylim_figweb, 'tickformat': self.ytickformat, }, legend = {'title': {'text': ['<b>Legend</b>', '<b>Légende</b>'][self.lang]}, 'xanchor': 'right', 'y': 1, 'yanchor': 'top', 'bgcolor': 'rgba(255, 255, 255, 0.2)', 'tracegroupgap': 0, # 'orientation': legend_orientation, }, font = {'size': self.txt_fontsize}, plot_bgcolor = "white", showlegend = self.showlegend, # legend = {'groupclick': 'togglegroup'}, width = self.plotdims[0], # paper[0], # wide[0], height = self.plotdims[1], # paper[1], # wide[1], ) # ---- Horizontal 0 line (when self.relative = True) if self.relative: self.figweb.add_hline(y = 0, line_color = 'rgba(255, 255, 255, 1)', line_width = 5, ) # ---- Relative bar chart if self.is_metric & (self.repres == "bar"): # & self.relative self.figweb.update_layout(barmode = 'relative') # center around 0 # ---- Credits if self.credit == 'auto': if self.scenario == 'SIM2': data_src = self.info_by_var_sim2[self.var][4] else: data_src = self.info_by_var[self.var][5] credit_text = [f"data: {data_src} | design: Alexandre Coche", f"données : {data_src} | conception : Alexandre Coche"][self.lang] elif isinstance(self.credit, str): credit_text = self.credit self.figweb.add_annotation(xanchor = 'right', xref = 'paper', x = 1, yref = 'paper', y = -0.2, yanchor = 'bottom', font = {'size': 8, 'color': 'rgba(0, 0, 0, 0.5)'}, text = f"<i>{credit_text}</i>", showarrow = False ) # ---- Figure export output_name = '_'.join([self.var + self.rel_suf, self.name, self.scenario.upper(), self.plot_type.replace('>', 'sup').replace('<', 'inf').replace('==', 'eq').replace('/', '-') + self.repres_suf, self.period_str, f'{self.rolling_days}d' \ + [f'_ms{self.annuality}', ''][self.is_metric], self.language, self.plotsize + self.cumul_suf, # datetime.datetime.now().strftime("%Y-%m-%d_%Hh%M"), datetime.datetime.now().strftime("%Y-%m-%d"), ]) self.figweb.write_html(os.path.join(self.root_folder, 'tempo', output_name + '.html' ) ) self.figweb.write_image(os.path.join(self.root_folder, 'tempo', output_name + '.png' ) )
[docs] def update(self, *, var=None, root_folder=None, scenario=None, epsg_data=None, coords=None, epsg_coords=None, relative=None, rolling_days=None, period_years=None, annuality=None, plot_type=None, repres=None, cumul=None, language=None, plotsize=None, color=None, name=None, credit=None, showlegend=None, shadow=None, verbose=None, ): # All default values are None so that the unspecified values are not # updated. # ---- Plot parameters # Annuality if annuality is not None: if annuality == 'calendar': self.annuality = 1 elif annuality in ['meteo', 'meteorological']: self.annuality = 9 elif annuality in ['hydro', 'hydrological']: self.annuality = 10 else: self.annuality = annuality if rolling_days is not None: self.rolling_days = rolling_days if period_years is not None: self.period_years = period_years if isinstance(self.period_years, tuple): self.period_years = [self.period_years] if isinstance(self.period_years, int): self.period_str = f'{self.period_years}y' elif isinstance(self.period_years, list): self.period_str = f'{len(self.period_years)}periods' # Cumul self.cumul = cumul if self.cumul: self.date_ini_cumul = '1900-01-01' self.cumul_suf = '_cumul' self.cumul_filloption = 'tozeroy' else: self.cumul_suf = '' self.date_ini_cumul = None self.cumul_filloption = None # Plot type if plot_type is not None: self.plot_type = plot_type # Representation mode if repres is not None: if (repres == 'area') & (isinstance(self.period_years, list)): print("\nWarning: The representation mode cannot be a curve ('area') when period_years is a list of periods and not an integer.") repres = "bar" self.repres = repres if self.is_metric: self.repres_suf = f"_{self.repres}" else: self.repres_suf = '' # ---- Layout parameters # Language if language is not None: language_dict = {"en": 0, "fr": 1,} self.language = language self.lang = language_dict[self.language] # Graphical plot size # Two formats are defined here: wide and paper if plotsize == 'wide': self.plotsize = plotsize self.plotdims = (1500, 700) # (width, height) self.title_fontsize = 26 self.axes_fontsize = 18 self.txt_fontsize = 15.5 elif plotsize == 'paper' : self.plotsize = plotsize self.plotdims = (1000, 550) self.title_fontsize = 20 self.axes_fontsize = 16 self.txt_fontsize = 12 # Output name if name is not None: self.name = name # Acknowledgement if credit is None: if not hasattr(self, 'credit'): self.credit = '' else: self.credit = credit # Color if color is not None: self.color = color # Show legend if showlegend is not None: self.showlegend = showlegend # Show daily shadows if shadow is not None: self.shadow = shadow # ---- Load parameters # For these attributes, we need more than an update, we need a reload if var is not None: self.var = var if root_folder is not None: self.root_folder = root_folder if scenario is not None: self.scenario = scenario if epsg_data is not None: self.epsg_data = epsg_data if coords is not None: self.coords = coords if epsg_coords is not None: self.epsg_coords = epsg_coords if relative is not None: self.relative = relative if not all(v is None for v in [var, root_folder, scenario, epsg_data, coords, epsg_coords, relative]): self = Figure( var=self.var, root_folder=self.root_folder, scenario=self.scenario, epsg_data=self.epsg_data, coords=self.coords, epsg_coords=self.epsg_coords, plot_type=self.plot_type, rolling_days=self.rolling_days, period_years=self.period_years, annuality=self.annuality, cumul=self.cumul, relative=self.relative, language=self.language, plotsize=self.plotsize, name=self.name, verbose=self.verbose, )
[docs] @classmethod def get_plot_mode(cls, plot_type): # This method is used in plot() and in metric() # Initialize cases is_logical = False is_date = False is_month = False months = None plot_mode = plot_type.split('_')[0] if len(plot_type.split('_')) > 1: agg_rule = plot_type.split('_')[1] else: agg_rule = None if plot_mode == "temporality": if agg_rule is None: agg_rule = 'mean' print("Warnin: agg_rule (aggregation rule such as '_min', '_mean', '_sum'...) has been imposed to 'mean'.") logical_expr = re.compile(".*(<|>|=).*") # date_expr = re.compile('(\d{2,2})/(\d{2,2})') calendar_expr = "JFMAMJJASONDJFMAMJJASOND" month_expr = re.compile("m[0-9]{1,2}") # The plot mode is a characteristic date if plot_mode == 'date': if agg_rule is None: print("Err: agg_rule (aggregation rule such as '_min', '_mean', '_sum'...) should be passed in plot_type argument.") return else: agg_rule = 'idx' + agg_rule # The plot mode is a condition if len(logical_expr.findall(plot_mode)) > 0: is_logical = True agg_rule = None # Set to None, as the number of days is the only aggregation rule possible # The plot mpode is a date (single day) elif len(re.compile('.*(/).*').findall(plot_mode)) > 0: # elif len(date_expr.findall(plot_mode)) > 0: is_date = True agg_rule = None # Set to None, as the value is the only aggregation rule possible # The plot mode is one or several months elif plot_mode in calendar_expr: is_month = True months = re.compile(plot_mode).search(calendar_expr).span() if agg_rule is None: print("Err: agg_rule (aggregation rule such as '_min', '_mean', '_sum'...) should be passed in plot_type argument.") return # Adjust values months = [m+1 for m in range(months[0], months[1])] for i in range(0, len(months)): if months[i] > 12: months[i] -= 12 elif len(month_expr.findall(plot_mode)) > 0: m_int = int(plot_mode[1:]) if (m_int > 0) & (m_int <= 12): is_month = True months = [m_int] if agg_rule is None: print("Err: agg_rule (aggregation rule such as '_min', '_mean', '_sum'...) should be passed in plot_type argument.") return return plot_mode, agg_rule, is_logical, is_date, is_month, months
[docs] @classmethod def metric(cls, merge_res, startdate, plot_type): # This function can be used without creating a Figure instance. # To use it: # F = Figure # (without parentheses!) # F.metric(...) # Decipher plot_type plot_mode, agg_rule, is_logical, is_date, is_month, months = cls.get_plot_mode(plot_type) if is_month: cond_chain = '|'.join([f"(merge_res.index.month == {m})" for m in months]) res = merge_res.loc[eval(cond_chain)] else: # plot_mode = 'annual', date, logical... res = merge_res.copy() if plot_type == 'date': metric_res = res.agg(agg_rule, axis = 0).to_frame(startdate).T for c in metric_res.columns: if metric_res[c].dt.dayofyear.item() >= startdate.dayofyear: metric_res[c] = metric_res[c].dt.dayofyear else: metric_res[c] = metric_res[c].dt.dayofyear + 365 elif is_date: date_expr = re.compile('(\d{2,2})/(\d{2,2})') day, month = date_expr.findall(plot_type)[0] metric_res = res[(res.index.month == int(month)) \ & (res.index.day == int(day))] metric_res.index = [startdate] elif is_logical: logical_expr = re.compile(".*(<|>|<=|>=|==)(.*)") cond = logical_expr.findall(plot_type)[0] metric_res = (eval(f"res {cond[0]} {cond[1]}")).sum().to_frame(startdate).T else: if agg_rule == 'range': metric_res = (res.max(axis = 0) - res.min(axis = 0)).to_frame(startdate).T elif agg_rule == 'decrease': print("Warning: Not implemented yet") elif agg_rule == 'increase': print("Warning: Not implemented yet") else: metric_res = res.agg(agg_rule, axis = 0).to_frame(startdate).T return metric_res
[docs] @staticmethod def timeseries(*, data_type, var, scenario, season, domain = 'Pays-Basque', epsg_data = None, coords = 'all', epsg_coords = None, language = 'fr', plotsize = 'wide', rolling_window = 1, plot_type = '2series', ): """ Examples -------- import climatic_plot as tjp tjp.timeseries(data_type = "Indicateurs DRIAS-Eau 2024 SWIAV", scenario = 'historical', season = 'JJA', domain = 'Pays Basque', rolling_window = 10, plot_type = '2series') # ---- Original and rolled series for SWIAV for season in ['JJA', 'SON', 'DJF', 'MAM']: for scenario in ['historical', 'rcp45', 'rcp85']: for domain in ['Pays Basque', 'Annecy']: tjp.timeseries(data_type = "Indicateurs DRIAS-Eau 2024", var = 'SWIAV', scenario = scenario, season = season, domain = domain, rolling_window = 10, plot_type = '2series') # ---- Same for SSWI for season in ['JJA', 'SON', 'DJF', 'MAM']: for scenario in ['rcp45', 'rcp85']: for domain in ['Pays Basque', 'Annecy']: tjp.timeseries(data_type = "Indicateurs DRIAS-Eau 2024", var = 'SSWI', scenario = scenario, season = season, domain = domain, rolling_window = 10, plot_type = '2series') # ---- All different colors with SIM2 tjp.timeseries(data_type = "Indicateurs DRIAS-Eau 2024", var = 'SWIAV', scenario = 'historical', season = 'JJA', domain = 'Pays Basque', rolling_window = 10, plot_type = 'all with sim2') # ---- Narratifs for season in ['JJA', 'SON', 'DJF', 'MAM']: for domain in ['Pays Basque', 'Annecy']: tjp.timeseries(data_type = "Indicateurs DRIAS-Eau 2024", var = 'SWIAV', scenario = 'rcp85', season = season, domain = domain, rolling_window = 10, plot_type = 'narratifs') Parameters ---------- * : TYPE DESCRIPTION. root_folder : TYPE DESCRIPTION. scenario : str 'historical' | 'rcp45' | 'rcp85' season : str 'DJF' | 'MAM' | 'JJA' | 'SON' | 'NDJFMA' | 'MAMJJASO' | 'JJASO' | 'SONDJFM' data_type : TYPE DESCRIPTION. epsg_data : TYPE, optional DESCRIPTION. The default is None. coords : TYPE, optional DESCRIPTION. The default is 'all'. epsg_coords : TYPE, optional DESCRIPTION. The default is None. language : TYPE, optional DESCRIPTION. The default is 'fr'. plotsize : TYPE, optional DESCRIPTION. The default is 'wide'. rolling_window : TYPE, optional DESCRIPTION. The default is 10. plot_type : TYPE, optional DESCRIPTION. The default is '2series'. : TYPE DESCRIPTION. Returns ------- None. """ #%% GENERAL SETTINGS # ---- Language language_dict = {"en": 0, "fr": 1,} lang = language_dict[language] # ---- Graphical plot size # Two formats are defined here: wide and paper if plotsize == 'wide': plotsize = (1500, 700) # (width, height) elif plotsize == 'paper' : plotsize = (1000, 550) #%% DRIAS-Eau Indicateurs if (data_type.casefold().replace(' ', '').replace('-', '') in [ 'indicateursexplore22024', 'indicateursdriaseau2024']) & (plot_type != 'narratifs'): # ---- Folder root_folder = rf"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees\8- Meteo\DRIAS\DRIAS-Eau\EXPLORE2-SIM2 2024\{domain}\Indicateurs\Eau-{var}_Saisonnier_EXPLORE2-2024_{scenario}" # ---- Season season_list = [d for d in os.listdir(root_folder) if os.path.isdir(os.path.join(root_folder, d))] if season not in season_list: print(r"Erreur : Les données ne contiennent pas la saison {season}.") folder = os.path.join(root_folder, season) # ---- Units unit = '-' ylabel = [f'{var} [{unit}]', f'{var} [{unit}]'] # ---- Loading filelist = [ os.path.join(folder, f) for f in os.listdir(folder) if (os.path.isfile(os.path.join(folder, f)) \ & (os.path.splitext(os.path.join(folder, f))[-1] == '.nc'))] labels = [] results = [] for f in filelist: model_pattern = re.compile(f"{scenario}_(.*)_SIM2") filename = os.path.split(f)[-1] model = model_pattern.findall(filename)[0] labels.append(model) timeseries = ghc.time_series( input_file = f, coords = coords, epsg_coords = epsg_coords, epsg_data = epsg_data, ) results.append(timeseries) # ============================================================================= # # Rolling window # for res in results: # # Rolling window # res.set_index('time', inplace = True) # res = res.rolling('365.25D', center = True).mean() # ============================================================================= #%%% Graphics #%%%% All different colors if plot_type == 'all': suffix = 'all' # Colormap color_map1 = cmg.discrete('trio', alpha = 1, black = False, color_format = 'rgba_str', alternate = True) color_map2 = cmg.discrete('wong', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map3 = cmg.discrete('ibm', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map = np.vstack([ # np.array([0, 0, 0, 1]), color_map1, color_map1, ]) # Line properties lwidth = [1.5]*len(results) lstyle = ['-']*len(color_map1) + ['dotted']*len(color_map1) # Rolling window for i in range(0, len(results)): results[i] = results[i].rolling(time = rolling_window, center = True).mean() # Figure [fig1, ax1, figweb] = ncp.plot_time_series(data = results, labels = labels, color_map = color_map, lwidth = lwidth, lstyle = lstyle, ) #%%%% All different colors with SIM2 elif plot_type.casefold().replace(' ', '').replace('-', '') == 'allwithsim2': suffix = 'withSIM2' # Colormap color_map1 = cmg.discrete('trio', alpha = 1, black = False, color_format = 'rgba_str', alternate = True) color_map2 = cmg.discrete('wong', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map3 = cmg.discrete('ibm', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map = np.vstack([ # np.array([0, 0, 0, 1]), color_map1, color_map1, ]) # Line properties lwidth = [1.5]*len(results) lstyle = ['-']*len(color_map1) + ['dotted']*len(color_map1) # Rolling window for i in range(0, len(results)): results[i] = results[i].rolling(time = rolling_window, center = True).mean() # Figure [fig1, ax1, figweb] = ncp.plot_time_series(data = results, labels = labels, color_map = color_map, lwidth = lwidth, lstyle = lstyle, ) # Add SIM2 if domain == 'Pays Basque': territoire_folder = r'15- Territoire Pays Basque' elif domain == 'Annecy': territoire_folder = r'16- Territoire Annecy' sim2_ts = ghc.time_series( input_file = r"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees\8- Meteo\Surfex\SIM2\compressed\SWI_Q_SIM2_1958_202410_comp.nc", coords = os.path.join(r"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees", territoire_folder, r"masque_zone_etude.shp")) # Seasonalization first_year = sim2_ts.time[0].dt.year.item() last_year = sim2_ts.time[-1].dt.year.item() sim2_ts = sim2_ts.sel( time = slice(f"{first_year}-12-01", f"{last_year-1}-11-30") ) if season == 'DJF': reduced_sim2 = sim2_ts.where((sim2_ts.time.dt.month <= 2) | (sim2_ts.time.dt.month >= 12), drop = True).copy() elif season== 'MAM': reduced_sim2 = sim2_ts.where((sim2_ts.time.dt.month >= 3) & (sim2_ts.time.dt.month <= 5), drop = True).copy() elif season == 'JJA': reduced_sim2 = sim2_ts.where((sim2_ts.time.dt.month >= 6) & (sim2_ts.time.dt.month <= 8), drop = True).copy() elif season == 'SON': reduced_sim2 = sim2_ts.where((sim2_ts.time.dt.month >= 9) & (sim2_ts.time.dt.month <= 11), drop = True).copy() group = reduced_sim2.time.dt.year \ + np.floor((reduced_sim2.time.dt.month)/12) - 1 group = group.astype(int) reduced_sim2 = reduced_sim2.groupby(group).mean() reduced_sim2 = reduced_sim2.rename({'group': 'time'}) # Rolling window for SIM2 reduced_sim2 = reduced_sim2.rolling(time = rolling_window, center = True).mean() labels = ['SIM2'] color_map = np.array([[0.0, 0.0, 0.0, 1.0]]) [fig1, ax1, figweb] = ncp.plot_time_series(figweb = figweb, data = [reduced_sim2], labels = labels, color_map = color_map, lwidth = lwidth, lstyle = lstyle, ) #%%%% Original and rolled series elif plot_type.replace(' ', '').replace('-', '') == '2series': suffix = '2series' # color_map1 = cmg.discrete('trio', alpha = 0.2, black = False, # color_format = 'rgba_str', alternate = False) color_map2 = cmg.discrete('wong', alpha = 0.3, black = False, color_format = 'rgba_str', alternate = False) color_map22 = cmg.discrete('wong', alpha = 0.5, black = False, color_format = 'rgba_str', alternate = False) # color_map3 = cmg.discrete('ibm', alpha = 0.2, black = False, # color_format = 'rgba_str', alternate = False) # Line properties lwidth = [1.5]*len(results) lstyle = ['-']*len(results) # Rolling window results_roll = results.copy() for i in range(0, len(results_roll)): results_roll[i] = results_roll[i].rolling(time = rolling_window, center = True).mean() # Figure [fig1, ax1, figweb] = ncp.plot_time_series(data = results, labels = labels, color_map = np.repeat(color_map2[[8]], len(results), axis = 0), lwidth = lwidth, lstyle = lstyle, legendgroup = 1, legendgrouptitle_text = ['originals', 'originaux'][lang], ) [fig1, ax1, figweb] = ncp.plot_time_series(figweb = figweb, data = results_roll, labels = labels, color_map = np.repeat(color_map22[[7]], len(results_roll), axis = 0), lwidth = lwidth, lstyle = lstyle, legendgroup = 2, legendgrouptitle_text = [ f'rolling mean: {rolling_window} years', f'moy glissante : {rolling_window} ans'][lang], ) #%%%% Original and rolled series with SIM2 elif plot_type.casefold().replace(' ', '').replace('-', '') == '2serieswithsim2': suffix = '2seriesSIM2' # color_map1 = cmg.discrete('trio', alpha = 0.2, black = False, # color_format = 'rgba_str', alternate = False) color_map2 = cmg.discrete('wong', alpha = 0.3, black = False, color_format = 'rgba_str', alternate = False) color_map22 = cmg.discrete('wong', alpha = 0.5, black = False, color_format = 'rgba_str', alternate = False) # color_map3 = cmg.discrete('ibm', alpha = 0.2, black = False, # color_format = 'rgba_str', alternate = False) # Line properties lwidth = [1.5]*len(results) lstyle = ['-']*len(results) # Rolling window results_roll = results.copy() for i in range(0, len(results_roll)): results_roll[i] = results_roll[i].rolling(time = rolling_window, center = True).mean() # Figure [fig1, ax1, figweb] = ncp.plot_time_series(data = results, labels = labels, color_map = np.repeat(color_map2[[8]], len(results), axis = 0), lwidth = lwidth, lstyle = lstyle, legendgroup = 1, legendgrouptitle_text = ['originals', 'originaux'][lang], ) [fig1, ax1, figweb] = ncp.plot_time_series(figweb = figweb, data = results_roll, labels = labels, color_map = np.repeat(color_map22[[7]], len(results_roll), axis = 0), lwidth = lwidth, lstyle = lstyle, legendgroup = 2, legendgrouptitle_text = [ f'rolling mean: {rolling_window} years', f'moy glissante : {rolling_window} ans'][lang], ) # Add SIM2 if domain == 'Pays Basque': territoire_folder = r'15- Territoire Pays Basque' elif domain == 'Annecy': territoire_folder = r'16- Territoire Annecy' sim2_ts = ghc.time_series( input_file = r"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees\8- Meteo\Surfex\SIM2\compressed\SWI_Q_SIM2_1958_202410_comp.nc", coords = os.path.join(r"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees", territoire_folder, r"masque_zone_etude.shp")) # Seasonalization first_year = sim2_ts.time[0].dt.year.item() last_year = sim2_ts.time[-1].dt.year.item() sim2_ts = sim2_ts.sel( time = slice(f"{first_year}-12-01", f"{last_year-1}-11-30") ) if season == 'DJF': reduced_sim2 = sim2_ts.where((sim2_ts.time.dt.month <= 2) | (sim2_ts.time.dt.month >= 12), drop = True).copy() elif season== 'MAM': reduced_sim2 = sim2_ts.where((sim2_ts.time.dt.month >= 3) & (sim2_ts.time.dt.month <= 5), drop = True).copy() elif season == 'JJA': reduced_sim2 = sim2_ts.where((sim2_ts.time.dt.month >= 6) & (sim2_ts.time.dt.month <= 8), drop = True).copy() elif season == 'SON': reduced_sim2 = sim2_ts.where((sim2_ts.time.dt.month >= 9) & (sim2_ts.time.dt.month <= 11), drop = True).copy() group = reduced_sim2.time.dt.year \ + np.floor((reduced_sim2.time.dt.month)/12) - 1 group = group.astype(int) reduced_sim2 = reduced_sim2.groupby(group).mean() reduced_sim2 = reduced_sim2.rename({'group': 'time'}) labels = ['SIM2'] color_map = np.array([[0.0, 0.0, 0.0, 0.3]]) [fig1, ax1, figweb] = ncp.plot_time_series(figweb = figweb, data = [reduced_sim2], labels = labels, color_map = color_map, lwidth = lwidth, lstyle = lstyle, legendgroup = 1, ) # Rolling window for SIM2 rolled_sim2 = reduced_sim2.rolling(time = rolling_window, center = True).mean() labels = ['SIM2'] color_map = np.array([[0.0, 0.0, 0.0, 1]]) [fig1, ax1, figweb] = ncp.plot_time_series(figweb = figweb, data = [rolled_sim2], labels = labels, color_map = color_map, lwidth = lwidth, lstyle = lstyle, legendgroup = 2, ) #%% DRIAS-Eau Indicateurs 4 narratifs elif (data_type.casefold().replace(' ', '').replace('-', '') in [ 'indicateursexplore22024', 'indicateursdriaseau2024']) & (plot_type == 'narratifs'): # ---- Folder root_folder = rf"D:\2- Postdoc\2- Travaux\1- Veille\4- Donnees\8- Meteo\DRIAS\DRIAS-Eau\EXPLORE2-SIM2 2024\{domain}\Indicateurs\Eau-{var}_Saisonnier_EXPLORE2-2024_{scenario}" # ---- Season season_list = [d for d in os.listdir(root_folder) if os.path.isdir(os.path.join(root_folder, d))] if season not in season_list: print(r"Erreur : Les données ne contiennent pas la saison {season}.") folder = os.path.join(root_folder, season) # ---- Units unit = '-' ylabel = [f'{var} [{unit}]', f'{var} [{unit}]'] # ---- Loading filelist = [ os.path.join(folder, f) for f in os.listdir(folder) if (os.path.isfile(os.path.join(folder, f)) \ & (os.path.splitext(os.path.join(folder, f))[-1] == '.nc'))] labels = [] results = [] for narratif in ['EC-EARTH_HadREM3-GA7-05', # narratif orange 'CNRM-CM5_ALADIN63', # narratif jaune 'HadGEM2-ES_CCLM4-8-17', # narratif violet 'HadGEM2-ES_ALADIN63', # narratif vert ]: for f in filelist: model_pattern = re.compile(f"{scenario}_(.*)_SIM2") filename = os.path.split(f)[-1] model = model_pattern.findall(filename)[0] if model == narratif: labels.append(model) timeseries = ghc.time_series( input_file = f, coords = coords, epsg_coords = epsg_coords, epsg_data = epsg_data, ) results.append(timeseries) labels = ['<b>narratif orange</b> : fort réchauffement<br>et fort assèchement (notamment en été)', '<b>narratif jaune</b> : changements futurs<br>relativement peu marqués', '<b>narratif violet</b> : fort réchauffement et forts<br> contrastes saisonniers en précipitations', '<b>narratif vert</b> : réchauffement marqué<br>et augmentation des précipitations'] #%%% Graphic suffix = 'narratifs' # Colormap color_map1 = cmg.discrete('wong', alpha = 0.15, black = False, color_format = 'rgba_str', alternate = False) color_map11 = cmg.discrete('wong', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map2 = cmg.discrete('trio', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map3 = cmg.discrete('ibm', alpha = 1, black = False, color_format = 'rgba_str', alternate = False) color_map1 = np.vstack([ color_map1, ])[[4, 0, 1, 5]] color_map11 = np.vstack([ color_map11, ])[[4, 0, 1, 5]] # Line properties lwidth = [1.5]*len(results) lstyle = ['-']*len(results) # Rolling window results_roll = results.copy() for i in range(0, len(results_roll)): results_roll[i] = results_roll[i].rolling(time = rolling_window, center = True).mean() # Figure [fig1, ax1, figweb] = ncp.plot_time_series(data = results, labels = labels, color_map = color_map1, lwidth = lwidth, lstyle = lstyle, legendgroup = 1, legendgrouptitle_text = ['originals', 'originaux'][lang], ) [fig1, ax1, figweb] = ncp.plot_time_series(figweb = figweb, data = results_roll, labels = labels, color_map = color_map11, lwidth = lwidth, lstyle = lstyle, legendgroup = 2, legendgrouptitle_text = [ f'rolling mean: {rolling_window} years', f'moy glissante : {rolling_window} ans'][lang], ) # Y ylim = [results[0].val.mean() - 0.3, results[0].val.mean() + 0.3] #%% Non conforme else: print(f'Data_type non reconnu : {data_type}') return #%% MISE EN FORME ET EXPORT # ---- Scales # X dates_lim = [results[0].time.iloc[0], results[0].time.iloc[-1] + pd.Timedelta(days = 3653)] # None # ['2004-01-04', '2024-01-05'] # Y if var == 'SWIAV': buff = 0.3 elif var == 'SSWI': buff = 3 elif var == 'SWI04D': buff = 40 else: buff = 1 ylim = [results[0].val.mean() - buff, results[0].val.mean() + buff] # ---- Title title = f"{os.path.split(root_folder)[-1]} : <b>{season}</b>" # ---- Figure update figweb.update_layout(#font_family = 'Open Sans', title = {'font': {'size': 20}, 'text': title, 'xanchor': 'center', 'x': 0.5, }, # annotations = {'xanchor': 'middle', # 'yanchor': 'top', # 'size': 20, # 'text': add_title + "\nK = 5e-6 m/s Poro = 0.1% e = 25 m"}, xaxis = {'title': {'font': {'size': 16}, 'text': ['Time [d]', 'Temps [j]'][lang]}, 'range': dates_lim, }, yaxis = {'title': {'font': {'size': 16}, 'text': ylabel[lang]}, # 'text': field_title + ' [m3/s]'}, 'type': 'linear', 'range': ylim,#_ylim_figweb, }, legend = {'title': {'text': 'Légende'}, 'xanchor': 'right', 'y': 1.1, 'yanchor': 'top', 'bgcolor': 'rgba(255, 255, 255, 0.2)', }, plot_bgcolor = "white", # legend = {'groupclick': 'togglegroup'}, width = plotsize[0], # paper[0], # wide[0], height = plotsize[1], # paper[1], # wide[1], ) # ---- Figure export figweb.write_html(os.path.join(root_folder, '_'.join([title, season, f'rolling{rolling_window}', suffix, datetime.datetime.now().strftime("%Y-%m-%d_%Hh%M"), ]) + '.html' ) ) figweb.write_image(os.path.join(root_folder, '_'.join([title, season, f'rolling{rolling_window}', suffix, datetime.datetime.now().strftime("%Y-%m-%d_%Hh%M"), ]) + '.png' ) )