Color maps in Matplotlib

Matplotlib provides colour maps to change the range of colours used in a plot. Changing the range of colours on a plot can be useful to show patterns within the data being displayed. Also, while the default colormap is functional, it's not particularly aesthetically pleasing. Figure 1 demonstrates the same plot drawn using two different colormaps.

Line plot with two different colormaps

Figure 1: Line plot using two different colormaps

There are three types of map:

Examples of each type of map are:

For many uses the way in which the map appears in grayscale for publication is important: a number of the popular maps such as Accent and jet do not convert well. Further information in Choosing Colormaps.

Setting the colormap

To set the colormap there are a few choices. The standard colormaps can be accessed through the pyplot interface, and there is also matplotlib.pyplot.set_cmap(), finally it can be done with a rcParams option:

# Set the colormap to 'copper'
plt.copper()
plt.set_cmap('copper')
plt.rcParams['image.cmap']='copper'

These call sets the colormap for all subsequent plots. To set the colormap to grayscale provide the parameter gray or use plt.gray().

Some plot types accept a colormap as one of the parameters (e.g scatter plot) and will automatically cycle through using colours from the map. For others (e.g bar chart) you have to manually set-up the colormap, select the colours you want and then use them are parameters to the plot call.

The colormap objects are set up so that values between 0-1 give you the grayscale hues, and the colours of the map are set between 1-255 [1]. If you query the color map then you get back an RGBA tuple.

Plots supporting colormaps

Having set a colormap we want our plot to use it by cycling through using different colours for different values. Some plot types support this capability natively by accepting a colormap as a parameter.

The first example creates two scatter plots using the same random selection of points, using two different colormaps, coolwarm and plasma. We plot the first 50 colours from each colormap. See Figure 1 for the output.

import matplotlib.pyplot as plt
import random

# Set-up standard style
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = 'Ubuntu'
plt.rcParams['font.sans-serif'] = []
plt.rcParams['font.monospace'] = 'Ubuntu Mono'
plt.rcParams['axes.labelsize'] = 10
plt.rcParams['font.size'] = 10
plt.rcParams['legend.fontsize'] = 8
plt.rcParams['xtick.labelsize'] = 8
plt.rcParams['ytick.labelsize'] = 8

plt.style.use('seaborn-notebook')

# Series 1 is our X axis, just random numbers from 1-50
# Series 2 will be regular positions on Y from 1-50
# clist is the first 50 positions from the colormap
series1 = random.sample(range(100), 50)
series2 = range(50)
clist = range(50)

plt.figure(figsize=(10, 8), dpi=200)

# Create the first plot using the coolwarm colormap
ax1 = plt.subplot(211)
ax1.scatter(series1, series2, s=80, c=clist, cmap=plt.cm.coolwarm, label='rain')

# Create the second plot using the plasma colormap
ax2 = plt.subplot(212)
ax2.scatter(series1, series2, s=80, c=clist, cmap=plt.cm.plasma, label='shine')

# Vertically space the plots apart by 0.4 of a figure
plt.subplots_adjust(hspace=0.4)

# Describe our plot - labels, title and legend
ax1.set_xlabel('Chart 1 X')
ax1.set_ylabel('Chart 1 Y')
ax1.set_title('Using coolwarm colormap')
ax1.legend(loc='best')

ax2.set_xlabel('Chart 2 X')
ax2.set_ylabel('Chart 2 Y')
ax2.set_title('Using plasma colormap')
ax2.legend(loc='best')

# Label the overall figure
plt.suptitle('Colormap scatterplot example', fontsize=14, fontweight='bold')

The first parameter to plt.scatter() is the X axis points, random numbers from 1-50. The second parameter is the Y axis, which is just the points from 1-50. The s parameter is the size of each mark on the graph, we've chosen a fixed size of 80 points for simplicity. The important parameter is cmap which is the colormap, it accepts any of the colormaps available - the first call uses plt.cm.coolwarm and the second plt.cm.plasma. The colormap with the parameter c (a list from 1-50) tells matplotlib to use the colormaps first 50 points in our plot.

See Chris Albon's Set The Color Of A Matplotlib Plot for another example, and Tony S. Yu shows a couple of options in line-color cycling.

Plots without colormap support

Some plot types don't accept a colormap to cycle through directly (e.g. line plot), instead you have to set-up the new colormap and then cycle through it manually selecting colours to use. There are two options for cycling through the colours. Some plot types use a Cycler object to define a set of colours to cycle through during the plot. If the plot doesn't support this then you can manually map from the colormap to an RGBA tuple that can be supplied to the plot itself.

Line plot - cycling through a color map

Figure 2: Cycling through a color map in a Line plot

The line plot supports cycling through colours using a cycler object. In the example below we define a custom color cycler and provide it with four defined colours for each of our line plots. The resulting plot is shown in Figure 2.

The key step is to set-up the cycler object on the line plot figure using the method set_prop_cycle() which accepts a cycler() object. In this case we provide the color one with an iterator (the list), telling it to return 'cyan', 'magenta', 'yellow' and 'black'. The list itself can be any colour that Matplotlib can translate to an RGBA value. For example, if we wanted grayscale we could provide numbers between 0-1, and it will also accept HTML colours (e.g '#04B431').

