Advanced Matplotlib: Part 1

Sandro Tosi

October 2009

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]:

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]:

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:




Container class for the Figure instance


Container for one or more Axes instances


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>]


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:


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


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)


  • 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

Some usage examples are:

ax = fig.add_subplot(1, 1, 1)

Where we want a Figure with just a single plot area (like in all the previous examples).

ax2 = fig.add_subplot(2, 1, 2)

Here, we define the plot's matrix as made of two subplots in two different rows, and we want to work on the second one (fignum=2).

An interesting feature is that we can specify these numbers as a single parameter merging the numbers in just one string (as long as all of them are less than 10). For example:

ax2 = fig.add_subplot(212)

which is equivalent to:

ax2 = fig.add_subplot(2, 1, 2)

A simple example can clarify a bit:

In [1]: import matplotlib.pyplot as plt
In [2]: fig = plt.figure()
In [3]: ax1 = fig.add_subplot(211)
In [4]: ax1.plot([1, 2, 3], [1, 2, 3]);
In [5]: ax2 = fig.add_subplot(212)
In [6]: ax2.plot([1, 2, 3], [3, 2, 1]);
In [7]:

Advanced Matplotlib: Part 1

We will use a simple naming convention for the variables that we are using. For example, we call all the Axes instance variables ax, and if there is more than one variable in the same code, then we add numbers at the end, for example, ax1, ax2, and so on.

This will allow us to make changes to the Axes instance after it's created, and in the case of multiple Axes, it will allow us to modify any of them after their creation. The same applies for multiple figures.


Multiple figures

Matplotlib also provides the capability to draw not only multiple Axes inside the same Figure, but also multiple figures.

We can do this by calling figure() multiple times, keeping a reference to the Figure object and then using it to add as many subplots as needed in exactly the same way as having a single Figure.

We can now see a code with two calls to figure():

In [1]: import matplotlib.pyplot as plt
In [2]: fig1 = plt.figure()
In [3]: ax1 = fig1.add_subplot(111)
In [4]: ax1.plot([1, 2, 3], [1, 2, 3]);
In [5]: fig2 = plt.figure()
In [6]: ax2 = fig2.add_subplot(111)
In [7]: ax2.plot([1, 2, 3], [3, 2, 1]);
In [8]:

This code snippet generates two windows with one line each:

Advanced Matplotlib: Part 1

Advanced Matplotlib: Part 1

Note how the Axes instances are generated by calling the add_subplot() method on the two different Figure instances. As a side note, when using pylab or pyplot, we can call figure() with an integer parameter to access a previously created Figure: figure(1) returns a reference to the first Figure, figure(2) to the second one, and so on.

Additional Y (or X) axes

There are situations where we want to plot two sets of data on the same image. In particular, this is the case when for the same X variable, we have two datasets (consider the situation where we take two measurements at the same time, and we want to plot them together to spot some relationships).

Matplotlib can do it:

In [1]: import matplotlib.pyplot as plt
In [2]: import numpy as np
In [3]: x = np.arange(0., np.e, 0.01)
In [4]: y1 = np.exp(-x)
In [5]: y2 = np.log(x)
In [6]: fig = plt.figure()
In [7]: ax1 = fig.add_subplot(111)
In [8]: ax1.plot(x, y1);
In [9]: ax1.set_ylabel('Y values for exp(-x)');
In [10]: ax2 = ax1.twinx() # this is the important function
In [11]: ax2.plot(x, y2, 'r');
In [12]: ax2.set_xlim([0, np.e]);
In [13]: ax2.set_ylabel('Y values for ln(x)');
In [14]: ax2.set_xlabel('Same X for both exp(-x) and ln(x)');
In [15]:

Advanced Matplotlib: Part 1

What's really happening here is that two different Axes instances are placed such that one is on top of the other. The data for y1 will go in the first Axes instance, and the data for y2 will go in the second Axes instance.

The twinx() function does the trick: it creates a second set of axes, putting the new ax2 axes at the exact same position of ax1, ready to be used for plotting.

This is the reason why we had to set the red color for the second line: the plot information was reset so that line would have been blue, as if it was part of a completely new figure.

We can see that by using ax1 and ax2 for referring to Axes instances, we are able to modify the information (in this case, the axes labels) for both of them. Of course, since X is shared between the two, we have to call set_xlabel() for just one Axes instance.

