Multi model comparison#

We often want to compare the result of multiple models.

Calibration. We have several “runs” of the same model with different settings. We would like to find the best.

Validation. We would like to compare our model with alternative models, e.g. a regional DHI model or an external model.

In this notebook, we will consider several wave models for the Southern North Sea and compare to both point measurements and satellite altimetry data.

import numpy as np
import modelskill as ms

Define observations#

o1 = ms.PointObservation('data/SW/HKNA_Hm0.dfs0', item=0, x=4.2420, y=52.6887, name="HKNA")
o2 = ms.PointObservation("data/SW/eur_Hm0.dfs0", item=0, x=3.2760, y=51.9990, name="EPL")
o3 = ms.TrackObservation("data/SW/Alti_c2_Dutch.dfs0", item=3, name="c2")

Define models#

mr1 = ms.model_result('data/SW/HKZN_local_2017_DutchCoast.dfsu', name='SW_1', item=0)
mr2 = ms.model_result('data/SW/HKZN_local_2017_DutchCoast_v2.dfsu', name='SW_2', item=0)
mr3 = ms.model_result('data/SW/ERA5_DutchCoast.nc', name='ERA5', item="swh")
##

Temporal coverage#

ms.plotting.temporal_coverage([o1, o2, o3], [mr1, mr2, mr3]);
_images/a64fc2325142c51b4b6528c6ef0a9689b3a7d17ef0232a42fbcb092a8f19b8ec.png

Match observations and model results#

# HKNA is outside ERA5 domain -> pick nearest grid cell
cc = ms.match(obs=[o1, o2, o3], mod=[mr1, mr2, mr3], spatial_method="nearest")
cc
<ComparerCollection>
Comparers:
0: HKNA - Significant wave height [m]
1: EPL - Significant wave height [m]
2: c2 - Significant wave height [m]
cc["EPL"]   # select a single comparer from the collection like this
<Comparer>
Quantity: Significant wave height [m]
Observation: EPL, n_points=66
Model(s):
0: SW_1
1: SW_2
2: ERA5

Perform analysis#

You can perform simple filtering on specific observation or specific model. You can refer to observations and models using their name or index.

The main analysis methods are:

  • skill()

  • mean_skill()

  • scatter()

  • taylor()

cc.skill()
n bias rmse urmse mae cc si r2
model observation
SW_1 HKNA 386 -0.194260 0.351964 0.293499 0.251839 0.971194 0.094489 0.905300
EPL 66 -0.066028 0.224919 0.215009 0.189791 0.969512 0.082773 0.932082
c2 102 -0.025059 0.354270 0.353383 0.297344 0.970862 0.131869 0.889056
SW_2 HKNA 386 -0.100426 0.293033 0.275287 0.214422 0.971194 0.088626 0.934358
EPL 66 0.001181 0.233964 0.233961 0.199915 0.969512 0.090069 0.926509
c2 102 0.050773 0.423351 0.420296 0.353069 0.970862 0.156839 0.841570
ERA5 HKNA 386 -0.437425 0.545329 0.325643 0.441374 0.974863 0.104837 0.772663
EPL 66 -0.207731 0.289960 0.202299 0.223748 0.972255 0.077880 0.887121
c2 102 -0.390274 0.486571 0.290582 0.407335 0.972701 0.108435 0.790720
cc.sel(observation="c2").skill()
n bias rmse urmse mae cc si r2
model observation
SW_1 c2 102 -0.025059 0.354270 0.353383 0.297344 0.970862 0.131869 0.889056
SW_2 c2 102 0.050773 0.423351 0.420296 0.353069 0.970862 0.156839 0.841570
ERA5 c2 102 -0.390274 0.486571 0.290582 0.407335 0.972701 0.108435 0.790720
cc.sel(model=0, observation=[0,"c2"]).mean_skill()
n bias rmse urmse mae cc si r2
model
ERA5 488 -0.413850 0.515950 0.308112 0.424354 0.973782 0.106636 0.781691
SW_1 488 -0.109659 0.353117 0.323441 0.274591 0.971028 0.113179 0.897178
SW_2 488 -0.024826 0.358192 0.347791 0.283745 0.971028 0.122732 0.887964
cc.sel(model='SW_1').plot.scatter(cmap='OrRd');
_images/948003094ec3721931e569e8ee56d28bd9cd61f57686bd287fd084a05422b6ec.png
cc.plot.taylor(normalize_std=True, aggregate_observations=False)
_images/3decd8b46928213c2ecdc79a1fd5add890f66af4cd853041f5e89518ad3c3029.png

