Customising figures in Matplotlib
Matplotlib is one of the longest standing and most comprehensive plotting libraries for Python. It is mostly used for creating static plots and its flexible customisation options make it a great choice for creating publication quality graphs.
In this blog post we will look at formatting and colourmap customisation in Matplotlib, and how to set a consistent plotting style throughout a project.
Note: If you wish to run the code snippets in this blog yourself you will need:
- Matplotlib > 3.6.0
- Numpy
Global formatting with rcParams
In Matplotlib it is possible to change styling settings globally with
runtime configuration (rc) parameters.
The default Matplotlib styling configuration is set with matplotlib.rcParams
.
This is a dictionary containing formatting settings and their values.
By changing these values we can change default settings throughout an
entire script or notebook.
For example, if you wanted to set the tick label size to 12pt you would use:
import matplotlib as mpl
mpl.rcParams["xtick.labelsize"] = 12
mpl.rcParams["ytick.labelsize"] = 12
If you are making a plot for publication, a useful thing to do is enable LaTeX and set the font to be consistent with your LaTeX document. This can be done with:
mpl.rcParams["text.usetex"] = True
mpl.rcParams["font.family"] = "Computer Modern Serif"
The full list of rcParams which you can configure can be found here.
If you want to later revert to the default settings for Matplotlib you can do this with:
mpl.rcdefaults()
Style sheets
Matplotlib comes with a selection of available style sheets. These define a range of plotting parameters and can be used to apply those parameters to your plots.
Inbuilt style sheets
After importing Matplotlib, you can see a list of available style sheets with:
import matplotlib.pyplot as plt
plt.style.available
We will use the plot below as an example. This was created with the default Matplotlib theme.
We can change the style of the figure, as well as the rest of the figures throughout a script/ notebook, with:
plt.style.use("dark_background")
If we now look at our example again, we can see that the formatting has changed.
If you like the default look of plots in the {ggplot2} R package, there is also a style sheet for that,
plt.style.use("ggplot")
Creating your own style sheet
If you are writing a paper or a report you may want to define your own set of plotting parameters to be used throughout. You may also want to be able to use these parameters in several scripts and be able to share them with collaborators to ensure a consistent aesthetic. You can do this by creating your own style sheet.
The inbuilt style sheets are defined in .mplstyle
files. You can find out where these are located by running
import os
os.path.join(mpl.get_data_path(), "stylelib")
If you are using miniconda the path returned will look something like
~/miniconda3/lib/python3.8/site-packages/mpl-data/stylelib
In the stylelib/
folder you will find all the inbuilt .mplstyle
files.
Taking a look at the ggplot.mplstyle
file,
# from https://everyhue.me/posts/sane-color-scheme-for-matplotlib/
patch.linewidth: 0.5
patch.facecolor: 348ABD # blue
patch.edgecolor: EEEEEE
patch.antialiased: True
font.size: 10.0
axes.facecolor: E5E5E5
axes.edgecolor: white
axes.linewidth: 1
axes.grid: True
axes.titlesize: x-large
axes.labelsize: large
axes.labelcolor: 555555
axes.axisbelow: True # grid/ticks are below elements (e.g., lines, text)
axes.prop_cycle: cycler('color', ['E24A33', '348ABD', '988ED5', '777777', 'FBC15E', '8EBA42', 'FFB5B8'])
# E24A33 : red
# 348ABD : blue
# 988ED5 : purple
# 777777 : gray
# FBC15E : yellow
# 8EBA42 : green
# FFB5B8 : pink
xtick.color: 555555
xtick.direction: out
ytick.color: 555555
ytick.direction: out
grid.color: white
grid.linestyle: - # solid line
figure.facecolor: white
figure.edgecolor: 0.50
we can see that it contains a collection of rcParam settings.
Creating your own style sheet is very straightforward. Simply create a file with a .mplstyle
extension, then put all your rcParam settings in here with the same format as the file shown above. If you save this file in stylelib/
you will be able to use your style sheet in your Python script with:
plt.style.use("style_sheet_name")
If you save your style sheet elsewhere you will need to specify the full or relative path,
plt.style.use("path_to_style_sheet/style_sheet_name.mplstyle")
Colourmaps
Matplotlib has a variety of inbuilt colourmaps to choose from. If those colours don’t take your fancy then there are also external libraries that provide additional colourmaps. A popular one is palettable, which includes a series of colourmaps generated from Wes Anderson movies.
If you are feeling creative, or if you want the colours of your plots to match a particular theme or company branding, then you can also create your own colourmap.
A colourmap object takes a number between 0 and 1 and maps this to a colour.
In Matplotlib, there are two colourmap classes: ListedColormap
and LinearSegmentedColormap
.
ListedColormaps
The colours for a ListedColormap
are stored in a .colors
attribute. We can take a look at the .colors
attribute of the inbuilt
“viridis” colourmap with:
# Sample 5 values from map
viridis = mpl.colormaps["viridis"].resampled(5)
print(viridis.colors)
## [[0.267004 0.004874 0.329415 1. ]
## [0.229739 0.322361 0.545706 1. ]
## [0.127568 0.566949 0.550556 1. ]
## [0.369214 0.788888 0.382914 1. ]
## [0.993248 0.906157 0.143936 1. ]]
This is a 5 x 4 array of RGBA values (as we sampled 5 values from the full map).
Creating a discrete ListedColormap
To create a discrete colourmap we can simply pass a list of
colours to ListedColormap
. These can be given as
named Matplotlib colours,
or as hex values.
from matplotlib.colors import ListedColormap
discrete_cmap = ListedColormap(["#12a79d", "#293d9b", "#4898a8", "#40b93c"])
To look at this colourmap we will use the following code. This plots a colourbar on its own.
def plot_cmap(cmap):
fig, cax = plt.subplots(figsize=(8, 1))
cb1 = mpl.colorbar.Colorbar(cax, cmap=cmap, orientation="horizontal")
plt.tight_layout()
plt.show()
plot_cmap(discrete_cmap)
We can also specify the number of colours we want in the colourmap with the argument, N
.
If N
is greater than the length of the list provided then the colours are repeated,
otherwise the map is truncated at N
.
discrete_cmap = ListedColormap(
["#12a79d", "#293d9b", "#4898a8", "#40b93c"], N=8
)
plot_cmap(discrete_cmap)
As well as using named/hex colours, we can also create a colourmap by passing an N x 3 or
N x 4 array of RGB or RGBA values to ListedColormap
.
To create a similar colourmap to above this would be:
import numpy as np
carray = np.array([
[18, 167, 157],
[41, 61, 155],
[72, 152, 168],
[64, 185, 60]
]) / 255
discrete_cmap = ListedColormap(carray)
plot_cmap(discrete_cmap)
Note that here the RGB values were originally on a scale of 0–255. However, Matplotlib expects a scale of 0–1. Hence the division of our array by 255.
Creating a continuous ListedColormap
To create a continuous colourmap we need an array of gradually changing colours.
This can be achieved using np.linspace(start, stop, num)
. For example, to generate a fading colourmap,
we can use an RGB value from above as the start point, and white (1) as the endpoint.
N = 100 # No. of colours (large enough to appear continuous)
# Create N x 3 array of ones
carray = np.ones((N, 3))
# Assign columns of array
carray[:, 0] = np.linspace(72 / 255, 1, N)
carray[:, 1] = np.linspace(152 / 255, 1, N)
carray[:, 2] = np.linspace(168 / 255, 1, N)
# Create colourmap
cont_cmap = ListedColormap(carray)
plot_cmap(cont_cmap)
LinearSegmentedColormaps
LinearSegmentedColormap
s do not have a .colors
attribute. However, we can access the values in the colourmap by calling it with an array of integers.
cool = mpl.colormaps["cool"].resampled(8)
cool(range(8))
## array([[0. , 1. , 1. , 1. ],
## [0.14285714, 0.85714286, 1. , 1. ],
## [0.28571429, 0.71428571, 1. , 1. ],
## [0.42857143, 0.57142857, 1. , 1. ],
## [0.57142857, 0.42857143, 1. , 1. ],
## [0.71428571, 0.28571429, 1. , 1. ],
## [0.85714286, 0.14285714, 1. , 1. ],
## [1. , 0. , 1. , 1. ]])
Rather than taking a list of colours that make up the map, LinearSegmentedColormap
s take
an argument called segmentdata
. This argument is a dictionary with the keys “red”, “green”
and “blue”. Each value in the dictionary is a list of tuples.
These tuples specify colour values before and after points in the colourmap as (i
, y[i-1]
, y[i+1]
). Here i
is
a point on the map, y[i-1]
is the colour value of the point before i
, and y[i+1]
is the colour value
after i
. The other colour values on the map are obtained by performing linear interpolation between
these specified anchor points.
For example, we could have the following segmentdata
dictionary:
cdict = {
"red": [
(0, 0, 0), # start off with r=0
(0.25, 1, 0), # r increases from 0-1 bewteen 0-0.25, then drops to 0
(1, 0, 0), # end with r=0
],
"green": [
(0, 0, 0), # start off with g=0
(0.25, 0, 0), # at 0.25, g is still 0
(0.75, 1, 0), # g increases from 0-1 between 0.25-0.75, then drops to 0
(1, 0, 0), # g is 0 between 0.75 and 1
],
"blue": [
(0, 0, 0), # start off with b=0
(0.75, 0, 0), # b is 0 between 0 and 0.75
(1, 1, 1), # b increases from 0 to 1 between points 0.75 and 1
],
}
In this map,
- red is increased from 0–1 over the first quarter of the map and then drops back to 0.
- green is increased from 0–1 over the middle half of the map and then drops back to zero.
- blue is 0 till the final quarter of the map and is then increased from 0–1 over the final quarter.
LinearSegmentedColormap
also takes a name argument. We can create a map from the dictionary
above with:
from matplotlib.colors import LinearSegmentedColormap
seg_cmap = LinearSegmentedColormap("seg_cmap", cdict)
plot_cmap(seg_cmap)
This way of creating a colourmap is a bit longwinded. Luckily, there is an easier
way to create a LinearSegmentedColormap
using the .from_list()
method. This takes a
list of colours to be used as equally spaced anchor points.
color_list = ["#12a79d", "#293d9b", "#4898a8", "#40b93c"]
seg_cmap = LinearSegmentedColormap.from_list("mymap", color_list)
plot_cmap(seg_cmap)
In this blog we have covered the basics of how you can format plots and create colourmaps in Matplotlib. Once we can do this there is a lot more to be said on how to choose these settings to create clear and accessible plots, but we will leave that for a future post.
Useful links
- Style sheets reference
- Available colourmaps reference
- Classes of colourmaps
- Matplotlib tutorial on colourmaps
- Palettable package documentation