Open In Colab   Open in Kaggle

Tutorial 3: Atmospheric Circulation#

Week 1, Day 2, Ocean-Atmosphere Reanalysis

Content creators: Momme Hell

Content reviewers: Katrina Dobson, Danika Gupta, Maria Gonzalez, Will Gregory, Nahid Hasan, Sherry Mi, Beatriz Cosenza Muralles, Jenna Pearson, Chi Zhang, Ohad Zivan

Content editors: Jenna Pearson, Chi Zhang, Ohad Zivan

Production editors: Wesley Banfield, Jenna Pearson, Chi Zhang, Ohad Zivan

Our 2023 Sponsors: NASA TOPS and Google DeepMind

Tutorial Objectives#

In the previous tutorial you examined atmospheric surface temperatures. Spatial variations in surface temperature created by uneven solar radiation (e.g., between the Equator and poles), are one of the main drivers of global-scale air movement. Other processes such as the Earth’s rotation, storm tracks, and surface topography can also influence global wind patterns.

By the end of this tutorial, you will be able to:

  • Describe the seasonal variations in surface winds.

  • Calculate seasonal climatologies and create global maps.

  • Compare statistics derived from these climatologies.

In this tutorial, you will again utilize the monthly-mean surface wind fields from ERA5 over a 30-year period.


# installations ( uncomment and run this cell ONLY when using google colab or kaggle )

# !pip install pythia_datasets
# !pip install cartopy
# !pip install geoviews

# !pip install boto3 --quiet
# !pip install intake
from intake import open_catalog
import matplotlib.pyplot as plt
import matplotlib
import os
import pooch
import tempfile
import boto3
import botocore
import numpy as np
import xarray as xr
import warnings
import datetime
from cartopy import crs as ccrs, feature as cfeature

#  Suppress warnings issued by Cartopy when downloading data files

Plotting Functions#

Hide code cell source
# @title Plotting Functions

def set_projection_figure(projection=ccrs.PlateCarree(), figsize=(5, 4.5)):
    # source:

    projLccNY = projection  # ccrs.LambertConformal(central_longitude=cLon, central_latitude=cLat)

    fig = plt.figure(figsize=figsize)
    ax = plt.subplot(1, 1, 1, projection=projLccNY)

    # ax.add_feature(cfeature.STATES)
    # ax.add_feature(cfeature.RIVERS)
    return fig, ax

