Code source de pyspc.model.grp22.cal_report

#!/usr/bin/python3
# -*- coding: utf-8 -*-
########################################################################
#
# This file is part of python module <pyspc>.
# Copyright (C) 2013-2021  R. Marty
#   (renaud.marty@developpement-durable.gouv.fr)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see COPYING.txt).
# If not, see <http://www.gnu.org/licenses/>.
#
########################################################################
"""
Modélisations hydrologiques - GRP version 2022 - Rapports PDF
"""
from datetime import datetime as dt
import os.path

import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from matplotlib.backends.backend_pdf import PdfPages
import numpy as np
import pandas as pnd

from pyspc import __date__, __version__
import pyspc.core.exception as _exception
from pyspc.convention.grp22 import (
    CAL_BASIN_OPTIONS, CAL_VERIF_NAMES, CAL_VERIF_SCORES)
from pyspc.model.grp22.cal_basin import GRP_Basin
from pyspc.model.grp22.cal_config import GRP_Run
from pyspc.model.grp22.cal_verif import GRP_Verif
from pyspc.core.config import Config

RADAR_COLORS = ['tab:olive', 'tab:orange', 'tab:red']


[docs] def report_model(filename=None, run=None, basin=None, list_rr=None, list_ta=None, hypso=None, hypso_lim=None): """ Rapport PDF décrivant les paramètres de la modélisation GRP v2022 Parameters ---------- filename : str Chemin du fichier PDF à imprimer run : pyspc.model.grp22.GRP_Run Information générale du calage du modèle basin : pyspc.model.grp22.GRP_Basin Information spécifique du modèle list_rr : str Chemin du fichier LISTE_PLUVIOMETRES.ini list_ta : str Chemin du fichier LISTE_TEMPERATURES.ini hypso : pandas.DataFrame Courbe hypsométrique (colonnes: ['pct', 'Z']) hypso_lim : tuple Limites des altitudes Returns ------- filename : str Chemin du fichier PDF à imprimer fig : matplotlib.pyplot.figure Si le chemin n'est pas spécifié Notes ----- Si le paramètre basin est complété par les périodes de données, Alors le rapport en tient compte. Ces compléments doivent respecter: - basin['MODELISATION']['start'] : datetime.datetime du départ des données - basin['MODELISATION']['end'] : datetime.datetime de la fin des données - basin['MODELISATION']['mv'] : float du ratio de valeurs manquantes - basin[station]['rr_start'] : datetime.datetime du départ 'P' - basin[station]['rr_end'] : datetime.datetime de la fin 'P' - basin[station]['rr_mv'] : float du ratio de valeurs manquantes 'P' - basin[station]['ta_start'] : datetime.datetime du départ 'T' - basin[station]['ta_end'] : datetime.datetime de la fin 'T' - basin[station]['ta_mv'] : float du ratio de valeurs manquantes 'T' See Also -------- pyspc.model.grp22.GRP_Basin pyspc.model.grp22.GRP_Cfg pyspc.model.grp22.GRP_Data pyspc.model.grp22.GRP_Run """ # --------------------------------------------------------------------- # 0 - Contrôles # --------------------------------------------------------------------- _exception.raise_valueerror(not isinstance(run, GRP_Run), "Paramètre 'run' incorrect") _exception.raise_valueerror(not isinstance(basin, GRP_Basin), "Paramètre 'basin' incorrect") basin = _add_list_rrta('rr', list_rr, basin) basin = _add_list_rrta('ta', list_ta, basin) list_P = [s for s in basin if CAL_BASIN_OPTIONS['P']['w'] in basin[s]] list_T = [s for s in basin if CAL_BASIN_OPTIONS['T']['w'] in basin[s]] # --------------------------------------------------------------------- # 1 - Création de la figure et de la zone graphique # --------------------------------------------------------------------- fig = plt.figure(constrained_layout=False, figsize=(8.27, 11.69), dpi=150) gs = GridSpec(nrows=4, ncols=2, figure=fig, left=0.08, right=0.95, top=0.92, bottom=0.03, hspace=0.25, wspace=0.20) # --------------------------------------------------------------------- # 2 - Information run # --------------------------------------------------------------------- axr = fig.add_subplot(gs[:2, :-1]) df = pnd.DataFrame(run) df.index = list(run._fields) df.columns = ['MODELE'] df = df.T df.DEB = df.DEB.apply( lambda x: dt.strptime(x, '%d/%m/%Y %H:%M').strftime('%Y-%m-%d')) df.FIN = df.FIN.apply( lambda x: dt.strptime(x, '%d/%m/%Y %H:%M').strftime('%Y-%m-%d')) df['Ratio MV'] = f"{basin['MODELISATION']['mv']:.2f}" df = df.T df2table(df, axr, pad=0.12) # --------------------------------------------------------------------- # 3 - Information hypso # --------------------------------------------------------------------- if hypso is not None: axh = fig.add_subplot(gs[:2, -1]) axh.plot(hypso.pct, hypso.Z, linewidth=2, marker='o', color='tab:blue') axh.set_xlim(0, 100) axh.set_xlabel('Pourcentage Bassin') if hypso_lim is not None: axh.set_ylim(hypso_lim) axh.set_ylabel('Altitude [m]') # --------------------------------------------------------------------- # 4 - Information P # --------------------------------------------------------------------- if list_P: axp = fig.add_subplot(gs[-2, :]) df = pnd.DataFrame({s: {o: basin[s][o] for o in basin[s] if o.startswith('rr')} for s in list_P}).T df.index.name = 'code' df = df.reset_index() df = df[['code', 'rr_NOM', 'rr_pdt', 'rr_pond', 'rr_start', 'rr_end', 'rr_mv']] # # df.start = df.start.apply(lambda x: x.strftime('%Y-%m-%d')) # # df.end = df.end.apply(lambda x: x.strftime('%Y-%m-%d')) df.rr_start = pnd.to_datetime( df.rr_start, errors='coerce').dt.strftime('%Y-%m-%d') df.rr_end = pnd.to_datetime( df.rr_end, errors='coerce').dt.strftime('%Y-%m-%d') # df.mv = df.mv.apply(lambda x: '{0:.2f}'.format(x)) df.rr_mv = df.rr_mv.apply('{0:.2f}'.format) # pylint: disable=consider-using-f-string df.columns = ['Code', 'Nom', 'Pas de Temps', 'Pct', 'Début', 'Fin', 'Pct MV'] df2table(df, axp) # --------------------------------------------------------------------- # 5 - Information T # --------------------------------------------------------------------- if list_T: axt = fig.add_subplot(gs[-1, :]) df = pnd.DataFrame({s: {o: basin[s][o] for o in basin[s] if o.startswith('ta')} for s in list_T}).T df.index.name = 'code' df = df.reset_index() df = df[['code', 'ta_NOM', 'ta_alti', 'ta_pond', 'ta_start', 'ta_end', 'ta_mv']] # df.start = df.start.apply(lambda x: x.strftime('%Y-%m-%d')) # df.end = df.end.apply(lambda x: x.strftime('%Y-%m-%d')) df.ta_start = pnd.to_datetime( df.ta_start, errors='coerce').dt.strftime('%Y-%m-%d') df.ta_end = pnd.to_datetime( df.ta_end, errors='coerce').dt.strftime('%Y-%m-%d') # df.mv = df.mv.apply(lambda x: '{0:.2f}'.format(x)) df.ta_mv = df.ta_mv.apply('{0:.2f}'.format) # pylint: disable=consider-using-f-string df.columns = ['Code', 'Nom', 'Altitude [m]', 'Pct', 'Début', 'Fin', 'Pct MV'] df2table(df, axt) # --------------------------------------------------------------------- # 6 - Création du fichier PDF # https://matplotlib.org/3.1.0/users/prev_whats_new/whats_new_2.1.0.html#metadata-savefig-keyword-argument # --------------------------------------------------------------------- plt.suptitle(f"Modélisation GRP v2022 - Bassin {run.CODE} - " f"Pas de temps {run.PDT}", fontsize=14, fontweight='bold') if filename is not None: plt.savefig( filename, dpi=150, metadata={'Title': f'{run.NOM} ({run.CODE}, {run.PDT})', 'Author': f'pyspc-{__version__} ({__date__})'}) plt.close(fig) return filename return fig
def _add_list_rrta(varname, filename, basin): """ Ajouter les informations de LISTE_PLUVIOMTERES et LISTE_TEMPERATURES. """ if isinstance(filename, str) and os.path.exists(filename): config = Config(filename) config.read() else: config = None for s in basin: if isinstance(config, dict) and s in config: for o in config[s]: basin[s][f'{varname}_{o}'] = config[s][o] return basin
[docs] def report_verif(run=None, src_dirname=None, datatype=None, dest_dirname=None): """ Rapport CSV synthétisant les performances du modèle Rapport PDF synthétisant les performances du modèle Parameters ---------- run : pyspc.model.grp22.GRP_Run Information générale du calage du modèle src_dirname : str Répertoire où chercher les fiches de performance datatype : str Nom du type de fiche de performance dest_dirname : str Répertoire de destination des rapports Returns ------- filenames : list Fichiers écrits Notes ----- Si datatype = 'cal', le document pdf contient 2 pages : le tableau et les histogrammes. Si datatype = 'rtime', le document pdf contient 1 page : le tableau avec la vision radar. See Also -------- pyspc.model.grp22.cal_verif.GRP_Verif pyspc.model.grp22.cal_verif.GRP_Verif.concat """ # --------------------------------------------------------------------- # 0- Contrôles # --------------------------------------------------------------------- _exception.raise_valueerror(not isinstance(run, GRP_Run), "Paramètre 'run' incorrect") _exception.check_str(src_dirname) _exception.check_str(dest_dirname) GRP_Verif.check_datatype(datatype=datatype) filenames = [] # --------------------------------------------------------------------- # 1 - Synthèse CSV des fiches de performance # --------------------------------------------------------------------- df = GRP_Verif.concat( loc=run.CODE, timestep=run.PDT, dirname=src_dirname, datatype=datatype) f = os.path.join( dest_dirname, f'synthese_{run.CODE}_{run.PDT}_{datatype}.csv') df.to_csv(f, sep=';', float_format='%.3f') filenames.append(f) # --------------------------------------------------------------------- # 2 - Synthèse PDF des fiches de performance # --------------------------------------------------------------------- dfh = df.set_index(['SA_RT', 'HOR', 'SC']) dfh.POD = dfh.POD / 100. dfh.FAR = dfh.FAR / 100. dfh.CSI = dfh.CSI / 100. svs = sorted(dfh.SV.unique()) f = os.path.join( dest_dirname, f'synthese_{run.CODE}_{run.PDT}_{datatype}.pdf') title = f'{run.NOM} ({run.CODE}, {run.PDT})\n{CAL_VERIF_NAMES[datatype]}' with PdfPages(f) as pdf: # ----------------------------------------------------------------- # 2.1 - page 1 - Tableau des valeurs # Radar plot si Calage complet # ----------------------------------------------------------------- if datatype == 'rtime': dfr = dfh.reset_index(drop=True).set_index(['SV']).fillna(0) fig = plt.figure( constrained_layout=False, figsize=(8.27, 11.69), dpi=150) gs = GridSpec(nrows=6, ncols=1, figure=fig, left=0.15, right=0.85, top=0.90, bottom=0.20, hspace=0.01, wspace=0.01) ax = fig.add_subplot(gs[0, 0]) axr = fig.add_subplot(gs[1:, 0], projection="polar") theta = np.arange( len(dfr.columns) + 1) / float(len(dfr.columns)) * 2 * np.pi kc = 0 axr.set_theta_direction('clockwise') # Sens horaire axr.set_theta_offset(1.70 * np.pi) # Décaler axr.set_rlabel_position(180) # Angle des labels 'rayon' axr.set_rgrids( [x / 10 for x in range(1, 10)], [f'{x / 10:.1f}' for x in range(1, 10)], color=[0.20, 0.20, 0.20], fontsize=8 ) for row in dfr.iterrows(): label = f'SV = {row[0]} $m^3/s$' color = RADAR_COLORS[kc] kc += 1 values = row[1].values values = np.append(values, values[0]) # draw the polygon and the mark the points # for each angle/value combination _, = axr.plot( theta, values, color=color, marker="o", label=label) # Summits labels plt.xticks(theta[:-1], dfr.columns, color='black', size=12) # axr.set_ticks(theta[:-1]) # axr.set_ticklabels(toprint_varlabels) # to increase the distance of the labels to the plot axr.tick_params(pad=15) # fill the area of the polygon with green and some transparency axr.fill(theta, values, color, alpha=0.3) axr.set_ylim(0, 1) axr.legend(bbox_to_anchor=(1.05, 1.05)) else: fig, ax = plt.subplots(figsize=(8.27, 11.69)) df2table(df, ax, fontsize=12, pad=0.15) plt.suptitle(title, fontsize=14, fontweight='bold') pdf.savefig(fig, bbox_inches='tight') plt.close(fig) # ----------------------------------------------------------------- # 2.2 - Page 2 - Histogrammes uniquement si calage-validation # ----------------------------------------------------------------- if datatype == 'cal': axsx = None axsy = None fig = plt.figure( constrained_layout=False, figsize=(11.69, 8.27), dpi=150) gs = GridSpec(nrows=len(svs), ncols=len(CAL_VERIF_SCORES), figure=fig, left=0.08, right=0.95, top=0.85, bottom=0.03, hspace=0.25, wspace=0.10) for kv, sv in enumerate(svs): for ks, ss in enumerate(CAL_VERIF_SCORES): ax = fig.add_subplot(gs[kv, ks], sharex=axsx, sharey=axsy) if axsx is None: axsx = ax if axsy is None: axsy = ax subdf = dfh[dfh.SV == sv] subdf.plot.bar(ax=ax, y=ss, rot=90, legend=False, color=index2colors(subdf.index)) if ks == int(len(CAL_VERIF_SCORES) / 2): axtitle = r'$\bf Seuil$ = ' + \ f'{sv:.2f}' + r' $\bf m^3/s$' + '\n' + ss else: axtitle = '\n' + ss ax.set_title(axtitle) ax.tick_params(axis='x', which='major', labelsize=8) ax.set_ylim(0, 1) plt.suptitle(title, fontsize=14, fontweight='bold') pdf.savefig(fig, bbox_inches='tight') plt.close(fig) # ----------------------------------------------------------------- # Ajout de méta-données # ----------------------------------------------------------------- # We can also set the file's metadata via the PdfPages object: # https://matplotlib.org/stable/gallery/misc/multipage_pdf.html d = pdf.infodict() # d['Title'] = 'Verification Report\n' + title d['Title'] = f'{run.NOM} ({run.CODE}, {run.PDT})' d['Author'] = f'pyspc-{__version__} ({__date__})' filenames.append(f) return filenames
def index2colors(index): """ Définir la couleur de l'histogramme vertical selon SMN/AMN et TAN/RNA """ colors_dict = {'SMN_TAN': 'tab:blue', 'SMN_RNA': 'tab:red', 'AMN_TAN': 'tab:cyan', 'AMN_RNA': 'tab:orange'} colors = [] for i in index: colors.append(colors_dict.get(i[0], 'tab:brown')) return colors def df2table(df, ax, loc='upper center', fontsize=10, pad=None): """ Imprimer un pandas.DataFrame comme tableau dans une image Parameters ---------- df : pandas.DataFrame Tableau de données ax : matplotlib.axes.Axes Zone graphique où insérer le tableau loc : str alignement du tableau fontsize : int Taille de la police pad : float Espace entre le texte et le bord de la cellule Par défaut: matplotlib.table.Cell.PAD Returns ------- table : matplotlib.table.Table Tableau imprimé Notes ----- https://stackoverflow.com/a/72957628 https://stackoverflow.com/a/60307690 https://stackoverflow.com/a/64595057 """ alternating_colors = [['white'] * len(df.columns), ['lightgray'] * len(df.columns)] * len(df) alternating_colors = alternating_colors[:len(df)] ax.axis('tight') ax.axis('off') table = ax.table(cellText=df.values, rowLabels=df.index, colLabels=df.columns, rowColours=['lightblue']*len(df), colColours=['lightblue']*len(df.columns), cellColours=alternating_colors, loc=loc) table.auto_set_font_size(False) table.set_fontsize(fontsize) # Provide integer list of columns to adjust table.auto_set_column_width(col=list(range(len(df.columns)))) if pad is not None: for c in table.get_celld().values(): rp = pad / c.PAD h = rp * c.get_height() c.set_height(h) return table