ERA5 Data Analysis

This notebook explores some of the variables available in the ERA5 dataset for use in the ATM-505 project.

Required packages: xarray (http://xarray.pydata.org/en/stable/), Numpy, Datetime, Cartopy (https://scitools.org.uk/cartopy/docs/latest/), Matplotlib

Some useful links that go beyond this notebook: ERA5 documentation (https://confluence.ecmwf.int/display/CKB/ERA5%3A+data+documentation), MetPy package for computing various meteorological variables using gridded data (https://unidata.github.io/MetPy/latest/index.html)

Computing distance [meters] from gridpoint lat/lon: MetPy (https://unidata.github.io/MetPy/latest/api/generated/metpy.calc.lat_lon_grid_deltas.html), Pyproj (https://janakiev.com/blog/gps-points-distance-python/)

In [1]:
import xarray as xr
import numpy as np
from datetime import datetime
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib.patches as mpatches
%matplotlib inline

Plot 500-hPa Geopotential Height

The first step is to load the ERA5 hourly data on pressure levels dataset for your case. In this example we'll look at the 05-08 February 2020 period.

This is where you can learn more about the ERA5 pressure level dataset, and even download data on your own:
https://cds.climate.copernicus.eu/cdsapp#!/dataset/reanalysis-era5-pressure-levels?tab=overview

How to cite: Copernicus Climate Change Service (C3S) (2017): ERA5: Fifth generation of ECMWF atmospheric reanalyses of the global climate. Copernicus Climate Change Service Climate Data Store (CDS), date of access. https://cds.climate.copernicus.eu/cdsapp#!/home

In [2]:
filename = '/home/disk/meso-home/jfinlon/ERA5_Data/20200205_20200208/era5.data_on_pressure_levels.2020020500_2020020821.nc'
ds = xr.open_dataset(filename) # This loads the NetCDF file as an xarray object

Let's take a look at how the data are structured in this file.

In [3]:
ds
Out[3]:
Show/Hide data repr Show/Hide attributes
xarray.Dataset
    • latitude: 181
    • level: 11
    • longitude: 481
    • time: 32
    • longitude
      (longitude)
      float32
      -170.0 -169.75 ... -50.25 -50.0
      units :
      degrees_east
      long_name :
      longitude
      array([-170.  , -169.75, -169.5 , ...,  -50.5 ,  -50.25,  -50.  ],
            dtype=float32)
    • latitude
      (latitude)
      float32
      65.0 64.75 64.5 ... 20.5 20.25 20.0
      units :
      degrees_north
      long_name :
      latitude
      array([65.  , 64.75, 64.5 , 64.25, 64.  , 63.75, 63.5 , 63.25, 63.  , 62.75,
             62.5 , 62.25, 62.  , 61.75, 61.5 , 61.25, 61.  , 60.75, 60.5 , 60.25,
             60.  , 59.75, 59.5 , 59.25, 59.  , 58.75, 58.5 , 58.25, 58.  , 57.75,
             57.5 , 57.25, 57.  , 56.75, 56.5 , 56.25, 56.  , 55.75, 55.5 , 55.25,
             55.  , 54.75, 54.5 , 54.25, 54.  , 53.75, 53.5 , 53.25, 53.  , 52.75,
             52.5 , 52.25, 52.  , 51.75, 51.5 , 51.25, 51.  , 50.75, 50.5 , 50.25,
             50.  , 49.75, 49.5 , 49.25, 49.  , 48.75, 48.5 , 48.25, 48.  , 47.75,
             47.5 , 47.25, 47.  , 46.75, 46.5 , 46.25, 46.  , 45.75, 45.5 , 45.25,
             45.  , 44.75, 44.5 , 44.25, 44.  , 43.75, 43.5 , 43.25, 43.  , 42.75,
             42.5 , 42.25, 42.  , 41.75, 41.5 , 41.25, 41.  , 40.75, 40.5 , 40.25,
             40.  , 39.75, 39.5 , 39.25, 39.  , 38.75, 38.5 , 38.25, 38.  , 37.75,
             37.5 , 37.25, 37.  , 36.75, 36.5 , 36.25, 36.  , 35.75, 35.5 , 35.25,
             35.  , 34.75, 34.5 , 34.25, 34.  , 33.75, 33.5 , 33.25, 33.  , 32.75,
             32.5 , 32.25, 32.  , 31.75, 31.5 , 31.25, 31.  , 30.75, 30.5 , 30.25,
             30.  , 29.75, 29.5 , 29.25, 29.  , 28.75, 28.5 , 28.25, 28.  , 27.75,
             27.5 , 27.25, 27.  , 26.75, 26.5 , 26.25, 26.  , 25.75, 25.5 , 25.25,
             25.  , 24.75, 24.5 , 24.25, 24.  , 23.75, 23.5 , 23.25, 23.  , 22.75,
             22.5 , 22.25, 22.  , 21.75, 21.5 , 21.25, 21.  , 20.75, 20.5 , 20.25,
             20.  ], dtype=float32)
    • level
      (level)
      int32
      200 250 300 400 ... 850 925 1000
      units :
      millibars
      long_name :
      pressure_level
      array([ 200,  250,  300,  400,  500,  600,  700,  800,  850,  925, 1000],
            dtype=int32)
    • time
      (time)
      datetime64[ns]
      2020-02-05 ... 2020-02-08T21:00:00
      long_name :
      time
      array(['2020-02-05T00:00:00.000000000', '2020-02-05T03:00:00.000000000',
             '2020-02-05T06:00:00.000000000', '2020-02-05T09:00:00.000000000',
             '2020-02-05T12:00:00.000000000', '2020-02-05T15:00:00.000000000',
             '2020-02-05T18:00:00.000000000', '2020-02-05T21:00:00.000000000',
             '2020-02-06T00:00:00.000000000', '2020-02-06T03:00:00.000000000',
             '2020-02-06T06:00:00.000000000', '2020-02-06T09:00:00.000000000',
             '2020-02-06T12:00:00.000000000', '2020-02-06T15:00:00.000000000',
             '2020-02-06T18:00:00.000000000', '2020-02-06T21:00:00.000000000',
             '2020-02-07T00:00:00.000000000', '2020-02-07T03:00:00.000000000',
             '2020-02-07T06:00:00.000000000', '2020-02-07T09:00:00.000000000',
             '2020-02-07T12:00:00.000000000', '2020-02-07T15:00:00.000000000',
             '2020-02-07T18:00:00.000000000', '2020-02-07T21:00:00.000000000',
             '2020-02-08T00:00:00.000000000', '2020-02-08T03:00:00.000000000',
             '2020-02-08T06:00:00.000000000', '2020-02-08T09:00:00.000000000',
             '2020-02-08T12:00:00.000000000', '2020-02-08T15:00:00.000000000',
             '2020-02-08T18:00:00.000000000', '2020-02-08T21:00:00.000000000'],
            dtype='datetime64[ns]')
    • z
      (time, level, latitude, longitude)
      float32
      ...
      units :
      m**2 s**-2
      long_name :
      Geopotential
      standard_name :
      geopotential
      [30645472 values with dtype=float32]
    • q
      (time, level, latitude, longitude)
      float32
      ...
      units :
      kg kg**-1
      long_name :
      Specific humidity
      standard_name :
      specific_humidity
      [30645472 values with dtype=float32]
    • t
      (time, level, latitude, longitude)
      float32
      ...
      units :
      K
      long_name :
      Temperature
      standard_name :
      air_temperature
      [30645472 values with dtype=float32]
    • u
      (time, level, latitude, longitude)
      float32
      ...
      units :
      m s**-1
      long_name :
      U component of wind
      standard_name :
      eastward_wind
      [30645472 values with dtype=float32]
    • v
      (time, level, latitude, longitude)
      float32
      ...
      units :
      m s**-1
      long_name :
      V component of wind
      standard_name :
      northward_wind
      [30645472 values with dtype=float32]
    • w
      (time, level, latitude, longitude)
      float32
      ...
      units :
      Pa s**-1
      long_name :
      Vertical velocity
      standard_name :
      lagrangian_tendency_of_air_pressure
      [30645472 values with dtype=float32]
  • Conventions :
    CF-1.6
    history :
    2020-05-13 22:47:23 GMT by grib_to_netcdf-2.16.0: /opt/ecmwf/eccodes/bin/grib_to_netcdf -S param -o /cache/data9/adaptor.mars.internal-1589409962.237314-17413-23-a3d17535-b485-456b-b173-d9b09f9d2b5f.nc /cache/tmp/a3d17535-b485-456b-b173-d9b09f9d2b5f-adaptor.mars.internal-1589409962.2378995-17413-7-tmp.grib

The domain for this project ranges in latitude from 20N to 65N and in longitude from 170W to 50W. Here's how it looks on a map.

In [4]:
proj = ccrs.LambertConformal(central_longitude=-110.0, central_latitude=35.0)

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection=proj)

ax.stock_img()
ax.add_patch(mpatches.Rectangle(xy=[-170, 20], width=120, height=45, facecolor='red', alpha=0.2, transform=ccrs.PlateCarree()))
ax.set_extent([-180, -30, 10, 70], crs=ccrs.PlateCarree())
ax.gridlines()
ax.coastlines()

plt.show()

Read the time, pressure level, latitude, longitude, and geopotential variables.

In [5]:
time = ds['time'].values # numpy datetime64 object
lvl = ds['level'].values # units of hPa
lat = ds['latitude'].values # units of degrees (postitive is northern hemisphere)
lon = ds['longitude'].values # units of degrees (negative is western hemisphere)

geopot = ds['z'].values # units of m**2 / s**2

# Compute the geopotential height [m] from the geopotential variable
geopot_hght = geopot / 9.81

print(time.shape, lvl.shape, lat.shape, lon.shape, geopot_hght.shape)
(32,) (11,) (181,) (481,) (32, 11, 181, 481)

The geopotential height variable needs to be trimmed if we're only interested in one time period and one pressure level...

In [6]:
# Obtain the time index that corresponds to 1200 UTC 07 February
time_ind = np.where(time==np.datetime64('2020-02-07T12:00:00.00'))[0][0]

# Obtain the pressure level index that corresponds to 500 hPa
lvl_ind = np.where(lvl==500.)[0][0]

geopot_hght_trimmed = geopot_hght[time_ind, lvl_ind, :, :]

To make a filled contour plot we need to transform the latitude and longitude arrays into bins.

In [7]:
[X, Y] = np.meshgrid(lon, lat)
print(X.shape, Y.shape)
(181, 481) (181, 481)

Finally, time to plot the geopotential height! Sections of this cell block are optional and for aesthetic purposes only.

In [8]:
proj = ccrs.Mercator() # You can use other projections, just make sure that PlateCarree is used in any transform arguments below.

fig = plt.figure(figsize=(16, 12))
ax = fig.add_subplot(111, projection=proj)

plt1 = ax.contourf(X, Y, geopot_hght_trimmed/10., levels=np.linspace(480, 600, 41), cmap='nipy_spectral', transform=ccrs.PlateCarree())
plt2 = ax.contour(X, Y, geopot_hght_trimmed/10., levels=np.linspace(480, 600, 21), colors='black', linewidths=2, transform=ccrs.PlateCarree())
ax.clabel(plt2, np.linspace(480, 600, 11), inline=True, fmt='%d', fontsize=14)
ax.set_title('500-hPa Geopotential Height [dm]\n1200 UTC 07 Feb 2020', fontsize=16)

# Add some additional borders (optional)
states = cfeature.NaturalEarthFeature(category='cultural', name='admin_1_states_provinces_lines', scale='50m', facecolor='none')
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.LAKES, facecolor='none', edgecolor='black')
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.BORDERS)
ax.add_feature(states, edgecolor='black')

