Group project: the Climate System¶

Instructions¶

Objectives

In this final project, you will apply the methods you learned over the past weeks to answer the questions below.

Deadline

Please submit your project via OLAT before Thursday January 12 at 00H (in the night from Wednesday to Thursday).

Formal requirements

You will work in groups of two. If we are an odd number of students, one group can have three participants. (Tip: I recommend that students who have not followed my programming class to team up with students who have).

Each group will submit one (executed) jupyter notebook containing the code, plots, and answers to the questions (text in the markdown format). Please also submit an HTML version of the notebook. Each group member must contribute to the notebook. The notebook should be self-contained and the answers must be well structured. The plots must be as understandable as possible (title, units, x and y axis labels, appropriate colors and levels…).

Please be concise in your answer. I expect a few sentences per answer at most - there is no need to write a new text book in this project! Use links and references to the literature or your class slides where appropriate.

Grading

I will give one grade per project, according to the following table (total 10 points):

  • correctness of the code and the plots: content, legends, colors, units, etc. (3 points)
  • quality of the answers: correctness, preciseness, appropriate use of links and references to literature or external resources (5 points)
  • originality and quality of the open research question (2 points)
1
2
3
4
5
6
7
8
9
# Import the tools we are going to need today:
import matplotlib.pyplot as plt  # plotting library
import numpy as np  # numerical library
import xarray as xr  # netCDF library
import pandas as pd  # tabular library
import cartopy  # Map projections libary
import cartopy.crs as ccrs  # Projections list
# Some defaults:
plt.rcParams['figure.figsize'] = (12, 5)  # Default plot size

Part 1 - temperature climatology¶

Open the ERA5 temperature data:

1
ds = xr.open_dataset(r'C:\Users\viola\Documents\Uni\WS 2022\VU Klimasystem\notebooks\ERA5_LowRes_Monthly_t2m.nc')
1
2
t2_tavg = ds.t2m.mean(dim='time')
t2c_tavg = t2_tavg - 273.15
1
2
3
4
ax = plt.axes(projection=ccrs.Robinson())
t2c_tavg.plot.imshow(ax=ax, transform=ccrs.PlateCarree(),cbar_kwargs={'label': '°C'})
plt.title('temporal mean temperature for the entire period - ERA5 1979-2018')
ax.coastlines(); ax.gridlines();
No description has been provided for this image
1
2
t_avg_dep = t2_tavg - t2_tavg.mean(dim='longitude')
#Abweichung zum zonalen Mittel
1
2
3
4
ax = plt.axes(projection=ccrs.Robinson())
t_avg_dep.plot.imshow(ax=ax, transform=ccrs.PlateCarree(),cbar_kwargs={'label': 'deviation in °C'})
plt.title('zonal temperature anomaly for the entire period - ERA5 1979-2018')
ax.coastlines(); ax.gridlines();
No description has been provided for this image
1
2
annual_cycle = ds.t2m.groupby('time.month').mean() #annual cycle
annual_cycle_range = annual_cycle.max(axis=0) - annual_cycle.min(axis=0)
1
2
3
4
ax = plt.axes(projection=ccrs.Robinson())
annual_cycle_range.plot.imshow(ax=ax, transform=ccrs.PlateCarree(),cbar_kwargs={'label': '°C'})
ax.coastlines(); ax.gridlines()
plt.title('average monthly temperature range (max-min) - ERA5 1979-2018');
No description has been provided for this image

Plot three global maps:

  • Compute and plot the temporal mean temperature $\overline{T}$ for the entire period (unit °C)
  • Compute and plot $\overline{T^{*}}$ (see lesson), the zonal anomaly map of average temperature.
  • Compute the monthly average temperature for each month $\overline{T_M}$ (annual cycle). I expect a variable of dimensions (month: 12, latitude: 241, longitude: 480). Hint: remember the .groupby() command we learned in the lesson. Now plot the average monthly temperature range, i.e. $\overline{T_M}max$ - $\overline{T_M}min$ on a map.

