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 levels, 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.

Learning goals

  • programming goals

    • get to know dictionaries and f-strings

    • using some new functions and methods

    • how to expand the analyis from single models to many models

  • scientific and data analyis goals

    • assess TXx at different global warming levels

    • see how spatial patterns are smoothed out when averaging over several 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:

d = {"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.

  1. Create a new dictionary named d (as above). Set "a" to 5 and "b" to 7.

  2. You can get the value from a dictionary using square brackets: d["a"]. Read back the value of "b".

  3. What happens if you pass a key that does not exist in the dictionary, e.g. d["c"]?

  4. There is an alternative way to get a key from a dictionary - using d.get("c"). What happens if you pass a key that does not exist in the dictionary to get, e.g., d.get("c")?

  5. How can we adjust d.get(key) such that it returns 0 if key is not in d? Read the docstring of d.get? to find out.

  6. We can assign new values to an existing dictionary using d["d"] = 9. Assign 11 to "e".

  7. 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:

    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=}")
    
  8. 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. However, if you want to use a key like "a b" (with space) or "ACCESS-CM2" (with a dash) you need to use the constructor with curly braces.

f-strings

To open the files with the data we need to pass the filename. Until now we constructed the filename manually, e.g.:

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':

var = "tas"
print(f"{var}")

Or for our case we can do:

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).

  1. Test this by copying the two lines to your notebook.

More on f-strings (optional)

f-strings can more than substitute values in a string - they also allow to customize the format, you can round floating point numbers (e.g. 3.1415 -> "3.14") or pad integers with zeros (1 -> "01"), etc.. This is done with a format specifier, e.g. f"{pi:0.2f}" where the format specifier starts after the : and this specific example rounds pi to two decimal places.

  1. Copy the following lines to a code cell and execute it so the variables are defined.

    model = "model1"
    pi = 3.1415
    i = 1
    
  2. Run the following examples and try to understand what each line does:

    print(f"{i}")
    print(f"{i:02d}")
    print(f"{i:03d}")
    print(f"{i: 2d}")
    print(f"{pi}")
    print(f"{pi:0.2f}")
    print(f"{pi:0.3f}")
    print(f"{pi:8.2f}")
    print(f"{10 * pi:0.2f}")
    print(f"{pi=}")
    print(f"{model = }")
    

    Tip

    The d in the format specifier stands for decimal and can be used to format integers while f stands for float.

There are several ways to create and concatenate strings in python. See the examples below for some variants that came to my mind. I often see variant (a) in code while I think variant (d) is much easier to write and understand - which is why we look at f-strings here! See also more examples at realpython/f-strings.

var = "tasmax"
time = "ann"
model = "model1"
scen = "historical"
num = 1
res = "g025"

a = var + "_" + time + "_" + model + "_" + scen + "_" + str(num) + res + ".nc"

b = "%s_%s_%s_%s_%d_%s.nc" % (var, time, model, scen, num, res)

c = "{}_{}_{}_{}_{}_{}.nc".format(var, time, model, scen, num, res)

d = f"{var}_{time}_{model}_{scen}_{num}_{res}.nc"

print(a)
print(b)
print(c)
print(d)
  1. Run the code above and confirm they all lead to the same result.

  2. Which do you find most readable? (Hopefully the f-string…)

Read global mean temperature

After these excursions let’s dive in to the analysis.

  1. Add a new markdown title e.g. # Analysis.

  2. Open the netCDF file with annual mean global mean temperatures you saved in Part 2 (“Convert to a Dastaset”).

  3. 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.

  1. Find the filename of the tasmax data you opened in Part 4.

  2. Create a new code cell

    • create an empty dictionary and name it tasmax (before the loop).

    • create a loop over tas.models.values

    • In the loop: adapt the “filename” and substitute the model name using an f-string.

    • Print the resulting filename.

  3. If the resulting filename is correct, replace the print function and open the netCDF file using xarray and assign it to the variable ds.

  4. Select the variable tasmax from ds.

  5. Calculate the anomalies of tasmax w.r.t 1850 - 1900 (in a new loop).

  6. Assign the anomalies to the tasmax dict use the model name as key.

Test the dictionary

  1. Read the model EC-Earth3 from the dict.

  2. Select the first timestep and plot it.

  3. 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.

  1. Create a new code cell

    • create an empty dict and name it _tasmax_at_15 (before of the loop).

    • create a loop over tas.models.values

  2. In the loop select the model from tas (name it _tas) and from tasmax (name it _tasmax). Remember that tas is a DataArray while tasmax is a dictionary so you need to select them differently.

  3. Using _tas compute the warming_level_period for a warming level of 1.5 °C, and name it period.

  4. Select the period from _tasmax and calculate the mean over the years.

  5. Assign the mean over the selected 20-years to the _tasmax_at_15 dict, use the model name as key.

Concatenate all models

Now we have one entry for each model in the _tasmax_at_15 dict. Next we want to concatenate all models into one DataArray.

  1. Concatenate all models to a single xarray Dataset using xr.concat over a new dimension named "models", naming the result tasmax_at_15. Because _tasmax_at_15 is a dict you have to pass the .values().

  2. Optional: Use tasmax_at_15 = tasmax_at_15.assign_coords(models=list(_tasmax_at_15.keys())) to assign the model names as coordinates.

Plot

  1. Calculate the mean over the "models" dimension, and create a plot (no need to do a map).

Create a function

  1. Create a function from the above steps. The function should have the following signature:

def at_warming_level(tas, index, warming_level):
    ...

where tas is the DataArray of global mean temperatures and index would the dict with the gridded data, here tasmax. 1. Copy the code from above and add it to the function. However, don’t calculate the mean over the models in the function - this should happen outside of it. 1. Rename the variables as necessary (e.g. tasmax -> index).

Comparison

  1. Calculate tasmax_15 again using the new function.

  2. Calculate the mean over the "models" dimension, and create a plot (no need to do a map).

  3. Make sure the plot shows the same as the one above.

  4. Optional: copy the function to computation.py.

Calculate tasmax at the 2.0 °C warming level

  1. Calculate tasmax_20, i.e. for a global warming level of 2.0 °C using the new function.

Create a plot of the two warming levels

  1. 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.

  2. Plot the mean over the models for tasmax_15 and tasmax_20.

  3. Make sure the two variables show the same color scale.

  4. 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.