# Format the gridlines (optional)
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--')
gl.xlabels_top = False
gl.ylabels_right = False
gl.xlines = True; gl.xlocator = mticker.FixedLocator(np.linspace(-170, -50, 13)); gl.xformatter = LONGITUDE_FORMATTER; gl.xlabel_style = {'size':16, 'color':'black'}
gl.ylines = True; gl.ylocator = mticker.FixedLocator(np.linspace(20, 65, 10)); gl.yformatter = LATITUDE_FORMATTER; gl.ylabel_style = {'size':16, 'color':'black'}

# Plot the colorbar
cbar_ax = fig.add_axes([0, 0, 0.1, 0.1]) # Dummy values prior to finetuning the cbar position
pos = ax.get_position() # Get the axes position
cbar_ax.set_position([pos.x0 + pos.width + 0.01, pos.y0, 0.04, pos.height])
cbar = plt.colorbar(plt1, cax=cbar_ax)
cbar_ax.tick_params(labelsize=16)

plt.show()

Plot Pressure on Dynamic Tropopause Surface

The first step is to load the ERA5 hourly data on potential vorticity levels dataset for your case. In this example we'll look at the 05-08 February 2020 period.

As a rule of thumb, the dynamic tropopause is often considered to be found where potental vorticity values are around 2 PVU in the mid-latitudes.