Time series plot (specifically for point comparisons)#

If you select an comparison from the collection which is a PointComparer, you can do a time series plot

cc['EPL'].plot.timeseries(figsize=(12,4));
_images/44fff974c8d1616cc88c7c98c957438ab9b1649c03dd4901f76ee4b1e388f776.png

Filtering on time#

Use the start and end arguments to do your analysis on part of the time series

cc.sel(model="SW_1", end='2017-10-28').skill()
n bias rmse urmse mae cc si r2
observation
HKNA 281 -0.097075 0.203241 0.178559 0.164563 0.968106 0.070167 0.916126
EPL 47 -0.100029 0.240775 0.219013 0.205827 0.919089 0.101695 0.811727
c2 66 -0.210375 0.323525 0.245786 0.268925 0.385374 0.121671 -1.855535
cc.sel(model='SW_2', start='2017-10-28').plot.scatter(cmap='OrRd', figsize=(6,7));
_images/9f116c0ff52d925736f38f87c4b696473cd9db0a50d3968e344fb1fac757b3fc.png

Filtering on area#

You can do you analysis in a specific area by providing a bounding box or a closed polygon

bbox = np.array([0.5,52.5,5,54])
polygon = np.array([[6,51],[0,55],[0,51],[6,51]])
cc.sel(model="SW_1", area=bbox).skill()
n bias rmse urmse mae cc si r2
observation
HKNA 386 -0.19426 0.351964 0.293499 0.251839 0.971194 0.094489 0.905300
c2 42 -0.05587 0.388404 0.384365 0.336023 0.952688 0.145724 0.749645
cc.sel(model="SW_2", area=polygon).plot.scatter();
_images/479a79d4ef0998a0d4d71fb685cfd21d01ece02e6f922001e30b0a0712384360.png

Skill object#

The skill() and mean_skill() methods return a skill object that can visualize results in various ways. The primary methods of the skill object are:

  • style()

  • plot_bar()

  • plot_barh()

  • plot_line()

  • plot_grid()

  • sel()

s = cc.skill()
s.style()
    n bias rmse urmse mae cc si r2
model observation                
SW_1 HKNA 386 -0.194 0.352 0.293 0.252 0.971 0.094 0.905
EPL 66 -0.066 0.225 0.215 0.190 0.970 0.083 0.932
c2 102 -0.025 0.354 0.353 0.297 0.971 0.132 0.889
SW_2 HKNA 386 -0.100 0.293 0.275 0.214 0.971 0.089 0.934
EPL 66 0.001 0.234 0.234 0.200 0.970 0.090 0.927
c2 102 0.051 0.423 0.420 0.353 0.971 0.157 0.842
ERA5 HKNA 386 -0.437 0.545 0.326 0.441 0.975 0.105 0.773
EPL 66 -0.208 0.290 0.202 0.224 0.972 0.078 0.887
c2 102 -0.390 0.487 0.291 0.407 0.973 0.108 0.791
s.style(columns='rmse')
    n bias rmse urmse mae cc si r2
model observation                
SW_1 HKNA 386 -0.194 0.352 0.293 0.252 0.971 0.094 0.905
EPL 66 -0.066 0.225 0.215 0.190 0.970 0.083 0.932
c2 102 -0.025 0.354 0.353 0.297 0.971 0.132 0.889
SW_2 HKNA 386 -0.100 0.293 0.275 0.214 0.971 0.089 0.934
EPL 66 0.001 0.234 0.234 0.200 0.970 0.090 0.927
c2 102 0.051 0.423 0.420 0.353 0.971 0.157 0.842
ERA5 HKNA 386 -0.437 0.545 0.326 0.441 0.975 0.105 0.773
EPL 66 -0.208 0.290 0.202 0.224 0.972 0.078 0.887
c2 102 -0.390 0.487 0.291 0.407 0.973 0.108 0.791
s['rmse'].plot.bar();
s['rmse'].plot.barh();  # horizontal version
_images/cdb2de82e0e2c9bc4cf708857c8c7f11f5351aa1a008fa6a3e47a0d9c64d54e9.png _images/17e3190c82938ab0d7538f5671add4d02f5cebee732d1b59f6c0dca8e264d50e.png
s = cc.skill(by=['model','freq:12H'], metrics=['bias','rmse','si'])
s.style()
    n bias rmse si