Using two different Axes also allows us to have different scales for the two plots.

The complementary function,   twiny(), allows us to share the Y-axis with two different X-axes.

Logarithmic Axes

Another interesting feature of Matplotlib is the possibility to set the axes scale to a logarithmic one. We can independently set the X, the Y, or both axes to a logarithmic scale.

Let's see an example where both subplots and the logarithmic scale are put together:

In [1]: import matplotlib as mpl
In [2]: mpl.rcParams['font.size'] = 10.
In [3]: import matplotlib.pyplot as plt
In [4]: import numpy as np
In [5]: x = np.arange(0., 20, 0.01)
In [6]: fig = plt.figure()
In [7]: ax1 = fig.add_subplot(311)
In [8]: y1 = np.exp(x/6.)
In [9]: ax1.plot(x, y1);
In [10]: ax1.grid(True)
In [11]: ax1.set_yscale('log')
In [12]: ax1.set_ylabel('log Y');
In [13]: ax2 = fig.add_subplot(312)
In [14]: y2 = np.cos(np.pi*x)
In [15]: ax2.semilogx(x, y2);
In [16]: ax2.set_xlim([0, 20]);
In [17]: ax2.grid(True)
In [18]: ax2.set_ylabel('log X');
In [19]: ax3 = fig.add_subplot(313)
In [20]: y3 = np.exp(x/4.)
In [21]: ax3.loglog(x, y3, basex=3);
In [22]: ax3.grid(True)
In [23]: ax3.set_ylabel('log X and Y');
In [24]:

The output of the preceding code is as follows:

Advanced Matplotlib: Part 1

Note how the characters in this image are smaller than those in the other plots. This is because we had to reduce the font size to avoid the labels and plots overlapping with each  other.

semilogx()  (and the twin function   semilogy()) is a commodity function that merges plot() and ax.set_xscale('log') functions in a single call. The same holds for loglog(), which makes a plot with log scaling on both X and Y axes.

The default logarithmic base is 10, but we can change it with the basex and basey keyword arguments for their respective axes. The functions set_xscale() or set_yscale() are more general as they can also be applied to polar plots, while semilogx(), semilogy(), or loglog() work for lines and scatter plots.

Share axes

With twinx(), we have seen that we can plot two Axes on the same plotting area sharing one axis. But what if we want to draw more than two plots sharing an axis? What if we want to plot on different Axes in the same figure, still sharing that axis? Some areas where we might be interested in such kind of graphs are:

  • Financial data—comparing the evolution of some economic indicators over the same time
  • Hardware testing—plotting the electrical signals received at each pin of a parallel or serial port
  • Health status— showing the development of some medical information in  a given time frame (such as blood pressure, beating heart rate, weight, and so on)

Note that while having the same unit measure on the shared axis, the other is free to have any unit; this is very important as it allows us to group up heterogeneous information.

Matplotlib makes it very easy to share an axis (for example, the X one) on different Axes instances, for example, pan and zoom actions on one graph are automatically replayed to all the others.

In [1]: import matplotlib as mpl
In [2]: mpl.rcParams['font.size'] = 11.
In [3]: import matplotlib.pyplot as plt
In [4]: import numpy as np
In [5]: x = np.arange(11)
In [6]: fig = plt.figure()
In [7]: ax1 = fig.add_subplot(311)
In [8]: ax1.plot(x, x);
In [9]: ax2 = fig.add_subplot(312, sharex=ax1)
In [10]: ax2.plot(2*x, 2*x);
In [11]: ax3 = fig.add_subplot(313, sharex=ax1)
In [12]: ax3.plot(3*x, 3*x);
In [13]:

Advanced Matplotlib: Part 1

Again, we have to use a smaller font for texts. When printed, it looks like a standard subplot image. However, if you run the code on ipython, then you'll observe that when zooming, panning, or performing other similar activities on a subplot, all the others will be modified too, according to the same transformation.

As we can expect, there are a couple of keyword arguments; sharex and sharey, and it's also possible to specify both of them together. In particular, this is useful when the subplots show data with the same units of measure.

>> Continue Reading Advanced Matplotlib: Part 2

[ 1 | 2 ]


If you have read this article you may be interested to view :


You've been reading and excerpt of:

Matplotlib for Python Developers

Explore Title
comments powered by Disqus