import matplotlib.pyplot as plt
from cycler import cycler

plt.style.use('bmh')

y_1 = [1,2,3,4,5]
x_1 = [1,4,9,12,16]
x_2 = [1,3,12,8,14]
x_3 = [2,6,7,9,6]
x_4 = [6,2,3,5,1]

plt.figure(figsize=(10, 8), dpi=200)
ax1 = plt.subplot(211)

# These two lines set-up the color map and tell it to cycle
plt.set_cmap('Accent')
ax1.set_prop_cycle( cycler('color', ['c', 'm', 'y', 'k']) )

ax1.plot(y_1, x_1, linewidth=2, linestyle=':', marker='o', label='Dogs')
ax1.plot(y_1, x_2, linewidth=2, linestyle='--', marker='v', label='Cats')
ax1.plot(y_1, x_3, linewidth=4, linestyle='-.', marker='s', label='Gerbils')
ax1.plot(y_1, x_4, linewidth=4, linestyle='--', marker='s', label='Turtles')

# Describe our plot - marks on X, the labels, title and legend
plt.xticks([1,2,3,4,5],['Jan','Feb','Mar','Apr','May'])
plt.xlabel('Sightings')
plt.ylabel('Time')
plt.title('Sightings of Cats and Dogs')
plt.legend(loc='best')

Normalise the colormap

In the previous example you'll have seen that we manually selected colours in the colormap that were some distance apart so that we had distinct colours. Rather than doing this manually we can automate it. We do this by splitting the colormap into a number of segments so that the plot uses the range of colours evenly and the colours don't overlap: if there are 100 values in the plot, we would divide the colormap into 100 segments so that each value would get a different colour.

Line plot colormap normalisation

Figure 3: Normalising the colormap for a line plot

There's a great example of this approach on the Empty Pipes blog [2]. Ali created a function [3] on Stackoverflow to normalise the colormap into the number of segments that you want, the next example uses it.

Imagine that you want to do a line plot with 10 different lines, and you want to ensure that the colours will each be different and distinct. The results are displayed in Figure 3

import matplotlib.pyplot as plt
import matplotlib.cm as cmx
import matplotlib.colors as colors
from cycler import cycler
import random

plt.style.use('seaborn-muted')

def get_cmap(N):
    ''' Returns a function that maps each index in 0, 1, ...
        N-1 to a distinct RGB color.'''
    color_norm  = colors.Normalize(vmin=0, vmax=N-1)
    #scalar_map = cmx.ScalarMappable(norm=color_norm, cmap='hsv')
    scalar_map = cmx.ScalarMappable(norm=color_norm, cmap='Accent')
    def map_index_to_rgb_color(index):
        return scalar_map.to_rgba(index)
    return map_index_to_rgb_color

y_1 = [1,2,3,4,5]
x_1 = [1,4,9,12,16]
x_2 = [1,3,12,8,14]
x_3 = [2,6,7,9,6]
x_4 = [6,2,3,5,1]

plt.figure(figsize=(10, 8), dpi=200)
ax1 = plt.subplot(211)

# Our plot is going to use up to 10 colours, so we split the colormap into
# twice as many segments (e.g 20), later we randomly select the 10 to use
colr_list = []
num_colr = 10
cmap = get_cmap(num_colr*2)

# We create a list with 10 points in the range 1-20 to match our
# cmap_list. Then we put those 10 RGB tuples into colr_list
tmp_list=random.sample(range(1,num_colr*2),num_colr)
for x in tmp_list:
    colr_list.append(cmap(float(x)))

# We have a list of 10 non-overlapping colours in our colr_list.
# We tell it to cycle through this list
ax1.set_prop_cycle( cycler('color', colr_list) )

ax1.plot(y_1, x_1, linewidth=5, linestyle=':', marker='o', label='Dogs')
ax1.plot(y_2, x_2, linewidth=5, linestyle='--', marker='v', label='Cats')
ax1.plot(y_1, x_3, linewidth=4, linestyle='-.', marker='d', label='Gerbils')
ax1.plot(y_1, x_4, linewidth=4, linestyle='-', marker='s', label='Turtles')

# Describe our plot - marks on X, the labels, title and legend
plt.xticks([1,2,3,4,5],['Jan','Feb','Mar','Apr','May'])
plt.xlabel('Sightings')
plt.ylabel('Time')
plt.title('Sightings of Cats and Dogs')
plt.legend(loc='best')

Final thoughts

Changing the colormap can improve the aesthetics and clarity of a plot. Colour has a powerful perceptional effect, so the color map should visually reflects the data truthfully. There's lots of great information about this effect [4] and its impact on colormap choice. At a library level it would be useful if Matplotlib provided a consistent interface for altering the colormap on each plot type: the cycler object is new so this seems to be developers direction. For now, we can use the methods above. If you have any other tips on using colormaps please leave a comment below!


[1]About matplotlib colormap and how to get RGB values of the map
[2]Scaled colormap in Matplotlib
[3]How to generate random colors in matplotlib
[4]How The Rainbow Color Map Misleads

Posted in Tech Thursday 31 March 2016
Tagged with python matplotlib