plt.subplot() or plt.subplots()? Understanding state-based vs. object-oriented programming in Pyplot | by Kaleb Nyquist | Towards Data Science

When I was learning how to code using the Python programming language, the introduction of the Pyplot module (part of the Matplotlib library, here referred to by the alias plt) was particularly non-intuitive...and, honestly, kind of disorientating. For example, the functions plt.subplot() and plt.subplots() may appear to be off by a single letter s, but typically the former is followed by a plotting function in the form plt.plot(data_x, data_y) whereas the latter is usually followed by ax[0,0].plot(data_x, data_y). Even more confusingly, even though the “journeys” may be different, the output “destinations” can often be identical, making anyone wonder what the actual difference is in the end.
After a deeper dive into the Pyplot documentation and with some help from the internet-at-large, I learned that behind these subtle error-generating differences are two radically different approaches to programming that are accommodated by the Pyplot interface: the first being “state-based” and the second being “object-oriented”.
While it is considered best practice for Matplotlib learners to start with the “object-oriented” approach and stick with it, taking a moment to work with the “state-based” approach is a good mini-lesson for better understanding one of the larger conceptual debates in computer science. Pyplot users like myself who do not take time to understand these subtle distinctions might find themselves in a never-ending struggle with a two-headed beast of a module, never quite able to tame it in order to get the data visualizations they envision.
For Matplotlib novices learning Pyplot, what follows is a brief historical note on why “state-based” and “object-oriented” programming philosophies exist, and how they try to exist simultaneously in a single module. In other words, this is the insight I wish I had back when I was first trying to make sense of this seemingly strange and contradictory tool.
The more Pythonic but also more complicated approach, “object-oriented programming”, has historical roots in the Norwegian Computing Center during the 1960’s, where Kristen Nygaard and Ole-Johan Dahl created the first version of the Simula programming language designed for programming computer simulations where different actors and unknown factors interacted with each other in complex yet reproducible ways (for example, if a nuclear reactor were to be built in 10,000 parallel universes, in how many of those universes would there be a severe accident?). Decades later, the rise of the Internet and other computer networks popularized “object-oriented programming”. Python is one of many languages that takes this common approach, along with other mainstays such as Java, C++, and Ruby.
As Python popularized, the Matplotlib library was created in 2003 to make Python an open-source alternative to the proprietary software MATLAB that had been first developed in the late 1970’s. However, rather than being “object-oriented”, MATLAB’s approach was closer to something called “state-based programming”. This simpler approach runs variables through established processes, resulting in a series of states that eventually terminates at a desired result. This is not unlike what happens when one uses a basic calculator. For example:
The calculator starts us with initial state, “0”
We input “1”, a constant variable. This replaces the initial state with a new state, “1”.
We now add the additive process “+” and a new constant variable, which in this case also happens to be “1”.
We combine state, process & variable using the equal sign “=”.
This gives us a new state, “2”. We can modify this state using more processes (“+”, “−”, “×”, “÷”, “±”, “√”, or “%”).
(🤦🏼‍♂️ I sometimes will accidentally write state-based programming as “stage-based” because in my imagination, I see the procedure as a series of stages each progressing towards a final goal. But the correct terminology is state-based.)
While being quite literally straight-forward, MATLAB and other state-based languages have the disadvantage of not being as easily able to encapsulate objects and abstract functions, resulting in lengthier and more disorganized code for more complex programs.
Matplotlib was meant to be a bridge for MATLAB users into Python, and accordingly it was originally designed to accommodate both state-based programming and object-oriented programming. Although some state-based programming features (especially Pylab) have have now been deprecated for the sake of easing confusion, the state-based MATLAB legacy lives on through the Pyplot module.
These different programming philosophies converge in the Pyplot module. Doing the digital version of an archeological dig, it is possible to contrast the two bring these histories to life by comparing two different sets of code that will eventually arrive at the same result.
For this example, we will try to create a figure with three subplots, as seen below.
Note that the following code assumes that Pyplot has been imported, and that data_x and data_y have have already been defined. You can find this preliminary code on GitHub.
First, we create this graph using the state-based approach.
plt.figure(facecolor='lightgrey')plt.subplot(2,2,1)plt.plot(data_x, data_y, 'r-')plt.subplot(2,2,2)plt.plot(data_x, data_y, 'b-')plt.subplot(2,2,4)plt.plot(data_x, data_y, 'g-')
I have found that a helpful trick for understanding what is going on here is that “subplot” is a noun and “plot” is a verb. That is, plt.subplot(2,2,1) creates and focuses the program on the first subplot (going left-to-right, up-to-down) in a 2×2 grid. Then plt.plot(data_x, data_Y, 'r-') actively plots a red line using the provided data in the first subplot.
(🕵🏽‍♀️ Careful observers might also notice that the third subplot has been omitted in the above code.)
Second, the same graph can be achieved through the object-oriented approach:
fig, ax = plt.subplots(2,2)fig.set_facecolor('lightgrey')ax[0,0].plot(data_x, data_y, 'r-')ax[0,1].plot(data_x, data_y, 'b-')fig.delaxes(ax[1,0])ax[1,1].plot(data_x, data_y, 'g-')
Here, the plt.subplots(2,2) (notice the additional s) has generated a comparable figure object (“ fig”) that holds a 2×2 array of subplots (or “axes” objects). Now, however, instead of focusing the program on a subplot and then plotting within that subplot, the object-oriented approach first retrieves the “axes” object from the ax array. Then, arguments within the axes method are responsible for the plotting.
(🕵🏽‍♀️ Notice that the third subplot, referred to here in array form as ax[1,0], is created and then deleted in the above code, rather than simply omitted as in the state-based approach.)
Other than saving a line of code in the object-oriented approach, these subtle differences might seem inconsequential. But, for those still learning Pyplot, they should be warned that unknowingly mixing these methods can make for seemingly inexplicable coding snafus. The animation below shows, line-by-line, the fundamental differences in the state-based versus object-oriented processes. It takes only a little bit of imagination to see how trying to mix-and-match these processes could create quite a headache!
For further experimentation in how these approaches differ from each other yet arrive at the same (or at least similar) results, plug-and-play with these snippets of Pyplot code for adding axis labels and graph titles:
plt.suptitle("Your Title Here")plt.xlabel("X Axis")plt.ylabel("Y Axis")
fig.suptitle("Your Title Here")ax[1,1].set_xlabel("X Axis")ax[1,1].set_ylabel("Y Axis")
(💡 Hint: when experimenting with the above approaches, note how the position of the code relative to plt.subplot() makes a difference in the state-based approach in determining which axis gets titled, whereas the object-oriented approach is position-agnostic thanks to the array of axes objects.)
For your convenience, a Jupyter Notebook is available on GitHub, containing the code used to generate the above graphs.
By now, you should have a solid foundation for understanding the dual nature of Pyplot’s interface, as well as having a small window into the DNA of coding languages.
If you would like to learn more about Pyplot’s dual nature, there are a number of online readings I found particularly helpful, including “The Lifecycle of a Plot” and “Effectively Using Matplotlib”. For more on object-oriented programming, “How to explain Object-Oriented Programming to a Six-Year-Old” was particularly illuminating. Finally, Ankit Gupta has helpful resources that contextualize the latest releases of Matplotlib within their historical development context.
An earlier draft of this article was completed as part of a 2019 Data Science Fellowship with the Flatiron School’s Washington DC Campus. Special thanks to my fellow cohort members and instructors for providing feedback.