Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Benchmarking recipes (Lauer et al.) #3598

Open
wants to merge 68 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
94c826e
quick and dirty implementation of diurnal cycle
axel-lauer Jan 15, 2024
7c7a2e6
added diurnal cycle plot to monitor.py
axel-lauer Jan 16, 2024
8a73469
added diurnal cycle example to model_evaluation/recipe_model_evaluati…
axel-lauer Jan 17, 2024
2daf3a9
added docu examples diurnal cycle
axel-lauer Jan 17, 2024
244d8d8
fixed style issues in monitor/multi_datasets.py
axel-lauer Jan 18, 2024
f45b25d
fixed typo in docu example
axel-lauer Jan 18, 2024
530106e
draft version of first benchmarking recipe (maps)
axel-lauer Jan 25, 2024
c8aa55c
snapshot 2024-02-01
axel-lauer Feb 1, 2024
080b8f5
Merge branch 'main' into diurnal_cycle
diegokam Feb 2, 2024
40d9167
snapshot 2024-02-02
axel-lauer Feb 2, 2024
e707342
first working version
axel-lauer Feb 6, 2024
904b291
fixed some flake8 issues
axel-lauer Feb 6, 2024
a7ab4e4
adding benchmarking boxplot
LisaBock Feb 7, 2024
b9b0a40
Merge branch 'benchmarking_boxplot' into benchmarking_maps4monitoring
LisaBock Feb 7, 2024
b25b9c6
extract plotting function
LisaBock Feb 7, 2024
ec4b1c1
added draft of recipe_model_benchmarking_timeseries.yml
axel-lauer Feb 8, 2024
2438b26
fix filename
LisaBock Feb 8, 2024
83d972e
Merge branch 'benchmarking_maps4monitoring' of github.com:ESMValGroup…
LisaBock Feb 8, 2024
30b8453
boxplots for more variables
LisaBock Feb 9, 2024
b864979
mv recipe
LisaBock Feb 9, 2024
dddc3a5
added zonal mean benchmarking plot
axel-lauer Feb 9, 2024
a8c5e1e
merged with lastest branch
axel-lauer Feb 9, 2024
d154eed
fixed some flake8 issues
axel-lauer Feb 12, 2024
128a77e
updated zonal mean benchmarking recipe
axel-lauer Feb 12, 2024
4ccc12c
addressing review comments
axel-lauer Feb 16, 2024
a99b522
Merge branch 'main' into diurnal_cycle
schlunma Feb 16, 2024
413cb61
clean recipe
LisaBock Feb 21, 2024
ed1e991
add var order and different distance metrics
LisaBock Feb 22, 2024
1241f20
first version of plot benchmarking_timeseries
axel-lauer Mar 4, 2024
dff982e
added benchmarking annual cycle plot
axel-lauer Mar 4, 2024
66a4bc5
added benchmarking diurnal cycle plot
axel-lauer Mar 6, 2024
50e498b
addressed some style issues
axel-lauer Mar 6, 2024
446b4ee
updated benchmarking recipes
axel-lauer Mar 7, 2024
b37e9b3
snapshot 2024-03-07
axel-lauer Mar 7, 2024
9455bf9
updated masking of bias data for benchmarking
axel-lauer Mar 19, 2024
2d92633
bugfix diag_scripts/clouds/clouds.ncl
axel-lauer Mar 20, 2024
ec23f76
remove unit if 1 from boxplots
LisaBock Mar 27, 2024
b3df631
change plotname for boxplots
LisaBock Mar 27, 2024
5001ac9
adjusting the recipes to use an EMAC simulation for benchmarking
hb326 Apr 5, 2024
d7653fc
adjusting so that EMAC can be used as model to be benchmarked
hb326 Apr 5, 2024
da794dc
Merge branch 'benchmarking_maps4monitoring' of github.com:ESMValGroup…
axel-lauer Apr 8, 2024
b616302
adding a preprocessor that filters EMAC's negative temperatures
hb326 Apr 8, 2024
7be6551
update recipe_model_benchmarking_diurnal_cycle.yml
axel-lauer Apr 9, 2024
013926a
Merge branch 'benchmarking_maps4monitoring' of github.com:ESMValGroup…
axel-lauer Apr 9, 2024
99b5d19
updates for EMAC comparison
axel-lauer Apr 11, 2024
d22c0e7
more updates for EMAC comparison
axel-lauer Apr 11, 2024
c71ef02
updates boxplots for EMAC comparison
axel-lauer Apr 13, 2024
e95e9e0
update recipe for boxplots
axel-lauer Apr 18, 2024
118d0a1
added default colorbar for sst
axel-lauer May 2, 2024
ff56640
preparing benchmarking recipes for PR
axel-lauer May 8, 2024
70012f9
added docu draft (no images)
axel-lauer May 8, 2024
ec9a1d9
merged with branch diurnal_cycle
axel-lauer May 8, 2024
69337fe
fixed merging conflicts
axel-lauer May 8, 2024
7f4dbdf
added example plots for benchmarking recipes
axel-lauer May 8, 2024
fc99b85
updated docu
axel-lauer May 15, 2024
d4a75e1
updated recipes
axel-lauer May 15, 2024
f807a0e
fixed some flake8 and pylint issues
axel-lauer May 15, 2024
be0f566
added zorder in _plot_benchmarking_boxplot
axel-lauer May 15, 2024
6592cc1
fixed style issue in cloud.ncl
axel-lauer May 16, 2024
13b5312
updated docu figures
axel-lauer May 16, 2024
6ac3bae
Merge branch 'main' into benchmarking_maps4monitoring
axel-lauer May 16, 2024
ffb8b8e
Update multi_datasets.py
axel-lauer May 28, 2024
d5c5375
Update recipe_benchmarking.rst
axel-lauer May 28, 2024
4723d44
Merge branch 'main' into benchmarking_maps4monitoring
axel-lauer May 29, 2024
eae63ca
Update recipe_benchmarking.rst
axel-lauer May 29, 2024
714b349
Merge branch 'main' into benchmarking_maps4monitoring
alistairsellar Jun 5, 2024
7a3c844
Update recipe_benchmarking.rst
axel-lauer Jun 7, 2024
1fbdf10
Update docu (recipe_benchmarking.rst)
axel-lauer Jun 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/sphinx/source/recipes/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ large variety of input data.
.. toctree::
:maxdepth: 1

