Arctic sea ice and snow cover and are two of the most prominent features of the cryosphere of the northern hemisphere and can be seen with a naked eye from the Moon. Measurements of sea ice area started around 1979 and snow cover a bit earlier. Both show a strong seasonal signal and area also a decline over the three decades. Even stronger is the decline calculated by a sea ice model run by the Polar Science Center, Washington. PIOMAS outputs daily sea ice volume for the same time allowing a good comparison of the three data sets. Instead of the usual line charts this notebook translates the daily data into a color range. Changes within a dataset are far better visible, while maintaining comparability.
A preview of the final results:¶
%pylab inline
import matplotlib.pyplot as plt
from matplotlib._png import read_png
fg, axes = plt.subplots(figsize=(20, 15), dpi=100, nrows=3, ncols=1)
for im, ax in zip(["piomas-colored.png", "area-colored.png", "snow-colored.png"], axes) :
ax.axes.get_yaxis().set_visible(False)
ax.axes.get_xaxis().set_visible(False)
ax.set_frame_on(False)
ax.imshow(read_png(im))
Preparation, imports and helpful functions¶
%pylab inline
import gzip, urllib, time
from StringIO import StringIO
from datetime import datetime, timedelta
import numpy as np
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from matplotlib._png import read_png
from matplotlib.colors import LinearSegmentedColormap
from IPython.core.display import Image
now = datetime.utcnow()
print
print " now: %s" % now
print "python: %s" % str(sys.version_info[:])
print " numpy: %s" % numpy.__version__
print " mpl: %s" % matplotlib.__version__
## little helper to pixel-perfectly render a figure as an inline image
def showFigure(fg, filename, dpi=100) :
plt.savefig(filename, dpi=dpi)
plt.close(fg)
return Image(filename=filename)
None of the build-in color palettes really picks up the details of the data. Here is how to make an optimized color palette for this purpose:
## the final result:
Image(filename="colorbar.png")
# human readable version
palette = [
(0.00, "000000"), # black
(0.04, "880000"), # dark red
(0.14, "ff0000"), # red
(0.24, "ff8800"), # orange
(0.34, "ffff00"), # yellow
(0.44, "33ff00"), # green
(0.57, "00ccff"), # light blue
(0.74, "4444cc"), # dark blue
(0.84, "aa00aa"), # violett
(0.94, "660066"), # dark violett
(1.00, "000000"), # black
]
# converter to mpl
def makeColormap(pal) :
class Color(object) :
def __init__(self, color) :
self.r = float(int(color[0:2], 16))/255
self.g = float(int(color[2:4], 16))/255
self.b = float(int(color[4:6], 16))/255
def __getitem__(self, rgb) :
return self.__dict__[rgb]
p = {}
for col in ['red', 'green', 'blue']:
akku = []; l = col[0];
for i, e in enumerate(pal) :
c = Color(e[1])
if i == 0: akku.append((e[0], 0.0, c[l]))
elif i == len(pal)-1 : akku.append((e[0], c[l], 1.0))
else : akku.append((e[0], c[l], c[l]))
p[col] = tuple(akku)
return LinearSegmentedColormap('cryo', p, 255)
## convert and display
cmap = makeColormap(palette)
fg, ax = plt.subplots(figsize=(4.00, 0.5), dpi=100)
bar = mpl.colorbar.ColorbarBase(ax, cmap=cmap, orientation='horizontal')
ax.set_position((0., 0., 1., 0.99))
showFigure(fg, "colorbar.png")
The plotting function for all three data sets. The only differences are axis decoration and scaling.
def plotData(data, vmax, xlocs, xlbls, ylocs=[], ylbls=[]) :
fg, ax = plt.subplots(figsize=(5, 3), dpi=100, nrows=1, ncols=1)
ax.set_position([0.08, 0.12, 0.81, 0.78])
## here the magic happens
img = ax.imshow(data, interpolation="nearest", aspect='auto',
vmin=0, vmax=vmax, cmap=cmap)
## only decoration left over
ax.set_xticks(xlocs)
ax.set_xticklabels(xlbls)
ax.tick_params(axis='x', labelsize=8)
ylbls = ylbls if len(ylbls) else \
[time.strftime("%b", datetime(2000, m, 1).timetuple()) for m in range(1, 13)]
ylocs = ylocs if len(ylocs) else \
[int(time.strftime("%j", datetime(2000, m, 1).timetuple())) for m in range(1, 13)]
ax.set_yticks(ylocs)
ax.set_yticklabels(ylbls)
ax.tick_params(axis='y', labelsize=10)
ax.grid(axis='y')
cax = fg.add_axes([0, 0.12, 1.08, 0.78], frameon=False)
cax.axes.get_xaxis().set_visible(False)
cax.axes.get_yaxis().set_visible(False)
bar = plt.colorbar(img, ax=cax, orientation='vertical')
fts = [t.set_fontsize(8) for t in bar.ax.get_yticklabels()]
return fg, ax
Volume First¶
PIOMAS volume comes as a three column text file, the import is simple. The data is usually updated once per month, well, except during a government shutdown.
url = "http://psc.apl.washington.edu/wordpress/wp-content/uploads/schweiger/ice_volume/PIOMAS.vol.daily.1979.2013.Current.v2.dat.gz"
rawVolu = np.genfromtxt(gzip.GzipFile( \
fileobj=StringIO(urllib.urlopen(url).read())), skiprows=1)
# check
print rawVolu[-1], "Last day", \
datetime(int(rawVolu[-1,0]), 1, 1) + timedelta(int(rawVolu[-1,1]) -1)
tmp = plot(rawVolu[:,2])
I hope you'll like my tendency to spend similar variables the same amount of characters. Readability tops orthography ;)
# shape and resize the data, nan for missing
yirs = rawVolu[:, 0]
days = rawVolu[:, 1]
volu = rawVolu[:, 2].copy()
yirs = np.arange(int(yirs.min()), int(yirs.max() +1), 1)
days = np.arange(int(days.min()), int(days.max() +1), 1)
volu.resize((len(yirs), len(days)));
volu[volu==0] = np.nan
volu = volu.transpose()
# PIOMAS ignores leap years, which makes days a little bit longer in a leap year.
print "shape: %s, min: %s, max: %s" % \
(volu.shape, np.nanmin(volu), np.nanmax(volu))
Now same data with volume encoded as color.
xlbls = np.arange(1980, 2011, 5)
xlocs = [lbl - 1979 for lbl in xlbls]
fg, ax = plotData(volu, 34, xlocs, xlbls)
plt.figtext(0.5, 0.98, u"PIOMAS - Arctic Sea Ice Volume (1000km³)",
ha='center', va="top", fontsize=12)
plt.figtext(0.98, 0.00, "Credit: Zhang, Jinlun and D.A. Rothrock\n" +
"http://psc.apl.washington.edu/", ha='right', va='bottom', fontsize=5)
showFigure(fg, "piomas-colored.png")
Sea Ice Area¶
Sea ice area ignores the third dimension and reduces the ice pack to a flat feature. Will this data expose a similar trend like volume?
url = "http://arctic.atmos.uiuc.edu/cryosphere/timeseries.anom.1979-2008"
rawArea = np.genfromtxt(url)
print rawArea[-1:]
Data is organized in four columns: time of year, anomaly, actual area and climatology.
## check
fg, axes = plt.subplots(figsize=(7, 10), dpi=100, nrows=3, ncols=1)
for dt, ax in zip([rawArea[:,1], rawArea[:,2], rawArea[:,3]], axes) :
ax.plot(dt)
## shape and resize
nans = np.empty(365); nans.fill(np.nan)
yirs = range(1979, 2014)
## Only the first 365 entries of a year will be used, dropping Dec 31th in case of a leap year.
def splitYearsArea(a, y, f) :
d = a[(a[:, 0] >= y) & (a[:, 0] < y +1)]
return np.hstack((d[:, f], nans))[:365]
area = np.transpose(np.array([splitYearsArea(rawArea, y, 2) for y in yirs]))
print "shape: %s, min: %s, max: %s" % (area.shape, np.nanmin(area), np.nanmax(area))
Plotting of area is similar to thickness
xlbls = np.arange(1980, 2011, 5)
xlocs = [lbl - 1979 for lbl in xlbls]
fg, ax = plotData(area, 16, xlocs, xlbls)
plt.figtext(0.5, 0.98, u"Arctic Sea Ice Area (1,000,000km²)",
ha='center', va="top", fontsize=12)
plt.figtext(0.98, 0.00, "Credit: The Cryosphere Today\n" +
"http://arctic.atmos.uiuc.edu/cryosphere/", \
ha='right', va='bottom', fontsize=5)
showFigure(fg, "area-colored.png")
NH Snow Cover¶
url = "http://climate.rutgers.edu/snowcover/files/wkcov.nhland.txt"
rawSnow = np.genfromtxt(url)
print rawSnow[-1:]
Snow comes as weekly data and includes a few gaps, so processing is a bit different.
url = "http://climate.rutgers.edu/snowcover/files/wkcov.nhland.txt"
rawSnow = np.genfromtxt(url)
snow = np.zeros((2015 - 1966) * 52);
snow.fill(np.nan)
for w in rawSnow : snow[(w[0] - 1966) * 52 + w[1]] = w[2] / 1000000
snow = snow.reshape(2015 - 1966, 52).transpose()
print "shape: %s, min: %s, max: %s" % \
(snow.shape, np.nanmin(snow), np.nanmax(snow))
## check
tmp = plot(rawSnow[:,2])
xlbls = np.arange(1975, 2011, 5)
xlocs = [lbl - 1970 for lbl in xlbls]
ylbls = np.arange(4, 49, 4)
ylocs = ylbls -1
fg, ax = plotData(snow, 52, xlocs, xlbls, ylocs, ylbls)
plt.figtext(0.5, 0.98, u"NH Weekly Snow Cover (1,000,000km²)",
ha='center', va="top", fontsize=12)
plt.figtext(0.98, 0.00, "Credit: Global Snow Lab\n" +
"http://climate.rutgers.edu/snowcover/index.php", \
ha='right', va='bottom', fontsize=5)
showFigure(fg, "snow-colored.png")
fg, axes = plt.subplots(figsize=(16, 9), dpi=72, nrows=3, ncols=1)
plt.subplots_adjust(left=0., right=1., top=1.0, bottom=0., wspace=0., hspace=0.)
for im, ax in zip(["piomas-colored.png", "area-colored.png", "snow-colored.png"], axes) :
ax.axes.get_yaxis().set_visible(False)
ax.axes.get_xaxis().set_visible(False)
ax.set_frame_on(False)
ax.imshow(read_png(im))
Comments !