logo

2.7. Global Warming Levels#

Time periods for which +1.5, +2, +3 and +4 degree Transient Global Warming Levels (GWLs) are reached, relative the pre-industrial 1850-1900 mean value, are precomputed in the C3S Atlas for CMIP5 and CMIP6 using a 20-year moving window, building on the datasets available in the C3S Climate Data Store.

The approach is similar to that described by, e.g., Nikulin et al. (2018) and Diez-Sierra et al. (2023). The values provided in the GWL tables in this directory CMIP5 Warming Levels and CMIP6 Warming Levels correspond to the 20-year window where the warming is first reached (the GWL period is thus calculated as [n, n+19]). Cells with ‘NA’ indicate that the GWL was not reached before (the initial year) 2100. Cells with ‘9999’ correspond to models with no available data for the particular scenario.

This notebook is a collection of recipes for calculating the GWL tables used in the “C3S Atlas”. For quick demonstration purposes, GWLs are calculated for a single member of CMIP6.

Please be advised that downloading the required data for this script may take several minutes.

2.7.1. Load Python packages and clone and install the c3s-atlas GitHub repository from the ecmwf-projects#

Clone (git clone) the C3S-atlas repository and install them (pip install -e .).

Further details on how to clone and install the repository are available in the requirements

import os
import sys
import numpy as np
import pandas as pd
import xarray as xr
import matplotlib as mp
import matplotlib.pyplot as plt
from matplotlib.pyplot import cm
import glob
from pathlib import Path
import cdsapi

from c3s_atlas.utils import (
    extract_zip_and_delete,
)

2.7.2. Define some specific functions#

## Unitary test for the rolling mean function implemented in xarray
da = xr.DataArray(
    np.linspace(0, 11, num=12),
    coords=[
        pd.date_range("1999-12-15",periods=12, freq=pd.DateOffset(months=1),)
    ],
    dims="time",
)
da_rolling = da.rolling(time=3, center=False, min_periods=1).mean()
msg = "The definition of the rolling window boundaries implemented in xarray have changed"
assert da_rolling[-1].data == 10, msg
def GSAT(ds: xr.Dataset) -> xr.Dataset:
    """
    Calculate GSAT (Global‐mean Surface Air Temperature)
    
    Parameters
    ----------
    ds : xr.Dataset
    
    Returns
    -------
    ds : xr.Dataset
    """
    # Temporal aggregation
    ds = ds.resample(time="YE").mean(skipna = True)
    # Spatial aggregation
    weights = np.cos(np.deg2rad(ds.lat))
    weights.name = "weights"
    tas_weighted_mean = ds.weighted(weights).mean(dim = ["lat"], skipna = True)
    tas_weighted_mean = tas_weighted_mean.mean(dim = ["lon"], skipna = True)
    return tas_weighted_mean

2.7.3. Download climate data with the CDS API#

To reduce data size and download time, a geographical subset focusing on a apecific area within the European region (Spain) is selected.

# define some global attributes for the CDS-API
CMIP6_years = {
    "historical": [str(n) for n in np.arange(1850, 2014)],
    "ssp5_8_5": [str(n) for n in np.arange(2014, 2101)]
}
variable = "tas"
simulation = "cmcc_esm2"
project = 'CMIP6'
file_dest_CMIP6 = Path('./data/CMIP6')
months = [
    '01', '02', '03',
    '04', '05', '06',
    '07', '08', '09',
    '10', '11', '12'
]
c = cdsapi.Client()
os.makedirs(file_dest_CMIP6, exist_ok=True)

for experiment in CMIP6_years.keys():
    for year in CMIP6_years[experiment]:
        path_zip = file_dest_CMIP6 / f"CMIP6_{variable}_{experiment}_mon_{year}.zip"
        c.retrieve(
            'projections-cmip6',
            {
                'format': 'zip',
                'temporal_resolution': 'monthly',
                'variable': "near_surface_air_temperature",
                'experiment': experiment,
                'model': simulation,
                'year': [year],
                'month': months,
            },
            path_zip)
        # Extract zip file into the specified directory and remove zip
        extract_zip_and_delete(path_zip) 

2.7.4. Define inputs#

GWLs = ['1.5', '2', '3', '4']
PI_period = slice('1850', '1900')
window_width = 20
project = 'CMIP6'
experiment = 'ssp585'

2.7.5. Load files with xarray#

# load CMIP6 files
ds_hist = xr.open_mfdataset(
    np.sort(glob.glob(str(file_dest_CMIP6 / "CMIP6_tas_historical_mon_*.nc"))), 
    concat_dim='time', combine='nested'
)
ds_sce = xr.open_mfdataset(
    np.sort(glob.glob(str(file_dest_CMIP6 / "CMIP6_tas_ssp5_8_5_mon_*.nc"))), 
    concat_dim='time', combine='nested'
)

2.7.6. Calculate GWL periods#

Create empty dataframe to save the results#

# create a empty dataframe to store the results
columns = pd.MultiIndex.from_tuples([(GWL, experiment) for GWL in GWLs])
df_GWLs = pd.DataFrame('9999', index = [simulation], columns = columns)
# create dataframe to store the rolling mean series
columns = pd.MultiIndex.from_tuples([(experiment, simulation)])
df_GWLs_serie = pd.DataFrame(index = pd.date_range(start = '1850', end = '2101', freq = 'YE'), columns = columns)