recipe_benchmarking
recipe_model_evaluation
recipe_monitor
recipe_psyplot
Expand Down
140 changes: 140 additions & 0 deletions doc/sphinx/source/recipes/recipe_benchmarking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
.. _recipe_benchmarking:

Model Benchmarking
==================

Overview
--------

These recipes and diagnostics are based on :ref:`recipe_monitor <recipe_monitor>`: that allow plotting arbitrary preprocessor output, i.e., arbitrary variables from arbitrary datasets. An extension of these diagnostics is used to benchmark a model simulation with other datasets (e.g. CMIP6). The benchmarking features are described in `Lauer et al.`_:.

.. _`Lauer et al.`: A. Lauer, Bock, L., Hassler, B., Jöckel, P., Ruhe, L., and Schlund, M.: Monitoring and benchmarking Earth System Model simulations with ESMValTool v2.12.0, Geosci. Model Dev. (submitted).

Available recipes and diagnostics
---------------------------------

Recipes are stored in `recipes/model_evaluation`

* recipe_model_benchmarking_annual_cycle.yml
* recipe_model_benchmarking_boxplots.yml
* recipe_model_benchmarking_diurnal_cycle.yml
* recipe_model_benchmarking_maps.yml
* recipe_model_benchmarking_timeseries.yml
* recipe_model_benchmarking_zonal.yml

Diagnostics are stored in `diag_scripts/monitor/`

* :ref:`multi_datasets.py
<api.esmvaltool.diag_scripts.monitor.multi_datasets>`:
Monitoring diagnostic to show multiple datasets in one plot (incl. biases).


Recipe settings
~~~~~~~~~~~~~~~

See :ref:`multi_datasets.py<api.esmvaltool.diag_scripts.monitor.multi_datasets>`: for a list of all possible configuration options that can be specified in the recipe.

.. note::
Please note that exactly one dataset (the dataset to be benchmarked) needs to specify the facet ``benchmark_dataset: True`` in the dataset entry of the recipe. For line plots (i.e. annual cycle, seasonal cycle, diurnal cycle, time series), it is recommended, to specify a particular line color and line style in the ``scripts`` section of the recipe for the dataset to be benchmarked (``benchmark_dataset: True``) so that this dataset is easy to identify in the plot. In the example below, MIROC6 is the dataset to be benchmarked and ERA5 is used as a reference dataset.

.. code-block:: yaml