In [9]:
filename = '/home/disk/meso-home/jfinlon/ERA5_Data/20200205_20200208/era5.data_on_pv_levels.2020020500_2020020821.nc'
ds2 = xr.open_dataset(filename) # This loads the NetCDF file as an xarray object
ds2
Out[9]:
Show/Hide data repr Show/Hide attributes
xarray.Dataset
    • latitude: 181
    • longitude: 481
    • time: 32
    • longitude
      (longitude)
      float32
      -170.0 -169.75 ... -50.25 -50.0
      units :
      degrees_east
      long_name :
      longitude
      array([-170.  , -169.75, -169.5 , ...,  -50.5 ,  -50.25,  -50.  ],
            dtype=float32)
    • latitude
      (latitude)
      float32
      65.0 64.75 64.5 ... 20.5 20.25 20.0
      units :
      degrees_north
      long_name :
      latitude
      array([65.  , 64.75, 64.5 , 64.25, 64.  , 63.75, 63.5 , 63.25, 63.  , 62.75,
             62.5 , 62.25, 62.  , 61.75, 61.5 , 61.25, 61.  , 60.75, 60.5 , 60.25,
             60.  , 59.75, 59.5 , 59.25, 59.  , 58.75, 58.5 , 58.25, 58.  , 57.75,
             57.5 , 57.25, 57.  , 56.75, 56.5 , 56.25, 56.  , 55.75, 55.5 , 55.25,
             55.  , 54.75, 54.5 , 54.25, 54.  , 53.75, 53.5 , 53.25, 53.  , 52.75,
             52.5 , 52.25, 52.  , 51.75, 51.5 , 51.25, 51.  , 50.75, 50.5 , 50.25,
             50.  , 49.75, 49.5 , 49.25, 49.  , 48.75, 48.5 , 48.25, 48.  , 47.75,
             47.5 , 47.25, 47.  , 46.75, 46.5 , 46.25, 46.  , 45.75, 45.5 , 45.25,
             45.  , 44.75, 44.5 , 44.25, 44.  , 43.75, 43.5 , 43.25, 43.  , 42.75,
             42.5 , 42.25, 42.  , 41.75, 41.5 , 41.25, 41.  , 40.75, 40.5 , 40.25,
             40.  , 39.75, 39.5 , 39.25, 39.  , 38.75, 38.5 , 38.25, 38.  , 37.75,
             37.5 , 37.25, 37.  , 36.75, 36.5 , 36.25, 36.  , 35.75, 35.5 , 35.25,
             35.  , 34.75, 34.5 , 34.25, 34.  , 33.75, 33.5 , 33.25, 33.  , 32.75,
             32.5 , 32.25, 32.  , 31.75, 31.5 , 31.25, 31.  , 30.75, 30.5 , 30.25,
             30.  , 29.75, 29.5 , 29.25, 29.  , 28.75, 28.5 , 28.25, 28.  , 27.75,
             27.5 , 27.25, 27.  , 26.75, 26.5 , 26.25, 26.  , 25.75, 25.5 , 25.25,
             25.  , 24.75, 24.5 , 24.25, 24.  , 23.75, 23.5 , 23.25, 23.  , 22.75,
             22.5 , 22.25, 22.  , 21.75, 21.5 , 21.25, 21.  , 20.75, 20.5 , 20.25,
             20.  ], dtype=float32)
    • time
      (time)
      datetime64[ns]
      2020-02-05 ... 2020-02-08T21:00:00
      long_name :
      time
      array(['2020-02-05T00:00:00.000000000', '2020-02-05T03:00:00.000000000',
             '2020-02-05T06:00:00.000000000', '2020-02-05T09:00:00.000000000',
             '2020-02-05T12:00:00.000000000', '2020-02-05T15:00:00.000000000',
             '2020-02-05T18:00:00.000000000', '2020-02-05T21:00:00.000000000',
             '2020-02-06T00:00:00.000000000', '2020-02-06T03:00:00.000000000',
             '2020-02-06T06:00:00.000000000', '2020-02-06T09:00:00.000000000',
             '2020-02-06T12:00:00.000000000', '2020-02-06T15:00:00.000000000',
             '2020-02-06T18:00:00.000000000', '2020-02-06T21:00:00.000000000',
             '2020-02-07T00:00:00.000000000', '2020-02-07T03:00:00.000000000',
             '2020-02-07T06:00:00.000000000', '2020-02-07T09:00:00.000000000',
             '2020-02-07T12:00:00.000000000', '2020-02-07T15:00:00.000000000',
             '2020-02-07T18:00:00.000000000', '2020-02-07T21:00:00.000000000',
             '2020-02-08T00:00:00.000000000', '2020-02-08T03:00:00.000000000',
             '2020-02-08T06:00:00.000000000', '2020-02-08T09:00:00.000000000',
             '2020-02-08T12:00:00.000000000', '2020-02-08T15:00:00.000000000',
             '2020-02-08T18:00:00.000000000', '2020-02-08T21:00:00.000000000'],
            dtype='datetime64[ns]')
    • pt
      (time, latitude, longitude)
      float32
      ...
      units :
      K
      long_name :
      Potential temperature
      [2785952 values with dtype=float32]
    • pres
      (time, latitude, longitude)
      float32
      ...
      units :
      Pa
      long_name :
      Pressure
      [2785952 values with dtype=float32]
  • Conventions :
    CF-1.6
    history :
    2020-05-14 05:40:56 GMT by grib_to_netcdf-2.16.0: /opt/ecmwf/eccodes/bin/grib_to_netcdf -S param -o /cache/data9/adaptor.mars.external-1589434834.904225-4977-7-857195ac-4a5f-46a2-9e22-6a6b723e7b2c.nc /cache/tmp/857195ac-4a5f-46a2-9e22-6a6b723e7b2c-adaptor.mars.external-1589434834.9047382-4977-3-tmp.grib

