Customising figures in Matplotlib

This article was first published on The Jumping Rivers Blog , and kindly contributed to python-bloggers. (You can report issue about the content on this page here)
Want to share your content on python-bloggers? click here.


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()

Data comes in all shapes and sizes. It can often be difficult to know where to start. Whatever your problem, Jumping Rivers can help.


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.

Graph with time on the x axis and amplitude on the y axis.
The plot shows two oscillating lines, one in blue and the other in orange.
The background of the plot is white, there are no grid lines.
The axes are labeled and the plot has the title "Damped oscillator".

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.

Graph with time on the x axis and amplitude on the y axis.
The plot shows two oscillating lines, one in blue and the other in yellow.
The background of the plot is black, the text is white and there are no grid lines.
The text is in the same font.

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")

Graph with time on the x axis and amplitude on the y axis.
The plot shows two oscillating lines, one in red and the other in blue.
The background of the plot is grey, the text is dark grey and the plot has grid lines.
The text has a larger font size.

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/pyhton3.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)

Horizontal colour bar, evenly split into four discrete colours: turquoise,
dark blue, light blue, green. The x-axis ranges from 0 to 1.

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)

Horizontal colour bar, evenly split into eight discrete colours. The four colours
turquoise, dark blue, light blue, green are repeated. The x-axis ranges from 0 to 1.

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)

Horizontal colour bar evenly split into four discrete colours: turquoise,
dark blue, light blue, green. The x-axis ranges from 0 to 1.

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)

Horizontal colour bar fading from turquoise on the left to white on the right. The x-axis ranges
from 0 to 1.

LinearSegmentedColormaps

LinearSegmentedColormaps 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, LinearSegmentedColormaps 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)

Horizontal colour bar with an x-axis range from 0 to 1. It starts as black at x=0 and
gradually changes to red at x=0.25. It goes sharply to black again and gradually changes to green
until x=0.75. It is black again and gradually changes to blue until x=1.0.

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)

Horizontal colour bar with an x-axis range from 0 to 1. It shows a sequential colourmap
with colours gradually changing from turquoise to dark blue to light blue to green.

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.

For updates and revisions to this article, see the original post

To leave a comment for the author, please follow the link and comment on their blog: The Jumping Rivers Blog .

Want to share your content on python-bloggers? click here.