Python matplotlib example Germany warming stripes and anomaly bars#
Software requirements:
Python 3
numpy
pandas
scipy
matplotlib
Run the warming stripes and anomaly bars script:
python German_states_warming_stripes_plot_yearly_temperature_average.py
Script German_states_warming_stripes_plot_yearly_temperature_average.py:
#!/usr/bin/env python
# coding: utf-8
# DKRZ example
#
# Warming stripes plot
#
# Most of us have already come across Ed Hawkins' depiction of the
# 'Warming Stripes'. In this example we show how to create this plot with
# Python using the annual mean temperature data from the DWD.
#
# The goal is to generate the Warming Stripes for a selected German state and
# additionally to draw the anomalies below the plot for a better understanding.
#
# Content
# - read all states CSV data files and concat them to a single Pandas DataFrame
# - use Rectangle and PatchCollection to generate the warmin stripes plot
# - define a function to compute the running mean
# - compute the climatology and the anomalies
# - use pyplot.bar to create the anomalies plot
# - attach both plots into one figure
#
# Data
# - 12 datasets containing the 16 German states temperature anomalies from
# Deutscher Wetterdienst (German Weather Service), Climate Data Center (CDC)
# https://www.dwd.de/DE/leistungen/zeitreihen/zeitreihen.html
#
# Based on
# - Ed Hawkins' 'Temperature changes around the world (1901-2018)'
# https://showyourstripes.info/s/globe
#
# 2023 copyright DKRZ, kmf
#
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
from matplotlib.colors import ListedColormap
# Input files
data_dir = os.environ['HOME']+'/data/DWD/'
input_files = ['Baden-Wuertenberg_Temperature_Anomaly_1881-2018_ym.txt',
'Bayern_Temperature_Anomaly_1881-2018_ym.txt',
'Berlin_Brandenburg_Temperature_Anomaly_1881-2018_ym.txt',
'Hamburg_Bremen_Niedersachsen_Temperature_Anomaly_1881-2018_ym.txt',
'Hessen_Temperature_Anomaly_1881-2018_ym.txt',
'Mecklenburg-Vorpommern_Temperature_Anomaly_1881-2018_ym.txt',
'Nordrhein-Westphalen_Temperature_Anomaly_1881-2018_ym.txt',
'Rheinland-Pfalz_Saarland_Temperature_Anomaly_1881-2018_ym.txt',
'Sachsen_Temperature_Anomaly_1881-2018_ym.txt',
'Sachsen-Anhalt_Temperature_Anomaly_1881-2018_ym.txt',
'Schleswig-Holstein_Temperature_Anomaly_1881-2018_ym.txt',
'Thueringen_Temperature_Anomaly_1881-2018_ym.txt']
input_files = [data_dir + f for f in input_files]
# Combine/concat data
#
# 1. read the state name which are not in the same order as their file names
# let us assume
# 2. read the data for each state
# 3. change the column name to the state name
# 4. concatenate the data
# 5. delete duplicated columns (Jahr)
#
# Each state file has a header of 24 lines describing the data followed by a
# column containing the year (Jahr) and a second column with the data value.
#
# For example the state file for 'Hamburg, Bremen, Niedersachsen':
#
# # Hamburg, Bremen, Niedersachsen
# #
# # Parameter Jahr Wert
# # Minimum [°C] 1940 6.9
# # Maximum [°C] 2014 10.8
# #
# # 30-jähriger Mittelwert [°C]
# # 1981-2010 9.3
# # 1971-2000 9.0
# # 1961-1990 8.6
# #
# # aktueller Wert [°C] 2018 10.7
# #
# # Abweichung vom Referenzzeitraum [K]
# # 1981-2010 1.4
# # 1971-2000 1.8
# # 1961-1990 2.1
# #
# # linearer Trend [K] 1881-2018 1.6
# #
# # aktuelle Platzierung (absteigend) 2018 2
# # aktuelle Platzierung (aufsteigend) 2018 137
# #
# # Gebietsmittel Jahr Wert
# 1881 7.5
# 1882 8.9
# 1883 8.4
# ...
for i,f in enumerate(input_files):
name = os.popen("head -1 "+f+" | tr -d '# \n'").read()
dfi = pd.read_csv(f, skiprows=range(0,23), usecols=['#', 'Gebietsmittel'], sep=' ')
dfi.columns = ['Jahr', name]
if i == 0:
df_all = dfi
else:
df_all = pd.concat([df_all, dfi], axis=1, join='inner')
df_all = df_all.loc[:,~df_all.columns.duplicated()]
# The new dataframe df_all:
#
# Jahr Baden-Wuerttemberg Bayern Brandenburg,Berlin Hamburg,Bremen,Niedersachsen Hessen Mecklenburg-Vorpommern Nordrhein-Westfalen Rheinland-Pfalz,Saarland Sachsen Sachsen-Anhalt Schleswig-Holstein Thueringen
# 0 1881 7.7 6.6 7.6 7.5 7.5 7.0 8.1 8.0 6.7 7.5 7.1 7.5
# 1 1882 8.1 7.3 9.0 8.9 8.2 8.5 9.0 8.6 8.1 8.8 8.8 8.8
# 2 1883 7.8 6.8 8.4 8.4 8.0 7.9 8.7 8.3 7.5 8.3 8.2 8.3
# 3 1884 8.4 7.5 9.1 9.1 8.6 8.7 9.4 9.0 8.2 8.9 8.9 8.9
# 4 1885 7.8 7.0 8.4 7.9 7.7 7.7 8.3 8.0 7.7 8.1 7.6 8.1
# ... ... ... ... ... ... ... ... ... ... ... ... ... ...
# 133 2014 10.1 9.6 10.7 10.8 10.3 10.2 11.0 10.7 10.1 10.7 10.5 10.7
# 134 2015 9.9 9.4 10.4 10.2 9.9 9.8 10.4 10.3 9.9 10.3 9.7 10.3
# 135 2016 9.3 8.9 10.0 9.9 9.4 9.6 10.1 9.8 9.4 10.1 9.6 10.1
# 136 2017 9.4 8.8 9.9 10.0 9.6 9.5 10.3 10.0 9.4 10.0 9.6 10.0
# 137 2018 10.4 9.9 10.8 10.7 10.5 10.2 11.0 10.9 10.3 10.9 10.2 10.9
#
# Time
#
# Set the time range and the 30-years reference time for the climatology.
start_year = df_all.Jahr[0]
end_year = df_all.Jahr[df_all.Jahr.count()-1]
ref_years = [1981, 2010]
# Select state
#
# Select the German state to be used, extract the data, and construct the plot
# file name.
# Here, we choose the state set **'Hamburg,Bremen,Niedersachsen'** but its up
# to you to choose another one.
state = 'Hamburg,Bremen,Niedersachsen'
data = df_all.loc[:,state]
# Create the warming stripes plot
#
# Create a colored rectangle for each year. Add a colorbar just to see the value range better.
fig = plt.figure(figsize=(16, 2))
ax = fig.add_axes([0, 0, 1, 1])
cmap = 'RdBu_r'
rect_coll = PatchCollection([Rectangle((y, 0), 1, 1) for y in range(start_year, end_year + 1)])
rect_coll.set_array(data)
rect_coll.set_cmap(cmap)
ax.add_collection(rect_coll)
ax.set_ylim(0, 1)
ax.set_xlim(start_year, end_year + 1)
ax.set_title(state, fontsize=20, loc='left')
cbar = plt.colorbar(rect_coll, pad=0.02)
plt.figtext(0.88, 0.05, 'Data source: Deutscher Wetterdienst',
rotation=270, fontsize=7)
plotfile1 = 'plot_'+state.replace(',', '_')+'_warming_stripes.png'
fig.savefig(plotfile1, bbox_inches='tight', facecolor='white')
# Function to calculate the running mean
#
# The Savitzky-Golay filter uses convolution process applied on an array for
# smoothing. The Python package scipy provide the function as shown in the
# next example.
import scipy.signal
def calc_running_mean(data, window_length=30, polyorder=3, mode='nearest'):
return scipy.signal.savgol_filter(data,
window_length,
polyorder,
mode='nearest')
# Compute the climatology and anomalies
climatology = df_all.loc[(df_all['Jahr'] >= ref_years[0]) &
(df_all['Jahr'] <= ref_years[1]),
state].mean()
anomaly = data - climatology
# Create the anomaly plot
fig = plt.figure(figsize=(16, 2))
ax = fig.add_axes([0, 0, 1, 1])
ax.set_facecolor('whitesmoke')
colors = ['red' if (value > 0) else 'blue' for value in anomaly]
xlabel = 'Year'
ylabel = 'Anomaly [°C]'
uppertitle = state
lowertitle = 'ref. time: 1981-2010'
ax.bar(df_all.Jahr, anomaly, color=colors)
ax.plot(df_all.Jahr, calc_running_mean(anomaly),
color='black',
linewidth=1.5)
plt.axhline(y=0., color='gray', linestyle=(0, (5, 5)), linewidth=0.8)
ax.grid(linestyle=(0, (5, 5)), linewidth=0.5, color='dimgray')
ax.set_xlim(start_year, end_year)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
tx1 = ax.text(xmin+1.5, ymax-(ymax-ymin)*0.04,
uppertitle,
fontsize=16,
ha='left',
va='top')
tx2 = ax.text(xmax-0.5, ymin+(ymax-ymin)*0.01,
lowertitle,
fontsize=8,
ha='right',
va='bottom')
source = plt.figtext(1.005, 0.05,
'Data source: Deutscher Wetterdienst',
rotation=270,
fontsize=7)
# Create both plots
#
# Attach the anomaly plot below the warming stripes plot.
fig = plt.figure(figsize=(14, 5))
#-- define a 2 rows and 1 column grid (panel) for the subplots
gs = fig.add_gridspec(2,1, hspace=0)
#-- figure title
fig.suptitle(state+' - yearly average temperature [°C]',
x=0.125,
y=0.97,
ha='left',
fontsize=20)
#---- ax1
ax1 = fig.add_subplot(gs[0, 0])
rect_coll = PatchCollection([Rectangle((y, 0), 1, 1) for y in range(start_year, end_year + 1)])
rect_coll.set_array(data)
rect_coll.set_cmap(cmap)
ax1.add_collection(rect_coll)
ax1.set_ylim(0, 1)
ax1.set_xlim(start_year, end_year + 1)
ax1.yaxis.set_visible(False)
#---- ax 2
ax2 = fig.add_subplot(gs[1, 0])
xlabel = 'Year'
ylabel = 'Anomaly [°C]'
uppertitle = state
lowertitle = 'ref. time: 1981-2010'
colors = ['red' if (value > 0) else 'blue' for value in anomaly]
ax2.bar(df_all.Jahr, anomaly, color=colors)
ax2.plot(df_all.Jahr, calc_running_mean(anomaly), color='black', linewidth=1.5)
plt.axhline(y=0., color='gray', linestyle=(0, (5, 5)), linewidth=0.8)
ax2.set_facecolor('whitesmoke')
ax2.grid(linestyle=(0, (5, 5)), linewidth=0.5, color='dimgray')
ax2.set_xlim(start_year, end_year)
ax2.set_xlabel(xlabel)
ax2.set_ylabel(ylabel)
#-- attach text inside ax2 (anomaly plot)
xmin, xmax = ax2.get_xlim()
ymin, ymax = ax2.get_ylim()
tx2 = ax2.text(xmax-0.5, ymin+(ymax-ymin)*0.01,
lowertitle,
fontsize=8,
ha='right',
va='bottom')
#---- ax3
ax3 = fig.add_axes([0.102, 0.515, 0.008, 0.35])
cbar = plt.colorbar(rect_coll, cax=ax3, aspect=100)
ax3.yaxis.set_ticks_position('left')
#-- data source and copyright
plt.figtext(0.856, 0.087, '© 2023 DKRZ', fontsize=7)
plt.figtext(0.905, 0.14, 'Data source: Deutscher Wetterdienst',
rotation=270, fontsize=7)
#-- save figure as PNG file
plotfile2 = 'plot_'+state.replace(',', '_')+'_warming_stripes_plus_anomaly_bars.png'
fig.savefig(plotfile2, bbox_inches='tight', facecolor='white')
Plot result: