The right way to plot: rcParams*

* The breakfast of champions ...

PyCoffee @ Vitacura - 6 April 2016 - fvogt@eso.org

premise:

  • matplotlib is the one-stop shop for all 2-D plotting in Python. Easy to make rapid plots & tons of examples.
  • But at times frustrating to get beautiful & complex plots

rcParams:

utility:

  • essential for those fed up of re-inventing the wheel
  • essential to get $\LaTeX$ working properly
  • life-changing for people that appreciate the beauty of uniformity

cost:

  • $\sim$10 lines (if you're lucky)
  • 10 lines + 1 headache (if your system $\LaTeX$ is not set-up properly)

gain:

  • total control over the plot
  • accuracy
  • elegance
  • infinite powers
  • ...

take-home message :

  • rcParams is worth it, once you know your style - or if you "seriously" need $\LaTeX$ !

A) Motivation: how to label a line-ratio diagram ?

I.e. how does one properly type the name of a forbidden emission-line like $[\text{O}\,\rm{\scriptsize III}]$ ?

My answer: $[\text{O}\,\rm{\scriptsize III}]=$[O\,\textsc{\smaller III}]

\smaller comes from the "relsize" packages. In an article .tex file, it ensures that the size ratio is conserved in Figure captions, footnotes, etc ...: any place with smaller fontsize !

B) $\LaTeX$ and matplotlib

Some basic $\LaTeX$ commands are already accessible in matplotlib by default, e.g.:

In [7]:
# Use some notebook magic - forget the next line in normal scripted code
%matplotlib inline

import matplotlib.pyplot as plt # Gives access to basic plotting functions

# Note the leading "r" !
plt.xlabel(r'$\sum_{i=0}^\infty\ \frac{x^i}{\sqrt{i}}$ M$_{\odot}$ + $\int_{j=0}^{32}\ \exp^{-\frac{x-\lambda_0^2}{2\cdot\sigma^2}}$',fontsize=20)
Out[7]:
<matplotlib.text.Text at 0x1113a4a10>

But you will not get access to special packages, exotic symbols, etc ... In particular, there is no elegant way to typeset a forbidden emission line ... sigh!

The solution: tell matplotlib to use your default system-$\LaTeX$ instead !

B) Set the stage: import the required modules

In [8]:
import numpy as np # For creating some fake data

import matplotlib.gridspec as gridspec # GRIDSPEC !
from matplotlib.colorbar import Colorbar # For dealing with Colorbars the proper way

C) Create some fake datasets to play with ...

In [9]:
# Ok, let us start by creating some fake data
x = np.random.randn(1000)
y = np.random.randn(1000)
z = np.sqrt(x**2+y**2)

# `randn` generates an array of shape ``(d0, d1, ..., dn)``, filled
# with random floats sampled from a univariate "normal" (Gaussian)
# distribution of mean 0 and variance 1.

D) Get started with the plotting: basic approach

In [11]:
# Now, create the gridspec structure, as required. A simple plot witha  colorbar
gs = gridspec.GridSpec(2,1, height_ratios=[0.05,1], width_ratios=[1])
gs.update(left=0.05, right=0.95, bottom=0.08, top=0.93, wspace=0.02, hspace=0.03)

# First, the scatter plot
# Use the gridspec magic to place it
# --------------------------------------------------------
ax1 = plt.subplot(gs[1,0]) # place it where it should be.
# --------------------------------------------------------

# The plot itself
plt1 = ax1.scatter(x, y, c = z, 
                   marker = 's', s=20, edgecolor = 'none',alpha =1,
                   cmap = 'magma_r', vmin =0 , vmax = 4)

# Define the limits, labels, ticks as required
ax1.grid(True)
ax1.set_xlim([-4,4])
ax1.set_ylim([-4,4])
ax1.set_xlabel(r' ') # Force this empty !
ax1.set_xticks(np.linspace(-4,4,9)) # Force this to what I want - for consistency with histogram below !
ax1.set_ylabel(r'My y label')
ax1.set_xlabel(r'$\log$ [O\,\textsc{\smaller III}] $\lambda$5007\AA')

# and let us not forget the colorbar  above !
# --------------------------------------------------------
cbax = plt.subplot(gs[0,:2]) # Place it where it should be.
# --------------------------------------------------------

cb = Colorbar(ax = cbax, mappable = plt1, orientation = 'horizontal', ticklocation = 'top')
cb.set_label(r'Colorbar !', labelpad=10)

E) Set up rcParams to use my system $\LaTeX$ instead

In [1]:
import matplotlib as mpl
# Use tex - a pain to setup, but it looks great when it works !
#mpl.rc('MacOSX')
# Default fonts - only used if usetex = False. The fontsize remains important though.
mpl.rc('font',**{'family':'sans-serif', 'serif':['Computer Modern Serif'], 
             'sans-serif':['Helvetica'], 'size':16, 
             'weight':500, 'variant':'normal'}) 
# You can set many more things in rcParams, like the default label weight, etc ...
mpl.rc('axes',**{'labelweight':'normal', 'linewidth':1})
mpl.rc('ytick',**{'major.pad':12, 'color':'k'})
mpl.rc('xtick',**{'major.pad':8,})
mpl.rc('contour', **{'negative_linestyle':'solid'}) # dashed | solid
# The default matplotlib LaTeX - only matters if usetex=False.
mpl.rc('mathtext',**{'default':'regular','fontset':'cm', 
                 'bf':'monospace:bold'})
# This is where the magic happens !
mpl.rc('text', **{'usetex':True}) 
 # And this is how one can load exotic packages to fullfill one's dreams !
mpl.rc('text.latex',preamble=r'\usepackage{cmbright},\usepackage{relsize},'+
         '\usepackage{upgreek}, \usepackage{amsmath}')

An elegant way to do this consistently is to store this code in a dedicated Python script, e.g. "fpav.py" in my case. Put this somwehere on your Path, and load it everytime: this will effectively set your custom rcParams ! E.g:

In [12]:
import numpy as np
import matplotlib.pyplot as plt
import fpav

Congratulations, you have just started using your very own Python module !

By the way, you can also add custom colorbars to your "fpav.py" file, constants that you re-use all the time, etc... e.g:

In [13]:
print r'The speed of light is %f km/s' % fpav.c
print ' '
print r'And this is how I access my own colorar:', fpav.alligator
The speed of light is 299792.458000 km/s
 
And this is how I access my own colorar: <matplotlib.colors.LinearSegmentedColormap object at 0x111fae090>

F) one last (but HUGE!) benefit of rcParams: figure uniformity in your articles

With gridspec and some smart plotting, you can ensure that your figures have a consistent fontsize in your article.

You simply need to decide whether a given figure will be single column or double column.

1) set the fontsize to 16 with rcParams.

2a) for a single column plot: plt.figure(1, figsize=(6.93,x))

2b) for a single column plot: plt.figure(1, figsize=(14.17,x))

3) Save your figure with plt.savefig('name.pdf') ... No bbox_inches='tight' anymore !

In [ ]: