Making Matplotlib Beautiful By Default | by Callum Ballard | Towards Data Science

If you’ve ever made a data visualisation in Python, chances are you’ve used Matplotlib. It’s a package that divides opinion. Some notionally simple visualisations take a baffling amount of code to produce. In this day and age, people expect to be able to interact with charts — something not offered with vanilla Matplotlib. And, perhaps most importantly, Matplotlib charts can often look plain bad.
It’s true that the default Matplotlib settings can make your visuals look plain, if not a little dated (if we’re being generous). Its default shade of blue haunts the dreams of many a data scientist.
One of the under-appreciated aspects of Matplotlib, however, is just how customisable it is. Far from the above example, you can create seriously intricate visuals (admittedly, this one took many lines of code, and a chunk of time rarely available to most data scientists).
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).
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 purpleCB91_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']
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.
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 pltimport seaborn as snssns.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})
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 usualimport matplotlib.pyplot as plt%matplotlib inlineimport seaborn as sns#Bring in our custom settings#Assumes that cb91visuals.py is in the repo's root folderfrom cb91visuals import *
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', 'Jó éjt', etc.#and X is the list of values determining bar length#Loop through these labelsfor 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.
Thanks for reading all the way to the end of the blog! I’d love to hear any comments about the above, or any of the concepts that the piece touches on. If you have any other Matplotlib tips and tricks, feel free to leave a message below, or reach out to me through LinkedIn.