Making publication quality plots

By David Paredes (davidparedes@ambages.es).

"Make things as simple as possible, but not simpler." - __Albert Einstein__

The following tutorial will cover the "basic" plotting tools to make a publication quality plot. We will follow the following steps:

  • load some data and make the simplest plot.
  • Identify the differences between simple and not-so-simple plot
  • Change the graph step by step.

We shall start by preparing some of the libraries required for now:


In [1]:
#Matplotlib magic
%matplotlib inline                        
from numpy import loadtxt, sqrt, array    #Some mathematical functions

import matplotlib.pyplot as plt           #For plotting
from matplotlib import rc                 #To alter the defaults of plots       
#from sys import path                      # In some implementations the 
#path.append('')
from IPython import display               #A helper to display figures that are being updated

Simplest plot

The following code obtains data from a file and plots it.


In [2]:
# Load the data:
# (see help(loadtxt) or scipy.org )
data = loadtxt('code/plot-basics/datag2.csv', delimiter=',')

#The first column of the file is the time in seconds
#The second column of the file is the autocorrelation function
datax = data[:,0]*1e6    
datay = data[:,1]



############## PLOT ###############
plt.ion()    # To do interactive plotting
plt.plot(datax,datay)
plt.savefig('images/simplefig.png',dpi=600)
plt.draw()


The plot represents the autocorrelation function of the detected counts produced by a 1us long square pulse measured by a single-photon avalanche photodiode (SPAD). It is a bad plot because:

  • it is incomplete
  • full of unnecessary information
  • does not show clearly important information

Identify things to change

In the following picture, the graph on the left shows the simplest graph. On the right hand side, a better graph (alla Jean-Luc Doumont). In the figure on the right we have removed the data outside the region of interest. We have also shown an inset that clearly depicts the central feature of the data (the dead-time of the detector). Finally, we have added labels and changed the size/style of the font to be more "publication like"

To obtain the figure of the right, first we need to think/know what we want to change, and then change it.

Some of the differences are:

  • Axes: limits, ticks, format
  • Labels
  • Colour
  • Inset

A plot, in python, is an object. The axes are objects. The lines are objects. Understanding which member functions alter which characteristics of each of the objects is the key to being able to use python for publication-quality plotting. You don't need to learn these by heart: all the information can be found at matplotlib.org

Question: What do you think each of these member functions (also called methods) do?

Constructing the figure

To construct the figure, we will need to:

  • Define some style constants, so that the labels and ticks are plotted with the right fonts
  • Create a figure object
  • Create axis in the figure (add_subplot)
  • Plot the data in the figure object
  • Format the spines of the figure

In [3]:
## - Change figure size and alter the fonts

# Graph 
golden_mean = (sqrt(5)-1.0)/2.0         # Aesthetic ratio
fig_width = 4                           # width in inches
fig_height = fig_width*golden_mean      # height in inches (4.5 for two vertical figures?)
fig_size = [fig_width,fig_height]

# Define format parameters
paramsscreen = {'backend': 'ps',
          'font.family':'serif',
          'text.usetex':'True',
          'axes.labelsize': 15,
          'text.fontsize': 15,
           'legend.fontsize': 15,
           'xtick.labelsize': 12,
           'ytick.labelsize': 12,
           'figure.figsize': array(fig_size)}

paramsprint = {'backend': 'ps',
          'font.family':'serif',
          'text.usetex':'True',
          'axes.labelsize': 10,
          'text.fontsize': 10,
           'legend.fontsize': 10,
           'xtick.labelsize': 8,
           'ytick.labelsize': 8,
           'figure.figsize': fig_size}


plt.rcParams.update(paramsscreen)

lightblue='#91B8BD'     # The colour used in the plot


## - Create figure object
fig=plt.figure() 

## - Create axis
bx=fig.add_subplot(111)

## - Plot in axis
bx.plot(datax,datay,'-',color =lightblue,lw=1)


Out[3]:
[<matplotlib.lines.Line2D at 0x9ea6da0>]

In [4]:
## Format the spines:
## - Remove upper and right spines
## - Remove unused ticks
## - Change limits of the plot and of the spines

# Spine formatting
labely=-0.2
bx.spines['left'].set_position(('outward',10))
bx.spines['bottom'].set_position(('outward',10))
bx.spines['top'].set_color('none')
bx.spines['right'].set_color('none')

# Even if the spines are removed from the top and right, the "ticks" are still present.
# To remove them:
bx.xaxis.set_ticks_position('bottom')
bx.yaxis.set_ticks_position('left')

# Change Y-Axis limits and format
bx.set_ylim([0,3000])
bx.set_yticks((0,1000,2000,3000))
bx.spines['left'].set_bounds(0,3000)   # Take the spine out, "alla Jean-Luc"

# Change X-Axis limits and format
bx.set_xlim(-1.05,1.05)                # The limit is set slightly over 1 for "artistic" purposes
bx.spines['bottom'].set_bounds(-1,1)
bx.set_xticks((-1,0,1))                # ... but we limit the spine to 1
display.display(fig)



In [5]:
# - Add Labels (note that it supports Latex formatting).
# If we require escape characters, we use "r" before the string
yLab = '$G^{(2)}$'             
xLab = r'$\tau\,\mbox{( }\mu\mbox{s)} '   

bx.set_xlabel(xLab)
bx.set_ylabel(yLab)

display.display(fig)


Inset

After we have created the main plot by removing the least important information and formatting a little, we now create the inset of the figure.

  • Create the axis that contain the inset.
  • Plot the data of interest
  • Format the spines (in the same way that we did before)

To format the axis in the inset we use an analogous procedure, but this time we refer to the inset axis instead of the figure one.


In [6]:
## - Plot inset:
## Requires location of the axis (as a percentage of the window)

######### INSET
inset = fig.add_axes((0.65, 0.6, 0.3, 0.3))     # (left, bottom, width, height)

inset.plot(datax,datay,'-',color =lightblue,lw=1)

## AXIS FORMATTING for the inset (analogous to the one before)
inset.spines['left'].set_color('none')
inset.spines['bottom'].set_position(('outward',10))
inset.spines['top'].set_color('none')
inset.spines['right'].set_color('none')
inset.xaxis.set_ticks_position('bottom')
inset.yaxis.set_ticks_position('left')

inset.set_yticks([])     #To avoid showing the vertical ticks.
inset.set_xlim(-0.06,0.06)
inset.set_xticks((0,28e-3))
inset.set_xticklabels(["0",r"$28\,\mbox{ns}$"])
inset.spines['bottom'].set_bounds(0,0.028)


plt.savefig('images/fancyfig.png',dpi=600)  # Changed puke '-' to '-.'
display.display(fig)


<matplotlib.figure.Figure at 0xa6438d0>

Sweet

And this is it! We have created our first non-trivial plot using matplotlib!

Now, you can try changing the parameters, adding different lines to each of the plots, changing the position and size of the inset,...