Introduction¶
In the previous chapter, we looked at time averaged quantities.
It’s often going to be useful to consider this total flow as consisting of a mean and a fluctuating part.
Time mean and zonal mean¶
We consider two means that are often used: time mean and zonal mean, along with deviations from these means.
We define the time mean as
and the zonal mean as
where is time, is the total time interval over which we are averaging, is longitude, and is whatever variable we are averaging.
This introduces a standardized notation, where the overbar will always indicate time averages and the square brackets will be reserved for zonal averages.
Example¶
Prove that the zonal and time mean mass flux across any latitude circle must vanish, for time scales greater than a year.
Let’s start with the continuity (mass) equation in pressure coordinates:
which we can write out in spherical coordinates like so:
So if we take the time and zonal average of the continuity equation we get
which we can also write as
The first term is zero. Why?
Let’s now integrate over the depth of the atmosphere, from surface pressure to zero:
The second term is the integral of a derivative so resolves to only boundary contributions. At the TOA , and so the term simplifies to just
We cannot have flow in or out of the surface. The boundary condition is complicated by the fact that the surface pressure is not constant in time, but over timescales of a year or more we do not expect any time-averaged changes in surface pressure. Therefore
and the average mass budget reduces to
so
for some constant
The integral above is proportional to the next mass flux across latitude circles. So what is the constant ?
One way to rationalize this is to consider the polar limit where and thus . This would then require that the integral becomes unboundedly large if .
We conclude that for all , and thus the zonal and time mean mass flxu across any latitude circle must vanish.
Departures from the mean¶
Let’s introduce some more standardized notation for departures from our two means
Departure from time mean¶
Departure from zonal mean¶
Some identities¶
Note that
and
as long as the averaging is the same as that used to find the perturbations.
Also the averages permute:
Combining zonal and time expansions¶
We can decompose the full field into four terms:
where our four terms are
Zonally symmetric part of the steady time-averaged quantities, e.g. trade winds and midlatitude westerlies
Asymmetric part of the time-averaged quantities, e.g. monsoon circulations, PNA, NAO (patterns which vary on only low frequencies)
Instantaneous fluctutations of symmetric part of quantities, e.g. time fluctuations in Hadley cells, mean jet locations
Instantaneous, zonally asymmetric part of quantities, e.g. transient cyclones and anticyclones
Examples using some real data¶
We are going to load in some zonal wind data from the ERA5 dataset.
For simplicity we’ll just work with a few months of data here from a single year, and focus on a single pressure level (500 hPa, mid-troposphere).
import numpy as np
import matplotlib.pyplot as plt
import xarray as xr
import cartopy.crs as ccrs
from arraylake import Client as alClient# ## If running this notebook on jupyterlab.its.albany.edu, uncomment and run this cell
# ## This sets up a cluster of workers to parallelize the calculations
# from dask_jobqueue import SLURMCluster
# from dask.distributed import Client as dClient
# cluster = SLURMCluster(cores=64, # size of a single job -- typically one node of the HPC cluster
# memory="374GB", # the max memory on the 64-core burst-daes nodes, I believe
# walltime="01:00:00",
# queue="burst-daes",
# )
# cluster.scale(1)
# dclient = dClient(cluster)
# dclientalclient = alClient()
alclient.login()repo = alclient.get_repo("earthmover-public/era5")
session = repo.readonly_session("main")
pressure_spatial = xr.open_zarr(session.store, group="pressure/spatial", chunks={})
pressure_spatialu500 = pressure_spatial.u.sel(pressure_level = 500., valid_time='2021')
u500# instantaneous u on one day
data = u500.sel(valid_time='2021-01-01T00').compute()
dataLet’s make a helper function to draw maps of lat-lon data:
def make_map(field, levels=np.linspace(-75,75,31), title=None):
p = field.plot.contourf(levels=levels,
subplot_kws={'projection': ccrs.EqualEarth()},
transform=ccrs.PlateCarree())
p.axes.coastlines()
if title is not None:
plt.title(title)
return pAnd make a map of the instantaneous on a particular day and time:
make_map(data);
To do some seasonal averaging, we need to pick a time interval. Let’s choose all dates in January, February and March.
This is easy to do by slicing on the time axis:
# A time slice containing all of January, February and March
u500_JFM = u500.sel(valid_time=slice('2021-01-01','2021-03-31'))
u500_JFMThis array has 2160 points on the time axis, corresponding to 31+28+31 = 90 days @ 24 times daily.
Let’s make a map of by taking a time average:
ubar = u500_JFM.mean(dim='valid_time').compute()
m = make_map(ubar, title=r'$\overline{u}$ at 500 hPa for January - March 2021')
If we further take the zonal average, we get
Since this quantity is one dimensional (latitude only), we’ll make a line plot instead of a map:
ubar.mean(dim=('longitude')).plot(y='latitude')
plt.title(r'$[\overline{u}]$ at 500 hPa for January - March 2021')
plt.grid();
Now here’s , the time averaged departures from zonal mean:
ustar = u500_JFM - u500_JFM.mean(dim='longitude')
ustarbar = ustar.mean(dim='valid_time').compute()
m = make_map(ustarbar, title=r'$\overline{u^*}$ at 500 hPa for January - March 2021')
And here’s (departures from time average) at one instant:
uprime = u500_JFM - ubar
make_map(uprime.sel(valid_time='2021-01-01T00'));
and its zonal average as a line plot:
uprime.sel(valid_time='2021-01-01T00').mean(dim='longitude').plot(y='latitude')
And finally the deviations away from that zonal average
make_map((uprime - uprime.mean(dim='longitude')).sel(valid_time='2021-01-01T00'))<cartopy.mpl.contour.GeoContourSet at 0x36d051810>
Covariances between quantities¶
We are often interested in covariances between two quantities, usually a component of the flow and another scalar (e.g. meridional wind and temperature)
The last term is the covariance of and in time.
Similarly for the zonal average
where the last term is the covariance of and is longitude.
We can also combine the time and zonal means
Three terms:
Mean meridional component
Stationary eddy component (zonal variations in time mean
Transient eddy component (time variations in time mean)
Example: the meridional heat flux at 500 hPa¶
Let and
and let’s consider the decomposition of the heat flux
Actually we are going to subtract the global average from the temperature data first so that the numbers are easier to interpret!
We will focus here on the single month of January 2021
jan2021 = slice('2021-01-01','2021-01-31')
v500 = pressure_spatial.v.sel(pressure_level = 500., valid_time=jan2021)
T500 = pressure_spatial.t.sel(pressure_level = 500., valid_time=jan2021)
T500# Compute the area-weighted global mean on lat-lon grid
T500_global = T500.mean(dim='valid_time').weighted(np.cos(np.deg2rad(T500.latitude))).mean(dim=('latitude','longitude')).compute()
T500_globalFirst we can compute the full field :
vTbar = (v500 * (T500 - T500_global)).mean(dim='valid_time')
levs = np.linspace(-180,180, 19)
make_map(vTbar, levels=levs, title=r'$\overline{vT}$ at 500 hPa for January 2021')<cartopy.mpl.contour.GeoContourSet at 0x3363d3ed0>
How much of the full field is captured by the time mean advection of the time mean temperature?
(Note, the time means here include any eddies that are stationary on our averaging timescale, which is seasonal here)
vbar = v500.mean(dim='valid_time').compute()
Tbar = T500.mean(dim='valid_time') - T500_global
make_map(vbar * Tbar, levels=levs, title=r'$\overline{v}~\overline{T}$ at 500 hPa for January 2021')<cartopy.mpl.contour.GeoContourSet at 0x32eb49090>
And now let’s look at the transient eddy contribution
vprime = v500 - vbar
Tprime = (T500 - T500_global) - Tbar
vprime_Tprime_bar = (vprime*Tprime).mean(dim='valid_time')
make_map(vprime_Tprime_bar, title=r'$\overline{v^\prime T^\prime}$ at 500 hPa for January 2021')<cartopy.mpl.contour.GeoContourSet at 0x339508550>
vTbar_zon = vTbar.mean(dim='longitude')
vbar_zon = vbar.mean(dim='longitude')
Tbar_zon = Tbar.mean(dim='longitude')
vbarzon_Tbarzon = vbar_zon * Tbar_zon
vbar_star = vbar - vbar_zon
Tbar_star = Tbar - Tbar_zon
vbarstar_Tbarstar_zon = (vbar_star * Tbar_star).mean(dim='longitude')
vprime_Tprime_bar_zon = vprime_Tprime_bar.mean(dim='longitude')
vTbar_zon.plot(y='latitude', label=r'Total, $[\overline{vT}]$')
vbarzon_Tbarzon.plot(y='latitude', label=r'Mean meridional, $[\overline{v}][\overline{T}]$')
vbarstar_Tbarstar_zon.plot(y='latitude', label=r'Stationary eddy, $[\overline{v}^* \overline{T}^*]$')
vprime_Tprime_bar_zon.plot(y='latitude', label=r'Transient eddy, $[\overline{v^\prime T^\prime}]$')
plt.legend()
plt.grid()
plt.title('Zonal mean heat flux decomposition at 500 hPa for January 2021');
What does it mean to have a positive “stationary eddy” component?¶
The plot above shows that there are maximum in poleward heat flux near 50º latitude in both hemispheres. While the transient eddy term (red) is large in both hemispheres, there is a substantial positive (northward) stationary eddy contribution in the Northern Hemisphere.
What is this, and what does it mean?
Map of zonal temperature anomalies ¶
Let’s take a quick look at the map of to get a sense of its contribution to
make_map(Tbar_star, levels=np.linspace(-10,10,21), title=r'$\overline{T}^*$ at 500 hPa for January 2021');
Focussing on the Northern mid-latitudes, there is a prominent wavenumber-2 pattern with alternating warm and cold anomalies. The warm anomalies are centered over the eastern parts of the ocean basins, while the cold anomalies are centered over the eastern continents.
This looks like a “smoothed out” version of the zonal temperature anomalies at 1000 hPa that we’ve already seen.
Map of zonal wind anomalies ¶
Similarly we can plot and look for spatial covariance between the anomalous meridional flow and the anomalous temperature fields:
make_map(vbar_star, levels=np.linspace(-12,12,25), title=r'$\overline{v}^*$ at 500 hPa for January 2021');
Here, focussing again on the Northern mid-latitudes, we can see that there are regions where the seasonally-averaged meridional flow is anomalously northward (red) and southward (blue).
The key point is that there is covariance between these two fields: the regions of northward flow (over the ocean basins) tend to also be regions of anomalously warm temperatures, while the regions of southward flow (over the continents) tend to also be regions of anomalously cold temperatures.
Thus the product is positive in the zonal average, even though the individual zonal averages , are both zero by definition!
Zonal mean heat fluxes in the opposite season¶
Let’s just repeat the whole calculation for June through August 2021.
First though, we’ll wrap our calculation and plotting code in functions for easier repetition with different time periods.
def calc_zonal_heat_flux(dateslice, lev=500):
v500 = pressure_spatial.v.sel(pressure_level = 500., valid_time=dateslice)
T500 = pressure_spatial.t.sel(pressure_level = 500., valid_time=dateslice)
T500_global = T500.mean(dim='valid_time').weighted(np.cos(np.deg2rad(T500.latitude))).mean(dim=('latitude','longitude')).compute()
vTbar = (v500 * (T500 - T500_global)).mean(dim='valid_time')
vbar = v500.mean(dim='valid_time')
Tbar = T500.mean(dim='valid_time') - T500_global
vTbar_zon = vTbar.mean(dim='longitude')
vbar_zon = vbar.mean(dim='longitude')
Tbar_zon = Tbar.mean(dim='longitude')
vprime = v500 - vbar
Tprime = (T500 - T500_global) - Tbar
vprime_Tprime_bar = (vprime*Tprime).mean(dim='valid_time')
vbarzon_Tbarzon = vbar_zon * Tbar_zon
vbar_star = vbar - vbar_zon
Tbar_star = Tbar - Tbar_zon
vbarstar_Tbarstar_zon = (vbar_star * Tbar_star).mean(dim='longitude')
vprime_Tprime_bar_zon = vprime_Tprime_bar.mean(dim='longitude')
decomp = {'vTbar': vTbar_zon,
'vbar_Tbar': vbarzon_Tbarzon,
'vbarstar_Tbarstar': vbarstar_Tbarstar_zon,
'vprime_Tprime_bar': vprime_Tprime_bar_zon}
return decomp
def plot_zonal_heat_flux(decomp):
decomp['vTbar'].plot(y='latitude', label=r'Total, $[\overline{vT}]$')
decomp['vbar_Tbar'].plot(y='latitude', label=r'Mean meridional, $[\overline{v}][\overline{T}]$')
decomp['vbarstar_Tbarstar'].plot(y='latitude', label=r'Stationary eddy, $[\overline{v}^* \overline{T}^*]$')
decomp['vprime_Tprime_bar'].plot(y='latitude', label=r'Transient eddy, $[\overline{v^\prime T^\prime}]$')
plt.legend()
plt.grid()
return plot_zonal_heat_flux(calc_zonal_heat_flux(dateslice=slice('2021-07-01','2021-07-31')))
plt.title('Zonal mean heat flux decomposition at 500 hPa for July 2021');
Here we can see a few differences:
The NH peak is much smaller in summer than winter (by a factor of ~4)
The SH peak is stronger in its summer, but the winter-summer contrast in the SH is smaller than in the NH
Unlike in NH winter, there is very little stationary eddy contribution in the SH winter
Dependence on averaging period¶
The decomposition between transient and stationary fluxes depend on the chosen averaging period.
For very short time periods, . The transient eddies disappear and all fluctuations appear as stationary eddies.
For timescales of ~3 months, the stationary eddies are seasonal features such as the Aleutian Low, and transient eddies are anything with residence times < 3 months (e.g. traveling synoptic systems)
At 12 month timescale, we have mostly transient eddies but still some stationary features due to orographic forcing.
Zonal mean flux over 1 week¶
Let’s repeat a calculation but change the averaging period from 1 months down to 1 week:
plot_zonal_heat_flux(calc_zonal_heat_flux(dateslice=slice('2021-01-01','2021-01-07')))
plt.title('Zonal mean heat flux decomposition at 500 hPa for Jan 1 to Jan 7 2021');
Zonal mean heat flux over 1 year¶
And do the whole year:
plot_zonal_heat_flux(calc_zonal_heat_flux(dateslice=slice('2021-01-01','2021-12-31')))
plt.title('Zonal mean heat flux decomposition at 500 hPa for all of 2021');
We can see that the partition between stationary eddy and transient eddy is particularly sensitive to our choice of averaging interval.