logo

1.1. Monthly count of days with maximum near-surface air temperature above 35 degC#

This Jupyter Notebook calculates the index tx35 (Monthly count of days with maximum near-surface (2-metre) air temperature above 35 degC) for the CMIP6-CMCC_ESM2 model.

For quick demonstration purposes, only one year from the future period (the year 2080) is calculated for a specific region (Spain).

First, the raw CMIP6 model is downloaded from the CDS, then the index is calculated, and the results are compared with the “Gridded dataset underpinning the Copernicus Interactive Climate Atlas”, also downloaded from the CDS.

1.1.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 it (pip install -e .).

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

import cdsapi
import os
from pathlib import Path
import xarray as xr
import xclim
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs

from c3s_atlas.utils import (
    extract_zip_and_delete,
    plot_month
)
from c3s_atlas.fixers import (
    apply_fixers
)
import c3s_atlas.interpolation as xesmfCICA

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

⚠️ Warning: Exposed API Credentials

For security reasons, it is not recommended to hardcode your Copernicus Climate Data Store (CDS) API credentials — such as cdsapi_url and cdsapi_key — directly in notebooks.

Instead, it is best to store them securely in a .cdsapirc file located in your home directory.

📄 More info: CDS API - How to use the API

cdsapi_url= "https://cds.climate.copernicus.eu/api"
cdsapi_key= ""
c = cdsapi.Client(url=cdsapi_url, key=cdsapi_key)

Running the code blocks below will download the data from the CDS as specified by the following API keywords:

Project: CMIP6 (Coupled Model Intercomparison Project Phase 6)
Variables: Daily Maximum Near-Surface Air Temperature
Temporal Resolution: Daily
Model: CMCC-ESM2
Experiment: SSP5-8.5
Year: 2080
Month: All
Day: All

# define some global attributes for the CDS-API
CMIP6_years = {
    "ssp5_8_5": ["2080"],
}
# variables
variables = {
    'tx': 'daily_maximum_near_surface_air_temperature',
}
# directory to download the files
file_dest_CMIP6 = Path('./data/CMIP6')
months = [
    '01', '02', '03',
    '04', '05', '06',
    '07', '08', '09',
    '10', '11', '12'
]
        
days = [
    '01', '02', '03',
    '04', '05', '06',
    '07', '08', '09',
    '10', '11', '12',
    '13', '14', '15',
    '16', '17', '18',
    '19', '20', '21',
    '22', '23', '24',
    '25', '26', '27',
    '28', '29', '30', '31'
]
os.makedirs(file_dest_CMIP6, exist_ok=True)

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

1.1.3. Load files with xarray#

# load files
ds = xr.open_dataset(file_dest_CMIP6 / "CMIP6_tx_ssp5_8_5_2080.nc")

1.1.4. Homogenization#

Once the data is downloaded from the CDS it undergoes a process of homogenization (see the “Homogenization” section for more details).

  • dataset_variable: Renames the variable using, respectively, the original (raw) variable name and the corresponding C3S Atlas variable name provided as inputs.

  • aggregation: Specifies the aggregation function applied to resample the data to either monthly or daily temporal resolution.

project_id = "cmip6"
variable = 'tasmax'
var_mapping = {
            "dataset_variable": {"tasmax": "tasmax"},
            "aggregation": {"data": "mean"},
        }
ds = apply_fixers(ds, variable, project_id, var_mapping)
2026-02-20 10:41:32,037 — Homogenization-fixers — INFO — Dataset has already the correct names for its coordinates
2026-02-20 10:41:32,045 — Homogenization-fixers — INFO — Fixing calendar for <xarray.Dataset> Size: 81MB
Dimensions:    (time: 365, bnds: 2, lat: 192, lon: 288)
Coordinates:
  * time       (time) object 3kB 2080-01-01 12:00:00 ... 2080-12-31 12:00:00
  * lat        (lat) float64 2kB -90.0 -89.06 -88.12 -87.17 ... 88.12 89.06 90.0
  * lon        (lon) float64 2kB 0.0 1.25 2.5 3.75 ... 355.0 356.2 357.5 358.8
    height     float64 8B ...
Dimensions without coordinates: bnds
Data variables:
    time_bnds  (time, bnds) object 6kB ...
    lat_bnds   (lat, bnds) float64 3kB ...
    lon_bnds   (lon, bnds) float64 5kB ...
    tasmax     (time, lat, lon) float32 81MB ...
Attributes: (12/48)
    Conventions:            CF-1.7 CMIP-6.2
    activity_id:            ScenarioMIP
    branch_method:          standard
    branch_time_in_child:   60225.0
    branch_time_in_parent:  60225.0
    comment:                none
    ...                     ...
    title:                  CMCC-ESM2 output prepared for CMIP6
    variable_id:            tasmax
    variant_label:          r1i1p1f1
    license:                CMIP6 model data produced by CMCC is licensed und...
    cmor_version:           3.6.0
    tracking_id:            hdl:21.14100/ba2e335b-8bac-45ec-abbe-f1f16299d2d4
2026-02-20 10:41:32,631 — UNITS_TRANSFORM — INFO — The dataset tasmax units are not in the correct magnitude. A conversion from K to Celsius will be performed.
2026-02-20 10:41:33,265 — Homogenization-fixers — INFO — The dataset is in daily or monthly resolution, we don't need to resample it from hourly frequency

1.1.5. Calculate index (tx35) and aggregate to monthly (MS) temporal resolution using xclim#

xclim is an operational Python library for climate services, providing a framework for constructing custom climate indicators and indices.

da_tx35 = xclim.indices.tx_days_above(ds['tasmax'], thresh='35.0 degC', 
                                      freq='MS', op='>')

“freq” attribute indicates output time frequency following pandas timeserie codes

# Convert DataArray to Dataset with specified variable name
ds_tx35 = da_tx35.to_dataset(name='tx35')

1.1.6. Interpolation to a common and regular grid using xESMF#

A wrapper for the xESMF Python package was developed within the framework of the C3S Atlas project to extend its functionalities to all datasets (regular, curvilinear, etc.)

# interpolate data
int_attr = {'interpolation_method' : 'conservative_normed', 
            'lats' : np.arange(-89.5, 90.5, 1),
            'lons' : np.arange(-179.5, 180.5, 1),
            'var_name' : 'tx35'
}
INTER = xesmfCICA.Interpolator(int_attr)
ds_tx35_i = INTER(ds_tx35)

1.1.7. Compare the results with the “Gridded dataset underpinning the Copernicus Interactive Climate Atlas”#

Running the code blocks below will download the data from the CDS as specified by the following API keywords:

Project: C3S Atlas dataset
Origin: CMIP6
Variables: Monthly Maximum Near-Surface Air Temperature
Experiment: SSP5-8.5
Year: 2015-2100
Month: All
Day: All
Area: [45.5, 5.5, 34, −11.5] (Cropped over Spain)

project = "CMIP6"
scenario = "ssp585"
var = 'tx35'
# directory to download the files
dest = Path('./data/CMIP6')
os.makedirs(dest, exist_ok=True)

Download SSP scenario#

filename = 'tx35_CMIP6_ssp585_mon_201501-210012.zip'
dataset = "multi-origin-c3s-atlas"
request = {
    "origin": "cmip6",
    "experiment": "ssp5_8_5",
    "period": "2015-2100",
    "variable": "monthly_extreme_hot_days",
    "bias_adjustment": "no_bias_adjustment",
    'area': [44.5, -9.5, 35.5, 3.5]
}

c.retrieve(dataset, request).download(dest / filename)
extract_zip_and_delete(dest / filename) 
# load data with xarray
ds_tx35_C3S_Atlas = xr.open_dataset(dest / "tx35_CMIP6_ssp585_mon_201501-210012.nc")
# select a specific member of the ensemble
select_member = [
    str(mem.data) for mem in ds_tx35_C3S_Atlas.member_id if "cmcc-esm2" in str(mem.data).lower()
][0]
print(select_member)
CMCC_CMCC-ESM2_r1i1p1f1
ds_tx35_C3S_Atlas_member_year = ds_tx35_C3S_Atlas.sel(
    member = np.where(ds_tx35_C3S_Atlas.member_id == select_member)[0], 
    time = "2080"
)

Plot results for one month#

Comparison of the results obtained with present Jupyter notebook and the reference C3S Atlas Dataset underpinning the C3S Atlas. A geographical subset focusing on Spain is selected to show the results.

zoomin_extent = [-9.5, 3.5, 35.5, 44.5]
title_size = 18

proj = ccrs.PlateCarree()
fig, ax = plt.subplots(
    nrows=2, ncols=2,
    subplot_kw={'projection': proj},
    figsize=(18, 12)
)

# calculate the difference
diff = ds_tx35_i - ds_tx35_C3S_Atlas_member_year

# user-tools
plot_month(ax[0, 0], ds_tx35_i, 'tx35', 8, 'Jupyter-book result', 'hot_r')
ax[0, 0].set_extent(zoomin_extent)
# C3S Atlas
plot_month(ax[0, 1], ds_tx35_C3S_Atlas_member_year, 'tx35', 8, 'C3S Atlas Dataset', 'hot_r')
ax[0, 1].set_extent(zoomin_extent)
# Difference
plot_month(ax[1, 0], diff, 'tx35', 8, 'Difference (Jupyter - C3S)', 'RdBu_r', vmin = -1, vmax = 1)
ax[1, 0].set_extent(zoomin_extent)
plt.subplots_adjust(wspace=0.01, hspace=0.1) 
fig.delaxes(ax[1, 1])
plt.tight_layout()
../_images/11996936529e7b3ca76959f158e6a040ed88c9c42fe134b3cea2dabae3eeb459.png

The figure demonstrates that the results obtained with this notebook are identical to those from the C3S Atlas datasets, and are therefore fully reproducible.