Pyplot: Different scales for normalized colorbars

Der Bei­trag die­se Woche ist gewis­ser­ma­ßen eine öffent­li­che Notiz für mich selbst.
Beim Erstel­len von Abbil­dun­gen für mei­ne Arbeit schaue ich oft die sel­ben Din­ge wie­der und wie­der nach. Des­halb habe ich mir gedacht, dass es sich viel­leicht lohnt, sol­che Din­ge hier zu sam­meln.
Schö­ner Neben­ef­fekt: wenn mich jemand fragt, wie etwas davon funk­tio­niert, kann ich ihn oder sie direkt hier hin schi­cken. Weil aller­dings die Leu­te, die mich sowas fra­gen, eher Eng­lisch als Deutsch spre­chen, wer­de ich die­se Art von Bei­trä­gen auf Eng­lisch schreiben.

Visua­li­zing two-dimen­sio­nal data sets can be quite hard, espe­ci­al­ly if not only qua­li­ta­ti­ve but also quan­ti­ta­ti­ve fea­tures should be cle­ar­ly cap­tu­red. Often, a nice way around is to plot some kind of „heat­map“ visua­li­zing nume­ri­cal values at cer­tain points by dif­fe­rent colors. For exam­p­le, many topo­gra­phic maps indi­ca­ting the ele­va­ti­on do exact­ly this.

In many such cases, the­re is some reasonable „cen­ter“ of the color­bar. For exam­p­le for a topo­gra­phic map indi­ca­ting ele­va­tions the sea level is often such a base­line. Howe­ver, moun­ta­ins often have a lar­ger ele­va­ti­on ran­ge than water has depth ran­ge. In such cases, it might be useful to use dif­fe­rent line­ar sca­les on both sides of the cen­ter value.

In this post, I’ll dis­cuss how to crea­te plots like the­se with Python’s Mat­plot­lib using code examp­les deri­ved from the Mat­plot­lib docu­men­ta­ti­on. In par­ti­cu­lar, I’ll com­ment on a weird ver­si­on-depen­dent beha­vi­or dif­fe­ring for Mat­plot­lib 3.4 and 3.5.

I tried to make this post as self con­tai­ned as pos­si­ble. The­r­e­fo­re, I first pre­sent the Python com­mands used step by step, while the com­ple­te code can be found here.

If you’­ve never used Mat­plot­lib befo­re, I recom­mend having a look at the who­le tool­kit – it’s real­ly ama­zing how simp­le it is to gene­ra­te nice figu­res with Matplotlib!

Loading the example data

Befo­re visua­li­zing any data, we first have to load it. We’ll be using some exam­p­le data included with the stan­dard Mat­plot­lib installation:

import matplotlib.cbook as cbook

# Load the example data
dem = cbook.get_sample_data('topobathy.npz', np_load=True)

# Compute elevations in meters instead of feet
topo = dem['topo']*0.3048

longitude = dem['longitude']
latitude = dem['latitude']

Creating the colormap

Next, we will crea­te our color­map based on the included terrain colormap:

import numpy as np
import matplotlib.colors as colors
import matplotlib.pyplot as plt

colors_undersea = plt.cm.terrain(np.linspace(0, 0.17, 256))
colors_land = plt.cm.terrain(np.linspace(0.25, 1, 256))
all_colors = np.vstack((colors_undersea, colors_land))
terrain_map = colors.LinearSegmentedColormap.from_list('terrain_map', all_colors)

Based on this, we could now go ahead and plot our first topo­gra­phic map:

# Plot our map
pcm = plt.pcolormesh(longitude, latitude, topo, rasterized=True,
                     cmap=terrain_map, shading='auto')

# Create a colorbar
cb = plt.colorbar(pcm, shrink=0.8)
cb.set_ticks([-200, 0, 400, 800, 1200])
cb.set_label('Elevation [m]')

# Set aspect ratio because distance between lines of longitude depends on latitude.
plt.gca().set_aspect(1 / np.cos(np.deg2rad(49)))

plt.show()
First attempt for our topo­gra­phic map. Note the weird cen­ter of the color­bar – we’ll fix this in a second…

While this gives alre­a­dy a nice first plot, note the weird posi­ti­on of the cen­ter of the color­bar. Right now, it is not cen­te­red at 0 m but some­whe­re around 50 m. This is defi­ni­te­ly not what we want, so we have to fix this.

Matplotlib’s TwoSlopeNorm

In fact, we can tell Mat­plot­lib to use dif­fe­rent line­ar sca­les on both sites of a cen­ter value using TwoSlopeNorm:

divnorm = colors.TwoSlopeNorm(vmin=-200., vcenter=0, vmax=1200.)

# Plot our map and tell Matplotlib to use our new normalized colormap
pcm = plt.pcolormesh(longitude, latitude, topo, rasterized=True,
                     cmap=terrain_map, norm=divnorm, shading='auto')

Doing this, we obtain a nice­ly cen­te­red color­bar and the sun­ken coasts in our map reap­pear in vivid green colors:

Matplotlib 3.4.3 vs 3.5.2

Depen­ding on your ver­si­on of Mat­plot­lib you might obtain one of the abo­ve plots. While the tech­ni­cal­ly out­da­ted Mat­plot­lib 3.4.3 gives a nice color­bar which takes into account the dif­fe­rent ran­ges for posi­ti­ve and nega­ti­ve values, the newer ver­si­on 3.5.2 uses the same sca­le for both sides.

Until now I could­n’t figu­re out a way to obtain the (in my opi­ni­on much nicer) con­sis­tent sca­le for posi­ti­ve and nega­ti­ve values using Mat­plot­lib 3.5.2. Lucki­ly, you can sim­ply get the older ver­si­on using pip install matplotlib==3.4.3 befo­re gene­ra­ting your two-slo­ped color­map and updating again after­wards pip install -U matplotlib.

3 Gedanken zu „Pyplot: Different scales for normalized colorbars“

  1. Thank you for the detail­ed explanation.
    In the new ver­si­ons, the colors­ca­le is given by the bit-length of each sca­le. Sin­ce both the under­sea and land sca­les have the length 256, the midd­le is at zero.
    By modi­fy­ing the length to 256*(abs(vmin or vmax)/(vax-vmin) you can keep a line­ar scale.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.