Calculate GSAT (Global Surface Average Temperature)#

ds_hist_sce = xr.concat([ds_hist, ds_sce], dim = 'time')
ds_hist_sce['tas'] = ds_hist_sce['tas'] - 273.15 # kelvin to degree C
ds_hist_sce_GSAT = GSAT(ds_hist_sce)
# Calculate Preindustrial GSAT 
ds_PI_GSAT = ds_hist_sce_GSAT.sel(time = PI_period)
ds_PI_GAST_mean = ds_PI_GSAT[variable].mean()
ds_hist_sce_GSAT[variable].plot(label='GSAT')
plt.axhline(y=ds_PI_GAST_mean, color='r', linestyle='--', 
            label='Mean Temperatue Preindustrial Period (1850-1900)')
plt.ylabel('Global‐mean Surface Air Temperature (°C)')
plt.legend()
plt.title(f"{simulation} ssp585")
plt.show()
../_images/331bbebea1b7f56b27918c68bc7c368c162fc0e7f87b2bc8d9297b6cb16b84bb.png

Calculate GWLs#

ds_hist_sce_GSAT_rolling = ds_hist_sce_GSAT.rolling(time = window_width, min_periods=1, center = False).mean() # [n - window + 1, n]
GWLs_tas = (ds_hist_sce_GSAT_rolling - ds_PI_GAST_mean)
# Store delta change series with respect 1850-1900
df_GWLs_serie[(experiment, simulation)] = (ds_hist_sce_GSAT_rolling[variable].to_dataframe()[variable] - ds_PI_GAST_mean.data).values
for GWL in GWLs:
    GWLs_time = GWLs_tas['time'].where(GWLs_tas[variable] > float(GWL)).dropna(dim = "time")
    if len(GWLs_time) == 0:
        df_GWLs.loc[simulation][(GWL, experiment)] = 'NA'
    else:
        window_left = GWLs_time[0]['time'].dt.year.data - (window_width - 1)
        window_right = GWLs_time[0]['time'].dt.year.data
        df_GWLs.loc[simulation][(GWL, experiment)] = str(int(window_left)) + '-' + str(int(window_right))
df_GWLs
1.5 2 3 4
ssp585 ssp585 ssp585 ssp585
cmcc_esm2 2020-2039 2030-2049 2046-2065 2061-2080

The above dataframe shows the period in which the CMCC-ESM2 model reaches different GWLs (1.5, 2, 3, and 4°C) relative to the pre-industrial period. These periods are used in the C3S Atlas to produce the different products involving GWLs.

2.7.7. Plotting example: GWL +2 degC (ssp585)#

GWL = '2'
df_GWLs_serie_sce = df_GWLs_serie[(experiment)]
df_GWLs_serie_sce = df_GWLs_serie_sce.dropna(axis = 1, how = 'all')
time_GWL = df_GWLs[(GWL, experiment)]
time_GWL = time_GWL[pd.notnull(time_GWL)]
time_GWL = time_GWL[time_GWL!='9999']
time_GWL_time = pd.DataFrame(index = time_GWL.index)
time_GWL_time['year_left'] = [pd.to_datetime(str(period.split('-')[0])+'-12-31', infer_datetime_format=True) 
                         for period in time_GWL.values]
time_GWL_time['year_right'] = [pd.to_datetime(str(period.split('-')[1])+'-12-31', infer_datetime_format=True) 
                         for period in time_GWL.values]
xticks = pd.to_datetime(["1850-12-31", "1900-12-31", "1950-12-31", "2000-12-31","2050-12-31", "2100-12-31"])
color = cm.rainbow(np.linspace(0, 1, np.shape(df_GWLs_serie_sce)[1]))
fig, ax = plt.subplots(figsize = (10, 8))
df_GWLs_serie_sce.plot(ax = ax, color = color)
ax.axhline(y = int(GWL), color='k', linestyle = '--')
for n, sim in enumerate(time_GWL_time.index):
    ax.axvline(x = time_GWL_time['year_right'].loc[sim], color = color[n], linestyle='--', 
               linewidth = 0.5, alpha = 1)
    ax.fill_between(x = [time_GWL_time['year_left'].loc[sim], time_GWL_time['year_right'].loc[sim]], 
                    y1 = -1, y2 = 6, color = 'grey', alpha = 0.2)
ax.legend(bbox_to_anchor = (1.05, 0.93), fontsize = 8)
ax.grid(True)
ax.set_ylabel("Delta change w.r.t. PI period 1850-1900 (degC)")
ax.set_xlabel("year")
ax.annotate('GWL range:' + str(np.min([int(period.split('-')[0]) for period in time_GWL.values])) 
                   + '-' + str(np.max([int(period.split('-')[1]) for period in time_GWL.values])), xy = (-10,int(GWL)+0.1))
ax.set_xticks(xticks)
ax.set_xticklabels([y.year for y in xticks])
ax.set_ylim([-1, 6])
plt.suptitle(f"CMIP6 ({experiment}) GWL +2 degC")
plt.title("20 - year window width", fontsize = 10, pad = 20)
Text(0.5, 1.0, '20 - year window width')
../_images/2ed0dec986c833b7e96dee61630f009aaccf0ff2c974362178740cd88ac7dc2c.png