Advanced Matplotlib: Part 1

0
128
7 min read

The basis for all of these topics is the object-oriented interface.

Object-oriented versus MATLAB styles

We have seen  a lot of examples, and in all of them we used the matplotlib.pyplot module to create and manipulate the plots, but this is not the only way to make use of the Matplotlib plotting power.

There are three ways to use Matplotlib:

  • pyplot: The module used so far in this article
  • pylab:  A module to merge Matplotlib and NumPy together in an environment closer to MATLAB
  • Object-oriented way: The Pythonic way to interface with Matplotlib

Let’s first elaborate a bit about the pyplot module: pyplot provides a MATLAB-style, procedural, state-machine interface to the underlying object-oriented library in Matplotlib.

A state machine is a system with a global status, where each operation performed on the system changes its status.

matplotlib.pyplot is stateful because the underlying engine keeps track of the current figure and plotting area information, and plotting functions change that information. To make it clearer, we did not use any object references during our plotting we just issued a pyplot command, and the changes appeared in the figure.

At a higher level, matplotlib.pyplot is a collection of commands and functions that make Matplotlib behave like MATLAB (for plotting).

This is really useful when doing interactive sessions, because we can issue a command and see the result immediately, but it has several drawbacks when we need something more such as low-level customization or application embedding.

If we remember, Matplotlib started as an alternative to MATLAB, where we have at hand both numerical and plotting functions. A similar interface exists for Matplotlib, and its name is pylab.

pylab (do you see the similarity in the names?) is a companion module, installed next to matplotlib that merges matplotlib.pyplot (for plotting) and numpy (for mathematical functions) modules in a single namespace to  provide an environment as near to MATLAB as possible, so that the transition would be easy.

We and the authors of Matplotlib discourage the use of pylab, other than for proof-of-concept snippets. While being rather simple to use, it teaches developers the wrong way to use Matplotlib.

The third way to use Matplotlib is through the object-oriented interface (OO, from now on). This is the most powerful way to write Matplotlib code because it allows for complete control of the result however it is also the most complex. This is the Pythonic way to use Matplotlib, and it’s highly encouraged when programming with Matplotlib rather than working interactively. We will use it a lot from now on as it’s needed to go down deep into Matplotlib.

Please allow us to highlight again the preferred style that the author of this article, and the authors of Matplotlib want to enforce: a bit of pyplot will be used, in particular for convenience functions, and the remaining plotting code is either done with the OO style or with pyplot, with numpy explicitly imported and used for numerical functions.

In this preferred style, the initial imports are:

import matplotlib.pyplot as plt
import numpy as np

In this way, we know exactly which module the function we use comes from (due to the module prefix), and it’s exactly what we’ve always done in the code so far.

Now, let’s present the same piece of code expressed in the three possible forms which we just described.

First, we present it in the style, pyplot only:

In [1]: import matplotlib.pyplot as plt
In [2]: import numpy as np
In [3]: x = np.arange(0, 10, 0.1)
In [4]: y = np.random.randn(len(x))
In [5]: plt.plot(x, y)
Out[5]: [<matplotlib.lines.Line2D object at 0x1fad810>]In [6]: plt.title('random numbers')
In [7]: plt.show()

The preceding code snippet results in:

Advanced Matplotlib: Part 1

Now, let’s see how we can do the same thing using the pylab interface:

$ ipython -pylab
...
In [1]: x = arange(0, 10, 0.1)
In [2]: y = randn(len(x))
In [3]: plot(x, y)
Out[3]: [<matplotlib.lines.Line2D object at 0x4284dd0>]
In [4]: title('random numbers')
In [5]: show()

Note that:

ipython -pylab

is not the same as running ipython and then:

from pylab import *

This is because ipython’s-pylab switch, in addition to importing everything from pylab, also enables a specific ipython threading mode so that both the interactive interpreter and the plot window can be active at the same time.

Finally, lets make the same chart by using OO style, but with some pyplot convenience functions:

In [1]: import matplotlib.pyplot as plt
In [2]: import numpy as np
In [3]: x = np.arange(0, 10, 0.1)
In [4]: y = np.random.randn(len(x))
In [5]: fig = plt.figure()
In [6]: ax = fig.add_subplot(111)
In [7]: l, = plt.plot(x, y)
In [8]: t = ax.set_title('random numbers')
In [9]: plt.show()

The pylab code is the simplest, and ,pyplot is in the middle, while the OO is the most complex or verbose.

As the Python Zen teaches us, “Explicit is better than implicit” and “Simple is better than complex” and those statements are particularly true for this example: for simple interactive sessions, pylab or ,pyplot are the perfect choice because they hide a lot of complexity, but if we need something more advanced, then the OO API makes clearer where things are coming from, and what’s going on. This expressiveness will be appreciated when we will embed Matplotlib inside GUI applications.

From now on, we will start presenting our code using the OO interface mixed with some pyplot functions.

A brief introduction to Matplotlib objects

Before we can go on in a productive way, we need to briefly introduce which Matplotlib objects compose a figure.

Let’s see from the higher levels to the lower ones how objects are nested:

Object

Description

FigureCanvas

Container class for the Figure instance

Figure

Container for one or more Axes instances

Axes

The rectangular areas to hold the basic elements, such as lines, text, and so on

 

 

Our first (simple) example of OO Matplotlib

In the previous pieces of code, we had transformed this:

...
In [5]: plt.plot(x, y)
Out[5]: [<matplotlib.lines.Line2D object at 0x1fad810>]...

into:

...
In [7]: l, = plt.plot(x, y)
...

The new code uses an explicit reference, allowing a lot more customizations. As we can see in the first piece of code, the plot() function returns a list of Line2D instances, one for each line (in this case, there is only one), so in the second code, l is a reference to the line object, so every operation allowed on Line2D can be done using l.

For example, we can set the line color with:

l.set_color('red')

Instead of using the keyword argument to plot(), so the line information can be changed after the plot() call.

Subplots

In the previous section, we have seen a couple of important functions without introducing them. Let’s have a look at them now:

  • fig = plt.figure(): This function returns a Figure, where we can add one or more Axes instances.
  • ax = fig.add_subplot(111): This function returns an Axes instance, where we can plot (as done so far), and this is also the reason why we call the variable referring to that instance ax (from Axes). This is a common way to add an Axes to a Figure, but add_subplot() does a bit more: it adds a subplot. So far we have only seen a Figure with one Axes instance, so only one area where we can draw, but Matplotlib allows more than one.

add_subplot() takes three parameters:

fig.add_subplot(numrows, numcols, fignum)

where:

  • numrows  represents the number of rows of subplots to prepare
  • numcols  represents the number of columns of subplots to prepare
  • fignum  varies from 1 to numrows*numcols and specifies the current subplot (the one used now)

Basically, we describe a matrix of numrows*numcols subplots that we want into the Figure; please note that fignum is 1 at the upper-left corner of the Figure and it’s equal to numrows*numcols at the bottom-right corner. The following table should provide a visual explanation of this:

 

numrows=2, numcols=2, fignum=1

numrows=2, numcols=2, fignum=2

numrows=2, numcols=2, fignum=3

numrows=2, numcols=2, fignum=4

LEAVE A REPLY

Please enter your comment!
Please enter your name here