model time        
SW_1 2017-10-27 00:00:00 84 -0.096 0.257 0.117
2017-10-27 12:00:00 150 -0.175 0.255 0.092
2017-10-28 00:00:00 76 -0.111 0.179 0.057
2017-10-28 12:00:00 84 -0.037 0.204 0.058
2017-10-29 00:00:00 82 -0.421 0.596 0.089
2017-10-29 12:00:00 78 -0.020 0.417 0.107
SW_2 2017-10-27 00:00:00 84 -0.070 0.236 0.110
2017-10-27 12:00:00 150 -0.157 0.248 0.096
2017-10-28 00:00:00 76 -0.055 0.150 0.056
2017-10-28 12:00:00 84 0.091 0.234 0.063
2017-10-29 00:00:00 82 -0.226 0.477 0.088
2017-10-29 12:00:00 78 0.141 0.455 0.111
ERA5 2017-10-27 00:00:00 84 -0.161 0.205 0.062
2017-10-27 12:00:00 150 -0.303 0.340 0.077
2017-10-28 00:00:00 76 -0.200 0.264 0.069
2017-10-28 12:00:00 84 -0.637 0.679 0.068
2017-10-29 00:00:00 82 -0.699 0.800 0.082
2017-10-29 12:00:00 78 -0.479 0.590 0.089
s['rmse'].plot.line(title='Hm0 rmse [m]');
_images/5d807e2a89b2474c84544a292e3672c3abe2804fb60e485d6f908f2ad9459da1.png
s.plot.grid('si', fmt='0.1%', title='Hm0 Scatter index');
/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages/modelskill/skill.py:279: FutureWarning: Selecting metric in plot functions like modelskill.skill().plot.grid(si) is deprecated and will be removed in a future version. Use modelskill.skill()['si'].plot.grid() instead.
  warnings.warn(
_images/eda0f048779aa7677b5c7d9919f713d00dae19bfba1060e57abf0ff8b7e163e7.png

The sel() method can subset the skill object#

A new skill object will be returned

s = cc.skill()
s.style()
    n bias rmse urmse mae cc si r2
model observation                
SW_1 HKNA 386 -0.194 0.352 0.293 0.252 0.971 0.094 0.905
EPL 66 -0.066 0.225 0.215 0.190 0.970 0.083 0.932
c2 102 -0.025 0.354 0.353 0.297 0.971 0.132 0.889
SW_2 HKNA 386 -0.100 0.293 0.275 0.214 0.971 0.089 0.934
EPL 66 0.001 0.234 0.234 0.200 0.970 0.090 0.927
c2 102 0.051 0.423 0.420 0.353 0.971 0.157 0.842
ERA5 HKNA 386 -0.437 0.545 0.326 0.441 0.975 0.105 0.773
EPL 66 -0.208 0.290 0.202 0.224 0.972 0.078 0.887
c2 102 -0.390 0.487 0.291 0.407 0.973 0.108 0.791
s.sel(model='SW_1').style()
  model n bias rmse urmse mae cc si r2
observation                  
HKNA SW_1 386 -0.194 0.352 0.293 0.252 0.971 0.094 0.905
EPL SW_1 66 -0.066 0.225 0.215 0.190 0.970 0.083 0.932
c2 SW_1 102 -0.025 0.354 0.353 0.297 0.971 0.132 0.889
s.sel(observation='HKNA').style()
  observation n bias rmse urmse mae cc si r2
model                  
SW_1 HKNA 386 -0.194 0.352 0.293 0.252 0.971 0.094 0.905
SW_2 HKNA 386 -0.100 0.293 0.275 0.214 0.971 0.089 0.934
ERA5 HKNA 386 -0.437 0.545 0.326 0.441 0.975 0.105 0.773
s.query('rmse>0.25').style()
    n bias rmse urmse mae cc si r2
model observation                
SW_1 HKNA 386 -0.194 0.352 0.293 0.252 0.971 0.094 0.905
c2 102 -0.025 0.354 0.353 0.297 0.971 0.132 0.889
SW_2 HKNA 386 -0.100 0.293 0.275 0.214 0.971 0.089 0.934
c2 102 0.051 0.423 0.420 0.353 0.971 0.157 0.842
ERA5 HKNA 386 -0.437 0.545 0.326 0.441 0.975 0.105 0.773
EPL 66 -0.208 0.290 0.202 0.224 0.972 0.078 0.887
c2 102 -0.390 0.487 0.291 0.407 0.973 0.108 0.791