Read the time, latitude, longitude, and pressure variables.

In [10]:
time = ds2['time'].values # numpy datetime64 object
lat = ds2['latitude'].values # units of degrees (postitive is northern hemisphere)
lon = ds2['longitude'].values # units of degrees (negative is western hemisphere)

pres = ds2['pres'].values / 100. # units of hPa

print(time.shape, lat.shape, lon.shape, pres.shape)
(32,) (181,) (481,) (32, 181, 481)

The pressure variable needs to be trimmed if we're only interested in one time period...

In [11]:
# Obtain the time index that corresponds to 1200 UTC 07 February
time_ind = np.where(time==np.datetime64('2020-02-07T12:00:00.00'))[0][0]

pres_trimmed = pres[time_ind, :, :]

Finally, time to plot the dynamic tropopause pressure! Sections of this cell block are optional and for aesthetic purposes only.

In [12]:
[X, Y] = np.meshgrid(lon, lat)
proj = ccrs.Mercator() # You can use other projections, just make sure that PlateCarree is used in any transform arguments below.

fig = plt.figure(figsize=(16, 12))
ax = fig.add_subplot(111, projection=proj)

plt1 = ax.contourf(X, Y, pres_trimmed, levels=np.linspace(40, 720, 35), cmap='nipy_spectral_r', transform=ccrs.PlateCarree())
ax.set_title('Dynamic Tropopause Pressure [hPa]\n1200 UTC 07 Feb 2020', fontsize=16)

