Making Matplotlib Beautiful By Default
By: Callum Ballard
Tags: Matplotlib, Data Visualization, Data Science, Towards Data Science, Python

What’s more, we can write some relatively code to set default visual settings. We can get quite granular. Don’t want that annoying box around every plot? Get rid of it by default. Want to use your brand’s colour palette without having to specify the hex codes every single time? Set them by default. Want to use Comic Sans for all your chart labels? Seek professional help (though you can set this by default as well, if you really insist).

Dealing With Colour

Though there are named colours in Matplotlib (such as the well known colours ‘bisque’, ‘lavenderblush’, and ‘lightgoldenrodyellow’), plots will also take colours in the form of hex codes. This is especially useful if we want to use a custom colour palette. For example, my CB91 brand uses a pre-defined set of colours.

We can set these as variables, by passing their hexes as strings:

CB91_Blue = '#2CBDFE'

CB91_Green = '#47DBCD'

CB91_Pink = '#F3A0F2'

CB91_Purple = '#9D2EC5'

CB91_Violet = '#661D98'

CB91_Amber = '#F5B14C'

Note — if you want to create your own colour palette, there are some useful tools online that can help. Colormind.io is especially cool — it uses deep learning to learn colour styles from photographs, movies, and popular art, and hence recommend hues and shades that go well together. Also, I can confirm that as a Brit, switching indiscriminately between ‘color’ and ‘colour’ has resulted in some deeply annoying code errors in my time.

We can put these defined colours into a list, and then pass this list into Matplotlib’s colour ‘cycler’.

color_list = [CB91_Blue, CB91_Pink, CB91_Green, CB91_Amber,

CB91_Purple, CB91_Violet] plt.rcParams['axes.prop_cycle'] = plt.cycler(color=color_list)

It will now use ‘CB91_Blue’ as the default chart colour for all plots in the notebook AND cycle through the colour list in order when creating multi-category plots.

You can also define gradients using lists of hex colours, which can then be used pretty much anywhere where viridis is a valid entry (e.g. Seaborn heatmaps). Of course, to make these gradients nice and smooth, you need lots of defined hex values in that list (at least 40 in my experience). Creating such a long list manually can be a pain — I instead suggest generating one automatically using a site like colordesigner.io (simply choose the colours that you want the gradient to transition between, max out the number of gradient steps, then extract the hex codes from the resulting HTML).

Edit: Peter Cahill has written a Python function that will generate gradient lists with n steps between two colour hexes— see the responses to this blog, below.

Here’s an example of a gradient running between two of my brand’s colours, and how they look on a matplotlib chart.

#A list of hex colours running between blue and purple

CB91_Grad_BP = ['#2cbdfe', '#2fb9fc', '#33b4fa', '#36b0f8',

'#3aacf6', '#3da8f4', '#41a3f2', '#449ff0',

'#489bee', '#4b97ec', '#4f92ea', '#528ee8',

'#568ae6', '#5986e4', '#5c81e2', '#607de0',

'#6379de', '#6775dc', '#6a70da', '#6e6cd8',

'#7168d7', '#7564d5', '#785fd3', '#7c5bd1',

'#7f57cf', '#8353cd', '#864ecb', '#894ac9',

'#8d46c7', '#9042c5', '#943dc3', '#9739c1',

'#9b35bf', '#9e31bd', '#a22cbb', '#a528b9',

'#a924b7', '#ac20b5', '#b01bb3', '#b317b1']

Tweaking Axes

One of Matplotlib’s nicest features comes in the form of an entirely different package; Seaborn. A library that uses Matplotlib as a base, Seaborn is a great way to create more complex chart types, like Heatmaps, Violins, and Joint Plots in a line or two of code.

An example of a Seaborn heatmap figure, which can take a list of hex colours as its cmap argument.

One lesser known feature of Seaborn is its ability to control Matplotlib plot defaults, using the .set() method. This is a neat way to tidy up plots, changing the colour and weight of axes (or removing them entirely), and the default font. We can also use the .set_context() method to fine-tune font size settings.

An example of how I use all of this is given below (a full list of everything you can control with the .set() method can be found in Matplotlib’s own tutorial on the matter).

import matplotlib.pyplot as plt

import seaborn as sns sns.set(font=’Franklin Gothic Book’,

rc={

‘axes.axisbelow’: False,

‘axes.edgecolor’: ‘lightgrey’,

‘axes.facecolor’: ‘None’,

‘axes.grid’: False,

‘axes.labelcolor’: ‘dimgrey’,

‘axes.spines.right’: False,

‘axes.spines.top’: False,

‘figure.facecolor’: ‘white’,

‘lines.solid_capstyle’: ‘round’,

‘patch.edgecolor’: ‘w’,

‘patch.force_edgecolor’: True,

‘text.color’: ‘dimgrey’,

‘xtick.bottom’: False,

‘xtick.color’: ‘dimgrey’,

‘xtick.direction’: ‘out’,

‘xtick.top’: False,

‘ytick.color’: ‘dimgrey’,

‘ytick.direction’: ‘out’,

‘ytick.left’: False,

‘ytick.right’: False}) sns.set_context("notebook", rc={"font.size":16,

"axes.titlesize":20,

"axes.labelsize":18})

Putting It All Together

How you structure a repo is, of course, a very personal thing. I find that having all of the discussed code at the top of a Jupyter notebook can be quite untidy – especially by the time you need to set up multiple different gradients and their associated lists of 40+ hex codes. Moreover, if you have multiple notebooks in a repo, it can be a pain to keep things consistent if you ever want to make changes to the visual defaults.

To address these issues, I like to keep all the code pertaining to visual defaults in a separate python file elsewhere in the repo (called ‘cb91visuals.py’) then import them as part of the standard imports at the top of each notebook. Thus, all the messy lists of hex codes are kept hidden, and if you do need to make an edit, this only needs to be done in one place, with changes automatically reflected everywhere as required.

#Bring in data visualisation libraries as usual

import matplotlib.pyplot as plt

%matplotlib inline

import seaborn as sns #Bring in our custom settings

#Assumes that cb91visuals.py is in the repo's root folder

from cb91visuals import *

Miscellaneous Tips

There are some other lesser-known Matplotlib/Seaborn commands that I use quite a lot. I tend not to have them set as default, but they can be useful nonetheless.

Legend frames: The little boxes around legends are often unnecessary, and add visual clutter. These can be switched off with the following:

plt.legend(frameon=False)

De-spining: Sometimes we want to remove an axis, for example, on bar charts. This is done with Seaborn’s despine method (specifying left, right, top, or bottom, as required):

sns.despine(left=True, bottom=True)

Numerical labels on bar charts: A feature that really ought to be available as part of the package, you can add numerical labels to the tops of bar chart columns with for-looping, and Matplotlib’s .text() method. For example, the following code…

#Here, 'labels' refers to the bigrams on the y-axis

#i.e. 'Look forward', 'Jó éjt', etc.

#and X is the list of values determining bar length #Loop through these labels

for n, i in enumerate(labels): #Create an axis text object

ax.text(X[n]-0.003, #X location of text (with adjustment)

n, #Y location

s=f'{round(X[n],3)}%', #Required label with formatting

va='center', #Vertical alignment

ha='right', #Horizontal alignment

color='white', #Font colour and size

fontsize=12)

…would put numerical labels on a horizontal bar chart, for example the following chart that featured in one of my blogs a couple of weeks ago. Note, to do this I also used the sns.despine() method to remove the x-axis, and passed plt.xticks([]) to remove the axis ticks.