scripts:
allplots:
script: monitor/multi_datasets.py
plot_folder: '{plot_dir}'
plot_filename: '{plot_type}_{real_name}_{mip}'
group_variables_by: variable_group
facet_used_for_labels: alias
plots:
diurnal_cycle:
annual_mean_kwargs: False
legend_kwargs:
loc: upper right
plot_kwargs:
'MIROC6':
color: red
label: '{alias}'
linestyle: '-'
linewidth: 2
zorder: 4
ERA5:
color: black
label: '{dataset}'
linestyle: '-'
linewidth: 2
zorder: 3
MultiModelPercentile10:
color: gray
label: '{dataset}'
linestyle: '--'
linewidth: 1
zorder: 2
MultiModelPercentile90:
color: gray
label: '{dataset}'
linestyle: '--'
linewidth: 1
zorder: 2
default:
color: lightgray
label: null
linestyle: '-'
linewidth: 1
zorder: 1

Variables
---------

Any, but the variables' number of dimensions should match the ones expected by each plot.

References
----------

* Lauer, A., L. Bock, B. Hassler, P. Jöckel, L. Ruhe, and M. Schlund: Monitoring and benchmarking Earth System Model simulations with ESMValTool v2.12.0, Geosci. Model Dev., xx, xxxx-xxxx,
doi: xxx, 202x.

Example plots
-------------

.. _fig_benchmarking_annual_cycle:
.. figure:: /recipes/figures/benchmarking/annual_cycle.png
:align: center
:width: 16cm

(Left) Multi-year global mean (2000-2004) of the seasonal cycle of near-surface temperature in K from a simulation of MIROC6 and the reference dataset HadCRUT5 (black). The thin gray lines show individual CMIP6 models used for comparison, the dashed gray lines show the 10% and 90% percentiles of these CMIP6 models. (Right) same as (left) but for area-weighted RMSE of near-surface temperature. The light blue shading shows the range of the 10% to 90% percentiles of RMSE values from the ensemble of CMIP6 models used for comparison. Created with recipe_model_benchmarking_annual_cycle.yml.

.. _fig_benchmarking_boxplots:
.. figure:: /recipes/figures/benchmarking/boxplots.png
:align: center
:width: 16cm

(Left) Global area-weighted RMSE (smaller=better), (middle) weighted Pearson’s correlation coefficient (higher=better) and (right) weighted Earth mover’s distance (smaller=better) of the geographical pattern of 5-year means of different variables from a simulation of MIROC6 (red cross) in comparison to the CMIP6 ensemble (boxplot). Reference datasets for calculating the three metrics are: near-surface temperature (tas): HadCRUT5, surface temperature (ts): HadISST, precipitation (pr): GPCP-SG, air pressure at sea level (psl): ERA5, shortwave (rsut) longwave (rlut) radiative fluxes at TOA and shortwave (swcre) and longwave (lwcre) cloud radiative effects: CERES-EBAF. Each box indicates the range from the first quartile to the third quartile, the vertical lines show the median, and the whiskers the minimum and maximum values, excluding the outliers. Outliers are defined as being outside 1.5 times the interquartile range. Created with recipe_model_benchmarking_boxplots.yml.

.. _fig_benchmarking_diurn_cycle:
.. figure:: /recipes/figures/benchmarking/diurnal_cycle.png
:align: center
:width: 10cm

Area-weighted RMSE of the annual mean diurnal cycle (year 2000) of precipitation averaged over the tropical ocean (ocean grid cells in the latitude belt 30°S to 30°N) from a simulation of MIROC6 averaged compared with ERA5 data (black). The light blue shading shows the range of the 10% to 90% percentiles of RMSE values from the ensemble of CMIP6 models used for comparison. Created with recipe_benchmarking_diurnal_cycle.yml.

.. _fig_benchmarking_map:
.. figure:: /recipes/figures/benchmarking/map.png
:align: center
:width: 10cm

5-year annual mean (2000-2004) area-weighted RMSE of the precipitation rate in mm day-1 from a simulation of MIROC6 compared with GPCP-SG data. The stippled areas mask grid cells where the RMSE is smaller than the 90% percentile of RMSE values from an ensemble of CMIP6 models. Created with recipe_model_benchmarking_maps.yml

.. _fig_benchmarking_timeseries:
.. figure:: /recipes/figures/benchmarking/timeseries.png
:align: center
:width: 16cm

