Warming Stripes#

Let’s create our own figure of “warming stripes” that use a red/blue color scale to represent annual air temperature deviations from some climate mean tempearture over a long timeseries. This notebook is based on, and adapts code from “Creating the Warming Stripes in Matplotlib” by Maximilian Nöthe.

We will use air temperature from NASA’s DayMet dataset, and access it through an API using the ulmo python package.

Since we don’t have ulmo installed on our JupyterHub by default, the first time you run this notebook, you’ll need to install the ulmo package into our python envrionment.

Run the cell below once. Uncomment it now, so that it reads !pip install ulmo, and run it. It will install ulmo on your JupyterHub. You shouldn’t need to re-run it next time you open this notebook, so it’s best to comment it out again when it completes.

When it is complete and you’ve commented it back out, restart the kernel by going to Kernel > Researt Kernel.

#!pip install ulmo

# packages for working with our data
import pandas as pd
import numpy as np

# page we'll use to access the data
import ulmo

# packages for plotting
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
from matplotlib.colors import ListedColormap
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
/tmp/ipykernel_2735/3717264912.py in <module>
      4 
      5 # page we'll use to access the data
----> 6 import ulmo
      7 
      8 # packages for plotting

ModuleNotFoundError: No module named 'ulmo'

To get a pandas dataframe of DayMet data, the function we’re using is ulmo.nasa.daymet.get_daymet_singlepixel()

We need to specify a longitude and latitude, the variables we want to download, what years we want (by saying years=None, it will respond with all years on record), and we specify that we want the result returned as a pandas dataframe.

# latitude and longitude for Seattle, WA
latitude = 47.654
longitude = -122.308

To see what data variables we can request, use the function ulmo.nasa.daymet.get_variables()

ulmo.nasa.daymet.get_variables()
{'tmax': 'maximum temperature',
 'tmin': 'minimum temperature',
 'srad': 'shortwave radiation',
 'vp': 'vapor pressure',
 'swe': 'snow-water equivalent',
 'prcp': 'precipitation',
 'dayl': 'daylength'}

We’re just interested in temperatures, so we’ll grab tmin and tmax:

df = ulmo.nasa.daymet.get_daymet_singlepixel(latitude, longitude, variables=['tmax', 'tmin'], years=None, as_dataframe=True)
making request for latitude, longitude: 47.654, -122.308

Take a look at what the dataframe looks like

df.head()
year yday tmax tmin
1980-01-01 1980 1 11.5 6.5
1980-01-02 1980 2 10.0 4.5
1980-01-03 1980 3 8.5 0.5
1980-01-04 1980 4 6.5 0.5
1980-01-05 1980 5 4.5 -0.5

Create a daily mean temperature from tmin and tmax

df['tmean'] = np.mean([df.tmax, df.tmin], axis=0)
df.head()
year yday tmax tmin tmean
1980-01-01 1980 1 11.5 6.5 9.00
1980-01-02 1980 2 10.0 4.5 7.25
1980-01-03 1980 3 8.5 0.5 4.50
1980-01-04 1980 4 6.5 0.5 3.50
1980-01-05 1980 5 4.5 -0.5 2.00

Resample the dataframe to annual mean values

df_annual = df.resample('Y').mean()
df_annual.head()
year yday tmax tmin tmean
1980-12-31 1980 183 15.794521 6.536986 11.165753
1981-12-31 1981 183 16.472603 6.791781 11.632192
1982-12-31 1982 183 15.638356 5.835616 10.736986
1983-12-31 1983 183 16.012329 6.504110 11.258219
1984-12-31 1984 183 15.620548 5.919178 10.769863

Now find the overall mean for all the years we’re looking at. We’ll use this as our “climate mean” air temperature to compare against just for this example.

climate_mean = df_annual.tmean.mean()
print(climate_mean)
11.347568493150685

Find the annual anomaly between the annual mean temperature and this climate mean.

df_annual['anomaly'] = df_annual.tmean - climate_mean
df_annual.head()
year yday tmax tmin tmean anomaly
1980-12-31 1980 183 15.794521 6.536986 11.165753 -0.181815
1981-12-31 1981 183 16.472603 6.791781 11.632192 0.284623
1982-12-31 1982 183 15.638356 5.835616 10.736986 -0.610582
1983-12-31 1983 183 16.012329 6.504110 11.258219 -0.089349
1984-12-31 1984 183 15.620548 5.919178 10.769863 -0.577705

Now that we have our data all in order, we can go ahead and make the plot. (The following plotting code is adapted from “Creating the Warming Stripes in Matplotlib” by Maximilian Nöthe)

This is our custom colormap from colorbrewer2, we could also use one of the colormaps that come with matplotlib, e.g. coolwarm or RdBu.

cmap = ListedColormap([
    '#08306b', '#08519c', '#2171b5', '#4292c6',
    '#6baed6', '#9ecae1', '#c6dbef', '#deebf7',
    '#fee0d2', '#fcbba1', '#fc9272', '#fb6a4a',
    '#ef3b2c', '#cb181d', '#a50f15', '#67000d',
])

Finally, we create bars for each year as a PatchCollection of Rectangles, make the plot and save it as a jpg image.

# Define the shape of each bar
rect_ll_y = df_annual.anomaly.min() # rectangle lower left y coordinate, minimum anomaly value
rect_height = np.abs(df_annual.anomaly.max()-df_annual.anomaly.min()) # rectangle height, range between min and max anomaly values
year_start = df_annual.year.min() # year to start the plot x axis
year_end = df_annual.year.max() + 1 # year to end the plot x axis

# create a collection with a rectangle for each year
col = PatchCollection([
    Rectangle((x, rect_ll_y), 1, rect_height)
    for x in range(year_start, year_end)
])

# Create the figure, assign the data, colormap and color limits and add it to the figure axes
fig = plt.figure(figsize=(5, 1))

# set up the axes
ax = fig.add_axes([0, 0, 1, 1])
ax.set_axis_off()

# set data, colormap and color limits
col.set_array(df_annual.anomaly) # use the anomaly data for the colormap
col.set_cmap(cmap) # apply our custom red/blue colormap colors
col.set_clim(-rect_height/2, rect_height/2) # set the limits of our colormap
ax.add_collection(col)

## plot anomaly graph
#df_annual.plot(x='year', y='anomaly', linestyle='-',color='w',ax=ax, legend=False)
## plot horizontal line at zero anomaly
#ax.axhline(0, linestyle='--', color='w')
## plot a text label
#ax.text(df.year.mean()-10,-.4,'Seattle, WA', fontsize=30, fontweight='bold', color='k')

# Make sure the axes limits are correct and save the figure.
ax.set_ylim(-rect_height/2, rect_height/2) # set y axis limits to rectanlge height centered at zero
ax.set_xlim(year_start, year_end); # set x axes limits to start and end year

# save the figure
fig.savefig('warming-stripes-seattle.jpg', dpi=150)
../../_images/9d7b4d7abf7b2ec00cfb44032f25169d56c16acbe0af8fab502a1b486103aca3.png