3. Module d’interpolation optimale

L’interpolation optimale est un outil qui permet de combiner un champ spatialement distribué (c’est-à-dire le « champ de fond ») avec des observations ponctuelles de telle sorte que l’ensemble du champ puisse être ajusté en fonction des écarts entre les observations et le champ au point d’observation. Par exemple, il peut être utilisé pour combiner un champ de précipitation d’une” réanalyse (par exemple ERA5) avec des enregistrements d’observation, et ainsi ajuster les précipitations de la réanalyse sur l’ensemble du domaine de manière statistiquement optimale.

This page demonstrates how to use xhydro to perform optimal interpolation using field-like simulations and point observations for hydrological modelling. In this case, the background field is a set of outputs from a distributed hydrological model and the observations correspond to real hydrometric stations. The aim is to correct the background field (i.e. the distributed hydrological simulations) using optimal interpolation, as in Lachance-Cloutier et al. (2017).

Lachance-Cloutier, S., Turcotte, R. and Cyr, J.F., 2017. Combining streamflow observations and hydrologic simulations for the retrospective estimation of daily streamflow for ungauged rivers in southern Quebec (Canada). Journal of hydrology, 550, pp.294-306.

[1]:
import datetime as dt
from functools import partial
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pooch
import xarray as xr
from scipy.stats import norm

