Matplotlib has a number of built-in colormaps accessible viamatplotlib.cm.get_cmap. There are also external libraries like[palettable] and [colorcet] that have many extra colormaps.Here we briefly discuss how to choose between the many options. Forhelp on creating your own colormaps, seeCreating Colormaps in Matplotlib.
Overview¶
The idea behind choosing a good colormap is to find a good representation in 3Dcolorspace for your data set. The best colormap for any given data set dependson many things including:
- Whether representing form or metric data ([Ware])
- Your knowledge of the data set (e.g., is there a critical valuefrom which the other values deviate?)
- If there is an intuitive color scheme for the parameter you are plotting
- If there is a standard in the field the audience may be expecting
For many applications, a perceptually uniform colormap is the bestchoice --- one in which equal steps in data are perceived as equalsteps in the color space. Researchers have found that the human brainperceives changes in the lightness parameter as changes in the datamuch better than, for example, changes in hue. Therefore, colormapswhich have monotonically increasing lightness through the colormapwill be better interpreted by the viewer. A wonderful example ofperceptually uniform colormaps is [colorcet].
Color can be represented in 3D space in various ways. One way to represent coloris using CIELAB. In CIELAB, color space is represented by lightness,\(L^*\); red-green, \(a^*\); and yellow-blue, \(b^*\). The lightnessparameter \(L^*\) can then be used to learn more about how the matplotlibcolormaps will be perceived by viewers.
An excellent starting resource for learning about human perception of colormapsis from [IBM].
Classes of colormaps¶
Colormaps are often split into several categories based on their function (see,e.g., [Moreland]):
- Sequential: change in lightness and often saturation of colorincrementally, often using a single hue; should be used forrepresenting information that has ordering.
- Diverging: change in lightness and possibly saturation of twodifferent colors that meet in the middle at an unsaturated color;should be used when the information being plotted has a criticalmiddle value, such as topography or when the data deviates aroundzero.
- Cyclic: change in lightness of two different colors that meet inthe middle and beginning/end at an unsaturated color; should beused for values that wrap around at the endpoints, such as phaseangle, wind direction, or time of day.
- Qualitative: often are miscellaneous colors; should be used torepresent information which does not have ordering orrelationships.
# sphinx_gallery_thumbnail_number = 2import numpy as npimport matplotlib as mplimport matplotlib.pyplot as pltfrom matplotlib import cmfrom colorspacious import cspace_converterfrom collections import OrderedDictcmaps = OrderedDict()
Sequential¶
For the Sequential plots, the lightness value increases monotonically throughthe colormaps. This is good. Some of the \(L^*\) values in the colormapsspan from 0 to 100 (binary and the other grayscale), and others start around\(L^*=20\). Those that have a smaller range of \(L^*\) will accordinglyhave a smaller perceptual range. Note also that the \(L^*\) function variesamongst the colormaps: some are approximately linear in \(L^*\) and othersare more curved.
cmaps['Perceptually Uniform Sequential'] = [ 'viridis', 'plasma', 'inferno', 'magma', 'cividis']cmaps['Sequential'] = [ 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']
Sequential2¶
Many of the \(L^*\) values from the Sequential2 plots are monotonicallyincreasing, but some (autumn, cool, spring, and winter) plateau or even go bothup and down in \(L^*\) space. Others (afmhot, copper, gist_heat, and hot)have kinks in the \(L^*\) functions. Data that is being represented in aregion of the colormap that is at a plateau or kink will lead to a perception ofbanding of the data in those values in the colormap (see [mycarta-banding] foran excellent example of this).
cmaps['Sequential (2)'] = [ 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', 'hot', 'afmhot', 'gist_heat', 'copper']
Diverging¶
For the Diverging maps, we want to have monotonically increasing \(L^*\)values up to a maximum, which should be close to \(L^*=100\), followed bymonotonically decreasing \(L^*\) values. We are looking for approximatelyequal minimum \(L^*\) values at opposite ends of the colormap. By thesemeasures, BrBG and RdBu are good options. coolwarm is a good option, but itdoesn't span a wide range of \(L^*\) values (see grayscale section below).
cmaps['Diverging'] = [ 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']
Cyclic¶
For Cyclic maps, we want to start and end on the same color, and meet asymmetric center point in the middle. \(L^*\) should change monotonicallyfrom start to middle, and inversely from middle to end. It should be symmetricon the increasing and decreasing side, and only differ in hue. At the ends andmiddle, \(L^*\) will reverse direction, which should be smoothed in\(L^*\) space to reduce artifacts. See [kovesi-colormaps] for moreinformation on the design of cyclic maps.
The often-used HSV colormap is included in this set of colormaps, although itis not symmetric to a center point. Additionally, the \(L^*\) values varywidely throughout the colormap, making it a poor choice for representing datafor viewers to see perceptually. See an extension on this idea at[mycarta-jet].
cmaps['Cyclic'] = ['twilight', 'twilight_shifted', 'hsv']
Qualitative¶
Qualitative colormaps are not aimed at being perceptual maps, but looking at thelightness parameter can verify that for us. The \(L^*\) values move all overthe place throughout the colormap, and are clearly not monotonically increasing.These would not be good options for use as perceptual colormaps.
cmaps['Qualitative'] = ['Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c']
Miscellaneous¶
Some of the miscellaneous colormaps have particular uses for whichthey have been created. For example, gist_earth, ocean, and terrainall seem to be created for plotting topography (green/brown) and waterdepths (blue) together. We would expect to see a divergence in thesecolormaps, then, but multiple kinks may not be ideal, such as ingist_earth and terrain. CMRmap was created to convert well tograyscale, though it does appear to have some small kinks in\(L^*\). cubehelix was created to vary smoothly in both lightnessand hue, but appears to have a small hump in the green hue area.
The often-used jet colormap is included in this set of colormaps. We can seethat the \(L^*\) values vary widely throughout the colormap, making it apoor choice for representing data for viewers to see perceptually. See anextension on this idea at [mycarta-jet].
cmaps['Miscellaneous'] = [ 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar']
First, we'll show the range of each colormap. Note that some seemto change more "quickly" than others.
nrows = max(len(cmap_list) for cmap_category, cmap_list in cmaps.items())gradient = np.linspace(0, 1, 256)gradient = np.vstack((gradient, gradient))def plot_color_gradients(cmap_category, cmap_list, nrows): fig, axes = plt.subplots(nrows=nrows) fig.subplots_adjust(top=0.95, bottom=0.01, left=0.2, right=0.99) axes[0].set_title(cmap_category + ' colormaps', fontsize=14) for ax, name in zip(axes, cmap_list): ax.imshow(gradient, aspect='auto', cmap=plt.get_cmap(name)) pos = list(ax.get_position().bounds) x_text = pos[0] - 0.01 y_text = pos[1] + pos[3]/2. fig.text(x_text, y_text, name, va='center', ha='right', fontsize=10) # Turn off *all* ticks & spines, not just the ones with colormaps. for ax in axes: ax.set_axis_off()for cmap_category, cmap_list in cmaps.items(): plot_color_gradients(cmap_category, cmap_list, nrows)plt.show()
Lightness of matplotlib colormaps¶
Here we examine the lightness values of the matplotlib colormaps.Note that some documentation on the colormaps is available([list-colormaps]).
mpl.rcParams.update({'font.size': 12})# Number of colormap per subplot for particular cmap categories_DSUBS = {'Perceptually Uniform Sequential': 5, 'Sequential': 6, 'Sequential (2)': 6, 'Diverging': 6, 'Cyclic': 3, 'Qualitative': 4, 'Miscellaneous': 6}# Spacing between the colormaps of a subplot_DC = {'Perceptually Uniform Sequential': 1.4, 'Sequential': 0.7, 'Sequential (2)': 1.4, 'Diverging': 1.4, 'Cyclic': 1.4, 'Qualitative': 1.4, 'Miscellaneous': 1.4}# Indices to step through colormapx = np.linspace(0.0, 1.0, 100)# Do plotfor cmap_category, cmap_list in cmaps.items(): # Do subplots so that colormaps have enough space. # Default is 6 colormaps per subplot. dsub = _DSUBS.get(cmap_category, 6) nsubplots = int(np.ceil(len(cmap_list) / dsub)) # squeeze=False to handle similarly the case of a single subplot fig, axes = plt.subplots(nrows=nsubplots, squeeze=False, figsize=(7, 2.6*nsubplots)) for i, ax in enumerate(axes.flat): locs = [] # locations for text labels for j, cmap in enumerate(cmap_list[i*dsub:(i+1)*dsub]): # Get RGB values for colormap and convert the colormap in # CAM02-UCS colorspace. lab[0, :, 0] is the lightness. rgb = cm.get_cmap(cmap)(x)[np.newaxis, :, :3] lab = cspace_converter("sRGB1", "CAM02-UCS")(rgb) # Plot colormap L values. Do separately for each category # so each plot can be pretty. To make scatter markers change # color along plot: # http://stackoverflow.com/questions/8202605/ if cmap_category == 'Sequential': # These colormaps all start at high lightness but we want them # reversed to look nice in the plot, so reverse the order. y_ = lab[0, ::-1, 0] c_ = x[::-1] else: y_ = lab[0, :, 0] c_ = x dc = _DC.get(cmap_category, 1.4) # cmaps horizontal spacing ax.scatter(x + j*dc, y_, c=c_, cmap=cmap, s=300, linewidths=0.0) # Store locations for colormap labels if cmap_category in ('Perceptually Uniform Sequential', 'Sequential'): locs.append(x[-1] + j*dc) elif cmap_category in ('Diverging', 'Qualitative', 'Cyclic', 'Miscellaneous', 'Sequential (2)'): locs.append(x[int(x.size/2.)] + j*dc) # Set up the axis limits: # * the 1st subplot is used as a reference for the x-axis limits # * lightness values goes from 0 to 100 (y-axis limits) ax.set_xlim(axes[0, 0].get_xlim()) ax.set_ylim(0.0, 100.0) # Set up labels for colormaps ax.xaxis.set_ticks_position('top') ticker = mpl.ticker.FixedLocator(locs) ax.xaxis.set_major_locator(ticker) formatter = mpl.ticker.FixedFormatter(cmap_list[i*dsub:(i+1)*dsub]) ax.xaxis.set_major_formatter(formatter) ax.xaxis.set_tick_params(rotation=50) ax.set_xlabel(cmap_category + ' colormaps', fontsize=14) fig.text(0.0, 0.55, 'Lightness $L^*$', fontsize=12, transform=fig.transFigure, rotation=90) fig.tight_layout(h_pad=0.0, pad=1.5) plt.show()
Grayscale conversion¶
It is important to pay attention to conversion to grayscale for colorplots, since they may be printed on black and white printers. If notcarefully considered, your readers may end up with indecipherableplots because the grayscale changes unpredictably through thecolormap.
Conversion to grayscale is done in many different ways [bw]. Some of thebetter ones use a linear combination of the rgb values of a pixel, butweighted according to how we perceive color intensity. A nonlinear method ofconversion to grayscale is to use the \(L^*\) values of the pixels. Ingeneral, similar principles apply for this question as they do for presentingone's information perceptually; that is, if a colormap is chosen that ismonotonically increasing in \(L^*\) values, it will print in a reasonablemanner to grayscale.
With this in mind, we see that the Sequential colormaps have reasonablerepresentations in grayscale. Some of the Sequential2 colormaps have decentenough grayscale representations, though some (autumn, spring, summer,winter) have very little grayscale change. If a colormap like this was usedin a plot and then the plot was printed to grayscale, a lot of theinformation may map to the same gray values. The Diverging colormaps mostlyvary from darker gray on the outer edges to white in the middle. Some(PuOr and seismic) have noticeably darker gray on one side than the otherand therefore are not very symmetric. coolwarm has little range of gray scaleand would print to a more uniform plot, losing a lot of detail. Note thatoverlaid, labeled contours could help differentiate between one side of thecolormap vs. the other since color cannot be used once a plot is printed tograyscale. Many of the Qualitative and Miscellaneous colormaps, such asAccent, hsv, and jet, change from darker to lighter and back to darker graythroughout the colormap. This would make it impossible for a viewer tointerpret the information in a plot once it is printed in grayscale.
mpl.rcParams.update({'font.size': 14})# Indices to step through colormap.x = np.linspace(0.0, 1.0, 100)gradient = np.linspace(0, 1, 256)gradient = np.vstack((gradient, gradient))def plot_color_gradients(cmap_category, cmap_list): fig, axes = plt.subplots(nrows=len(cmap_list), ncols=2) fig.subplots_adjust(top=0.95, bottom=0.01, left=0.2, right=0.99, wspace=0.05) fig.suptitle(cmap_category + ' colormaps', fontsize=14, y=1.0, x=0.6) for ax, name in zip(axes, cmap_list): # Get RGB values for colormap. rgb = cm.get_cmap(plt.get_cmap(name))(x)[np.newaxis, :, :3] # Get colormap in CAM02-UCS colorspace. We want the lightness. lab = cspace_converter("sRGB1", "CAM02-UCS")(rgb) L = lab[0, :, 0] L = np.float32(np.vstack((L, L, L))) ax[0].imshow(gradient, aspect='auto', cmap=plt.get_cmap(name)) ax[1].imshow(L, aspect='auto', cmap='binary_r', vmin=0., vmax=100.) pos = list(ax[0].get_position().bounds) x_text = pos[0] - 0.01 y_text = pos[1] + pos[3]/2. fig.text(x_text, y_text, name, va='center', ha='right', fontsize=10) # Turn off *all* ticks & spines, not just the ones with colormaps. for ax in axes.flat: ax.set_axis_off() plt.show()for cmap_category, cmap_list in cmaps.items(): plot_color_gradients(cmap_category, cmap_list)
Color vision deficiencies¶
There is a lot of information available about color blindness (e.g.,[colorblindness]). Additionally, there are tools available to convert imagesto how they look for different types of color vision deficiencies.
The most common form of color vision deficiency involves differentiatingbetween red and green. Thus, avoiding colormaps with both red and green willavoid many problems in general.
References¶
[colorcet] | (1, 2) https://colorcet.pyviz.org |
[Ware] | http://ccom.unh.edu/sites/default/files/publications/Ware_1988_CGA_Color_sequences_univariate_maps.pdf |
[Moreland] | http://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf |
[list-colormaps] | https://gist.github.com/endolith/2719900#id7 |
[mycarta-banding] | https://mycarta.wordpress.com/2012/10/14/the-rainbow-is-deadlong-live-the-rainbow-part-4-cie-lab-heated-body/ |
[mycarta-jet] | (1, 2) https://mycarta.wordpress.com/2012/10/06/the-rainbow-is-deadlong-live-the-rainbow-part-3/ |
[kovesi-colormaps] | https://arxiv.org/abs/1509.03700 |
[bw] | http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/ |
[colorblindness] | http://www.color-blindness.com/ |
[IBM] | https://doi.org/10.1109/VISUAL.1995.480803 |
[palettable] | https://jiffyclub.github.io/palettable/ |
Total running time of the script: ( 0 minutes 4.634 seconds)
Download Python source code: colormaps.py
Download Jupyter notebook: colormaps.ipynb
Keywords: matplotlib code example, codex, python plot, pyplotGallery generated by Sphinx-Gallery