@@ -17,18 +17,21 @@ keypoints:
1717- " Use `datasets_to_recipe` helper to start making recipes"
1818---
1919
20- In this episode we will introduce the ESMValCore API in a jupyter notebook. This is reformatted from material from
21- this [ blog post] ( https://blog.esciencecenter.nl/easy-ipcc-powered-by-esmvalcore-19a0b6366ea7 ) {: target ="_ blank"}
22- by Peter Kalverla. There's also material from the [ example notebooks] [ docs-notebooks ] {: target ="_ blank"} and the
20+ In this episode we will introduce the ESMValCore API in a jupyter notebook. This is reformatted
21+ from material from this [ blog post] [ easy-ipcc-blog ] {: target ="_ blank"}
22+ by Peter Kalverla. There's also material from the
23+ [ example notebooks] [ docs-notebooks ] {: target ="_ blank"} and the
2324[ API reference documentation] [ api-reference ] {: target ="_ blank"}.
2425
2526## Start JupyterLab
26- A [ jupyter notebook] ( https://jupyter.org/ ) {: target ="_ blank"} is an interactive document where you can run code.
27+ A [ jupyter notebook] ( https://jupyter.org/ ) {: target ="_ blank"} is an interactive document where
28+ you can run code.
2729You will need to use a python environment with ESMValTool and ESMValCore installed.
2830
2931## Find Datasets with facets
30- We have seen from running available recipes that ESMValTool is able to find data from facets that were given in
31- the recipe. We can use this in a Notebook, including filling out the facets for data definition.
32+ We have seen from running available recipes that ESMValTool is able to find data from facets that
33+ were given in the recipe. We can use this in a Notebook, including filling out the facets for
34+ data definition.
3235To do this we will use the ` Dataset ` object from the API. Let's look at this example.
3336
3437``` python
@@ -47,7 +50,8 @@ dataset.augment_facets()
4750print (dataset)
4851```
4952> ## Pro tip: Augmented facets in the output
50- > When running a recipe there is a ` _filled ` recipe ` yml ` file in the output ` /run ` folder which augments the facets.
53+ > When running a recipe there is a ` _filled ` recipe ` yml ` file in the output ` /run ` folder which
54+ > augments the facets.
5155> > ## Example recipe output folder
5256> > ``` output
5357> > esmvaltool_output/flato13ipcc_figure914_20240729_043707/run
@@ -63,7 +67,8 @@ print(dataset)
6367{: .callout}
6468
6569> ## Search available
66- > Search from files available locally with wildcard functionality `'*'` to get all the available datasets.
70+ > Search from files available locally with wildcard functionality `'*'` to get all the available
71+ > datasets.
6772> - How can you search for all available ensembles?
6873>
6974> > ## Solution
@@ -84,8 +89,8 @@ print(dataset)
8489> > ```
8590> {: .solution}
8691> There is also the ability to search on ESGF nodes and download. See
87- > [reference](https://docs.esmvaltool.org/projects/ESMValCore/en/latest/ api/esmvalcore. esgf.html) {:target="_blank"}
88- > for more details. Check the configuration settings for this.
92+ > [reference][ api- esgf] {:target="_blank"} for more details.
93+ > Check the configuration settings for this.
8994>```python
9095>from esmvalcore.config import CFG
9196>CFG['search_esgf'] = 'always'
@@ -253,7 +258,7 @@ print(da)
253258The output from the preprocessor functions are Iris cubes.
254259[ Iris] ( https://scitools-iris.readthedocs.io/en/latest/index.html ) {: target ="_ blank"}
255260has wrappers for [ matplotlib] ( https://matplotlib.org/ ) {: target ="_ blank"} to [ plot the processed
256- cubes] ( https://scitools- iris.readthedocs.io/en/latest/userguide/plotting_a_cube.html#iris-cube-plotting ) {: target ="_ blank"}.
261+ cubes] [ iris-plot ] {: target ="_ blank"}.
257262This is useful in a notebook to help develop your recipe with the esmvalcore preprocessors.
258263``` python
259264from iris import quickplot
@@ -270,70 +275,71 @@ quickplot.plot(cube)
270275> - Apply the preprocessors to each dataset and plot the result
271276>
272277> > ## Solution
273- > > ``` python
274- > > import cf_units
275- > > import matplotlib.pyplot as plt
276- > > from iris import quickplot
277- > >
278- > > from esmvalcore.config import CFG
279- > > from esmvalcore.dataset import Dataset
280- > > from esmvalcore.preprocessor import annual_statistics, anomalies, area_statistics
281- > >
282- > >
283- > > # Settings for automatic ESGF search
284- > > CFG [' search_esgf' ] = ' when_missing'
285- > >
286- > > # Declare common dataset facets
287- > > template = Dataset(
288- > > short_name = ' tos' ,
289- > > mip = ' Omon' ,
290- > > project = ' CMIP6' ,
291- > > exp = ' *' , # We'll fill this below
292- > > dataset = ' *' , # We'll fill this below
293- > > ensemble = ' r4i1p1f1' ,
294- > > grid = ' gn' ,
295- > > )
296- > >
297- > > # Substitute data sources and experiments
298- > > datasets = []
299- > > for dataset_id in [" CESM2" , " MPI-ESM1-2-LR" , " ACCESS-ESM1-5" ]:
300- > > for experiment_id in [' ssp126' , ' ssp585' ]:
301- > > dataset = template.copy(dataset = dataset_id, exp = [' historical' , experiment_id])
302- > > dataset.add_supplementary(short_name = ' areacello' , mip = ' Ofx' , exp = ' historical' )
303- > > dataset.augment_facets()
304- > > datasets.append(dataset)
305- > >
306- > > # Set the reference period for anomalies
307- > > reference_period = {
308- > > " start_year" : 1950 , " start_month" : 1 , " start_day" : 1 ,
309- > > " end_year" : 1979 , " end_month" : 12 , " end_day" : 31 ,
310- > > }
311- > >
312- > > # (Down)load, pre-process, and plot the cubes
313- > > for dataset in datasets:
314- > > cube = dataset.load()
315- > > cube = area_statistics(cube, operator = ' mean' )
316- > > cube = anomalies(cube, reference = reference_period, period = ' month' ) # notice 'month'
317- > > cube = annual_statistics(cube, operator = ' mean' )
318- > > cube.convert_units(' degrees_C' )
319- > >
320- > > # Make sure all datasets use the same calendar for plotting
321- > > tcoord = cube.coord(' time' )
322- > > tcoord.units = cf_units.Unit(tcoord.units.origin, calendar = ' gregorian' )
323- > >
324- > > # Plot
325- > > quickplot.plot(cube, label = f " { dataset[' dataset' ]} - { dataset[' exp' ]} " )
326- > >
327- > > # Show the plot
328- > > plt.legend()
329- > > plt.show()
330- > > ```
278+ > > ``` python
279+ > > import cf_units
280+ > > import matplotlib.pyplot as plt
281+ > > from iris import quickplot
282+ > >
283+ > > from esmvalcore.config import CFG
284+ > > from esmvalcore.dataset import Dataset
285+ > > from esmvalcore.preprocessor import annual_statistics, anomalies, area_statistics
286+ > >
287+ > >
288+ > > # Settings for automatic ESGF search
289+ > > CFG [' search_esgf' ] = ' when_missing'
290+ > >
291+ > > # Declare common dataset facets
292+ > > template = Dataset(
293+ > > short_name = ' tos' ,
294+ > > mip = ' Omon' ,
295+ > > project = ' CMIP6' ,
296+ > > exp = ' *' , # We'll fill this below
297+ > > dataset = ' *' , # We'll fill this below
298+ > > ensemble = ' r4i1p1f1' ,
299+ > > grid = ' gn' ,
300+ > > )
301+ > >
302+ > > # Substitute data sources and experiments
303+ > > datasets = []
304+ > > for dataset_id in [" CESM2" , " MPI-ESM1-2-LR" , " ACCESS-ESM1-5" ]:
305+ > > for experiment_id in [' ssp126' , ' ssp585' ]:
306+ > > dataset = template.copy(dataset = dataset_id, exp = [' historical' , experiment_id])
307+ > > dataset.add_supplementary(short_name = ' areacello' , mip = ' Ofx' , exp = ' historical' )
308+ > > dataset.augment_facets()
309+ > > datasets.append(dataset)
310+ > >
311+ > > # Set the reference period for anomalies
312+ > > reference_period = {
313+ > > " start_year" : 1950 , " start_month" : 1 , " start_day" : 1 ,
314+ > > " end_year" : 1979 , " end_month" : 12 , " end_day" : 31 ,
315+ > > }
316+ > >
317+ > > # (Down)load, pre-process, and plot the cubes
318+ > > for dataset in datasets:
319+ > > cube = dataset.load()
320+ > > cube = area_statistics(cube, operator = ' mean' )
321+ > > cube = anomalies(cube, reference = reference_period, period = ' month' ) # notice 'month'
322+ > > cube = annual_statistics(cube, operator = ' mean' )
323+ > > cube.convert_units(' degrees_C' )
324+ > >
325+ > > # Make sure all datasets use the same calendar for plotting
326+ > > tcoord = cube.coord(' time' )
327+ > > tcoord.units = cf_units.Unit(tcoord.units.origin, calendar = ' gregorian' )
328+ > >
329+ > > # Plot
330+ > > quickplot.plot(cube, label = f " { dataset[' dataset' ]} - { dataset[' exp' ]} " )
331+ > >
332+ > > # Show the plot
333+ > > plt.legend()
334+ > > plt.show()
335+ > > ```
331336> {: .solution}
332337{: .challenge}
333338
334339> # # Pro tip: Convert to recipe
335- > We can use the helper to start making the recipe. A recipe can be used for reproducibility of an
336- > analysis. This list the datasets in a recipe format and we would then have to create the preprocessors
340+ > We can use the helper to start making the recipe. A recipe can be used for
341+ > reproducibility of an analysis. This list the datasets in a
342+ > recipe format and we would then have to create the preprocessors
337343> and diagnostic script.
338344> ```python
339345> from esmvalcore.dataset import datasets_to_recipe
@@ -418,87 +424,87 @@ quickplot.plot(cube)
418424>
419425> > # # 1. Define datasets:
420426> >
421- > > ```python
422- > > from esmvalcore.dataset import Dataset
423- > > obs = Dataset(
424- > > short_name = ' siconc' , mip = ' SImon' , project = ' OBS6' , type = ' reanaly' ,
425- > > dataset = ' NSIDC-G02202-sh' , tier = ' 3' , version = ' 4' , timerange = ' 1979/2018' ,
426- > > )
427- > > # Add areacello as supplementary dataset
428- > > obs.add_supplementary(short_name = ' areacello' , mip = ' Ofx' )
429- > >
430- > > model = Dataset(
431- > > short_name = ' siconc' , mip = ' SImon' , project = ' CMIP6' , activity = ' CMIP' ,
432- > > dataset = ' ACCESS-ESM1-5' , ensemble = ' r1i1p1f1' , grid = ' gn' , exp = ' historical' ,
433- > > timerange = ' 1960/2010' , institute = ' *' ,
434- > > )
435- > >
436- > > om_facets= {' dataset' :' ACCESS-OM2' , ' exp' :' omip2' , ' activity' :' OMIP' , ' timerange' :' 0306/0366' }
437- > >
438- > > model.add_supplementary(short_name = ' areacello' , mip = ' Ofx' )
439- > >
440- > > model_om = model.copy(** om_facets)
441- > > ```
427+ > > ```python
428+ > > from esmvalcore.dataset import Dataset
429+ > > obs = Dataset(
430+ > > short_name = ' siconc' , mip = ' SImon' , project = ' OBS6' , type = ' reanaly' ,
431+ > > dataset = ' NSIDC-G02202-sh' , tier = ' 3' , version = ' 4' , timerange = ' 1979/2018' ,
432+ > > )
433+ > > # Add areacello as supplementary dataset
434+ > > obs.add_supplementary(short_name = ' areacello' , mip = ' Ofx' )
435+ > >
436+ > > model = Dataset(
437+ > > short_name = ' siconc' , mip = ' SImon' , project = ' CMIP6' , activity = ' CMIP' ,
438+ > > dataset = ' ACCESS-ESM1-5' , ensemble = ' r1i1p1f1' , grid = ' gn' , exp = ' historical' ,
439+ > > timerange = ' 1960/2010' , institute = ' *' ,
440+ > > )
441+ > >
442+ > > om_facets= {' dataset' :' ACCESS-OM2' , ' exp' :' omip2' , ' activity' :' OMIP' , ' timerange' :' 0306/0366' }
443+ > >
444+ > > model.add_supplementary(short_name = ' areacello' , mip = ' Ofx' )
445+ > >
446+ > > model_om = model.copy(** om_facets)
447+ > > ```
442448> {: .solution}
443449> > # # Tip: Check dataset files can be found
444450> > The observational dataset used is a Tier 3 , so with some licensing restrictions.
445451> > Check files can be found for all the datasets:
446452> >
447- > > ```python
448- > > for ds in [model, model_om, obs]:
449- > > print (ds[' dataset' ],' : ' ,ds.files)
450- > > print (ds.supplementaries[0 ].files)
451- > > ```
452- > > You can try to find and add another observational dataset. eg:
453- > > ```python
454- > > obs_other = Dataset(
455- > > short_name = ' siconc' , mip = ' *' , project = ' OBS' , type = ' *' ,
456- > > dataset = ' *' , tier = ' *' , timerange = ' 1979/2018'
457- > > )
458- > > obs_other.files
459- > > ```
453+ > > ```python
454+ > > for ds in [model, model_om, obs]:
455+ > > print (ds[' dataset' ],' : ' ,ds.files)
456+ > > print (ds.supplementaries[0 ].files)
457+ > > ```
458+ > > You can try to find and add another observational dataset. eg:
459+ > > ```python
460+ > > obs_other = Dataset(
461+ > > short_name = ' siconc' , mip = ' *' , project = ' OBS' , type = ' *' ,
462+ > > dataset = ' *' , tier = ' *' , timerange = ' 1979/2018'
463+ > > )
464+ > > obs_other.files
465+ > > ```
460466> {: .solution}
461467> > # # 2. Use esmvalcore API preprocessors on the datasets and plot results
462468> >
463- > > ```python
464- > > import iris
465- > > import matplotlib.pyplot as plt
466- > > from iris import quickplot
467- > > from esmvalcore.preprocessor import (
468- > > mask_outside_range,
469- > > extract_region,
470- > > area_statistics,
471- > > annual_statistics
472- > > )
473- > > # model_om - at index 1 to offset years
469+ > > ```python
470+ > > import iris
471+ > > import matplotlib.pyplot as plt
472+ > > from iris import quickplot
473+ > > from esmvalcore.preprocessor import (
474+ > > mask_outside_range,
475+ > > extract_region,
476+ > > area_statistics,
477+ > > annual_statistics
478+ > > )
479+ > > # model_om - at index 1 to offset years
480+ >
481+ > > load_data = [model, model_om, obs]
474482> >
475- > > load_data = [model, model_om, obs]
476- > >
477- > > # function to use for both min and max ['max','min']
478- > >
479- > > def trends_seaicearea(min_max):
480- > > plt.clf()
481- > > for i,data in enumerate (load_data):
482- > > cube = data.load()
483- > > cube = mask_outside_range(cube, 15 , 100 )
484- > > cube = extract_region(cube,0 ,360 ,- 90 ,0 )
485- > > cube = area_statistics(cube, ' sum' )
486- > > cube = annual_statistics(cube, min_max)
487- > >
488- > > iris.util.promote_aux_coord_to_dim_coord(cube, ' year' )
489- > > cube.convert_units(' km2' )
490- > > if i == 1 : # # om years 306/366 apply offset
491- > > cube.coord(' year' ).points = [y + 1652 for y in cube.coord(' year' ).points]
492- > > label_name = data[' dataset' ]
493- > > print (label_name, cube.shape)
494- > > quickplot.plot(cube, label = label_name)
495- > >
496- > > plt.title(f ' Trends in Sea-Ice { min_max.title()} ima ' )
497- > > plt.ylabel(' Sea-Ice Area (km2)' )
498- > > plt.legend()
499- > >
500- > > trends_seaicearea(' min' )
501- > > ```
483+ > > # function to use for both min and max ['max','min']
484+ > >
485+ > > def trends_seaicearea(min_max):
486+ > > plt.clf()
487+ > > for i,data in enumerate (load_data):
488+ > > cube = data.load()
489+ > > cube = mask_outside_range(cube, 15 , 100 )
490+ > > cube = extract_region(cube,0 ,360 ,- 90 ,0 )
491+ > > cube = area_statistics(cube, ' sum' )
492+ > > cube = annual_statistics(cube, min_max)
493+ > >
494+ > > iris.util.promote_aux_coord_to_dim_coord(cube, ' year' )
495+ > > cube.convert_units(' km2' )
496+ > > if i == 1 : # # om years 306/366 apply offset
497+ > > cube.coord(' year' ).points = [y + 1652 for y in cube.coord(' year' ).points]
498+ > > label_name = data[' dataset' ]
499+ > > print (label_name, cube.shape)
500+ > > quickplot.plot(cube, label = label_name)
501+ > >
502+ > > plt.title(f ' Trends in Sea-Ice { min_max.title()} ima ' )
503+ > > plt.ylabel(' Sea-Ice Area (km2)' )
504+ > > plt.legend()
505+ > >
506+ > > trends_seaicearea(' min' )
507+ > > ```
502508> {: .solution}
503509{: .challenge}
504510
0 commit comments