import xhydro as xh
import xhydro.optimal_interpolation.optimal_interpolation_fun as opt
from xhydro.optimal_interpolation.ECF_climate_correction import general_ecf
from xhydro.testing.helpers import deveraux
/home/docs/checkouts/readthedocs.org/user_builds/xhydro-fr/conda/latest/lib/python3.12/site-packages/esmpy/interface/loadESMF.py:94: VersionWarning: ESMF installation version 8.8.0, ESMPy version 8.8.0b0
  warnings.warn("ESMF installation version {}, ESMPy version {}".format(
ERROR 1: PROJ: proj_create_from_database: Open of /home/docs/checkouts/readthedocs.org/user_builds/xhydro-fr/conda/latest/share/proj failed

3.1. Un exemple rapide

Imaginez un scénario dans lequel nous disposons de 3 stations d’observation des débits et d’un modèle hydrologique qui simule les débits sur ces 3 sites et sur 2 autres sites supplémentaires (pour un total de 5 sites de simulation). Nous souhaitons améliorer la qualité des simulations sur chacun des 5 sites et encore plus sur les 2 sites supplémentaires où il n’y a pas d’observations pour aider à entraîner le modèle. La configuration pourrait ressembler à ceci :

  • Station 1 : Observé + simulé

  • Station 2 : Observé + simulé

  • Station 3 : Observé + simulé

  • Station 4 : Simulation uniquement

  • Station 5 : Simulation uniquement

L’interpolation optimale peut aider, mais nous aurons besoin de quelques informations de base concernant chacune des stations (simulées et observées) :

  • Taille du bassin versant (pour mettre à l’échelle les erreurs)

  • Latitudes/longitudes du bassin versant, pour développer le modèle d’erreur spatiale

  • Données observées aux 3 emplacements jaugés

  • Données simulées sur les 5 sites

Définissons-les maintenant et montrons les stations sur une carte :

[2]:
# Define the station coordinates. Start with observed (Stations 1-3)
lat_obs = np.array([45.0, 45.5, 45.8])
lon_obs = np.array([-71.0, -70.4, -71.8])

# And now the simulated (Stations 1-5). Notice that the first three stations are the same as
# the observation stations.
lat_est = np.array([45.0, 45.5, 45.8, 44.2, 45.4])
lon_est = np.array([-71.0, -70.4, -71.8, -70.2, -71.9])

# We need catchment areas to scale data, in the same order as the latitudes and longitudes.
drainage_area = np.array([480.0, 270.0, 620.0, 1000.0, 700.0])

# We also need streamflow data for each site. In this example, we will use a single day, but
# it would be possible to apply to each day of a time series. Notice that while the first three
# stations correspond to the same stations for both the observed_flow and simulated_flow data,
# The data is different because one is observed and the other is simulated.
observed_flow = np.array([100.0, 110.0, 150.0])
simulated_flow = np.array([108.0, 135.0, 148.0, 154.0, 88.0])

test

Nous disposons désormais des données de base nécessaires pour démarrer le traitement en utilisant une interpolation optimale. Cependant, avant de le faire, nous devons fournir quelques hyperparamètres. Certains sont plus complexes que d’autres, décomposons donc les principales étapes.

The first is the need to compute differences (also referred to as « departures ») between observations and simulations where they both occur simultaneously. We also need to scale the data by the catchment area to ensure errors are relative and can then be interpolated. We then take the logarithm of these values to ensure extrapolation does not cause negative streamflow. We will reverse the transformation later.

[3]:
# Log-transform the scaled flow data
scaled_simulated_flow = np.log(simulated_flow / drainage_area)
scaled_observed_flow = np.log(observed_flow / drainage_area[0:3])

# Compute the departure for the three observation stations.
departures = scaled_simulated_flow[0:3] - scaled_observed_flow

Nous aurons maintenant besoin de certaines informations qui pourront (ou non) être disponibles pour nos sites d’observation et de simulation. Il s’agit notamment d’estimations de :

  • La variance des observations sur les sites jaugés.

  • La variance des débits simulés aux sites d’observation. Il s’agit d’un vecteur de taille 3 dans cet exemple, soit une valeur par site d’observation. A noter que cette variance est celle des simulations sur les sites d’observation, et non la variance des observations elles-mêmes.

  • La variance des débits simulés aux sites d’estimation. Il s’agit d’un vecteur de taille 5 dans cet exemple, soit une valeur par point de simulation, y compris ceux qui correspondent également à un site d’observation.

Nous ne connaissons pas ces valeurs pour cet exemple de test, mais ces valeurs peuvent être estimées dans des applications réelles en utilisant de longues séries chronologiques de débits log-transformés et mis à l’échelle ou en utilisant l’erreur de mesure de l’instrumentation sur des sites jaugés. Pour cet exemple, nous supposerons des valeurs simples de 1,0 pour chaque cas.

[4]:
# Define the ratio of the observed variance to that of the simulations. We set it to 0.15, but it could be adjusted
# according to the level of confidence in each measure.
var_obs = np.array([0.15, 0.15, 0.15])

# Define the background field (simulations) variance at the observed sites
bg_var_obs = (np.array([1.0, 1.0, 1.0]),)

# Define the background field (simulations) variance at the simulated sites. Note that the first three stations
# are the same as in the previous variable, as in our test case, the first three simulated stations are located
# at the observation sites.
bg_var_est = (np.array([1.0, 1.0, 1.0, 1.0, 1.0]),)

Si nous disposions de meilleures estimations de ces variables, nous pourrions remplacer les valeurs 1,0 par des valeurs plus appropriées. Toutefois, celles-ci peuvent également être ajustées en fonction de l’expérience passée ou par essais et erreurs.

La dernière pièce du puzzle est celle de la fonction de covariance d’erreur. En un mot, l’interpolation optimale prendra en compte la distance entre une observation (ou plusieurs observations) et le site où nous devons estimer la nouvelle valeur de débit. On comprend aisément qu’une station de simulation très proche d’une station d’observation doive être fortement corrélée avec celle-ci, alors qu’un point plus éloigné serait moins corrélé. Nous avons donc besoin d’une fonction de covariance qui estime (1) le degré de covariabilité entre un point observé et simulé en fonction de (2) la distance entre ces points. Il s’agit de la fonction ECF dont plusieurs modèles existent dans la littérature. Dans de nombreux cas, une forme de modèle sera imposée et les paramètres seront ajustés de telle sorte que le modèle représente la covariance existante entre les points.

Dans cet exemple de test, nous avons trop peu de points et pas assez de pas de temps pour établir un modèle (et un paramétrage) significatif à partir des données. Nous imposons donc un modèle. Il y en a quatre qui sont intégrés à xhydro, où par[0] et par[1] sont les paramètres du modèle à calibrer (dans des circonstances normales) et où h est la distance entre les points :

  • Modèle 1 : par[0] * (1 + h / par[1]) * exp(-h / par[1]) – D’après Lachance-Cloutier et al. 2017.

  • Modèle 2 : par[0] * exp(-0,5 * (h / par[1])**2)

  • Modèle 3 : par[0] * exp(-h / par[1])

  • Modèle 4 : par[0] * exp(-(h ** par[1]) / par[0])

Nous utiliserons le modèle n°4, mais vous pouvez le modifier ci-dessous et voir comment cela affecte les résultats. Les paramètres peuvent également être modifiés pour évaluer leurs impacts.

[5]:
# Define the ECF function model. We use partial functions to do so, with the models being predefined in the
# xhydro.optimal_interpolation.ECF_climate_correction package.
ecf_fun = partial(general_ecf, form=4)

# Now we can parameterize the ecf_fun with the model parameters that we want.
# In this test example we will use values of 1.0 and 0.5 for par[0] and par[1], respectively.
par = [1.0, 0.5]
ecf = partial(ecf_fun, par=par)

Nous pouvons maintenant exécuter l’algorithme d’interpolation optimal et récupérer la valeur estimée et la variance de l’incertitude pour chaque site simulé.

[6]:
print(f"lat_est: {lat_est}")
print(f"lon_est: {lon_est}")
print(f"lat_obs: {lat_obs}")
print(f"lon_obs: {lon_obs}")
print(f"bg_departures: {departures}")
print(f"bg_est: {scaled_simulated_flow}")
print(f"bg_var_obs: {bg_var_obs}")
print(f"bg_var_est: {bg_var_est}")
print(f"var_obs: {var_obs}")
print(f"ecf: {ecf}")
lat_est: [45.  45.5 45.8 44.2 45.4]
lon_est: [-71.  -70.4 -71.8 -70.2 -71.9]
lat_obs: [45.  45.5 45.8]
lon_obs: [-71.  -70.4 -71.8]
bg_departures: [ 0.07696104  0.20479441 -0.01342302]
bg_est: [-1.49165488 -0.69314718 -1.4325072  -1.87080268 -2.07374352]
bg_var_obs: (array([1., 1., 1.]),)
bg_var_est: (array([1., 1., 1., 1., 1.]),)
var_obs: [0.15 0.15 0.15]
ecf: functools.partial(<function general_ecf at 0x71ed1d678a40>, form=4, par=[1.0, 0.5])
[7]:
# Display all the data that will be used for the optimal interpolation:


# Launch the optimal interpolation with all the pre-established values.
v_est, var_est, _ = opt.optimal_interpolation(
    lat_est=lat_est,
    lon_est=lon_est,
    lat_obs=lat_obs,
    lon_obs=lon_obs,
    bg_departures=departures,
    bg_est=scaled_simulated_flow,
    bg_var_obs=bg_var_obs,
    bg_var_est=bg_var_est,
    var_obs=var_obs,
    ecf=ecf,
    precalcs={},  # To speedup calculations, not required here.
)

Enfin, nous pouvons calculer la valeur réelle de la meilleure estimation et la variance de la distribution de l’incertitude à partir de ces résultats :

[8]:
# Transform back into absolute values and rescale by the drainage area
estimated_flow = np.exp(v_est) * drainage_area

print(f"Estimated values are: {estimated_flow}")
print(f"Simulated values were: {simulated_flow}")
print(f"Observed values are: {observed_flow}")
Estimated values are: [113.44733348 161.14678052 142.02962921 154.00037475  87.99639936]
Simulated values were: [108. 135. 148. 154.  88.]
Observed values are: [100. 110. 150.]

Dans une dernière étape, voici comment estimer la distribution des valeurs possibles aux sites d’estimation. Le v_est est l’emplacement de la distribution et le v_est est la variance. Cela signifie que nous pouvons modéliser la distribution et générer des valeurs de quantiles.

[9]:
# Get the log-normal error model, whose variance depends on the interpolation variance and the variance of the departures.
var_bg = np.var(departures)  # Variance of the departures of the background field
var_est = (
    var_est * var_bg
)  # Complete error model that includes the interpolation variance and the departure variance.

# Using the uncertainty estimation, get the 25th percentile of the estimated flows, and un-transform
percentile_values = norm.ppf(np.array(25.0) / 100.0, loc=v_est, scale=np.sqrt(var_est))
flows_25th_percentile = np.exp(percentile_values) * drainage_area

# Do the same but with the 75th percentile.
percentile_values = norm.ppf(np.array(75.0) / 100.0, loc=v_est, scale=np.sqrt(var_est))
# Get the values in real units and scale according to drainage area
flows_75th_percentile = np.exp(percentile_values) * drainage_area

print(f"Estimated values for the 25th percentile are: {flows_25th_percentile}")
print(f"Estimated values for the 50th percentile are: {estimated_flow}")
print(f"Estimated values for the 75th percentile are: {flows_75th_percentile}")
Estimated values for the 25th percentile are: [[111.26527089 158.04725979 139.29781056 144.97663776  82.84020497]]
Estimated values for the 50th percentile are: [113.44733348 161.14678052 142.02962921 154.00037475  87.99639936]
Estimated values for the 75th percentile are: [[115.67218928 164.30708704 144.81502252 163.58577347  93.473529  ]]

Notice that there are a few problems with the model presented here:

  1. The optimal interpolation worsened the estimated results at the gauged sites compared to the raw simulation.

  2. The 25th and 75th percentile values for the estimated flows at the gauged sites are « nan ».

  3. The estimated flows at the ungauged sites did not change (or changed very little).

These problems arise due to some methodological choices:

  • Forcing of a covariance function model and parameterization that is inadequate.

  • Very few observation stations, making it extremely difficult to assess spatial patterns.

  • Simulated and observed flows that were randomly generated and have no (or little) correlation, very small covariance.

Cela signifie que le problème est mal défini et que l’interpolation optimale ne doit pas être appliquée dans ces cas. Avec plus de données, les résultats deviennent bien meilleurs, comme le montrera la section suivante.

3.2. Application sur données réelles du modèle hydrologique HYDROTEL

La section précédente a montré comment implémenter l’algorithme d’interpolation optimal de manière autonome. Toutefois, cela n’est pas pratique lorsque de nombreuses stations doivent être traitées simultanément. Des outils ont donc été intégrés à xhydro pour faciliter tout le traitement et, en tant que tels, ont des exigences spécifiques en matière de données. Ici, nous explorons le contenu d’un fichier d’entrée complet, et nous ajouterons des détails un peu plus tard. Commençons par importer quelques données de test depuis le dépôt xhydro-testdata :

[10]:
# Get data
test_data_path = deveraux().fetch(
    "optimal_interpolation/OI_data_corrected.zip",
    pooch.Unzip(),
)
directory_to_extract_to = Path(test_data_path[0]).parent

# Read-in all the files and set to paths that we can access later.
flow_obs_info_file = directory_to_extract_to / "A20_HYDOBS_TEST_corrected.nc"
flow_sim_info_file = directory_to_extract_to / "A20_HYDREP_TEST_corrected.nc"
corresponding_station_file = directory_to_extract_to / "station_correspondence.nc"
selected_station_file = (
    directory_to_extract_to / "stations_retenues_validation_croisee.csv"
)

We now have 4 files:

  • flow_obs_info_file: The dataset file (.nc) that contains the point observations and station metadata.

  • flow_sim_info_file: The dataset file (.nc) that contains the background field simulations, including simulated station metadata.

  • corresponding_station_file: The dataset file (.nc) that links the station identifiers between observations and simulated stations. This is necessary because observed stations have « real world » identifiers and distributed simulations are often coded or numbered sequentially. However, we need to be able to find which of the background field stations (simulation points) correspond to each real-world station.

  • selected_station_file: The list of stations from the observation set that we wish to use (thus discarding the others from the flow_obs_info_file set).

Nous pouvons maintenant les traiter pour extraire certaines valeurs qu’il faudra envoyer au contrôleur principal de l’interpolation optimale :

[11]:
# We first open the .nc files that contain our required data (qobs for the observed flows,
# qsim for the simulated flows and the station correspondence file)
qobs = xr.open_dataset(flow_obs_info_file)
qsim = xr.open_dataset(flow_sim_info_file)
station_correspondence = xr.open_dataset(corresponding_station_file)

# Also read the .csv file that contains the list of observation stations to include.
df_validation = pd.read_csv(selected_station_file, sep=None, dtype=str)
observation_stations = list(df_validation["No_station"])

Explorons le contenu de ces fichiers :

[12]:
# First show the contents of the observed streamflow file:
display(qobs)
<xarray.Dataset> Size: 2MB
Dimensions:        (time: 1097, station: 274)
Coordinates:
  * time           (time) datetime64[ns] 9kB 2016-01-01 ... 2019-01-01
    lat            (station) float32 1kB ...
    lon            (station) float32 1kB ...
    station_id     (station) <U6 7kB ...
Dimensions without coordinates: station
Data variables:
    drainage_area  (station) float32 1kB ...
    centroid_lat   (station) float32 1kB ...
    centroid_lon   (station) float32 1kB ...
    classement     (station) float32 1kB ...
    streamflow     (station, time) float64 2MB ...
Attributes: (12/13)
    title:                          Observations hydrométriques aux stations ...
    summary:                        Streamflow measurement at water gauge of ...
    institution:                    DEH (Direction de l'Expertise Hydrique)
    institute_id:                   DEH
    contact:                        simon.lachance-cloutier@environnement.gou...
    date_created:                   2020-10-13
    ...                             ...
    featureType:                    timeSeries
    cdm_data_type:                  station
    license:                        ODC-BY
    keywords:                       hydrology, Quebec, observed, streamflow, ...
    conventions:                    CF-1.6
    project_internal_codification:  CQ2

3.2.1. IMPORTANT:

Notice that there are a few keywords that are important in these files that the code expects:

  1. The streamflow observations must be in a data variable named « streamflow », with dimensions « station » and « time ».

  2. There must be the catchment drainage area in a variable named « drainage_area » with dimensions « station ».

  3. The « centroid_lat » and « centroid_lon » are also required under those specific names to allow computing distances. These are the centroids of the catchments, and not the latitude and longitude of the hydrometric stations.

  4. There should be a « time » variable.

  5. There should be a « station_id » variable, that has an identifier for each station. This will be used to map the observation station IDs to the simulated station IDs using the correspondence tables.

Notez qu’il y a 274 stations observées, ce qui devrait contribuer à augmenter la précision de la fonction de covariance d’erreur.

Nous pouvons maintenant explorer le débit simulé « qsim », qui est assez similaire :

[13]:
# Next show the contents of the simulated streamflow file:
display(qsim)
<xarray.Dataset> Size: 1MB
Dimensions:        (time: 1097, station: 274)
Coordinates:
  * time           (time) datetime64[ns] 9kB 2016-01-01 ... 2019-01-01
    lat            (station) float32 1kB ...
    lon            (station) float32 1kB ...
    station_id     (station) <U9 10kB ...
Dimensions without coordinates: station
Data variables:
    drainage_area  (station) float32 1kB ...
    streamflow     (station, time) float32 1MB ...

3.2.2. IMPORTANT:

We can again see some specific variables in the « qsim » dataset:

  1. The streamflow simulations must be in a data variable named « streamflow », with dimensions « station » and « time ».

  2. There must be the catchment drainage area as simulated by the model in a variable named « drainage_area » with dimensions « station ».

  3. The « lat » and « lon » are also required under those specific names to allow computing distances. These are the centroids of the catchments, and not the latitude and longitude of the hydrometric stations, which do not exist in the simulation mode.

  4. There should be a « time » variable.

  5. There should be a « station_id » variable, that has an identifier for each station. This will be used to map the observation station IDs to the simulated station IDs using the correspondence tables.

Notez qu’il y a encore 274 stations, comme dans le jeu de données « qobs ». En effet, ce jeu de données spécifique a été utilisé pour effectuer une validation croisée sans intervention afin d’évaluer les performances d’interpolation optimales et, en tant que telle, seules les simulations sur des sites jaugés présentent un intérêt. Dans un contexte opérationnel, il n’y a pas de limite sur le nombre de stations pour « qsim ».

Now let’s take a look at the correspondence tables and the observed station dataset.

[14]:
# Show the correspondence table
display(station_correspondence)
<xarray.Dataset> Size: 18kB
Dimensions:     (station: 296)
Dimensions without coordinates: station
Data variables:
    reach_id    (station) <U9 11kB ...
    station_id  (station) <U6 7kB ...

Pour séparer les noms des stations observées et de simulation, la nomenclature suivante a été adoptée :

  • Les stations observées sont étiquetées comme « station_id » dans le jeu de données station_correspondence

  • Les stations simulées sont étiquetées comme « reach_id » dans le jeu de données station_correspondence

Notez qu’il y a 296 stations dans ce tableau, alors que nous n’avions que 274 stations dans les jeux de données de débits. Ceci est tout à fait acceptable, tant que toutes les paires observation-simulation se trouvent dans le jeu de données station_correspondence. S’il en manque, le code déclenchera une exception.

Enfin, voyons le contenu de la variable observation_stations, qui indique au modèle laquelle des 274 stations d’observation doit être utilisée pour construire le modèle de covariance d’erreur et effectuer l’interpolation optimale. Ces stations doivent constituer un sous-ensemble des 274 stations observées.

[15]:
print(
    f"There are a total of {len(observation_stations)} selected observation stations."
)
print(observation_stations)
There are a total of 96 selected observation stations.
['011508', '011509', '020602', '020802', '021601', '021915', '021916', '022003', '022301', '022601', '022704', '023303', '023401', '023402', '023702', '024003', '024007', '024010', '024014', '024015', '030101', '030103', '030215', '030234', '030282', '030335', '030345', '030424', '030425', '030905', '030907', '030919', '030921', '040129', '040204', '040841', '040830', '040840', '041902', '041903', '042103', '043012', '050144', '050135', '050304', '050408', '050409', '050702', '050801', '050812', '050915', '050916', '051001', '051005', '051006', '051301', '051502', '052228', '052231', '052601', '052606', '052805', '060101', '060102', '060202', '060601', '060704', '060901', '061020', '061022', '061024', '061028', '061307', '061502', '061801', '061901', '061905', '061909', '062102', '062114', '062701', '062803', '070401', '072301', '073502', '073503', '073801', '074701', '074902', '075702', '075705', '076201', '076601', '080101', '080106', '080707']

Comme on peut le constater, il s’agit simplement d’une liste de stations. Il peut être généré par tous les moyens par les utilisateurs, à condition qu’il soit sous forme de liste et qu’il inclue les stations issues des variables qobs « station_id ». Pour ce cas de test, nous avons utilisé seulement 96 bassins versants comportant un nombre suffisant d’enregistrements de débits observés.

Nous pouvons désormais fournir plus de détails sur certains hyperparamètres. Notez que de nombreux hyperparamètres de l’exemple de test ne sont pas requis ici, car le modèle imposera certains choix et déterminera directement d’autres valeurs à partir des données. Par exemple, le modèle ECF utilisé est le « Modèle 3 » et ses paramètres sont optimisés pour s’adapter au mieux aux données disponibles.

A ce stade, les seules données manquantes requises sont les suivantes :

[16]:
# Start and end dates for the simulation. We use a short period for this notebook, but it can be extended.
start_date = dt.datetime(2018, 11, 1)
end_date = dt.datetime(2019, 1, 1)

# The ratio of observed-to-simulated variance.
ratio_var_bg = 0.15

# The percentiles desired to estimate the flow uncertainty at each timestep and station
percentiles = [25.0, 50.0, 75.0]

# The number of variogram bins required to fit the error covariance function. 10 is a good number, but could be increased with more data.
variogram_bins = 10

Nous pouvons maintenant effectuer un peu de traitement pour nous assurer de fournir uniquement les données souhaitées :

[17]:
# Extract only the desired time period for the flow datasets
qobs = qobs.sel(time=slice(start_date, end_date))
qsim = qsim.sel(time=slice(start_date, end_date))

# Other computational options

# If we do a leave-one-out cross-validation over the 96 catchments, the entire optimal interpolation process is repeated 96 times but
# only over the observation sites, each time leaving one station out and kept independent for validation. This is time-consuming and
# can be parallelized by adjusting this flag and setting an appropriate number of CPU cores according to your computer. By default,
# the code will only use 1 core. However, if increased, the maximum number that will be actually used is ([number-of-available-cores / 2] - 1)
# CPU cores as to not overexert the computer.
parallelize = False
max_cores = 1

# However, if leave_one_out_cv is set to False, then a simple operational application is performed and the model will estimate flows
# at all "qsim" simulation sites. Here we set to "True" to generate a Leave-One-Out Cross-Validation and thus get flows that can
# be evaluated and compared to actual observations.
leave_one_out_cv = True

Nous sommes maintenant prêts à effectuer l’interpolation optimale, à renvoyer les résultats sous la forme d’un jeu de données et à explorer ce jeu de données :

[18]:
# Launch the optimal interpolation by calling the main controller
ds = opt.execute_interpolation(
    qobs=qobs,
    qsim=qsim,
    station_correspondence=station_correspondence,
    observation_stations=observation_stations,
    ratio_var_bg=ratio_var_bg,
    percentiles=percentiles,
    variogram_bins=variogram_bins,
    parallelize=parallelize,
    max_cores=max_cores,
    leave_one_out_cv=leave_one_out_cv,
)

display(ds)
<xarray.Dataset> Size: 148kB
Dimensions:        (percentile: 3, station_id: 96, time: 62, station: 96,
                    nbnds: 2)
Coordinates:
  * percentile     (percentile) float64 24B 25.0 50.0 75.0
  * station        (station) <U6 2kB '011508' '011509' ... '080106' '080707'
Dimensions without coordinates: station_id, time, nbnds
Data variables:
    streamflow     (percentile, station_id, time) float64 143kB 2.124 ... 334.6
    lat            (station_id) float64 768B 48.55 48.4 49.04 ... 47.99 48.73
    lon            (station_id) float64 768B -67.59 -67.35 ... -77.73 -76.79
    drainage_area  (station_id) float64 768B 555.0 2.753e+03 ... 365.0 2.228e+04
    time_bnds      (time, nbnds) datetime64[ns] 992B 2018-10-31T23:59:59.9999...

Nous pouvons voir que le jeu de données renvoyé a une variable appelée « streamflow » de taille [percentile, station_id, time].

Cette variable peut être explorée pour obtenir l’estimation du débit pour chaque centile demandé pour évaluer l’incertitude. Par exemple, explorons la valeur du 50e centile, c’est-à-dire la valeur du centile à l’indice 1.

[19]:
display(ds["streamflow"].sel(percentile=50.0))
<xarray.DataArray 'streamflow' (station_id: 96, time: 62)> Size: 48kB
array([[   3.0610731 ,    3.08483217,    3.40292931, ...,    7.2128244 ,
           7.17462588,    7.1169376 ],
       [  29.01020146,   28.1849044 ,   37.62200019, ...,   35.08836719,
          33.54176179,   32.22055908],
       [   9.59002346,    9.01571449,   12.5050935 , ...,    6.63573967,
           6.2305281 ,    6.00329311],
       ...,
       [ 169.17296931,  203.09736304,  184.97046225, ...,   51.93371024,
          48.56404609,   60.16432331],
       [  10.93679601,   13.37650394,   11.88052393, ...,    3.16821044,
           2.96101015,    3.68349568],
       [1043.77137398, 1291.28646564, 1219.34514919, ...,  247.07583756,
         227.94144981,  286.17829966]])
Coordinates:
    percentile  float64 8B 50.0
Dimensions without coordinates: station_id, time
Attributes:
    long_name:              Streamflow
    standard_name:          outgoing_water_volume_transport_along_river_channel
    units:                  m3 s-1
    cell_methods:           time: mean
    coverage_content_type:  modelResult

Nous pouvons aller plus loin et extraire les données pour un bassin versant. Nous les stockerons également dans une variable distincte pour une analyse plus approfondie.

[20]:
# Change to see another catchment.
selected_catchment = 0
interpolated_flow_select = (
    ds["streamflow"].sel(percentile=50.0).isel(station_id=selected_catchment)
)

# Get the station ID for comparing
interpolated_flow_select_station_id = str(
    ds["station"].isel(station=selected_catchment).data
)

Nous pouvons faire un traitement similaire pour les données observées et brutes de simulation :

[21]:
# Since we went from 274 to 96 catchments, the indexing is not preserved between the outputs and input files. Furthermore, there is
# no guarantee that the 274 simulation and 274 observation stations are in the same order between both files. This code realigns everything.
index_correspondence = np.where(
    station_correspondence["station_id"] == interpolated_flow_select_station_id
)[0][0]
station_code = station_correspondence["reach_id"][index_correspondence]
index_in_sim = np.where(qsim["station_id"].values == station_code.data)[0][0]
index_in_obs = np.where(qobs["station_id"] == interpolated_flow_select_station_id)[0][0]

# Extract the correct data from the observed and raw simulation files.
observed_flow_select = qobs["streamflow"].isel(station=index_in_obs)
raw_simulated_flow_select = qsim["streamflow"].isel(station=index_in_sim)

Nous pouvons représenter graphiquement ces résultats et rechercher une amélioration dans les simulations après l’interpolation optimale :

[22]:
plt.plot(observed_flow_select, label="Observed flow")
plt.plot(raw_simulated_flow_select, label="Raw simulation")
plt.plot(interpolated_flow_select, label="Interpolated simulation")
plt.xlabel("Simulation day")
plt.ylabel("Streamflow (m³/s)")
plt.legend()
plt.show()
../_images/notebooks_optimal_interpolation_45_0.png

We can see that the optimal interpolation generally helped bring the model similation back in-line with the observations. Note that here the observations were not available and to the optimal interpolation algorithm in this leave-one-out cross-validation implementation, so the improvement is blind to the gauge observation data at this site.