Plot Wflow results
hydroMT provides a simple interface to model results from which we can make beautiful plots:
Results layers are saved to the model
results
attribute as a dictionnary ofxarray.Dataset
orxarray.DataArray
.
These plots can be useful to analyze the model results or also compare model runs with different settings (different precipitation source or different parameters values).
Load dependencies
[1]:
import pandas as pd
import xarray as xr
import numpy as np
from os.path import join, dirname
import os
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib import cm, colors
[2]:
import hydromt
Read the model run(s) results
The wflow_piave_subbasin model was run using the default global data sources of the hydromt_wflow plugin. The different variables to save after the wflow were set in a separate wflow configuration file: wflow_sbm_results.toml.
A second run of the model was also done, where the KsatHorFrac parameter of wflow was set to 10 (instead of the default 100 value) using an alternative configuration file: wflow_sbm_results2.toml.
We will use the below runs
dictionnary to define the model run(s) we want to read and some settings for plotting. If you want to plot and compare several runs together, you can simply add them to the runs
dictionnary.
[3]:
# Dictionnary listing the different wflow models and runs to compare, including plotting options
runs = {
"run1": {
"longname": "default",
"color": "blue",
"root": "wflow_piave_subbasin",
"config_fn": "wflow_sbm_results.toml",
},
"run2": {
"longname": "KsatHorFrac10",
"color": "green",
"root": "wflow_piave_subbasin",
"config_fn": "wflow_sbm_results2.toml",
},
}
mainrun = "run1"
[4]:
# Initialize the different model run(s)
for r in runs:
run = runs[r]
mod = hydromt.WflowModel(root=run["root"], mode="r+", config_fn=run["config_fn"])
runs[r].update({"mod": mod})
Wflow can save different types of outputs (netcdf gridded output, netcdf scalar netcdf, csv scalar timeseries) that are also reflected in the organisation of the hydroMT results
attribute:
a “output” xarray.Dataset for the gridded netcdf file (output section of the TOML)
a “netcdf” xarray.Dataset for the netcdf point timeseries file (netcdf section of the TOML)
different hydromt.GeoDataArrays for the csv file , one per column (csv section and csv.column sections of the TOML). The xy coordinates are the coordinates of the station or of the representative point of the subcatch/area. The variable name in the GeoDataArray corresponds to the csv header attribute or header_map when available.
Below you can see how to access to the results of run1 and its contents:
[5]:
mod1 = runs["run1"]["mod"]
mod1.results
[5]:
{'output': <xarray.Dataset>
Dimensions: (lon: 58, lat: 53, layer: 4, time: 9)
Coordinates:
* lon (lon) float64 11.78 11.8 11.82 11.83 ... 12.68 12.7 12.72 12.73
* lat (lat) float64 45.82 45.83 45.85 45.87 ... 46.63 46.65 46.67 46.68
* layer (layer) int64 1 2 3 4
* time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10
Data variables:
q_river (time, lat, lon) float32 dask.array<chunksize=(9, 53, 58), meta=np.ndarray>
h_land (time, lat, lon) float32 dask.array<chunksize=(9, 53, 58), meta=np.ndarray>,
'netcdf': <xarray.Dataset>
Dimensions: (time: 9, Q_gauges: 1, temp_bycoord: 1)
Coordinates:
* time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10
* Q_gauges (Q_gauges) object '1'
* temp_bycoord (temp_bycoord) object 'temp_bycoord'
Data variables:
Q (time, Q_gauges) float32 dask.array<chunksize=(9, 1), meta=np.ndarray>
temp_coord (time, temp_bycoord) float32 dask.array<chunksize=(9, 1), meta=np.ndarray>,
'Q-max': <xarray.DataArray 'Q-max' (time: 9, index: 1)>
array([[ 92.29159319],
[214.84545211],
[278.7532426 ],
[317.66886014],
[338.05469058],
[346.00525086],
[337.03409614],
[319.22097847],
[304.51376705]])
Coordinates:
* time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10
* index (index) int64 0
x (index) float64 12.26
y (index) float64 46.25,
'res-volume': <xarray.DataArray 'res-volume' (time: 9, index: 1)>
array([[42800000.],
[42800000.],
[42800000.],
[42800000.],
[42800000.],
[42800000.],
[42800000.],
[42800000.],
[42800000.]])
Coordinates:
* time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10
* index (index) int64 0
x (index) float64 12.08
y (index) float64 46.17,
'temp_bycoord': <xarray.DataArray 'temp_bycoord' (time: 9, index: 1)>
array([[-0.07535708],
[ 0.80580747],
[ 1.95656431],
[ 2.93230295],
[ 4.55180979],
[ 6.96596432],
[ 5.75890207],
[ 2.75527048],
[ 2.07729197]])
Coordinates:
* time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10
* index (index) int64 0
x (index) float64 11.95
y (index) float64 45.9,
'temp_byindex': <xarray.DataArray 'temp_byindex' (time: 9, index: 1)>
array([[-0.07535708],
[ 0.80580747],
[ 1.95656431],
[ 2.93230295],
[ 4.55180979],
[ 6.96596432],
[ 5.75890207],
[ 2.75527048],
[ 2.07729197]])
Coordinates:
* time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10
* index (index) int64 0
x (index) float64 11.95
y (index) float64 45.9,
'Q_gauges_grdc': <xarray.DataArray 'Q_gauges_grdc' (index: 3, time: 9)>
array([[ 5.65726064, 10.23746886, 12.94857033, 13.4642965 , 12.93064957,
12.23056571, 11.51420303, 10.88308009, 10.3122563 ],
[24.14968045, 41.4367125 , 55.46918112, 60.2240498 , 60.21861451,
58.43010672, 55.0491823 , 51.84542554, 49.09354881],
[15.21117463, 21.63727294, 27.21875575, 30.66606831, 33.1589512 ,
33.26008825, 32.09006663, 30.99882382, 30.10063939]])
Coordinates:
* index (index) int64 6349411 6349400 6349410
* time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10
x (index) float64 12.12 12.52 12.22
y (index) float64 46.6 46.53 46.43
value (index) float64 6.349e+06 6.349e+06 6.349e+06
spatial_ref int64 1,
'P_subcatchment': <xarray.DataArray 'P_subcatchment' (index: 1, time: 9)>
array([[ 0.12844024, 0.14628571, 0.08870213, 0.02100732, 13.49765681,
6.09464082, 2.09232876, 0.06205084, 0.37978556]])
Coordinates:
* index (index) int64 1
* time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10
x (index) float64 12.18
y (index) float64 46.25
value (index) float64 1.0
spatial_ref int64 1}
Read observations
You can also use hydroMT to read observations data in order to analyze your model results. Here a fictional observations timeseries was prepared for the gauges_grdc locations.
[6]:
# Discharge data
timeseries_fn = "gauges_observed_flow.csv" # observed discharge timeseries
name = "gauges_grdc" # gauges locations in staticgeoms
stationID = "grdc_no" # column name in staticgeoms containing the stations IDs
# Read the observations data
# read timeseries data and match with existing gdf
gdf = runs[mainrun]["mod"].staticgeoms[name]
gdf.index = gdf[stationID]
da_ts = hydromt.io.open_timeseries_from_table(timeseries_fn, name=name, sep=";")
da = hydromt.vector.GeoDataArray.from_gdf(gdf, da_ts, index_dim="index")
obs = da
obs
[6]:
<xarray.DataArray 'gauges_grdc' (index: 3, time: 9)> array([[14.21117463, 20.63727294, 26.21875575, 29.66606831, 32.1589512 , 32.26008825, 31.09006663, 29.99882382, 29.10063939], [23.14968045, 40.4367125 , 54.46918112, 59.2240498 , 59.21861451, 57.43010672, 54.0491823 , 50.84542554, 48.09354881], [ 4.65726064, 9.23746886, 11.94857033, 12.4642965 , 11.93064957, 11.23056571, 10.51420303, 9.88308009, 9.31225629]]) Coordinates: (12/27) * index (index) int64 6349410 6349400 6349411 * time (time) datetime64[ns] 2010-02-02 2010-02-03 ... 2010-02-10 x (index) float64 12.22 12.52 12.12 y (index) float64 46.43 46.53 46.6 altitude (index) float64 818.0 848.0 1.33e+03 t_start (index) int64 1930 1932 1943 ... ... t_yrs (index) int64 79 77 66 wmo_reg (index) int64 6 6 6 r_volume_yr (index) float64 0.2984 0.3268 0.07115 m_end (index) object None None None grdc_no (index) int64 6349410 6349400 6349411 spatial_ref int64 1
- index: 3
- time: 9
- 14.21 20.64 26.22 29.67 32.16 32.26 ... 11.93 11.23 10.51 9.883 9.312
array([[14.21117463, 20.63727294, 26.21875575, 29.66606831, 32.1589512 , 32.26008825, 31.09006663, 29.99882382, 29.10063939], [23.14968045, 40.4367125 , 54.46918112, 59.2240498 , 59.21861451, 57.43010672, 54.0491823 , 50.84542554, 48.09354881], [ 4.65726064, 9.23746886, 11.94857033, 12.4642965 , 11.93064957, 11.23056571, 10.51420303, 9.88308009, 9.31225629]])
- index(index)int646349410 6349400 6349411
array([6349410, 6349400, 6349411])
- time(time)datetime64[ns]2010-02-02 ... 2010-02-10
array(['2010-02-02T00:00:00.000000000', '2010-02-03T00:00:00.000000000', '2010-02-04T00:00:00.000000000', '2010-02-05T00:00:00.000000000', '2010-02-06T00:00:00.000000000', '2010-02-07T00:00:00.000000000', '2010-02-08T00:00:00.000000000', '2010-02-09T00:00:00.000000000', '2010-02-10T00:00:00.000000000'], dtype='datetime64[ns]')
- x(index)float6412.22 12.52 12.12
array([12.216667, 12.516667, 12.116667])
- y(index)float6446.43 46.53 46.6
array([46.433333, 46.533333, 46.6 ])
- altitude(index)float64818.0 848.0 1.33e+03
array([ 818., 848., 1330.])
- t_start(index)int641930 1932 1943
array([1930, 1932, 1943])
- r_height_yr(index)float64953.2 915.5 867.6
array([953.23353355, 915.51569748, 867.62458537])
- d_miss(index)float6436.77 33.53 51.14
array([36.77005718, 33.53244444, 51.14282159])
- m_yrs(index)objectNone None None
array([None, None, None], dtype=object)
- t_end(index)int642008 2008 2008
array([2008, 2008, 2008])
- area(index)float64313.0 357.0 82.0
array([313., 357., 82.])
- sub_reg(index)int6449 49 49
array([49, 49, 49])
- m_miss(index)objectNone None None
array([None, None, None], dtype=object)
- d_start(index)int641930 1932 1943
array([1930, 1932, 1943])
- d_end(index)int642008 2008 2008
array([2008, 2008, 2008])
- d_yrs(index)int6479 77 66
array([79, 77, 66])
- lta_discharge(index)float649.461 10.36 2.256
array([ 9.461, 10.364, 2.256])
- country(index)object'IT' 'IT' 'IT'
array(['IT', 'IT', 'IT'], dtype=object)
- river(index)object'BOITE, TORRENTE' ... 'BOITE, TO...
array(['BOITE, TORRENTE', 'PIAVE, FIUME', 'BOITE, TORRENTE'], dtype=object)
- station(index)object'CANCIA' ... 'PODESTAGNO'
array(['CANCIA', 'PONTE DELLA IASTA', 'PODESTAGNO'], dtype=object)
- m_start(index)objectNone None None
array([None, None, None], dtype=object)
- t_yrs(index)int6479 77 66
array([79, 77, 66])
- wmo_reg(index)int646 6 6
array([6, 6, 6])
- r_volume_yr(index)float640.2984 0.3268 0.07115
array([0.2983621 , 0.3268391 , 0.07114522])
- m_end(index)objectNone None None
array([None, None, None], dtype=object)
- grdc_no(index)int646349410 6349400 6349411
array([6349410, 6349400, 6349411])
- spatial_ref()int641
- index_dim :
- index
- x_dim :
- x
- y_dim :
- y
- crs_wkt :
- GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]
array(1)
Plot model results
Here we plot the different model results for the gauges_grdc locations.
[7]:
# Plotting options
# select the gauges_grdc results (name in csv column of wflow results to plot)
result_name = "Q_gauges_grdc"
# selection of runs to plot (all or a subset)
runs_subset = ["run1", "run2"]
[8]:
# Plots
station_ids = list(runs[mainrun]["mod"].results[result_name].index.values)
for i, st in enumerate(station_ids):
n = 2
fig, axes = plt.subplots(n, 1, sharex=True, figsize=(15, n * 4))
axes = [axes] if n == 1 else axes
# Discharge
obs_i = obs.sel(index=st)
obs_i.plot.line(ax=axes[0], x="time", label="obs", color="black")
for r in runs_subset:
run = runs[r]
run_i = run["mod"].results[result_name].sel(index=st)
# Stats
nse_i = hydromt.stats.nashsutcliffe(run_i, obs_i).values.round(2)
kge_i = hydromt.stats.kge(run_i, obs_i)["kge"].values.round(2)
labeltxt = f"{run['longname']}, NSE: {nse_i}, KGE: {kge_i}"
run_i.plot.line(
ax=axes[0],
x="time",
label=labeltxt,
color=f"{run['color']}",
linestyle="--",
)
axes[0].set_title(f"Simulated discharge at station {st}")
axes[0].set_ylabel("Discharge [m3/s]")
axes[0].legend()
# Cumulative Discharge
obs_i = obs.sel(index=st)
obs_i.cumsum().plot.line(ax=axes[1], x="time", label="obs", color="black")
for r in runs_subset:
run = runs[r]
run_i = run["mod"].results[result_name].sel(index=st)
run_i.cumsum().plot.line(
ax=axes[1],
x="time",
label=f"{run['longname']}",
color=f"{run['color']}",
linestyle="--",
)
axes[1].set_title(f"Cumulative discharge at station {st}")
axes[1].set_ylabel("Cumulative Discharge [m3/s]")
axes[1].legend()



You can see on the discharge plots legends that some statistical criteria were computed using the fictional observations and the model runs results.
These statistics were computed using the stats module of hydroMT. You can find the available statisctics functions in the documentation.
And finally once the results
are loaded, you can use them to derive more statistics or plots to further analyze your model.