Making publication-quality figures in Python (Part I): Fig and Axes | by Guangyuan(Frank) Li | Towards Data Science

In this whole series, I will share with you how I usually make publication-quality figures in Python. I want to really convey the ideas of how to gain full control of every element in a python plot. By the end of this tutorial, you will be able to fully understand the philosophy of making figures in Python.
Since it will be a huge topic, I decide to break it down to several parts. In this Part I tutorial, I will focus on the very first step — understanding the canvas (Fig object) you will be drawing on and the boundary of each figures (Ax object). And in the following tutorials, I will walk you through of how to actually make all kinds of figures you’ve been familiar with (i.e. scatter plot, boxplot, heatmap, etc).
The tutorial I: Fig and Ax objectTutorial II: Line plot, legend, colorTutorial III: box plot, bar plot, scatter plot, histogram, heatmap, colormapTutorial IV: violin plot, dendrogramTutorial V: Plots in Seaborn (cluster heatmap, pair plot, dist plot, etc)
The tutorial I: Fig and Ax object
Tutorial II: Line plot, legend, color
Tutorial III: box plot, bar plot, scatter plot, histogram, heatmap, colormap
Tutorial IV: violin plot, dendrogram
Tutorial V: Plots in Seaborn (cluster heatmap, pair plot, dist plot, etc)
All the code and additional information are available at: https://github.com/frankligy/python_visualization_tutorial
Let’s get started!
As a side note, there are several python plotting packages, like matplotlib, seaborn, plotly, etc. I would like to demonstrate all the techniques in matplotlib because it is the most low-level library and learning matplotlib can give you a full understanding of all the nitty-gritties. We will touch on seaborn at the end of the tutorial where you can see the benefit of this high-level library. However, there won’t be shortcut of learning anything, if you want to make publication-quality figures and have the full control of the figures we are making, matplotlib is the best choice as far as I can suggest.
First, let’s load necessary packages, don’t be scared, I often feel overwhelmed seeing people load ten different packages and I have no idea what they are. You basically load matplotlib package, the weapon we are going to use. Then a sub package pyplot in maplotlib package, this is just for convenience. Finally, we need to use some useful functions from another sub-package ticker, which will be used for us to customize the ticks of the axes. So you see, you really just use matplotlib, nothing complicated!
# load packageimport matplotlib as mplimport matplotlib.pyplot as pltfrom matplotlib.ticker import MultipleLocator,FormatStrFormatter,MaxNLocator
Before we start to draw, I’d like to introduce some global setting of matplotlib, it a lot of default setting of your figure elements, like font type, font size, tick pads etc. Hence it is great to get a sense of that, again, the goal of this article is to show you how to fully understand every detail.
You can access the global parameters as below:
a = list(mpl.rcParams.keys) # just a dictionarylen(a)# 305
As you can see, there are 305 global parameters, let’s have a peek of them:
...axes.gridaxes.grid.axisaxes.grid.whichaxes.labelcoloraxes.labelpadaxes.labelsizeaxes.labelweightaxes.linewidthaxes.prop_cycleaxes.spines.bottomaxes.spines.leftaxes.spines.right...
The full list is available at my github page: https://github.com/frankligy/python_visualization_tutorial/blob/main/rcParams.txt
You can tweak any parameters any time by simply change the values of this huge python dictionary, if there are some parameters you want all your following figures to stick with.
I found it useful to change this setting like below, the reason for that is when we make publication-quality figure, we sometime will use Adobe Illustrator to do the final formatting, the default font type is not recognizable in Adobe Illustrator, so I would change them to NO. 42. Also, in academic, we prefer using “Arial” font, so I changed it as well.
mpl.rcParams['pdf.fonttype'] = 42mpl.rcParams['ps.fonttype'] = 42mpl.rcParams['font.family'] = 'Arial'
Let’s create a canvas now:
fig = plt.figure(figsize=(10,6))
The default figsize is (6.4,4.8) in the unit of inch, remember 6.4 inch is the width and 4.8 inch will be the height. It again can be accessed by:
mpl.rcParams['figure.figsize']# [6.4, 4.8]
Let’s see the effect of different figsize, I use Pycharm as my python programming IDE, and it will immediately pop up a canvas window so you can clearly see the differences:
Ok, so this is the canvas you are going to draw figures on. Let’s start to draw a figures on the canvas, each individual figure will be an Ax object, we can create an ax by specifying (left,bottom,width,height) of the figure, it will be clear when you see the result,
fig = plt.figure(figsize=(10,6))ax = plt.axes((0.1,0.1,0.5,0.8))
Let’s see the values of this tuple, (0.1,0.1) means the left bottom corner of this figure sits on this coordinates (0.1,0.1) of the whole canvas, the whole canvas will be in range (0,1). Then (0.5,0.8) determine the width will be 0.5, and the height will be 0.8. Done!
First, I want to get rid of the spines of this figure, I locate ax.spines object and set them invisible.
fig = plt.figure(figsize=(10,6))ax = plt.axes((0.1,0.1,0.5,0.8))ax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['bottom'].set_visible(False)ax.spines['top'].set_visible(False)
You see, they are all gone, and now you can clearly see, a figure is made up of four spines and ticks and labels. This is the message I want to convey here.
Next, we want to play with the x-axis ticks and tick labels, I use tick_params function to set all the parameters to whatever I’d like to.
fig = plt.figure(figsize=(10,6))ax = plt.axes((0.1,0.1,0.5,0.8))ax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['bottom'].set_visible(False)ax.spines['top'].set_visible(False)ax.tick_params(axis='x',which='major',direction='out',length=10,width=5,color='red',pad=15,labelsize=15,labelcolor='green',               labelrotation=15)
What happened here? First, I increased the length of each x tick, and the width as well, then I changed the color of the x ticks to red. I increased the pad/gap between x ticks to their corresponding labels, then I changed the label size and label color, finally I rotated the label counter-clockwise by 15 degree. I left Y-axis untouched so you can clear see what I’ve tweaked. Again, I hope you could get full control of every element in a python figure.
In addition to change the format, you can change the tick and tick label themself as well, we use set_xticks() function and set_xticklabels function to achieve that, see the effect below:
fig = plt.figure(figsize=(10,6))ax = plt.axes((0.1,0.1,0.5,0.8))ax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['bottom'].set_visible(False)ax.spines['top'].set_visible(False)ax.tick_params(axis='x',which='major',direction='out',length=10,width=5,color='red',pad=15,labelsize=15,labelcolor='green',               labelrotation=15)ax.set_xticks([0.2,1])ax.set_xticklabels(['pos1','pos2'])
Next, let’s briefly cover a little advanced topic, what we’ve tried above are all major ticks, what if you want to add minor tick between each major tick interval? We will use set_minor_locator() function, along with the Locator() object we imported from ticker sub package at the beginning. Basically, Locator() determine the location of ticks and Formatter() determine the format of the ticks. Here we demonstrate that in Y-axis instead.
fig = plt.figure(figsize=(10,6))ax = plt.axes((0.1,0.1,0.5,0.8))ax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['bottom'].set_visible(False)ax.spines['top'].set_visible(False)ax.tick_params(axis='x',which='major',direction='out',length=10,width=5,color='red',pad=15,labelsize=15,labelcolor='green',               labelrotation=15)ax.set_xticks([0.2,1])ax.set_xticklabels(['pos1','pos2'])ax.yaxis.set_major_locator(MultipleLocator(0.5))ax.yaxis.set_minor_locator(MultipleLocator(0.1))
MultipleLocator is able to create ticks at the positions that are multiple of certain base, in the above example, every 0.1 unit interval will have a minor tick and every 0.5 unit interval will have a major tick.
You can also adjust minor tick parameters using tick_params() function as well, just specify the “which” argument to “minor”.
There are a bunch of additional classes of Locator() and Formatter(), I would like to refer you to a very well-explained blog: https://jakevdp.github.io/PythonDataScienceHandbook/04.10-customizing-ticks.html
Finally, I want to show you one more thing of manipulating Ax object, that is adding grid lines, sometimes this will make our figures look much better.
fig = plt.figure(figsize=(10,6))ax = plt.axes((0.1,0.1,0.5,0.8))ax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['bottom'].set_visible(False)ax.spines['top'].set_visible(False)ax.tick_params(axis='x',which='major',direction='out',length=10,width=5,color='red',pad=15,labelsize=15,labelcolor='green',               labelrotation=15)ax.set_xticks([0.2,1])ax.set_xticklabels(['pos1','pos2'])ax.yaxis.set_major_locator(MultipleLocator(0.5))ax.yaxis.set_minor_locator(MultipleLocator(0.1))a=ax.yaxis.get_major_locator()b=ax.yaxis.get_major_formatter()ax.grid(True,which='major',axis='both',alpha=0.3)
We use grid() function and specify we want grid line (True) based on major ticks on both axis, the opacity will be 0.3.
Here I want to introduce a class of very important and useful methods, the figure (or Ax object) we created is basically a python object, we can access all its attributes by definition. Sometimes, we will need those information to get full control of our figure generating process.
You can achieve that using get method, you can basically get every element we just played with. For instance, I want to get the x axis ticks and tick labels.
fig = plt.figure(figsize=(10,6))ax = plt.axes((0.1,0.1,0.5,0.8))ax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['bottom'].set_visible(False)ax.spines['top'].set_visible(False)ax.tick_params(axis='x',which='major',direction='out',length=10,width=5,color='red',pad=15,labelsize=15,labelcolor='green',               labelrotation=15)ax.set_xticks([0.2,1])ax.set_xticklabels(['pos1','pos2'])ax.yaxis.set_major_locator(MultipleLocator(0.5))ax.yaxis.set_minor_locator(MultipleLocator(0.1))a=ax.yaxis.get_major_locator()b=ax.yaxis.get_major_formatter()ax.grid(True,which='major',axis='both',alpha=0.3)c = ax.get_xticks()d = ax.get_xticklabels()
We used get_xticks() and get_xticklabels() function and stored the returned objects in two variables c and d. Now we have a look at c and d.
print(c)# [0.2 1. ]print(d)# [Text(0.2, 0, 'pos1'), Text(1.0, 0, 'pos2')]
Makes sense, right? I hope till now you feel more comfortable with python Fig and Ax objects. I actually didn’t show you how to make pretty figures, but I gave you the toolkit you need to make whatever figure you’d like to.
We frequently encounter the situation where we want to make multiple figures on one canvas. One way is just to manually create Ax1 object by specifying its position tuple like we did above, then do the same thing for Ax2 object, again and again. Sometimes it’s a good way to do so. But what if you want to generate, let’s say 63 figures in a 9 x 7 grid, how can we achieve that?
We can use plt.subplots() function,
fig,axes = plt.subplots(nrows=4,ncols=4,figsize=(10,6))
Here I build a 4 x 4 grid, also specify the figsize as we did above. Let’s see the effect:
You see what’s wrong with this canvas? There are a lot of overlapping between each figure, you can remedy that by either increase the figsize, or decrease the label font size, but I provide a better way to do so.
fig,axes = plt.subplots(nrows=4,ncols=4,figsize=(10,6),gridspec_kw={'wspace':0.5,'hspace':0.5})
I basically tell python to leave some space between two figures, wspace is the horizontal gap and hspace is the vertical gap. Let’s see the effect:
All right, that’s all that I have for now. I hope now you have a better sense of using python to design and create your own figures. I will probably add new stuff to this story if I find tips and tricks that are interesting to share in the future. Also, stay tuned for my following tutorials, we will delve into how to make each kind of figures. Don’t hesitate to ask me questions. All the codes are here: https://github.com/frankligy/python_visualization_tutorial
If you like these tutorials, follow me on medium, thank you so much for your support. Connect me on my Twitter or LinkedIn, also please ask me questions about which kind of figure you’d like to learn how to draw in a succinct fashion, I will respond!
Tutorial II: line plots, legends and colors
Tutorial III: box plot, bar plot, scatter plot, histogram, heatmap, colormap
Tutorial IV: violin plot, dendrogram
Tutorial V: Plots in Seaborn (cluster heatmap, pair plot, dist plot, etc)