# Add some additional borders (optional)
states = cfeature.NaturalEarthFeature(category='cultural', name='admin_1_states_provinces_lines', scale='50m', facecolor='none')
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.LAKES, facecolor='none', edgecolor='black')
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.BORDERS)
ax.add_feature(states, edgecolor='black')

# Format the gridlines (optional)
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--')
gl.xlabels_top = False
gl.ylabels_right = False
gl.xlines = True; gl.xlocator = mticker.FixedLocator(np.linspace(-170, -50, 13)); gl.xformatter = LONGITUDE_FORMATTER; gl.xlabel_style = {'size':16, 'color':'black'}
gl.ylines = True; gl.ylocator = mticker.FixedLocator(np.linspace(20, 65, 10)); gl.yformatter = LATITUDE_FORMATTER; gl.ylabel_style = {'size':16, 'color':'black'}

# Plot the colorbar
cbar_ax = fig.add_axes([0, 0, 0.1, 0.1]) # Dummy values prior to finetuning the cbar position
pos = ax.get_position() # Get the axes position
cbar_ax.set_position([pos.x0 + pos.width + 0.01, pos.y0, 0.04, pos.height])
cbar = plt.colorbar(plt1, cax=cbar_ax)
cbar_ax.tick_params(labelsize=16)

plt.show()
In [ ]: