
Tutorial on Climatologies using Climate Data from C3S#
In this tutorial we will access data from the Climate Data Store (CDS) of the Copernicus Climate Change Service (C3S), and analyse climatologies and trends in near-surface air temperature. A climatology is defined as a statistical representation of the climate conditions for a specific period and region.
Learning objectives π§ #
Understanding what are climatologies and what are they used for.
Learning what are climate normals and anomalies and how to calculate them.
Getting familiarized with time series of climate data and with the different ways in which they can be analysed.
Getting familiarized with different ways to analyze and visualise climatology data.
Target audience π―#
Anyone interested in accessing, manipulating and using climatology data from the Copernicus Climate Data Store (CDS).
Prerequisites and assumed knowledge π#
Basic Programming Skills: Familiarity with programming concepts, particularly in Python, as the tutorial involves using Python libraries like xarray for dealing with multidimensional arrays and matplotlib to make visualisations.
Familiarity with API Usage: Understanding of how to use Application Programming Interfaces (APIs) will be useful for accessing data through the CDS API.
Familiarity with multidimensional data structure: Comprehending what are data dimensions and how they are organized in an array will be helpful.
Difficulty
2/5
Run the tutorial
WEKEO
WEkEO serves as the official platform of the European Centre for Medium-Range Weather Forecasts (ECMWF), offering access to an extensive array of climate data and tools for analysis and visualization. It provides a robust environment for conducting in-depth analysis and exploration of climate-related datasets. To learn more about WEkEO and its offerings, visit their website.
Possible Cloud Services
While Kaggle, Binder, and Colab are popular options for running notebooks in the cloud, itβs essential to note that these are just a few among many available choices. Each platform has its unique features and capabilities, catering to diverse user needs and preferences.
Outline#
Libraries
Install the necessary libraries
Import the necessary libraries
Access data with the CDS API
Inspect and view data
Convert longitude to [-180, 180] grid
Change temperature units from Kelvin to Celsius
Plot data
Calculate climate normals
Anomaly calculation
Calculate monthly climatology and anomalies
Calculate monthly climatology
Visualise monthly climatology
Calculate monthly anomalies
Visualise European near-surface temperature anomalies for one specific month
Time series analysis
Average Arctic near-surface temperature over latitude and longitude
Seasonal analysis of Arctic near-surface air temperature
1. Libraries#
1.A. Install the necessary libraries#
Note the exclamation mark in the lines of code below. This means the code will run as a shell (as opposed to a notebook) command.
ADS API#
If you still havenβt done it, you will need to install the Application Programming Interface (API) of the Copernicus Climate Data Store (CDS). This will allow us to programmatically download data.
!pip install cdsapi
Defaulting to user installation because normal site-packages is not writeable
Collecting cdsapi
Downloading cdsapi-0.7.0-py2.py3-none-any.whl (12 kB)
Collecting tqdm
Downloading tqdm-4.66.4-py3-none-any.whl (78 kB)
βββββββββββββββββββββββββββββββββββββββ 78.3/78.3 KB 756.2 kB/s eta 0:00:00 kB/s eta 0:00:01
?25hCollecting cads-api-client>=0.9.2
Downloading cads_api_client-1.0.3-py3-none-any.whl (20 kB)
Requirement already satisfied: requests>=2.5.0 in /home/randbee/.local/lib/python3.10/site-packages (from cdsapi) (2.32.1)
Requirement already satisfied: attrs in /home/randbee/.local/lib/python3.10/site-packages (from cads-api-client>=0.9.2->cdsapi) (23.2.0)
Collecting multiurl
Downloading multiurl-0.3.1.tar.gz (18 kB)
Preparing metadata (setup.py) ... ?25ldone
?25hRequirement already satisfied: typing-extensions in /home/randbee/.local/lib/python3.10/site-packages (from cads-api-client>=0.9.2->cdsapi) (4.11.0)
Requirement already satisfied: idna<4,>=2.5 in /usr/lib/python3/dist-packages (from requests>=2.5.0->cdsapi) (3.3)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/lib/python3/dist-packages (from requests>=2.5.0->cdsapi) (1.26.5)
Requirement already satisfied: certifi>=2017.4.17 in /usr/lib/python3/dist-packages (from requests>=2.5.0->cdsapi) (2020.6.20)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/randbee/.local/lib/python3.10/site-packages (from requests>=2.5.0->cdsapi) (3.3.2)
Requirement already satisfied: python-dateutil in /home/randbee/.local/lib/python3.10/site-packages (from multiurl->cads-api-client>=0.9.2->cdsapi) (2.9.0.post0)
Requirement already satisfied: pytz in /usr/lib/python3/dist-packages (from multiurl->cads-api-client>=0.9.2->cdsapi) (2022.1)
Requirement already satisfied: six>=1.5 in /usr/lib/python3/dist-packages (from python-dateutil->multiurl->cads-api-client>=0.9.2->cdsapi) (1.16.0)
Building wheels for collected packages: multiurl
Building wheel for multiurl (setup.py) ... ?25ldone
?25h Created wheel for multiurl: filename=multiurl-0.3.1-py3-none-any.whl size=21148 sha256=25dcede40173a3301abc5a6294622da2465b16eb88189cc2e503e09c90007702
Stored in directory: /home/randbee/.cache/pip/wheels/9b/d9/5f/36a39fd10d15b5b2d362ad6dc8a1bd28a3b1e14e08357944bf
Successfully built multiurl
Installing collected packages: tqdm, multiurl, cads-api-client, cdsapi
Successfully installed cads-api-client-1.0.3 cdsapi-0.7.0 multiurl-0.3.1 tqdm-4.66.4
Xarray#
Check the libraries that will be imported in section 1.B. If you havenβt got any of them installed yet, do it adapting the code below, used to install the xarray library.
!pip install xarray
For xarray to function it depends on other packages (numpy and pandas for example). As we are using xarray for reading netCDF files, we will need the netCDF4 dependency:
!pip install netCDF4
For the netCDF4 installation to take effect, you must restart you Kernel (check the Kernel tab in your menu bar above).
1.B. Import the necessary libraries#
We will be working with data in NetCDF format. To best handle this data we will use libraries for working with multidimensional arrays, in particular Xarray. We will also need libraries for plotting and viewing data, in this case we will use Matplotlib and Cartopy.
# CDS API
import cdsapi
# Libraries for working with multidimensional arrays
import numpy as np
import xarray as xr
# Libraries for plotting and visualising data
import matplotlib.path as mpath
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.feature as cfeature
# Disable warnings for data download via API
import urllib3
urllib3.disable_warnings()
2. Access data with the CDS API#
To access data from the CDS, you will need first to register at the ADS registration page (if you havenβt already done so), log in, and accept the Terms and Conditions at the end of the Download data tab.
To obtain data programmatically from the CDS, you will need an API Key that can be found in the api-how-to page. Here your key will appear automatically in the black window, assuming you have already registered and logged into the ADS. Your API key is the entire string of characters that appears after key:
Now copy your API key into the code cell below, replacing ####### with your key.
URL = 'https://cds.climate.copernicus.eu/api/v2'
KEY = '################'
Here we specify a data directory in which we will download our data and all output files that we will generate:
DATADIR = './'
To search for data, visit the Climate Data Store.
Here you can search for ERA5 data using the search bar. The data we need for this tutorial is the ERA5 monthly averaged data on single levels from 1979 to present. ERA5 is the 5th version of the ECMWF Reanalysis dataset. Reanalysis uses a state of the art forecast model and data assimilation system to create a consistent map without gaps of observed and modelled climate variables over the past decades.
Having selected the correct dataset, we now need to specify what product type, variables, temporal and geographic coverage we are interested in. These can all be selected in the Download data tab. In this tab a form appears in which we will select the following parameters to download:
Product type:
Monthly averaged reanalysisVariable:
2m temperatureYear:
1979 to year before presentMonth:
allTime:
00:00(default)Geographical area:
EuropeFormat:
NetCDF
At the end of the download form, select Show API request. This will reveal a block of code, which you can simply copy and paste into a cell of your Jupyter Notebook, like this:
c = cdsapi.Client(url=URL, key=KEY)
c.retrieve(
'reanalysis-era5-single-levels-monthly-means',
{
'product_type': 'monthly_averaged_reanalysis',
'variable': '2m_temperature',
'year': [
'1979', '1980', '1981',
'1982', '1983', '1984',
'1985', '1986', '1987',
'1988', '1989', '1990',
'1991', '1992', '1993',
'1994', '1995', '1996',
'1997', '1998', '1999',
'2000', '2001', '2002',
'2003', '2004', '2005',
'2006', '2007', '2008',
'2009', '2010', '2011',
'2012', '2013', '2014',
'2015', '2016', '2017',
'2018', '2019', '2020',
],
'month': [
'01', '02', '03',
'04', '05', '06',
'07', '08', '09',
'10', '11', '12',
],
'time': '00:00',
'area': [
72, -25, 34,
40,
],
'format': 'netcdf',
},
f'{DATADIR}era5_monthly_t2m_eur.nc')
2024-06-11 10:44:28,769 INFO Welcome to the CDS
2024-06-11 10:44:28,770 INFO Sending request to https://cds.climate.copernicus.eu/api/v2/resources/reanalysis-era5-single-levels-monthly-means
2024-06-11 10:44:28,949 INFO Request is completed
2024-06-11 10:44:28,950 INFO Downloading https://download-0021.copernicus-climate.eu/cache-compute-0021/cache/data3/adaptor.mars.internal-1710010077.3505461-15490-3-44d8be3c-3900-4e19-9104-538ed930d793.nc to ./era5_monthly_t2m_eur.nc (38.4M)
2024-06-11 10:44:35,353 INFO Download rate 6M/s
Result(content_length=40257208,content_type=application/x-netcdf,location=https://download-0021.copernicus-climate.eu/cache-compute-0021/cache/data3/adaptor.mars.internal-1710010077.3505461-15490-3-44d8be3c-3900-4e19-9104-538ed930d793.nc)
3. Inspect and view data#
Now that we have downloaded the data, we can inspect it. We have requested the data in NetCDF format. This is a commonly used format for gridded (array-based) scientific data, which is data that has the following characteristics:
A regular, evenly spaced grid structure where each point on the grid is defined by its coordinates (e.g., latitude, longitude, altitude, or time) and has a corresponding value for a variable (e.g., temperature).
One or multiple dimensions, which is why they are referred to as multidimensional or ND arrays. Examples of dimensions can be:
1D: time series (as temperature over time at a single location).
2D: Spatial data (as sea surface temperature over an area).
3D: Volumetric data (as atmospheric temperature at various altitudes).
4D: spatiotemporal data (as temperature over an area at different times).
To read and process this data we will make use of the Xarray library. Xarray is an open source project and Python package that makes working with labelled multi-dimensional arrays simple and efficient. We will read the data from our NetCDF file into an xarray.Dataset.
t2m = f'{DATADIR}era5_monthly_t2m_eur.nc'
# Create Xarray Dataset
ds = xr.open_dataset(t2m)
Now we can query our newly created Xarray dataset:
ds
<xarray.Dataset> Size: 161MB
Dimensions: (longitude: 261, latitude: 153, time: 504)
Coordinates:
* longitude (longitude) float32 1kB -25.0 -24.75 -24.5 ... 39.5 39.75 40.0
* latitude (latitude) float32 612B 72.0 71.75 71.5 71.25 ... 34.5 34.25 34.0
* time (time) datetime64[ns] 4kB 1979-01-01 1979-02-01 ... 2020-12-01
Data variables:
t2m (time, latitude, longitude) float64 161MB ...
Attributes:
Conventions: CF-1.6
history: 2024-03-09 18:47:59 GMT by grib_to_netcdf-2.25.1: /opt/ecmw...We see that the dataset has one variable called t2m, which stands for β2 metre temperatureβ, and three dimensions: longitude, latitude and time.
3.A. Convert longitude to [-180, 180] grid#
Notice that the longitude variables in the Xarray Dataset and Data Array objects are in the range of [0, 359.75]. By default, ECMWF data are on a [0, 360] grid. Should you wish to, there are two options to bring the longitude coordinates to a [-180, 180] grid. The first option is to specify the area keyword argument right when you request data with the CDS API. The area keyword then automatically reprojects the requested data onto a [-180, 180] grid. The second option, in case you already have the data downloaded, is to assign values to coordinates with the xarray function assign_coords(). The code below shifts your longitude coordinates from [0, 359.75] to [-180, 179.75].
ds_180 = ds.assign_coords(longitude=(((ds.longitude + 180) % 360) - 180)).sortby('longitude')
While an Xarray dataset may contain multiple variables, an Xarray data array holds a single multi-dimensional variable and its coordinates. To make the processing of the t2m data easier, we convert in into an Xarray data array.
# Create Xarray Data Array
da = ds_180['t2m']
Letβs view this data:
da
<xarray.DataArray 't2m' (time: 504, latitude: 153, longitude: 261)> Size: 161MB
[20126232 values with dtype=float64]
Coordinates:
* latitude (latitude) float32 612B 72.0 71.75 71.5 71.25 ... 34.5 34.25 34.0
* time (time) datetime64[ns] 4kB 1979-01-01 1979-02-01 ... 2020-12-01
* longitude (longitude) float32 1kB -25.0 -24.75 -24.5 ... 39.5 39.75 40.0
Attributes:
units: K
long_name: 2 metre temperature3.B. Change temperature units from Kelvin to Celsius#
Notice that the ERA-5 temperature data are in units of Kelvin, the base unit for temperature in the International System of Units (SI). If you want to convert the values from Kelvin to degrees Celsius, you have to subtract 273.15.
da_degc = da - 273.15
If you inspect the characteristics of the data above, you see that when you convert the data values, the data arrayβs Attributes are dropped. However, we want to keep the information provided by the Attributes and for this reason, we re-assign the Attributes from the previous, unconverted object with the function assign_attrs(). Since the unit has changed, we assign a new unit measure to the units attribute.
da_degc = da_degc.assign_attrs(da.attrs)
da_degc.attrs['units'] = 'Β° C'
3.C. Plot data#
Now, let us visualize one time step to get a better idea of the data. xarray offers built-in matplotlib functions that allow you to plot a DataArray. With the function plot(), you can easily plot, e.g., the first time step of the loaded array.
da_degc[0,:,:].plot()
<matplotlib.collections.QuadMesh at 0x723738c04ac0>
4. Calculate climate normals#
Standard reference period and climate normals#
Anthropogenic activities and natural variations from years to decades shape the Earthβs climate. In order to evaluate anomalous conditions of a specific month or year, the World Meteorological Organization (WMO) defines standard reference periods used to create climate normals. Climate normals can be considered as the typical climate for the period the normals are based on.
Until 2020, the most current and widely used standard reference period was the 30-year range of 1981-2010. With the start of 2021, the WMO recommended updating the climate normal reference period to the range 1991-2020.
First, let us calculate the near-surface air temperature climate normal for the reference period 1991-2020. For this, we will create for each year the average near-surface air temperature. You can use the xarray function groupby() to group the data by year (specify time.year) and then to create the average of each year with the function mean(). The resulting object contains for each year from 1979 to 2020 the average annual global mean surface air temperature.
yearly_mean = da_degc.groupby('time.year').mean(keep_attrs=True)
From the data array object above, we can then filter the years of the reference period with the function where(). Inside the function, you can filter on the data dimensions. In the example below, we will apply a filter to keep data for all years in the range 1991 to 2020.
ref = yearly_mean.where((yearly_mean.year > 1990) & (yearly_mean.year < 2021), drop=True)
In a last step, we want to create the average of the yearly-mean near-surface air temperature for the reference period 1991-2020. We can do this by applying the function mean() and specify the dimension year as the dimension to be averaged. The resulting data array is left with two dimensions, latitude and longitude.
ref_mean = ref.mean(dim="year", keep_attrs=True)
ref_mean
<xarray.DataArray 't2m' (latitude: 153, longitude: 261)> Size: 319kB
array([[-13.05093713, -11.42301713, -10.44290186, ..., 1.43984608,
1.43878503, 1.4215015 ],
[-13.38575253, -11.72730137, -10.07435528, ..., 1.61782063,
1.60055961, 1.58289894],
[ -9.3566959 , -9.1192267 , -8.71600047, ..., 1.79361961,
1.78391257, 1.76965176],
...,
[ 19.47542605, 19.44565198, 19.41946353, ..., 20.09356929,
20.3711416 , 20.64145824],
[ 19.55795416, 19.52995038, 19.50329473, ..., 19.65315293,
20.02093397, 20.37276553],
[ 19.63398089, 19.61081516, 19.58625345, ..., 19.34004257,
19.70218627, 20.13355981]])
Coordinates:
* latitude (latitude) float32 612B 72.0 71.75 71.5 71.25 ... 34.5 34.25 34.0
* longitude (longitude) float32 1kB -25.0 -24.75 -24.5 ... 39.5 39.75 40.0
Attributes:
units: Β° C
long_name: 2 metre temperature5. Anomaly calculation#
The next step is now to calculate the anomaly of a specific year with respect to the climate normal. The term anomaly refers to the deviation of a value from the long-term average. Positive or negative anomalies indicate that the average temperatures of a particular year were respectively warmer or cooler than the reference value.
Let us calculate the near-surface air temperature anomaly for the year 2016. In a first step, we select the average near-surface temperature values for the year 2016 from the xarray.DataArray object yearly_mean. With the xarray function sel(), you can select a data array based on coordinate labels. The coordinate label of interest is year=2016.
t2m_2016 = yearly_mean.sel(year=2016)
Next, we calculate the near-surface air temperature anomaly for 2016 by subtracting the climate normal (i.e. the reference near-surface air temperature values) from the average near-surface air temperature for 2016.
anom_2016 = t2m_2016 - ref_mean
Letβs visualize the global near-surface air temperature anomaly for 2016 to see which regions were warmer or cooler compared to the reference period. This time we will make use of a combination of the plotting libraries matplotlib and Cartopy to create a more customised figure. One of Cartopyβs key features is its ability to transform array data into different geographic projections. In combination with matplotlib, it is a very powerful way to create high-quality visualisations and animations.
# create the figure panel and the map using the Cartopy PlateCarree projection
fig, ax = plt.subplots(1, 1, figsize = (16, 8), subplot_kw={'projection': ccrs.PlateCarree()})
# Plot the data
im = plt.pcolormesh(anom_2016.longitude, anom_2016.latitude, anom_2016, cmap='RdBu_r', vmin=-3, vmax=3)
# Set the figure title, add lat/lon grid and coastlines
ax.set_title('Near-surface air temperature anomaly for 2016 (with respect to 1991-2020 mean)', fontsize=16)
ax.gridlines(draw_labels=True, linewidth=1, color='gray', alpha=0.5, linestyle='--')
ax.coastlines(color='black')
ax.set_extent([-25, 40, 34, 72], crs=ccrs.PlateCarree())
# Specify the colourbar
cbar = plt.colorbar(im,fraction=0.05, pad=0.04)
cbar.set_label('temperature anomaly')
# Save the figure
fig.savefig(f'{DATADIR}ERA5_global_2016_anomaly_eur.png')
/home/randbee/.local/lib/python3.10/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/50m_physical/ne_50m_coastline.zip
warnings.warn(f'Downloading: {url}', DownloadWarning)
6. Calculate monthly climatology and anomalies#
We will now calculate the monthly climatology of near-surface air temperature for the period 1991 to 2020. We will also view the anomalies with respect to the climatology for a particular month. To do this, we return to the data array created in Section 3.
6.A. Calculate monthly climatology#
We first create again a temporal subset for the period 1991 to 2020, but this time keeping the monthly temporal resolution.
clim_period = da_degc.sel(time=slice('1991-01-01', '2020-12-01'))
We will now calculate the monthly climatology by first applying the function groupby() to group the data array by month and then calculating the average for each monthly group. The resulting data array is the monthly climatology for air temperature in Europe based on the reference period 1991 to 2020.
clim_month = clim_period.groupby('time.month').mean()
Let us view this monthly climatology averaged over the entire European region. To do this we need to average over the latitude and longitude dimensions. A very important consideration however is that the gridded data cells do not all correspond to the same areas. The size covered by each data point varies as a function of latitude. We need to take this into account when averaging. One way to do this is to use the cosine of the latitude as a proxy for the varying sizes.
First, we calculate the weights by using the cosine of the latitude, then we apply these weights to the data array with the xarray function weighted().
weights = np.cos(np.deg2rad(clim_month.latitude))
weights.name = "weights"
clim_month_weighted = clim_month.weighted(weights)
The next step is to compute the mean across the latitude and longitude dimensions of the weighted data array with the function mean().
mean = clim_month_weighted.mean(["longitude", "latitude"])
This data array now gives us the monthly climatology of Europe for the period 1991 to 2020.
6.B. Visualize monthly climatology#
We can now create a simple plot of the monthly climatology of Europe for the period 1991 to 2020:
mean.plot()
[<matplotlib.lines.Line2D at 0x7236e5964370>]
One more step we can take is to repeat the process above for the standard deviation, to enable us to plot both in a single figure.
clim_std = clim_period.groupby('time.month').std()
weights = np.cos(np.deg2rad(clim_std.latitude))
weights.name = "weights"
clim_std_weighted = clim_std.weighted(weights)
std = clim_std_weighted.mean(["longitude", "latitude"])
We can now plot the European monthly climatology and standard deviation in a figure:
fig, ax = plt.subplots(1, 1, figsize = (12, 6))
ax.plot(mean.month, mean, color='blue', label='mean')
ax.fill_between(mean.month, (mean + std), (mean - std), alpha=0.1, color='green', label='+/- 1 SD')
ax.set_title('European monthly climatology of 2m temperature (1991 to 2020)')
ax.set_ylabel('Β° C')
ax.set_xlabel('month')
ax.set_xlim(1,12)
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels)
ax.grid(linestyle='--')
fig.savefig(f'{DATADIR}Eur_monthly_t2m_clim.png')
6.C. Calculate monthly anomalies#
We will now inspect monthly anomalies with respect to this climatology. We will return to the data that we created prior to aggregating by latitude and longitude. We can compute the monthly anomalies for each month of our original European time series by subtracting from it the monthly climatology. Important to mention here is to first group by time.month. In this way, xarray understands to use the correct value of the monthly climatology to calculate the anomalies.
anom_month = da_degc.groupby('time.month') - clim_month
6.D. Visualise European near-surface temperature anomalies for one specific month#
The last step is to visualize the air temperature anomaly field in Europe for one specific month. In Europe, 2020 was the hottest year on record and the northwest has expierenced a heatwave during the second half of July and first half of August. Let us select the air temperature anomalies for August for each year from 1979 to 2020. Through the grouping process, the data array anom_month has month as additional coordinate information. Based on this coordinate information, you can specify that you only want to select the data array entries for August (month = 8).
anom_aug = anom_month[anom_month.month == 8]
Now let us visualise the near-surface air temperature anomaly for Europe in August 2020. Again we make use of the function visualize_pcolormesh.
fig, ax = plt.subplots(1, 1, figsize = (16, 8), subplot_kw={'projection': ccrs.PlateCarree()})
im = plt.pcolormesh(anom_aug.longitude, anom_aug.latitude, anom_aug[41,:,:], cmap='RdBu_r', vmin=-3, vmax=3)
ax.set_title('Near-surface air temperature anomaly for Aug 2020 (with respect to 1991-2020 Aug climatology)', fontsize=16)
ax.gridlines(draw_labels=True, linewidth=1, color='gray', alpha=0.5, linestyle='--')
ax.coastlines(color='black')
ax.set_extent([-25, 40, 34, 72], crs=ccrs.PlateCarree())
cbar = plt.colorbar(im,fraction=0.05, pad=0.04)
cbar.set_label('temperature anomaly')
fig.savefig(f'{DATADIR}ERA5_Europe_2020-08_anomaly.png')
7. Time series analysis#
In this section we focus on time series analysis, where we will plot and analyse changes in near-surface air temperature over time. We focus on another subset area over the Arctic, where increasing temperatures are particularly apparent.
We will download another subset of the dataset ERA5 monthly averaged data on single levels from 1979 to present, this time for the Arctic.
c = cdsapi.Client(url=URL, key=KEY)
c.retrieve(
'reanalysis-era5-single-levels-monthly-means',
{
'product_type': 'monthly_averaged_reanalysis',
'variable': '2m_temperature',
'year': [
'1979', '1980', '1981',
'1982', '1983', '1984',
'1985', '1986', '1987',
'1988', '1989', '1990',
'1991', '1992', '1993',
'1994', '1995', '1996',
'1997', '1998', '1999',
'2000', '2001', '2002',
'2003', '2004', '2005',
'2006', '2007', '2008',
'2009', '2010', '2011',
'2012', '2013', '2014',
'2015', '2016', '2017',
'2018', '2019', '2020',
],
'month': [
'01', '02', '03',
'04', '05', '06',
'07', '08', '09',
'10', '11', '12',
],
'time': '00:00',
'area': [
90, -180, 66.55,
180,
],
'format': 'netcdf',
},
f'{DATADIR}era5_monthly_t2m_Arc.nc')
2024-06-11 14:45:24,923 INFO Welcome to the CDS
2024-06-11 14:45:24,925 INFO Sending request to https://cds.climate.copernicus.eu/api/v2/resources/reanalysis-era5-single-levels-monthly-means
2024-06-11 14:45:25,158 INFO Request is queued
2024-06-11 14:45:38,646 INFO Request is running
2024-06-11 14:51:44,275 INFO Request is completed
2024-06-11 14:51:44,276 INFO Downloading https://download-0017.copernicus-climate.eu/cache-compute-0017/cache/data5/adaptor.mars.internal-1718110206.5449736-7642-16-281bb1ba-8b46-49d4-9081-aa42ebff9e29.nc to ./era5_monthly_t2m_Arc.nc (130.1M)
2024-06-11 14:52:06,957 INFO Download rate 5.7M/s
Result(content_length=136452100,content_type=application/x-netcdf,location=https://download-0017.copernicus-climate.eu/cache-compute-0017/cache/data5/adaptor.mars.internal-1718110206.5449736-7642-16-281bb1ba-8b46-49d4-9081-aa42ebff9e29.nc)
We will repeat the steps in Section 3 to read the data into an xarray data array:
Arc_file = f'{DATADIR}era5_monthly_t2m_Arc.nc'
Arc_ds = xr.open_dataset(Arc_file)
Arc_da = Arc_ds['t2m']
We will also repeat the steps in Section 3 to convert the values from degrees Kelvin to Celsius:
Arc = Arc_da - 273.15
Arc = Arc.assign_attrs(Arc_da.attrs)
Arc.attrs['units'] = 'Β° C'
Letβs view the first time step of this data. Notice that we change the projection from ccrs.PlateCarree() to ccrs.Orthographic(central_latitude=90) to better view the Arctic region. Note that we need to insert a transform keyword in the pcolormesh function to transform the data values into the orthographic projection:
fig, ax = plt.subplots(1, 1, figsize = (8, 8), subplot_kw={'projection': ccrs.Orthographic(central_latitude=90)})
im = ax.pcolormesh(Arc.longitude, Arc.latitude, Arc[0,:,:], transform = ccrs.PlateCarree(), cmap='coolwarm')
ax.gridlines(draw_labels=True, linewidth=1, color='gray', alpha=0.5, linestyle='--')
ax.set_title('Near-surface air temperature, Jan 1979', fontsize=16)
ax.coastlines(color='black')
cbar = fig.colorbar(im, fraction=0.04, pad=0.07)
cbar.set_label('Β° C')
fig.savefig(f'{DATADIR}ERA5_Arctic_t2m_Jan1979.png')
7.A. Average Arctic near-surface temperature over latitude and longitude#
We would like to analyse the time series of near-surface air temperature aggregated over the Arctic. To do this we need to average over the latitude and longitude dimensions. As before, we will need to take into account the varying size of the gridded data cells as a function of latitude.
weights = np.cos(np.deg2rad(Arc.latitude))
weights.name = "weights"
Arc_weighted = Arc.weighted(weights)
Arc_mean = Arc_weighted.mean(["longitude", "latitude"])
Let us create a simple plot of this data to see how it looks:
Arc_mean.plot()
[<matplotlib.lines.Line2D at 0x7236e57a1180>]
Notice that the plot() function now creates a 2 dimensional graph of temperature as a function of time.
The trend in rising temperatures in the past decades is particularly noticable in the Arctic, and it is much easier to see this if we view the time series of yearly averages.
Arc_yearly = Arc_mean.groupby('time.year').mean()
Arc_yearly.plot()
[<matplotlib.lines.Line2D at 0x7236e5967e20>]
Here we can see a clear warming trend.
A typical way to view such a time series is to convert the absolute temperature values into anomalies with respect to a climate normal and view the time series as a bar chart. This can clearly highlight which years were on average warmer or cooler than the climate normal.
To do this, we calculate the anomalies with respect to a climate normal:
Arc_subset = Arc_yearly.where((Arc_yearly.year > 1990) & (Arc_yearly.year < 2021), drop=True)
Arc_clim = Arc_subset.mean()
Arc_anom = Arc_yearly - Arc_clim
We will now create a bar chart to visualize the time series of anomalies. We want to apply the color blue for years that are cooler than the reference period (negative anomalies) and the color red for years that are warmer than the reference period. For this, the next step is to convert the xarray.DataArray object into a pandas.DataFrame with the function to_dataframe(). Let us add an additional column called positive, indicating with a boolean value whether the air temperature anomaly is positive (True) or negative (False). The result is a data frame with 42 rows (one row per year) and two columns (t2m, positive).
df = Arc_anom.to_dataframe()
df['positive'] = df['t2m'] > 0
df.head() # show only first five rows
| t2m | positive | |
|---|---|---|
| year | ||
| 1979 | -1.990015 | False |
| 1980 | -1.263199 | False |
| 1981 | -0.570732 | False |
| 1982 | -1.707722 | False |
| 1983 | -1.338904 | False |
The pandas.DataFrame can then be visualized with the pandas built-in plot() function. You can customize the plot with the following keyword arguments:
figsize: Define the size of the plotkind: Type of plot, e.g.line,bar, etc.color: color of the bars. Withdf.positive.map(), the columpositiveis used to distinguish between bars that shall be colored in blue and bar that shall be colored in redfontsize: fontsize for x- and yticksxlabel,ylabel: Labels for x and y axestitle: Title of the plot
See here a full list of possible keyword arguments.
plt.rcParams.update({'font.size': 18}) # must set in top
df['t2m'].plot(
figsize=(14,8),
kind='bar',
color=df.positive.map({True: 'red', False: 'blue'}),
fontsize=14,
xlabel='\nYear',
ylabel='Temperature Anomaly',
title='\nYearly anomalies of Arctic air temperature (reference period 1991-2020)\n',
)
plt.savefig(f'{DATADIR}Arctic_t2m_anom_bar.png')
In the plot above, you see that since 2005, each year the Arctic near-surface air temperature was warmer than the reference mean for the period 1991-2020.
7.B. Seasonal analysis of Arctic near-surface air temperature#
In the final part of this tutorial we will compare seasonal trends in Arctic near-surface air temperature. To do this we return to our monthly geographically averaged dataset, and we will downsample the monthly averages to seasonal averages using the function resample(). By specifying time='QS-DEC', the data is split into consecutive three-month periods, anchored at December. If we add additionally the function mean(), we calculate the average of the three-month period.
Arc_seasonal = Arc_mean.resample(time='QS-DEC').mean()
In the code below, before visualising the seasonal average air temperature data for the Arctic, we create for each season (winter, spring, summer and autumn) a pandas.DataFrame. We then create a for loop to visualise each seasonal time series in a successive plot.
fig, axs = plt.subplots(4, 1, figsize = (6, 9))
season_list = ["winter", "spring", "summer", "autumn"]
for i in range(4):
df = Arc_seasonal[i::4].to_dataframe()
mid_range = np.around(df.mean().values)
axs[i].plot(df.t2m, color='blue')
axs[i].set_ylim([mid_range[0]-4, mid_range[0]+4])
axs[i].set_title(season_list[i], fontsize=12)
axs[i].tick_params(axis='x', labelsize=10)
axs[i].tick_params(axis='y', labelsize=10)
fig.suptitle('\nArctic seasonal averages of 2m air temp - 1979-2020\n', fontsize=16)
fig.tight_layout(pad=1.0)
fig.savefig(f'{DATADIR}Arctic_seasonal.png')
Note the difference in variability of the seasonal average of air temperature in the Arctic: mean summer temperatures seem to be more constant compared to the other seasons.
Key Messages to Take Home π#
Climatologies are statistical representations of the climate conditions for specific periods and regions.
Climate normals can be considered as the typical climate for the period the normals are based on. They are calculated for standard reference periods defined by the World Meteorological Organization (WMO).
Anomaly refers to the deviation of a value from the long-term average (which can be the climate normal). Positive or negative anomalies indicate that the average temperatures of a particular year were respectively warmer or cooler than the reference value.
Climatologies, climate normals and anomalies can be considered based on different temporal units, as years, months or seasons.