(Left) Time series from 2000 through 2014 of global average monthly mean temperature anomalies (reference period 2000-2009) of the near-surface temperature in K from a simulation of MIROC6 (red) and the reference dataset HadCRUT5 (black). The thin gray lines show individual CMIP6 models used for comparison, the dashed gray lines show the 10% and 90% percentiles of these CMIP6 models. (Right) same as (left) but for area-weighted RMSE of the near-surface air temperature. The light blue shading shows the range of the 10% to 90% percentiles of RMSE values from the ensemble of CMIP6 models used for comparison. Created with recipe_model_benchmarking_timeseries.yml.

.. _fig_benchmarking_zonal:
.. figure:: /recipes/figures/benchmarking/zonal.png
:align: center
:width: 10cm

5-year annual mean bias (2000-2004) of the zonally averaged temperature in K from a historical simulation of MIROC6 compared with ERA5 reanalysis data. The stippled areas mask grid cells where the absolute BIAS (${\abs{BIAS}}$) is smaller than the maximum of the absolute 10% (${\abs{p10}}$) and the absolute 90% (${\abs{p90}}$) percentiles from an ensemble of CMIP6 models, i.e. ${\abs{BIAS} \geq max( \abs{p10}, \abs{p90})}$. Created with recipe_model_benchmarking_zonal.yml.
7 changes: 7 additions & 0 deletions doc/sphinx/source/recipes/recipe_model_evaluation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,10 @@ Zonal mean precipitation.
:width: 14cm

Annual cycle of Southern Ocean total cloud cover.

.. _fig_6:
.. figure:: /recipes/figures/model_evaluation/diurnal_cycle_clt_sepacific_3hr.png
:align: center
:width: 14cm

Diurnal cycle of Southeast Pacific total cloud cover.
12 changes: 12 additions & 0 deletions doc/sphinx/source/recipes/recipe_monitor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,18 @@ Timeseries of tas including a reference dataset.

Annual cycle of tas including a reference dataset.

.. _fig_diurnal_cycle:
.. figure:: /recipes/figures/monitor/diurnalcycle_pr_tropics_EC-Earth3_3hr_historical_r1i1p1f1.png
:align: center
:width: 14cm

.. _fig_diurnal_cycle_with_ref:
.. figure:: /recipes/figures/monitor/diurnal_cycle_clt_tropics_3hr.png
:align: center
:width: 14cm

Diurnal cycle of clt including a reference dataset.

.. _fig_map_with_ref:
.. figure:: /recipes/figures/monitor/map_with_ref.png
:align: center
Expand Down
1 change: 1 addition & 0 deletions esmvaltool/config-references.yml
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,7 @@ projects:
crescendo: EU H2020 project CRESCENDO
dlrveu2: DLR project VEU2
dlrveu: DLR project VEU
dlrmabak: DLR project MABAK
embrace: EU FP7 project EMBRACE
esm2025: EU H2020 project ESM2025 - Earth system models for the future
esmval: DLR project ESMVal
Expand Down
7 changes: 6 additions & 1 deletion esmvaltool/diag_scripts/clouds/clouds.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ begin

variables = metadata_att_as_array(variable_info, "short_name")
if (.not. any(variables .eq. var0)) then
errstr = "diagnostic " + diag + " requires the following variable: " + var0
errstr = "diagnostic " + DIAG_SCRIPT \
+ " requires the following variable: " + var0
error_msg("f", DIAG_SCRIPT, "", errstr)
end if

Expand Down Expand Up @@ -539,6 +540,10 @@ begin
res@cnLevels = ispan(0, 60, 5)
end if

if (var0.eq."ts") then
res@cnLevels = ispan(274, 304, 2)
end if

; res@lbLabelBarOn = False
res@gsnRightString = ""

Expand Down
69 changes: 65 additions & 4 deletions esmvaltool/diag_scripts/monitor/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
produce multi panel plots for data with `shape_id` or `region`
coordinates of length > 1. Supported coordinates: `time`, `shape_id`
(optional) and `region` (optional).
- Diurnal cycle (plot type ``diurnal_cycle``): Generate a diurnal cycle
plot (timeseries like climatological from 0 to 24 hours). It will
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
plot (timeseries like climatological from 0 to 24 hours). It will
plot (timeseries like climatological from 0 to 23 hours). It will

I presume it doesn't show the 0 (=24) hour twice, though that would be a valid choice if it did and was documented as such.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact the example diurnal plot in this PR only appears to plot hours 1 to 22 inclusive. Is that a data or code limitation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a data limitation. We wrote 0-24 hours as (if data are available), one could plot 0:00:00 to 23:59:59.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a data limitation.
Does MIROC not provide a full hourly timeseries? Or some days are missing some hours and the diurnal cycle mean requires them all?