Questions:

  1. Look at the zonal temperature anomaly map.
    • Explain why norther Europe and the North Atlantic region is significantly warmer than the same latitudes in North America or Russia.
    • Explain why the Northern Pacific Ocean does not have a similar pattern.
  2. Look at the average monthly temperature range map.
    • Explain why the temperature range is smaller in the tropics than at higher latitudes
    • Explain why the temperature range is smaller over oceans than over land
    • Where is the temperature range largest? Explain why.

Answers:

  • Thats because of the gulf stream, which brings a lot of energy from the equitorial atlantic.
  • The gulf stream has not only a bigger flow rate than the Kuroshio but also the geographic circumstances do not let the Kuroshio flow as far north as the gulf stream.
  • In higher latitudes there are larger temperature ranges because of the seaons. The radiation energy differs more between winter and summer in higher latitudes.
  • Water changes its temperatures less than land. It has a bigger heat capacity and storages more heat. That's why water lower temperature fluctuations.
  • Sibiria is the largest continental landmass and therefore a distinct continental climate. In summer the land heats and in winter it gets super cold very quickly. Because of the large landmass in the west of sibiria and the gulf stream in front it has a even larger temperature range than North America/Canada.

Part 2 - Precipitation climatology¶

Open the precipitation file and explore it. The units of monthly precipitation are wrongly labeled (unfortunately). They should read: m per day.

1
2
ds_tp = xr.open_dataset(r'C:\Users\viola\Documents\Uni\WS 2022\VU Klimasystem\notebooks\ERA5_LowRes_Monthly_tp.nc')
#ds_tp = xr.open_dataset('Data/ERA5_LowRes_Monthly_tp.nc')
1
2
#Umrechnung
tp_mm = ds_tp.tp * 1000
1
2
3
tp_mm_avg_annual = tp_mm.groupby('time.month').mean()
tp_jan = tp_mm_avg_annual[0,:,:]
tp_aug = tp_mm_avg_annual[7,:,:]
1
2
3
4
5
ax = plt.axes(projection=ccrs.Robinson())
tp_jan.plot.imshow(ax=ax, transform=ccrs.PlateCarree(),cmap='YlGnBu',levels=[0.5, 1, 2, 3, 4, 5, 7, 10, 15, 20, 40],
                  cbar_kwargs={'label': 'precipitation in mm'})
plt.title('average daily precipitation January - ERA5 1979-2018')
ax.coastlines(); ax.gridlines();
No description has been provided for this image
1
2
3
4
5
ax = plt.axes(projection=ccrs.Robinson())
tp_aug.plot.imshow(ax=ax, transform=ccrs.PlateCarree(),cmap='YlGnBu',levels=[0.5, 1, 2, 3, 4, 5, 7, 10, 15, 20, 40],
            cbar_kwargs={'label': 'precipitation in mm'})
plt.title('average daily precipitation August - ERA5 1979-2018')
ax.coastlines(); ax.gridlines();
No description has been provided for this image