def format_axes(ax):
    ax.add_feature(cfeature.LAKES, edgecolor="black", facecolor="None", alpha=0.3)
    gl = ax.gridlines(
        draw_labels=True, linewidth=1, color="black", alpha=0.5, linestyle="--"
    gl.xlocator = matplotlib.ticker.MaxNLocator(7)
    gl.ylocator = matplotlib.ticker.MaxNLocator(5)
    gl.xlabels_top = False
    gl.ylabels_left = False
    # gl.xlines = False

# helping functions:
def geographic_lon_to_360(lon):
    return 360 + lon

def inverted_geographic_lon_to_360(lon):
    return lon - 180

def cbar_label(DD):
    return DD.attrs["long_name"] + " [" + DD.attrs["units"] + "]"

Helper functions#

Hide code cell source
# @title Helper functions

def pooch_load(filelocation=None, filename=None, processor=None):
    shared_location = "/home/jovyan/shared/Data/tutorials/W1D2_StateoftheClimateOceanandAtmosphereReanalysis"  # this is different for each day
    user_temp_cache = tempfile.gettempdir()

    if os.path.exists(os.path.join(shared_location, filename)):
        file = os.path.join(shared_location, filename)
        file = pooch.retrieve(
            fname=os.path.join(user_temp_cache, filename),

    return file

Figure Settings#

Hide code cell source
# @title Figure Settings
import ipywidgets as widgets  # interactive display

%config InlineBackend.figure_format = 'retina'

Video 1: The Wind Driven Circulation#

Section 1: Surface Winds#

The large-scale atmospheric circulation significantly influences the climate we experience at different latitudes. The global atmospheric circulation is often depicted as a three-cell structure, as visualized in this figure showing the surface winds:

Earth Global Circulation - en.svg.

This schematic of atmospheric circulation cells reveal meridional (north-south) and zonal (east-west) components of the large-scale surface winds.

Let’s see if you are able to detect these large-scale patterns in reanalysis data! For this you will load ERA5 wind data from Pangeo catalog

Section 1.1: Annual Mean Wind Speed#

You should start investigating this data by examining the global surface winds. These winds are represented as vectors, consisting of a zonal component denoted as u10 and a meridional component denoted as v10.

Recall from the previous tutorial that the magnitude of the wind vector represents the wind speed you will use later in the tutorial, given by:

(2)#\[\begin{align} ||u|| = \sqrt{u^2 + v^2} \end{align}\]

To examine long-term changes in the wind field you should visualize the zonal wind component \(u\) and the meridional wind component \(v\) with monthly-mean data. With the help of xarray, you can derive monthly means from higher-resolution data (such as those used in tutorial 2) using the xr.resample('1M').mean('time') function.

For your convenience, we have already performed this step, and you can load the data using the following instructions:

  • The variable si10 represents the wind speed in this dataset.

  • To calculate the long-term mean, we selected 30 years of data ranging from 1980 to 2010.

Let’s grab the reanalysis data from before that we have prepocessed (noting naming convention changes):

# note this can take a few minutes to download
filename_era5_mm = ""
url_era5_mm = ""

ERA5_mm = xr.open_dataset(pooch_load(url_era5_mm, filename_era5_mm)).sel(
    time=slice("1980", "2010")
)  # select the timeslice during loading
Downloading data from '' to file '/tmp/'.
SHA256 hash of downloaded file: 8899062c8e9ef24bcbf947e7c618aa7b4ada26b4e0fb5611d0a94850ac75f0d5
Use this value as the 'known_hash' argument of 'pooch.retrieve' to ensure that the file hasn't changed if it is downloaded again in the future.
<xarray.Dataset> Size: 4GB
Dimensions:    (longitude: 1440, latitude: 721, time: 312)
  * longitude  (longitude) float32 6kB 0.0 0.25 0.5 0.75 ... 359.2 359.5 359.8
  * latitude   (latitude) float32 3kB 90.0 89.75 89.5 ... -89.5 -89.75 -90.0
  * time       (time) datetime64[ns] 2kB 1980-01-01 1980-02-01 ... 2005-12-01
Data variables:
    u10        (time, latitude, longitude) float32 1GB ...
    v10        (time, latitude, longitude) float32 1GB ...
    si10       (time, latitude, longitude) float32 1GB ...
    Conventions:  CF-1.6
    history:      2023-04-06 23:25:45 GMT by grib_to_netcdf-2.25.1: /opt/ecmw...

Now take the annual mean zonal surface wind speed.

# take the time mean
ERA5_ANN = ERA5_mm.mean("time")

To visualize this data, we will be using some helper functions defined in the beginning of the tutorial just to set the map features and aesthetics. Please take a look at them if you are interested in seeing how they work.

# adjust colorlevels to weaker amplitudes
colorlevels_clim = np.arange(-10, 11, 1)

var = "u10"  # select our variable
fig, ax = set_projection_figure(
    projection=ccrs.PlateCarree(), figsize=(9, 5.5)
)  # same plot function as Part I
ax.set_title("Mean " + var, loc="left")
dataplot = ax.contourf(
fig.colorbar(dataplot, orientation="vertical", label="m/s", shrink=0.55, pad=0.08)
<matplotlib.colorbar.Colorbar at 0x7f37e0ea6310>

In the zonal wind speed figure you created, there are two distinct wind bands between 35 to 65 degrees both north and south of the equator that blow from west to east (red, positive wind speeds). These mid-latitude wind bands are known as the westerlies. Additionally, you can see that winds predominantly blow from the east to the west (blue, negative wind speeds) in the tropics (less than 30° N/S), and are referred to as the easterlies.

Coding Exercise 1.1#

Reproduce the previous figure, but modify it to plot meridional wind rather than zonal wind

var = ...
fig, ax = set_projection_figure(projection=ccrs.PlateCarree(), figsize=(9, 5.5))
ax.set_title("Mean " + str(var), loc="left")
# dataplot = ax.contourf(
#     ERA5_ANN.longitude,
#     ERA5_ANN.latitude,
#     ERA5_ANN[var],
#     levels=colorlevels_clim,
#     transform=ccrs.PlateCarree(),
# )
# fig.colorbar(dataplot, orientation="vertical", label="m/s", shrink=0.55, pad=0.08)
Text(0.0, 1.0, 'Mean Ellipsis')

Click for solution

Example output:

Solution hint

There are strong southward winds in the subtropics of the Northern Hemisphere (blue, negative wind speed), and northward winds in the subtropics of the Southern Hemisphere (red, positive wind speed). The meridional winds are strongest on the western side of the continents.

Questions 1.1#

  1. Among the three atmospheric “cells” (the Hadley Cell, Ferrel Cell, and Polar Cell) depicted in the figure from Section 1 above, which ones correspond to the zonal wind bands that we visualized in the first plot above?

  2. How do the zonal and meridional winds compare in magnitude, longitude, and latitude?

Click for solution

Section 1.2 : Zonal-mean Wind Speed#

To examine the latitudinal variation in the surface winds, you can plot the zonal mean of the annual mean zonal and meridional winds:

fig, ax = plt.subplots(figsize=(9, 5.5))
    label="zonal wind", ax=ax
)  # find the u10 mean value over the latitude
    label="meridional wind", ax=ax
)  # find the v10 mean value over the latitude
ax.legend()  # add legend
ax.set_title("Annual mean, zonal mean winds", loc="left")  # add title
ax.set_ylabel("wind speed (m/s)")
ax.axhline(0, color="black")  # add a black line at x=0
<matplotlib.lines.Line2D at 0x7f37e968c4f0>

Questions 1.2#

  1. Considering the zonal-mean wind speed figure provided, what factors contribute to the noticeable disparities between the Northern and Southern Hemispheres in terms of wind patterns?

Click for solution

Section 2: Monthly Climatology#

Now, you should examine how the zonal mean winds vary throughout the seasons. You will focus on the zonal wind component and create a special type of diagram called a Hoffmöller diagram. In this diagram, the horizontal axis represents latitude, while the vertical axis represents time.

By using the Hoffmöller diagram, you can visualize how the average east-west winds change with each season.

# the groupby command regroups the data by month, such that all Januarys, all Februaries, .. and so on are taken the mean over
# note how we use several commands in one line. python perfom them by order. slice (long, u10), groupby, mean and lastely plot.
<matplotlib.collections.QuadMesh at 0x7f37e9516e20>

Coding Exercises 2#

Extend the above analysis to create a Hoffmöller diagram of the meridional winds.

# ERA5_mm.mean("longitude")[...].groupby("time.month").mean().plot()

Click for solution

Example output:

Solution hint


  • The winds in the Southern Hemisphere appear to be generally stronger compared to the Northern Hemisphere.

  • The period between June and September shows strong meridional winds. These winds result from the seasonal variation of the Hadley cell. During the winter hemisphere in each respective hemisphere, the Hadley cell becomes much stronger, leading to the intensification of meridional winds.

Bonus exercise#

  • Plot the global map for DJF and JJA of the zonal wind. What do you see when you compare the mid-latitudes? (you can also plot their difference!)

  • Plot trend of the zonal mean zonal wind in DJF

That is because the solar insulation leads to varying circulation patterns with seasons. More about this you can find in Global Physical Climatology, The Atmospheric General Circulation or the first few chapters of this evolving draft Physics of Earth’s Climate

# note, this codes takes a while to run
ERA5_season = ...

F, ax = set_projection_figure(projection = ccrs.PlateCarree(), figsize = (9, 5.5) )
ax.set_title('Zonal Wind DJF mean | '+ var , loc ='left')
dataplot = ...
_ = ... # colorbar

F, ax = set_projection_figure(projection = ccrs.PlateCarree(), figsize = (9, 5.5) )
ax.set_title('Zonal Wind DJF mean | '+ var , loc ='left')
dataplot = ...
_ = ... # colorbar

# difference:
pdata = ...

F, ax = set_projection_figure(projection = ccrs.PlateCarree(), figsize = (9, 5.5) )
ax.set_title(... , loc ='left')
dataplot = ...
_ = ... # colorbar
../../../_images/10355bf8f5f9737cc1c22a30abcf5ea35ca2ff7ffcc10ef2edbdf74413fb96c9.png ../../../_images/10355bf8f5f9737cc1c22a30abcf5ea35ca2ff7ffcc10ef2edbdf74413fb96c9.png ../../../_images/d6226428625ea88fca65feacb0f63e79583063285f154b55893992b6cb541ec9.png

Click for solution

Example output:

Solution hint Solution hint Solution hint

Additional Reading: Extra-tropical Storms#

In the wind speed figure, you can notice areas of strong winds over the Southern Ocean, North Pacific, and North Atlantic. These powerful winds are caused by weather systems known as extratropical storms or mid-latitude cyclones. These storms occur in the middle latitudes, between 30 and 60 degrees north or south of the equator. During winter, they are particularly strong over the Southern Ocean and the oceans in the Northern Hemisphere.

Extratropical storms form when warm and cold air masses interact. They have a low-pressure center and produce winds that circulate counterclockwise in the Northern Hemisphere and clockwise in the Southern Hemisphere. These storms can be intense, bringing strong winds, heavy rain, snow, and sleet. They often lead to problems like flooding, power outages, and disruptions in transportation.

The strength of these storms depends on factors such as the temperature difference between air masses, the speed of the jet stream, and the amount of moisture in the air. If you want to learn more about extratropical storms, you can refer to basic meteorology and atmospheric dynamics resources, or you can explore online sources such as the following:

Although an individual storm may last only a few days and doesn’t create significant ocean currents, the continuous winds and the occurrence of multiple storms over a year can influence the development of ocean currents. These currents are a response to the persistent wind patterns.


Within this tutorial, you analysed the global atmospheric circulation by using ERA5 reanalysis data. You explored the distribution of westerlies and easterlies across the globe, observed their seasonal variations. You observed that the strongest winds were found to occur over the Southern Ocean.


Data from this tutorial can be accessed here.