We wrote 0-24 hours as (if data are available), one could plot 0:00:00 to 23:59:59.
Thanks, that makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 3-hourly CMIP6 output used in the example provides data at 1.30 (0.00-3.00), 4.30 (3.00-6.00), ..., 22.30 (21.00-24.00). CMIP6 data are converted to full hours by preprocessor resample_hours so that they can be compared to ERA5 data (provided at full hours). The preprocessor distance_metric, applied afterwards requires all datasets to have the same time coordinate.

produce multi panel plots for data with `shape_id` or `region`
coordinates of length > 1. Supported coordinates: `time`, `shape_id`
(optional) and `region` (optional).

Configuration options in recipe
-------------------------------
Expand All @@ -39,10 +44,10 @@
monitor configuration file can be found :ref:`here <monitor_config_file>`.
plots: dict, optional
Plot types plotted by this diagnostic (see list above). Dictionary keys
must be ``clim``, ``seasonclim``, ``monclim``, ``timeseries`` or
``annual_cycle``. Dictionary values are dictionaries used as options for
the corresponding plot. The allowed options for the different plot types
are given below.
must be ``clim``, ``seasonclim``, ``monclim``, ``timeseries``,
``annual_cycle`` or ``diurnal_cycle``. Dictionary values are dictionaries
used as options for the corresponding plot. The allowed options for the
different plot types are given below.
plot_filename: str, optional
Filename pattern for the plots.
Defaults to ``{plot_type}_{real_name}_{dataset}_{mip}_{exp}_{ensemble}``.
Expand Down Expand Up @@ -98,6 +103,10 @@
----------------------------------------------------
None

Configuration options for plot type ``diurnal_cycle``
-----------------------------------------------------
None

.. hint::

Extra arguments given to the recipe are ignored, so it is safe to use yaml
Expand Down Expand Up @@ -166,6 +175,7 @@ def compute(self):

self.timeseries(cube, var_info)
self.plot_annual_cycle(cube, var_info)
self.plot_diurnal_cycle(cube, var_info)
self.plot_monthly_climatology(cube, var_info)
self.plot_seasonal_climatology(cube, var_info)
self.plot_climatology(cube, var_info)
Expand Down Expand Up @@ -280,6 +290,57 @@ def plot_annual_cycle(self, cube, var_info):
caption=caption,
)

def plot_diurnal_cycle(self, cube, var_info):
"""Plot the diurnal cycle according to configuration.

The key 'diurnal_cycle' must be passed to the 'plots' option in the
configuration.

Parameters
----------
cube: iris.cube.Cube
Data to plot. Must be 1D with time or 2D with an extra 'shape_id'
or 'region' coordinate. In that case, the plot will be a multiple
one with one figure for each region
var_info: dict
Variable's metadata from ESMValTool

Warning
-------
The hourly climatology is done inside the function so the users can
plot both the timeseries and the diurnal cycle in one go
"""
if 'diurnal_cycle' not in self.plots:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a curious place to put the control statement, rather than where the method is called, e.g.

if 'diurnal_cycle' in self.plots:
   self.plot_diurnal_cycle(cube, var_info)

However, this seems to be an existing design decision for monitor.py prior to this PR, and arguably outside the scope of my science review, so feel free to ignore this comment,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is right, I just followed the structure that was there already.

return
cube = climate_statistics(cube, period='hour')

plotter = PlotSeries()
plotter.outdir = self.get_plot_folder(var_info)
plotter.img_template = self.get_plot_path('diurnalcycle', var_info,
add_ext=False)
plotter.filefmt = self.cfg['output_file_type']
region_coords = ('shape_id', 'region')
options = {
'xlabel': '',
'xlimits': None,
'suptitle': 'Diurnal cycle',
}
for region_coord in region_coords:
if cube.coords(region_coord):
plotter.multiplot_cube(cube, 'month', region_coord, **options)
return
plotter.plot_cube(cube, 'hour', **options)
caption = (f"Diurnal cycle of {var_info[n.LONG_NAME]} of "
f"dataset {var_info[n.DATASET]} (project "
f"{var_info[n.PROJECT]}) from {var_info[n.START_YEAR]} to "
f"{var_info[n.END_YEAR]}.")
self.record_plot_provenance(
self.get_plot_path('diurnalcycle', var_info),
var_info,
'Diurnal cycle',
caption=caption,
)

def plot_monthly_climatology(self, cube, var_info):
"""Plot the monthly climatology as a multipanel plot.

Expand Down
Loading