Matplotlib is a great data visualization library for Python and there are two ways of using it. The functional interface (also known as
pyplot interface) allows us to interactively create simple plots. The object-oriented interface on the other hand gives us more control when we create figures that contain multiple plots. While having two interfaces gives us a lot of freedom, they also cause some confusion. The most common error is to use the functional interface when using the object-oriented would be much easier. For beginners it is now highly recommended to use the object-oriented interface under most circumstances because they have a tendency to overuse the functional one. I made that mistake myself for a long time. I started out with the functional interface and only knew that one for a long time. Here I will explain the difference between both, starting with the object-oriented interface. If you have never used it, now is probably the time to start.
Figures, Axes & Methods
When using the object-oriented interface, we create objects and do the plotting with their methods. Methods are the functions that come with the object. We create both a figure and an axes object with
plt.subplots(1). Then we use the
ax.plot() method from our axes object to create the plot. We also use two more methods,
ax.set_ylabel() to label our axes.
import matplotlib.pyplot as plt import numpy as np x = np.arange(0, 10, 0.1) y = np.sin(np.pi * x) + x fig, ax = plt.subplots(1) ax.plot(x, y) ax.set_xlabel("x") ax.set_ylabel("y")
The big advantage is that we can very easily create multiple plots and we can very naturally keep track of where we are plotting what, because the method that does the plotting is associated with a specific axes object. In the next example we will plot on three different axes that we create all with
x = np.arange(0,10,0.1) ys = [np.sin(np.pi*x) + x, np.sin(np.pi*x) * x, np.sin(np.pi*x) / x] fig, ax = plt.subplots(3) ax.plot(x,ys) ax.plot(x,ys) ax.plot(x,ys) ax.set_title("Addition") ax.set_title("Multiplication") ax.set_title("Division") for a in ax: a.set_xlabel("x") a.set_ylabel("y")
When we create multiple axes objects, they are available to us through the
ax array. We can index into them and we can also loop through all of them. We can take the above example even further and pack even the plotting into the for loop.
x = np.arange(0,10,0.1) ys = [np.sin(np.pi*x) + x, np.sin(np.pi*x) * x, np.sin(np.pi*x) / x] fig, ax = plt.subplots(3) titles = ["Addition", "Multiplication", "Division"] for idx, a in enumerate(ax): a.plot(x, ys[idx]) a.set_title(titles[idx]) a.set_xlabel("x") a.set_ylabel("y")
This code produces exactly the same three axes figure as above. There are other ways to use the object-oriented interface. For example, we can create an empty figure without axes using
fig = plt.figure(). We can then create subplots in that figure with
ax = fig.add_subplot(). This is exactly the same concept as always but instead of creating figure and axes at the same time, we use the figure method to create axes. If personally prefer
fig, ax = plt.subplots() but
fig.add_subplot() is slightly more flexible in the way it allows us to arrange the axes. For example,
plt.subplots(x, y) allows us to create a figure with axes arranged in
x rows and
y columns. Using
fig.add_subplot() we could create a column with 2 axes and another with 3 axes.
fig = plt.figure() ax1 = fig.add_subplot(2,2,1) ax2 = fig.add_subplot(2,2,3) ax3 = fig.add_subplot(3,2,2) ax4 = fig.add_subplot(3,2,4) ax5 = fig.add_subplot(3,2,6)
Personally I prefer to avoid these arrangements, because things like
tight_layout don’t work but it is doable and cannot be done with
plt.subplots(). This concludes our overview of the object-oriented interface. Simply remember that you want to do your plotting through the methods of an axes object that you can create either with
fig, ax = plt.subplots() or
fig.add_subplot(). So what is different about the functional interface? Instead of plotting through axes methods, we do all our plotting through functions in the
One pyplot to Rule Them All
The functional interface works entirely through the
pyplot module, which we import as
plt by convention. In the example below we use it to create the exact same plot as in the beginning. We use
plt to create the figure, do the plotting and label the axes.
import matplotlib.pyplot as plt import numpy as np x = np.arange(0,10,0.1) y = np.sin(np.pi*x) + x plt.figure() plt.plot(x, y) plt.xlabel("x") plt.ylabel("y")
You might be wondering, why we don’t need to tell
plt where to plot and which axes to label. It always works with the currently active
axes object. If there is no active figure,
plt.plot() creates its own, including the axes. If a figure is already active, it creates an axes in that figure or plots into already existing axes. This make the functional interface less explicit and slightly less readable, especially for more complex figures. For the object-oriented interface, there is a specific object for any action, because a method must be called through an object. With the functional interface, it can be a guessing game where the plotting happens and we make ourselves highly dependent on the location in our script. The line we call
plt.plot() on becomes crucial. Let’s recreate the three subplots example with the functional interface.
x = np.arange(0,10,0.1) ys = [np.sin(np.pi*x) + x, np.sin(np.pi*x) * x, np.sin(np.pi*x) / x] plt.figure() plt.subplot(3, 1, 1) plt.plot(x, ys) plt.xlabel("x") plt.ylabel("y") plt.title("Addition") plt.subplot(3, 1, 2) plt.plot(x, ys) plt.xlabel("x") plt.ylabel("y") plt.title("Multiplication") plt.subplot(3, 1, 3) plt.plot(x, ys) plt.xlabel("x") plt.ylabel("y") plt.title("Division")
This one is much longer than the object-oriented code because we cannot label the axes in a for loop. To be fair, in this particular example we can put the entirety of our plotting and labeling into a for loop, but we have to put either everything or nothing into the loop. This is what I mean when I say the functional interface is less flexible.
x = np.arange(0,10,0.1) ys = [np.sin(np.pi*x) + x, np.sin(np.pi*x) * x, np.sin(np.pi*x) / x] plt.figure() titles = ["Addition", "Multiplication", "Division"] for idx, y in enumerate(ys): plt.subplot(3,1,idx+1) plt.plot(x, y) plt.xlabel("x") plt.ylabel("y") plt.title(titles[idx])
Both interfaces are very similar. You might have noticed that the methods in the object-oriented API have the form
set_attribute. This is by design and follows from an object oriented convention, where methods that change attributes have a
set prefix. Methods that don’t change attributes but create entirely new objects have an
add prefix. For example
add_subplot. Now that we have seen both APIs at work, why is the object-oriented API recommended?
Advantages of the Object-Oriented API
First of all, Matplotlib is internally object-oriented. The
pyplot interface masks that fact in an effort to make the usage more MATLAB like by putting a functional layer on top. If we avoid
plt and instead work with the object-oriented interface, our plotting becomes slightly faster. More importantly, the object-oriented interface is considered more readable and explicit. Both are very important when we write Python. Readability can be somewhat subjective but I hope the code could convince you that going through the plotting methods of an axes object makes it much more clear where we are plotting. We also get more flexibility to structure our code. Because
plt depends on the order of plotting, we are constraint. With the object-oriented interface we can structure our code more clearly. We can for example split plotting, labeling and other tasks into their own code blocks.
In summary, I hope you will be able to use the object-oriented interface of Matplotlib now. Simply remember to create axes with
fig, ax = plt.subplots() and then most of the work happens through the
ax object. Finally, the object-oriented interface is recommended because it is more efficient, readable, explicit and flexible.