Using .groupby(), compute the average daily precipitation for each month of the year (I expect a variable of dimensions (month: 12, latitude: 241, longitude: 480)). Convert the units to mm per day. Plot a map of average daily precipitation in January and in August with the levels [0.5, 1, 2, 3, 4, 5, 7, 10, 15, 20, 40] and the colormap `YlGnBu'

Questions:

  1. Describe the location of the ITCZ in January and February. Without going into the details, explain (in one or two sentences)
  2. Describe the precipitation seasonality in West Africa and in India. Name the phenomenon at play.

Answers:

  1. You can see the ITCZ along the equator in both maps. In August it's located more north than in January.
  2. In August there is strong precepitation in West Africa and India. The seasonal oscillating ITCZ causes the rainy and dry seasons in these areas. Especially in India it is called Monsun (strongest occurence) but it is a worldwide matter and the same phenomenon in West Africa.

Part 3: sea-level pressure and surface winds¶

Open the file containing the surface winds (u10 and v10) and sea-level pressure (msl).

1
2
ds3 = xr.open_dataset(r'C:\Users\viola\Documents\Uni\WS 2022\VU Klimasystem\notebooks\ERA5_LowRes_MonthlyAvg_uvslp.nc')
#ds3 = xr.open_dataset('Data/ERA5_LowRes_MonthlyAvg_uvslp.nc', engine='h5netcdf')
1
2
slp = np.divide(ds3.msl,100) #[hPa]
slp_avg = slp.mean(dim=['longitude','month'])
1
2
3
4
5
p_standard_atm = 1013.25 #hPa
slp_avg.plot()
plt.title('temporal and zonal average of sea-level pressure')
plt.xlabel('Latitude in ° north'); plt.ylabel('sea-level-pressure (hPa)')
plt.axhline(y = 1013.25, color ="red", linestyle ="--");
No description has been provided for this image
1
2
u10_avg = ds3.u10.mean(dim=['longitude','month'])
v10_avg = ds3.v10.mean(dim=['longitude','month'])
1
2
3
4
5
6
u10_avg.plot(label='u10')
v10_avg.plot(label='v10')
plt.axhline(y = 0, color ="red", linestyle ="--")
plt.title('average 10m winds - ERA5 1979-2018')
plt.xlabel('Latitude in ° north');plt.ylabel('surface winds in m s$^{-1}$')
plt.legend(loc='best');
No description has been provided for this image

Compute $\left[ \overline{SLP} \right]$ (the temporal and zonal average of sea-level pressure). Convert it to hPa, and plot it (line plot). With the help of plt.axhline, add the standard atmosphere pressure line to the plot to emphasize high and low pressure regions. Repeat with $\left[ \overline{u_{10}} \right]$ and $\left[ \overline{v_{10}} \right]$ (in m s$^{-1}$) and add the 0 horizontal line to the plot (to detect surface westerlies from easterlies for example).

Questions:

  1. Based on your knowledge about the general circulation of the atmosphere, explain the latitude location of the climatological high and low pressure systems of Earth.
  2. Similarly, explain the direction and strength of the zonal and meridional winds on earth (tip: the sea-level pressure plot helps)

Answers: 1.

  • climatological high pressure system: Hadley North and South of equator (until +/- 30° latitude)
  • climatological low pressure system: extra strong West Wind zone on the southern hemisphere (- 60° latitude); towards the poles there is a high pressure system.
  • zonal: Polar Jet and suptropical Jet
  • meridional: trade winds north and south of the equator
  • in general stronger winds on the southern hemisphere beause of sea surface (especially Polar Jet stream !!)
  • very low meridional and winds on northern hemisphere/north pole beause of more landmass
  • significantly stronger zonal and meridional winds (< -70° latitude) at the south pole because of high and low pressure belts around Antarctica

Part 4: temperature change and CO$_2$ concentrations¶

Download the global average CO$_2$ concentration timeseries data in the CSV format (source: NOAA). Here, let me help your read them using pandas:

1
2
df = pd.read_csv('co2_mm_gl.csv', skiprows=55, parse_dates={'date' : [0, 1]}, index_col='date')
df
decimal average average_unc trend trend_unc
date
1979-01-01 1979.042 336.56 0.10 335.92 0.09
1979-02-01 1979.125 337.29 0.09 336.25 0.09
1979-03-01 1979.208 337.88 0.10 336.51 0.09
1979-04-01 1979.292 338.32 0.11 336.72 0.09
1979-05-01 1979.375 338.26 0.04 336.71 0.10
... ... ... ... ... ...
2022-05-01 2022.375 418.43 0.10 416.79 0.06
2022-06-01 2022.458 417.48 0.10 416.89 0.06
2022-07-01 2022.542 415.72 0.10 417.01 0.06
2022-08-01 2022.625 414.38 0.10 417.22 0.06
2022-09-01 2022.708 414.57 0.10 417.40 0.06

525 rows × 5 columns

1
2
3
4
5
co2 = df.average
co2.plot()
plt.title('monthly global CO$_2$ concentration')
plt.xlabel('time')
plt.ylabel('concentration in ppm');
No description has been provided for this image
1
2
3
4
5
co2_annual_avg = co2.resample("y").mean()
co2_annual_avg.plot()
plt.title('annual average timeseries of global CO$_2$ concentration')
plt.xlabel('time')
plt.ylabel('concentration in ppm');
No description has been provided for this image
1
2
3
4
5
6
7
8
# Meridional weights
weight = np.cos(np.deg2rad(ds.latitude))
weight = weight / weight.sum()

# Meridionally weighted zonal mean
zonal_mean_t2_c = ds.t2m.mean(dim='longitude') - 273.15  # convert into Celsius
weighted_zonal_mean_t2_c = zonal_mean_t2_c * weight
weighted_ts_t2_c = weighted_zonal_mean_t2_c.sum(dim='latitude')
1
tsa_t2_c = weighted_ts_t2_c.resample(time='A').mean()
1
2
T = tsa_t2_c.to_pandas()
type(T)
pandas.core.series.Series
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fig, ax1 = plt.subplots()

ax2 = ax1.twinx()
ax1.plot(co2_annual_avg, 'g-',label='global CO$_2$ concentration')
ax2.plot(T, 'b-',label='global 2m temperature - ERA5')

ax1.legend(loc='upper left')
ax2.legend(loc='lower right')

ax1.set_ylabel('concentration in ppm')
ax2.set_ylabel('Temperature in °C')
ax2.set_title('annual average CO$_2$ concentration and global 2m temperature');
No description has been provided for this image

Prepare three plots:

  • plot the monthly global CO$_2$ concentration as a function of time.
  • plot the annual average timeseries of global CO$_2$ concentration as a function of time.
  • plot the annual average timeseries of global CO$_2$ concentration and of global 2m temperature from ERA5 on the same plot (using a secondary y axis for temperature).

Questions:

  1. Describe and explain the annual cycle of CO$_2$ concentrations
  2. What was the CO$_2$ concentration in the atmosphere in the pre-industrial era? Compute the annual increase in CO$_2$ concentration (unit: ppm per year) between 1980 and 1985 and between 2016 and 2021.
  3. Describe the relationship between global temperatures and CO$_2$ concentrations. Beside CO$_2$, name three processes that can influence temperature variability and change at the global scale.

Answers: 1.

  • rising oscillating CO$_2$ concentration because of the stronger plant uptake of CO$_2$ in spring and summer at the northern hemisphere
  • in autumn and winter the vegetation releases more CO$_2$ (netto)
  • CO$_2$ concentration in preindustrial-era was 280 ppm.(https://de.wikipedia.org/wiki/Kohlenstoffdioxid_in_der_Erdatmosph%C3%A4re#:~:text=Die%20gegenw%C3%A4rtige%20Konzentration%20liegt%20fast,vergangenen%20800.000%20Jahren%20jemals%20erreichten)
  • All in all the temperature has been rising with the CO$_2$ concentration.
  • three processes that can influence temperature variability and change at the global scale: Orbital variations, volcanoe erruptions, Ocean-atmosphere variability
1
2
3
4
5
6
df_1980 = df.loc['1980-01-01':'1980-12-01']
df_1985 = df.loc['1985-01-01':'1985-12-01']
mean_80 = df_1980['average'].mean()
mean_85 = df_1985['average'].mean()
rise1 = (mean_85-mean_80)/5;
print("average annual increase in CO2 concentration 1980-1985:",rise1.round(2))
average annual increase in CO2 concentration 1980-1985: 1.33
1
2
3
4
5
6
df_2016 = df.loc['2016-01-01':'2016-12-01']
df_2021 = df.loc['2021-01-01':'2021-12-01']
mean_16 = df_2016['average'].mean()
mean_21 = df_2021['average'].mean()
rise2 = (mean_21-mean_16)/5;
print("average annual increase in CO2 concentration 2016-2021:",rise2.round(2))
average annual increase in CO2 concentration 2016-2021: 2.33

Part 5: variability and ENSO (open research question)¶

Using the available data, describe the global effect of an El Niño year and a La Niña year on sea-surface temperature, precipitation, and air temperature. I suggest to look for literature or a google search on strong ENSO events in the past 40 years, and select one good example for a positive and a negative phase. Then use annual or seasonal anomaly maps to show the patterns of SST, temperature and precipitation anomalies during these events. With citation or links, explain why you picked these years as examples.

Results:

We decided for the El Niño event in 1998 and the following La Niña event in 2000. The El Niño in 1998 event "was regarded as one of the most powerful El Niño–Southern Oscillation events in recorded history" (https://en.wikipedia.org/wiki/1997%E2%80%9398_El_Ni%C3%B1o_event) with heavy effects on nature. The following La Niña was a "2-year-long moderate-to-strong" (https://agupubs.onlinelibrary.wiley.com/doi/full/10.1029/2008JD011185) event.

Effects:

The SST clearly rises even globally in 1998 what you can see already on the first plot. Obviously we have the highest SST anomaly and precipitation anomaly in front of the west coast of South America, Peru. In this area it also affected the 2m temperature and brings warm air to Chile. Contrary Brasil was dry in the north and warm in the south. In South east Asia it was in general also warmer and dryer. Because of the changing sea circulation. Around China you can see higher precipition anomaly and warmer temperatures.The strongest period was in the winter 1997/98. The following maps contain the average values over the entire year 1998.

In the following years La Niña brought dry and cool air to the tropics over the pacific ocean and the coast of Peru. It is the contrary event to EL Niño. The trade winds are even stronger than usual, pushing more warm water towards Asia. Off the west coast of America, upwelling increases, bringing cold water to the surface. Thus there was more precipitaion in South East Asia. Brazil got cooler and more precipitation. Dry and warm conditions in the South of USA and cold air over Canada. Last but not least we can see wet and cool conditions in the region betweehn Madagaskar and South Africa.

Sources:

  • https://bildungsserver.hamburg.de/ozean-und-klima/4337308/enso-1997-98-artikel/
  • http://charlie.weathertogether.net/2015/10/what-was-1997-1998-el-nino-like/
  • https://agupubs.onlinelibrary.wiley.com/doi/full/10.1029/2008JD011185
  • https://en.wikipedia.org/wiki/1997%E2%80%9398_El_Ni%C3%B1o_event
1
2
ds_sst = xr.open_dataset(r'C:\Users\viola\Documents\Uni\WS 2022\VU Klimasystem\notebooks\ERA5_LowRes_Monthly_sst.nc')
#ds_sst = xr.open_dataset('Data/ERA5_LowRes_Monthly_sst.nc')
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
sst = ds_sst.sst.sel(time=slice('1995','2005'))
# Meridional weights
weight = np.cos(np.deg2rad(ds_sst.latitude))
weight = weight / weight.sum()

# Meridionally weighted zonal mean
zonal_mean_sst = sst.mean(dim='longitude') - 273.15  # convert into Celsius
weighted_zonal_mean_sst = zonal_mean_sst * weight
weighted_ts_sst = weighted_zonal_mean_sst.sum(dim='latitude')

tsa_sst = weighted_ts_sst.resample(time='M').mean()

sst = tsa_sst.to_pandas()
type(T)

T = ds.t2m.sel(time=slice('1995','2005'))
# Meridional weights
weight = np.cos(np.deg2rad(ds.latitude))
weight = weight / weight.sum()

# Meridionally weighted zonal mean
zonal_mean_T = T.mean(dim='longitude') - 273.15  # convert into Celsius
weighted_zonal_mean_T = zonal_mean_T * weight
weighted_ts_T = weighted_zonal_mean_T.sum(dim='latitude')

tsa_T = weighted_ts_T.resample(time='M').mean()

T = tsa_T.to_pandas()
type(T)

fig, ax1 = plt.subplots()

ax2 = ax1.twinx()
ax1.plot(sst, 'b-',label='Sea Surface Temperature')
ax2.plot(T, 'lightskyblue',label='Global 2m Temperature')

ax1.legend(loc='upper left')
ax2.legend(loc='lower right')

ax1.set_ylabel('Temperature in °C')
ax2.set_ylabel('SST in °C')
ax1.set_xlabel('time')
ax2.set_title('sea surface and 2m temperature - ERA5 1995-2005');
No description has been provided for this image
1
2
sst_aavg = ds_sst.resample(time='AS').mean()
sst_1998 = sst_aavg.sel(time='1998')
1
2
3
#nc_inv = xr.open_dataset('Data/ERA5_LowRes_Invariant.nc')
#nc_inv = nc_inv.drop('time')
#masked_sst_1998 = sst_1998.where(nc_inv.lsm < 0.5)
1
2
3
4
5
6
7
sst_avg = ds_sst.sst.mean(dim='time')
sst_an = sst_1998-sst_avg

ax = plt.axes(projection=ccrs.Robinson())
sst_an.sst.plot(ax=ax, transform=ccrs.PlateCarree(),cbar_kwargs ={'label': 'sea surface temperature anomaly °C'})
ax.coastlines(); ax.gridlines()
plt.title('sea surface temperature anomaly El Niño (1998)');
No description has been provided for this image
1
2
p_aavg = ds_tp.resample(time='AS').mean()
p_1998 = p_aavg.sel(time='1998')
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
p_avg = ds_tp.tp.mean(dim='time')
p_an = p_1998-p_avg
p_an = np.multiply(p_an, 1000)

ax = plt.axes(projection=ccrs.Robinson())
p_an.tp.plot(ax=ax, transform=ccrs.PlateCarree(),
             levels=[-10, -7, -5, -3, -4, -2, -1, -0.5, 0, 0.5, 1, 2, 3, 4, 5, 7, 10],
             cbar_kwargs ={'label': 'precipitation anomaly (mm)'}, cmap = 'PiYG')
ax.coastlines(); ax.gridlines()
plt.title('average daily precipitation anomaly El Niño (1998)');
No description has been provided for this image
1
2
t2_aavg = ds.resample(time='AS').mean()
t2_1998 = t2_aavg.sel(time='1998')
1
2
3
4
5
6
7
8
t2_an = t2_1998-t2_tavg

ax = plt.axes(projection=ccrs.Robinson())
t2_an.t2m.plot(ax=ax, transform=ccrs.PlateCarree(),
             levels=[-7, -5, -3, -4, -2, -1, -0.5, 0, 0.5, 1, 2, 3, 4, 5, 7],
             cbar_kwargs ={'label': 'temperature anomaly (°C)'}, cmap = 'jet')
ax.coastlines(); ax.gridlines()
plt.title('2m temperature anomaly El Niño (1998)');
No description has been provided for this image
1
2
3
4
5
6
7
8
sst_2000 = sst_aavg.sel(time='2000')
sst_an_2000 = sst_2000-sst_avg

ax = plt.axes(projection=ccrs.Robinson())
sst_an_2000.sst.plot(ax=ax, transform=ccrs.PlateCarree(),
                     cbar_kwargs ={'label': 'sea surface temperature anomaly °C'})
ax.coastlines(); ax.gridlines()
plt.title('sea surface temperature anomaly La Niña 2000');
No description has been provided for this image
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
p_2000 = p_aavg.sel(time='2000')
p_an_2000 = p_2000-p_avg
p_an_2000 = np.multiply(p_an_2000, 1000)

ax = plt.axes(projection=ccrs.Robinson())
p_an_2000.tp.plot(ax=ax, transform=ccrs.PlateCarree(),
             levels=[-10, -7, -5, -3, -4, -2, -1, -0.5, 0, 0.5, 1, 2, 3, 4, 5, 7, 10],
             cbar_kwargs ={'label': 'precipitation anomaly (mm)'}, cmap = 'PiYG')
ax.coastlines(); ax.gridlines()
plt.title('average daily precipitation anomaly La Niña (2000)');
No description has been provided for this image
1
2
3
4
5
6
7
8
9
t2_2000 = t2_aavg.sel(time='2000')
t2_an_2000 = t2_2000-t2_tavg

ax = plt.axes(projection=ccrs.Robinson())
t2_an_2000.t2m.plot(ax=ax, transform=ccrs.PlateCarree(),
             levels=[-7, -5, -3, -4, -2, -1, -0.5, 0, 0.5, 1, 2, 3, 4, 5, 7],
             cbar_kwargs ={'label': 'temperature anomaly (mm)'}, cmap = 'jet')
ax.coastlines(); ax.gridlines()
plt.title('2m temperature anomaly La Niña (2000)');
No description has been provided for this image