Part 5 - Global warming levels (ii)
===================================
Prepared by *Mathias Hauser*.
In the fifth part, we will continue our work on global warming levels.
Instead of comparing the pattern of two models at the same
warming level, we want to compare the average over all available models for two different
warming levels. At the end of this part we will have a figure
that is very similar to one from Chapter 11 of the sixth (i.e. most recent) Assessment
Report of Working Group I of the Intergovernmental Panel on Climate Change
(IPCC AR6 WG1) --- `Figure 11.11 `__.
Loading 20 (or so) models manually
would be quite tedious. We will therefore introduce (or repeat) some
python constructions that will hopefully make our lives easier.
The first problem we will tackle is how to handle the 20+ models in
python. We could store each model in a separate variable
(``tas_model1 =``, ``tas_model2 =``, etc.), but this would become
annoying very quickly. There are many alternative ways to do this -
we'll see one of them. What we need is some sort of "container" where we can
can store the data from more than one model. There are several options,
we could
- combine all models into a single ``DataArray`` (or ``Dataset``) as for the global mean temperatures
- use a tuple or a list,
- a dictionary, or
- something else.
There is no right solution - each of them would work and has different
advantages and disadvanges. Here, we will go with dictionaries which are
described below.
Then we also need a way to dynamically construct the filename and should
be able to "insert" the model name into a template. We will use
f-strings for this.
.. admonition:: Learning goals
:class: important
- **programming goals**
- get to know dictionaries and f-strings
- using some new functions and methods
- how to expand the analysis from single models to many models
- **scientific and data analysis goals**
- assess TXx at different global warming levels
- see how spatial patterns are smoothed when averaging across multiple climate
models
Preparation
-----------
Create a new notebook with the name `p5_warming_level_comparison_name.ipynb`.
Dictionaries
------------
Dictionaries are a native python data container that store data as
*key*-*value* pairs. In the following example the ``"a"`` is the `key`
and ``5`` the `value`:
.. code:: python
d = dict(a=5)
The key needs to be a string while the value can be any python object.
We will use this to work with data from different models - the name of
the model (e.g., ``"ACCESS-CM2"``) will be the *key* while its data (an
xarray ``DataArray`` or ``Dataset``) will be the *value*. Passing the
*key* to the dictionary in square brackets, e.g. ``d["a"]`` will then return the *value*.
#. Create a new dictionary named ``d`` (as above). Set ``"a"`` to 5 and
``"b"`` to 7.
#. You can get the value from a dictionary using square brackets:
``d["a"]``. Read back the value of ``"b"``.
#. What happens if you pass a key that does not exist in the dictionary,
e.g. ``d["c"]``?
#. There is an alternative way to get a key from a dictionary - using
``d.get("b")``. What happens if you pass a key that does not exist in the dictionary
to ``get``, e.g., ``d.get("c")``?
#. We can assign new values to an existing dictionary using
``d["d"] = 9``. Assign ``11`` to ``"e"``.
#. What happens if you do ``d["a"] = 7``? Is the old value overwritten?
We have now learned how to get and assigned values from a dictionary.
Before moving on we need to see how we can loop through a dictionary.
There are three possibilities - per default it loops over the *keys*,
but we can also loop through the *values* or both:
.. code:: python
for key in d:
print(f"{key=}")
for value in d.values():
print(f"{value=}")
for key, value in d.items():
print(f"{key=}, {value=}")
#. Copy the three for loops and execute them - do they do what you
expect?
There are two ways to create dictionaries. Either with curly braces (``d = {"a": 5}``)
or using ``d = dict(a=5)``. They result in the *same* dictionary.
f-strings
---------
To open the files with the data we need to pass the filename. Until now
we constructed the filename manually, e.g.:
.. code:: python
file1 = "tasmax_ann_ACCESS-CM2_historical_r1i1p1f1_g025.nc"
file2 = "tasmax_ann_GFDL-ESM4_historical_r1i1p1f1_g025.nc"
However, (i) this becomes cumbersome fast and (ii) the only part that is
different is the name of the model. Wouldn't it be nice if we can
somehow substitute the model name in the string? And indeed this is
possible with so-called f-strings, which are strings where an "f" is
added *before* the quotes: ``f""`` and the "f" stands for *format*. In
an f-string everything in curly braces (and including the braces) is
substituted by a variable of the same name. Thus, the following example will print
``'tas'``:
.. code:: python
var = "tas"
print(f"{var}")
Or for our case we can do:
.. code:: python
model = "ACCESS-CM2"
f"tasmax_ann_{model}_historical_r1i1p1f1_g025.nc"
where ``"{model}"`` would now be replaced by ``"ACCESS-CM2"`` (test this
by copying it to a code cell).
#. Test this by copying the two lines to your notebook.
Read global mean temperature
----------------------------
After these excursions let's dive in to the analysis.
#. Add a new markdown title e.g. ``# Analysis``.
#. Open the netCDF file with annual mean global mean temperatures you
saved in Part 2 ("Convert to a Dastaset").
#. Compute anomalies w.r.t. 1850 -- 1900 and select the variable tas.
Load tasmax data
----------------
Now we are finally ready to load the tasmax data of all the models. We
want to load the netCDF files in a for loop over all model names, create
the filename using a f-string and store it in a dictionary.
Create filename dynamically
~~~~~~~~~~~~~~~~~~~~~~~~~~~
#. Start with the filename and adapt it such that it shows `"EC-Earth3"` instead of
`"MIROC6"` using an f-string:
.. code:: python
model = "EC-Earth3"
filename = "../data/cmip6/tasmax/txx/txx_tasmax_day_MIROC6_historical-ssp585_r1i1p1f1_g025.nc
print(filename)
#. Create the new filename in a loop:
- create a loop over ``tas.models.values``
- Copy the adapted "filename" from above and print the result.
Load data
~~~~~~~~~
To load the data we need to we need to have the following structure:
.. code :: python
# create dictionary to store the data
tasmax_dict = dict()
# loop over models
for model in tas.models.values:
# create filename
filename = ...
# read netcdf
ds = xr.open_dataset(filename)
# get the DataArray from the Dataset
da = ds.tasmax
# calculate the anomaly
da = ...
# assign to dict
tasmax_dict[model] = da
#. Update the code above to load and process all models.
Test the dictionary
-------------------
#. Read the model `"EC-Earth3"` from the dict.
#. Select the first timestep and plot it.
#. Create a weighted or unweighted mean (over whole time period) and
plot the resulting time series.
Calculate tasmax at the 1.5 °C warming level
--------------------------------------------
The next step is to calculate tasmax for all models for a certain
warming level. Again we will loop through all models, calculate the
warming level period and select the respective years from the tasmax
data and average over them. Thus adapt the following code:
.. code :: python
tasmax_at_15_dict = dict()
for model in tas.models.values:
# select global mean temperature for the model
tas_ = ...
# select TXx for the model
tasmax = ...
# compute the warming level period
period = ...
# average over the warming level period
tasmax = tasmax.sel(year=period).mean("year")
# assign to dict
tasmax_at_15_dict[model] = tasmax
.. tip ::
``tas`` is a ``DataArray`` while ``tasmax_dict`` is a dictionary so you need to select
the model differently.
Concatenate all models
----------------------
Now we have one entry for each model in the ``tasmax_at_15_dict``. The dictionary was
helpful to read and process each individual model. However, we now want to average over
all the models, for this it's better to have all models in the same ``xr.DataArray``
(as ``tas``). Therefore we concatenate all models into one DataArray:
#. Using :py:func:`xr.concat` concatenate all models into a single ``DataArray``
over a new dimension named ``"models"``. Because ``tasmax_at_15_dict`` is a dict you
have to pass the ``.values()`` to the function.
#. Use
.. code :: python
tasmax_at_15 = tasmax_at_15.assign_coords(models=list(tasmax_at_15_dict.keys()))
to assign the model names as coordinates.
Plot
----
#. Calculate the mean over the ``"models"`` dimension, and create a plot.
Check the function
------------------
#. There is a function
.. code:: python
def at_warming_level(tas, index, warming_level):
...
in *computation.py*. Have a look at the it to see how it compares with yours.
Comparison
----------
#. Calculate ``tasmax_at_15`` again using the function.
#. Calculate the mean over the ``"models"`` dimension, and create a plot.
#. Make sure the plot shows the same as the one above.
Calculate tasmax at the 2.0 °C warming level
--------------------------------------------
#. Calculate ``tasmax_at_20``, i.e. for a global warming level of 2.0 °C
using the new function.
Create a plot of the two warming levels
---------------------------------------
#. Prepare a map plot with two subplots. Choose a projection of your
liking. Add coastlines.
- You can copy the code from Part 4 but be sure to adapt everything
that is necessary.
#. Plot the mean over the models for ``tasmax_at_15`` and ``tasmax_at_20``.
#. Make sure the two variables show the same color scale.
#. Add a the name of the model as title.
Compare your figure to the one from the IPCC: `Figure 11.11 `__.
This concludes Part 5.