Code source de pyspc.io.meteofrance.reader

#!/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/>.
#
########################################################################
"""
Bibliothèque pyspc du projet pyspc - IO - Météo-France - read
"""
from datetime import datetime as dt
import numpy as np
import pandas as pnd

from pyspc.convention.lamedo import BDAPBP_SCENS
from pyspc.convention.meteofrance import DATE_FORMATS, MDG_DATE_COLNAME
from pyspc.core.serie import Serie
from pyspc.core.series import Series
from pyspc.core.timeutil import str2dt
import pyspc.core.exception as _exception
from pyspc.core.convention import EXTERNAL_VARNAMES
from pyspc.data.meteofrance import (
    MF_Data, MF_OpenAPI, MF_OpenData, BP_Data, Sympo_Data)


[docs] def read_MF_Data(filename=None, codes=None, warning=True): """ Créer une instance Series à partir d'observations de Météo-France Parameters ---------- filename : str Nom du fichier d'observations de Météo-France codes : list Liste des identifiants des stations à conserver. Par défaut, toutes les stations sont retenues warning : bool Imprimer les erreurs ? Returns ------- series : pyspc.core.series.Series Collection de séries de données Examples -------- >>> from pyspc.io.meteofrance import read_MF_Data Cas d'un fichier RR1 >>> f = 'data/data/mf/RR1.data' >>> series = read_MF_Data(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_Data * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 1 * ---------------------------------- * SERIE #1 * - CODE = 42039003 * - VARNAME = PH * - META = None ************************************* Cas d'un fichier RR sans sélection de stations >>> f = 'data/data/mf/RR.data' >>> series = read_MF_Data(filename=f, codes=['07075001']) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_Data * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 3 * ---------------------------------- * SERIE #1 * - CODE = 07075001 * - VARNAME = PJ * - META = None * ---------------------------------- * SERIE #2 * - CODE = 07105001 * - VARNAME = PJ * - META = None * ---------------------------------- * SERIE #3 * - CODE = 07154005 * - VARNAME = PJ * - META = None ************************************* Cas d'un fichier RR avec une sélection de stations >>> f = 'data/data/mf/RR.data' >>> series = read_MF_Data(filename=f, codes=['07075001']) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_Data * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 1 * ---------------------------------- * SERIE #1 * - CODE = 07075001 * - VARNAME = PJ * - META = None ************************************* """ # ------------------------------------------------------------------------- # 0- Contrôles # ------------------------------------------------------------------------- _exception.check_str(filename) if codes is not None: _exception.check_listlike(codes) _exception.check_bool(warning) # ------------------------------------------------------------------------- # 1- Lecture # ------------------------------------------------------------------------- # Création du lecteur reader = MF_Data(filename=filename) # Lecture des données df = reader.read() # Colonne 'DATE' -> Index df = df.set_index(keys='DATE', drop=True) # Colonne 'POSTE' -> Multi-Index (None, POSTE) avec None: ['VAR', 'QVAR'] df = df.pivot(columns='POSTE') # 1er niveau du multi-index en tant que VAR au lieu de None df.columns.rename('VAR', level=0, inplace=True) # MultiIndex : (POSTE, VAR) df.columns = df.columns.swaplevel() # Multi-Index -> Index de tuple df.columns = df.columns.to_flat_index() # Drop colonnes : grandeur Qxxxx et codes df = df.drop(columns=[c for c in df.columns if c[1].startswith('Q')]) if codes is not None: df = df.drop(columns=[c for c in df.columns if c[0] not in codes]) # ------------------------------------------------------------------------- # 2- Conversion en Series # ------------------------------------------------------------------------- series = Series(datatype='obs', name='MF_Data') for c in df.columns: d = df[c].to_frame() d.index = [dt.strptime(i, DATE_FORMATS[c[1]]) for i in d.index] serie = Serie( d, code=c[0], provider='MF_Data', varname=c[1], warning=warning) series.add(serie=serie) return series
[docs] def read_MF_OpenAPI(filename=None, warning=True): """ Créer une instance Series à partir d'observations de METEO.DATA.GOUV.FR. Parameters ---------- filename : str Nom du fichier d'observations de Météo-France warning : bool Imprimer les erreurs ? Returns ------- series : pyspc.core.series.Series Collection de séries de données Examples -------- >>> from pyspc.io.meteofrance import read_MF_OpenData CAS DE DONNEES EN MINUTES >>> f = 'data/data/mf/open_api/43091005_MN_202410170500_202410170700.csv' >>> series = read_MF_OpenData(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_OpenAPI * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 1 * ---------------------------------- * SERIE #1 * - CODE = 43091005 * - VARNAME = P6m * - META = None ************************************* CAS DE DONNEES HORAIRES >>> f = 'data/data/mf/open_api/43091005_H_202410170000_202410171200.csv' >>> series = read_MF_OpenData(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_OpenAPI * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 2 * ---------------------------------- * SERIE #1 * - CODE = 43091005 * - VARNAME = PH * - META = None * ---------------------------------- * SERIE #2 * - CODE = 43091005 * - VARNAME = TH * - META = None ************************************* CAS DE DONNEES JOURNALIERES >>> f = 'data/data/mf/open_api/43091005_MN_202410170500_202410170700.csv' >>> series = read_MF_OpenData(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_OpenAPI * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 2 * ---------------------------------- * SERIE #1 * - CODE = 43091005 * - VARNAME = PJ * - META = None * ---------------------------------- * SERIE #2 * - CODE = 43091005 * - VARNAME = TJ * - META = None ************************************* """ # ------------------------------------------------------------------------- # 0- Contrôles # ------------------------------------------------------------------------- _exception.check_str(filename) _exception.check_bool(warning) # ------------------------------------------------------------------------- # 1- Lecture # ------------------------------------------------------------------------- # Création du lecteur reader = MF_OpenAPI(filename=filename) # Lecture des données df = reader.read() # Enlever les ' ' superflus dans les noms des colonnes df.columns = [c.strip() for c in df.columns] # Simplifier le tableau cols = ['POSTE', 'DATE'] cols.extend([k[1] for k in EXTERNAL_VARNAMES.keys() if k[0] == 'MF_OpenAPI' and k[1] in df.columns and k[2] == reader.prefix]) df = df[cols] # Colonne 'DATE' -> Index df = df.set_index(keys='DATE', drop=True) # Colonne 'POSTE' -> Multi-Index (None, POSTE) avec None: ['VAR', 'QVAR'] df = df.pivot(columns='POSTE') # 1er niveau du multi-index en tant que VAR au lieu de None df.columns.rename('VAR', level=0, inplace=True) # MultiIndex : (POSTE, VAR) df.columns = df.columns.swaplevel() # Multi-Index -> Index de tuple df.columns = df.columns.to_flat_index() # ------------------------------------------------------------------------- # 2- Conversion en Series # ------------------------------------------------------------------------- series = Series(datatype='obs', name='MF_OpenAPI') for c in df.columns: d = df[c].to_frame() serie = Serie( d, code=c[0], provider='MF_OpenAPI', varname=EXTERNAL_VARNAMES[('MF_OpenAPI', c[1], reader.prefix)], warning=warning) series.add(serie=serie) return series
[docs] def read_MF_OpenData(filename=None, codes=None, warning=True): """ Créer une instance Series à partir d'observations de METEO.DATA.GOUV.FR. Parameters ---------- filename : str Nom du fichier d'observations de Météo-France codes : list Liste des identifiants des stations à conserver. Par défaut, toutes les stations sont retenues warning : bool Imprimer les erreurs ? Returns ------- series : pyspc.core.series.Series Collection de séries de données Examples -------- >>> from pyspc.io.meteofrance import read_MF_OpenData CAS DE DONNEES EN MINUTES >>> f = 'data/data/mf/open_data/MN_43_previous-2020-2022.csv.gz' >>> series = read_MF_OpenData(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_OpenData * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 2 * ---------------------------------- * SERIE #1 * - CODE = 43091005 * - VARNAME = P6m * - META = None * ---------------------------------- * SERIE #2 * - CODE = 43130002 * - VARNAME = P6m * - META = None ************************************* CAS DE DONNEES HORAIRES >>> f = 'data/data/mf/open_data/H_43_2010-2019.csv.gz' >>> series = read_MF_OpenData(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_OpenData * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 4 * ---------------------------------- * SERIE #1 * - CODE = 43091005 * - VARNAME = PH * - META = None * ---------------------------------- * SERIE #2 * - CODE = 43130002 * - VARNAME = PH * - META = None * ---------------------------------- * SERIE #3 * - CODE = 43091005 * - VARNAME = TH * - META = None * ---------------------------------- * SERIE #4 * - CODE = 43130002 * - VARNAME = TH * - META = None ************************************* CAS DE DONNEES JOURNALIERES >>> f = 'data/data/mf/open_data/Q_07_latest-2023-2024_RR-T-Vent.csv.gz' >>> series = read_MF_OpenData(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_OpenData * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 4 * ---------------------------------- * SERIE #1 * - CODE = 07154005 * - VARNAME = PJ * - META = None * ---------------------------------- * SERIE #2 * - CODE = 07232001 * - VARNAME = PJ * - META = None * ---------------------------------- * SERIE #3 * - CODE = 07154005 * - VARNAME = TJ * - META = None * ---------------------------------- * SERIE #4 * - CODE = 07232001 * - VARNAME = TJ * - META = None ************************************* CAS DE DONNEES JOURNALIERES AVEC SELECTION PAR CODE >>> f = 'data/data/mf/open_data/Q_07_latest-2023-2024_RR-T-Vent.csv.gz' >>> codes = ['07154005'] >>> series = read_MF_OpenData(filename=f, codes=codes) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = MF_OpenData * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 2 * ---------------------------------- * SERIE #1 * - CODE = 07154005 * - VARNAME = PJ * - META = None * ---------------------------------- * SERIE #2 * - CODE = 07154005 * - VARNAME = TJ * - META = None ************************************* """ # ------------------------------------------------------------------------- # 0- Contrôles # ------------------------------------------------------------------------- _exception.check_str(filename) if codes is not None: _exception.check_listlike(codes) _exception.check_bool(warning) # ------------------------------------------------------------------------- # 1- Lecture # ------------------------------------------------------------------------- # Création du lecteur reader = MF_OpenData(filename=filename) # Lecture des données df = reader.read(codes=codes) # Ajout codes pour mode lecture 4 # Enlever les ' ' superflus dans les noms des colonnes df.columns = [c.strip() for c in df.columns] # Simplifier le tableau dt_colname = MDG_DATE_COLNAME[reader.prefix] cols = ['NUM_POSTE', dt_colname] cols.extend([k[1] for k in EXTERNAL_VARNAMES.keys() if k[0] == 'MF_OpenData' and k[1] in df.columns and k[2] == reader.prefix]) df = df[cols] # Colonne 'DATE' -> Index df = df.set_index(keys=dt_colname, drop=True) # Colonne 'POSTE' -> Multi-Index (None, POSTE) avec None: ['VAR', 'QVAR'] df = df.pivot(columns='NUM_POSTE') # 1er niveau du multi-index en tant que VAR au lieu de None df.columns.rename('VAR', level=0, inplace=True) # MultiIndex : (POSTE, VAR) df.columns = df.columns.swaplevel() # Multi-Index -> Index de tuple df.columns = df.columns.to_flat_index() if codes is not None: df = df.drop(columns=[c for c in df.columns if c[0] not in codes]) # ------------------------------------------------------------------------- # 2- Conversion en Series # ------------------------------------------------------------------------- series = Series(datatype='obs', name='MF_OpenData') for c in df.columns: d = df[c].to_frame() d.index = d.index.map(str2dt) serie = Serie( d, code=c[0], provider='MF_OpenData', varname=EXTERNAL_VARNAMES[('MF_OpenData', c[1], reader.prefix)], warning=warning) series.add(serie=serie) return series
[docs] def read_Sympo(filename=None, zones=None, warning=True): """ Créer une instance Series à partir d'un fichier de prévision SYMPO Parameters ---------- filename : str Nom du fichier d'observations de Météo-France zones : list Liste des identifiants des zones à conserver. Par défaut, toutes les zones sont retenues warning : bool Imprimer les erreurs ? Returns ------- series : pyspc.core.series.Series Collection de séries de données Examples -------- >>> from pyspc.io.meteofrance import read_Sympo Cas d'un fichier RR3 >>> f = 'data/data/mf/rr3_200811010425' >>> series = read_Sympo(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = Sympo * TYPE DE COLLECTION = fcst * NOMBRE DE SERIES = 2 * ---------------------------------- * SERIE #1 * - CODE = 0708 * - VARNAME = P3H * - META = 2008-11-01 04:25:00, Sympo, RR3 * ---------------------------------- * SERIE #2 * - CODE = 0709 * - VARNAME = P3H * - META = 2008-11-01 04:25:00, Sympo, RR3 ************************************* Cas d'un fichier Sympo >>> f = 'data/data/mf/sympo_201611200658' >>> series = read_Sympo(filename=f) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = Sympo * TYPE DE COLLECTION = fcst * NOMBRE DE SERIES = 2 * ---------------------------------- * SERIE #1 * - CODE = 0708 * - VARNAME = P3H * - META = 2016-11-20 06:58:00, Sympo, RR3 * ---------------------------------- * SERIE #2 * - CODE = 0709 * - VARNAME = P3H * - META = 2016-11-20 06:58:00, Sympo, RR3 ************************************* Cas d'un fichier Sympo avec sélection de zones >>> f = 'data/data/mf/sympo_201611200658' >>> series = read_Sympo(filename=f, zones='0708') >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = Sympo * TYPE DE COLLECTION = fcst * NOMBRE DE SERIES = 1 * ---------------------------------- * SERIE #1 * - CODE = 0708 * - VARNAME = P3H * - META = 2016-11-20 06:58:00, Sympo, RR3 ************************************* """ # ------------------------------------------------------------------------- # 0- Contrôles # ------------------------------------------------------------------------- _exception.check_str(filename) if zones is not None: _exception.check_listlike(zones) _exception.check_bool(warning) # ------------------------------------------------------------------------- # 1- Lecture # ------------------------------------------------------------------------- # Création du lecteur reader = Sympo_Data(filename=filename) runtime = reader.date_prod # Lecture des données df = reader.read() # Colonne 'DATE' -> Index df = df.set_index(keys='DATE', drop=True) # Colonne 'POSTE' -> Multi-Index (None, POSTE) avec None: ['VAR', 'QVAR'] df = df.pivot(columns='ZONE') # 1er niveau du multi-index en tant que VAR au lieu de None df.columns.rename('VAR', level=0, inplace=True) # MultiIndex : (POSTE, VAR) df.columns = df.columns.swaplevel() # Multi-Index -> Index de tuple df.columns = df.columns.to_flat_index() # Drop colonnes : zones if zones is not None: df = df.drop(columns=[c for c in df.columns if c[0] not in zones]) # ------------------------------------------------------------------------- # 2- Conversion en Series # ------------------------------------------------------------------------- series = Series(datatype='fcst', name='Sympo') for c in df.columns: d = df[c].to_frame() try: serie = Serie( d, code=c[0], provider='Sympo', varname=c[1], warning=warning) except ValueError: continue series.add(serie=serie, meta=(runtime, 'Sympo', 'RR3')) return series
[docs] def read_BP(filename=None, zones=None, observation=False, warning=True): """ Créer une instance Series à partir d'un bulletin BP au format xml Parameters ---------- filename : str Nom du fichier du bulletin BP au format xml zones : list Liste des identifiants des zones à conserver. Par défaut, toutes les zones sont retenues observation : bool Convertir les observations (True) ou les prévisions (False) ? Par défaut: False warning : bool Imprimer les erreurs ? Par défaut: True Returns ------- series : pyspc.core.series.Series Collection de séries de données observées/prévues Examples -------- >>> from pyspc.io.meteofrance import read_BP >>> f = 'data/data/mf/bp_ic_201605310503.xml' >>> z = ['71002', '71005'] Cas des prévisions BP, avec sélection de zones >>> series = read_BP(filename=f, zones=z) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = BP * TYPE DE COLLECTION = fcst * NOMBRE DE SERIES = 8 * ---------------------------------- * SERIE #1 * - CODE = 71002 * - VARNAME = PJ * - META = 2016-05-31 05:02:05, BP, MoyInf * ---------------------------------- * SERIE #2 * - CODE = 71005 * - VARNAME = PJ * - META = 2016-05-31 05:02:05, BP, MoyInf * ---------------------------------- * SERIE #3 * - CODE = 71002 * - VARNAME = PJ * - META = 2016-05-31 05:02:05, BP, MoySup * ---------------------------------- * SERIE #4 * - CODE = 71005 * - VARNAME = PJ * - META = 2016-05-31 05:02:05, BP, MoySup * ---------------------------------- * SERIE #5 * - CODE = 71002 * - VARNAME = PJ * - META = 2016-05-31 05:02:05, BP, LocInf * ---------------------------------- * SERIE #6 * - CODE = 71005 * - VARNAME = PJ * - META = 2016-05-31 05:02:05, BP, LocInf * ---------------------------------- * SERIE #7 * - CODE = 71002 * - VARNAME = PJ * - META = 2016-05-31 05:02:05, BP, LocSup * ---------------------------------- * SERIE #8 * - CODE = 71005 * - VARNAME = PJ * - META = 2016-05-31 05:02:05, BP, LocSup ************************************* Cas des observations BP >>> series = read_BP(filename=f, zones=z, observation=True) >>> series ************************************* ********** SERIES ******************* ************************************* * NOM DE LA COLLECTION = BP * TYPE DE COLLECTION = obs * NOMBRE DE SERIES = 4 * ---------------------------------- * SERIE #1 * - CODE = 71002 * - VARNAME = PJ * - META = MAXRR * ---------------------------------- * SERIE #2 * - CODE = 71005 * - VARNAME = PJ * - META = MAXRR * ---------------------------------- * SERIE #3 * - CODE = 71002 * - VARNAME = PJ * - META = AVGRR * ---------------------------------- * SERIE #4 * - CODE = 71005 * - VARNAME = PJ * - META = AVGRR ************************************* """ # ------------------------------------------------------------------------- # 0- Contrôles # ------------------------------------------------------------------------- _exception.check_str(filename) if zones is not None: _exception.check_listlike(zones) _exception.check_bool(observation) _exception.check_bool(warning) # ------------------------------------------------------------------------- # 1- Lecture # ------------------------------------------------------------------------- # Création du lecteur reader = BP_Data(filename=filename) # Lecture des données content = reader.read() # ------------------------------------------------------------------------- # 2- Cas des observations # ------------------------------------------------------------------------- if observation: df = content['obs'] # Colonne 'DATE' -> Index df['DATE'] = df['DATE DEBUT'].map( lambda x: x.replace(hour=0, minute=0)) df = df.set_index(keys='DATE', drop=True) # Nettoyer les colonnes superflues df = df.drop(columns=['NAME', 'DTPROD', 'DATE DEBUT', 'PROVIDER']) # Colonne 'Code' -> Multi-Index (None, Code) avec None: ['Debit (l/s)'] df = df.pivot(columns='CODE') # MultiIndex : (CODE, VAR) df.columns = df.columns.swaplevel() # Conversion en Series series = Series(datatype='obs', name='BP') for c in df.columns: zone = c[0] if isinstance(zones, list) and zone not in zones: continue sim = c[1] code = f'{zone}_{sim}' d = df[c].to_frame() try: serie = Serie( d, code=code, provider='BP', varname='PJ', warning=warning) except ValueError: continue series.add(serie=serie, code=zone, meta=sim) return series # ------------------------------------------------------------------------- # 3- Cas des observations # ------------------------------------------------------------------------- df = content['prv'] # Récupérer les dates de production par zone runtimes = {x[0]: x[1].to_pydatetime() for x in df[['CODE', 'DTPROD']].values} # Définir BP[Moy,Loc][Inf,Sup] col = df.apply(list_values, axis=1) df = df.assign(VALUE=col.values) # VALUE : colonne avec les 4 valeurs df[BDAPBP_SCENS] = pnd.DataFrame(df.VALUE.to_list(), index=df.index) # Colonne 'DATE' -> Index df['DATE'] = df['DATE DEBUT'].map(lambda x: x.replace(hour=0, minute=0)) df = df.set_index(keys='DATE', drop=True) # Nettoyer les colonnes superflues df = df.drop(columns=['NAME', 'DTPROD', 'DATE DEBUT', 'PROVIDER', 'AVGRR', 'MAXRR', 'VALUE']) # Colonne 'Code' -> Multi-Index (None, Code) avec None: ['Debit (l/s)'] df = df.pivot(columns='CODE') # MultiIndex : (CODE, VAR) df.columns = df.columns.swaplevel() # Conversion en Series series = Series(datatype='fcst', name='BP') for c in df.columns: zone = c[0] if isinstance(zones, list) and zone not in zones: continue scen = c[1] runtime = runtimes[zone] d = df[c].to_frame() try: serie = Serie( d, code=zone, provider='BP', varname='PJ', warning=warning) except ValueError: continue series.add(serie=serie, meta=(runtime, 'BP', scen)) return series
def list_values(row): """ Calculer les valeurs 'MoyInf', 'MoySup', 'LocInf', 'LocSup' selon les colonnes 'AVGRR', 'MAXRR' Parameters ---------- row : pandas.Series Ligne d'un tableau de données Returns ------- list Valeurs 'MoyInf', 'MoySup', 'LocInf', 'LocSup' """ mi, ms = split_values(row['AVGRR']) li, ls = split_values(row['MAXRR']) return [mi, ms, li, ls] def split_values(text): """ COnvertir un texte en couple (valeur inférieure / valeur supérieure) """ if not text: return np.nan, np.nan try: values = text.split("/") except AttributeError: return np.nan, np.nan try: vi = float(values[0]) except ValueError: if values[0].lower() == 'tr': vi = 0.5 else: vi = np.nan try: vs = float(values[-1]) except ValueError: vs = np.nan return vi, vs