This is the first of several exercise pages on visualization using Matplotlib that were prepared for use in the course ITSE 1302 at Austin Community College.
These exercise pages were prepared using Jupyter Notebook. They will be delivered to students as HTML downloads. However, students in this course are not expected to know how to create Jupyter Notebooks, but will learn how to use that tool later.
These exercise pages are not intended to be used as stand-alone learning resources. Instead, they are provided as supplementary material for students using various online resources, such as section 1.4 of the Scipy lecture notes as their primary learning resources.
A variety of programming examples are provided in these exercise pages. In order to make it possible to copy the code for the examples into a Python script and run them under the stand-alone Python interpreter, with the exception of the import statements and utility functions, if any, most of the code in these exercise pages was written in such a way that it can stand alone and run independently of any code that was written earlier in the notebook. Therefore, students should not need to know how to create Jypyter Notebooks to make effective use of these programming examples.
To run a section of Python code under the Python interpreter,
Note, you must have installed a Python version 3+ distribution such as Anaconda or WinPython that inludes matplotlib, numpy, scipy, etc.
This exercise page concentrates on the different ways that you can arrange multiple plots in a single output image. In order to understand some of the code used in these examples, (such as axes for example) you will need to study Visualization Exercises Part 2 in parallel with your study of this page.
import numpy as np
import matplotlib.pyplot as plt
Being able to present the results of a Data Science project in text, tables, graphs, and plots in such a way as to clearly communicate those results to a reader is a very important capability. Often this will entail the need to combine plots, perhaps in the manner shown in this demonstration from the Thumbnail Gallery.
np.random.seed(0)
n_bins = 10
x = np.random.randn(1000, 3)
fig, axes = plt.subplots(nrows=2, ncols=2)
ax0, ax1, ax2, ax3 = axes.flatten()
colors = ['red', 'tan', 'lime']
ax0.hist(x, n_bins, normed=1, histtype='bar', color=colors, label=colors)
ax0.legend(prop={'size': 10})
ax0.set_title('bars with legend')
ax1.hist(x, n_bins, normed=1, histtype='bar', stacked=True)
ax1.set_title('stacked bar')
ax2.hist(x, n_bins, histtype='step', stacked=True, fill=False)
ax2.set_title('stack step (unfilled)')
# Make a multiple-histogram of data-sets with different length.
x_multi = [np.random.randn(n) for n in [10000, 5000, 2000]]
ax3.hist(x_multi, n_bins, histtype='bar')
ax3.set_title('different sample sizes')
fig.tight_layout()
plt.show()
As you will learn in the document titled Visualization Exercises Part 2, when all you need is a single plot, the syntax can be very simple and straightforward. However, when you need to combine two or more plots in the same figure, that can become one of the most complex aspects of using matplotlib.
We will address some of that complexity in the programming examples that follow.
You learned how to quickly create line plots in earlier exercise pages. However, those pages ignored some big-picture issues that you need to know about. Arranging plots in a Figure is one of those big-picture issues. A Figure can be thought of as the container for one or more plots or subplots.
If you don't explicitly create a figure when you create a plot, one is automatically created for you. This is evidenced by the following code that doesn't explicitly create a figure. The call to the plt.gcf method returns a reference to the current figure, presumeably displaying the dimensions of the figure in pixels.
The code also calls the savefig method causing the current figure to be written into an output image file.
t = np.arange(0, 6.0, 0.05)
plt.plot(t, np.exp(-t) * np.cos(2*np.pi*t))
print(plt.gcf())#Get a reference to the current figure.
plt.savefig('myFileName.jpg')
plt.show()
One of the reasons that arranging subplots in a figure can be complex is because there are different ways to create an arrangement and you need to keep the different ways separated in your mind.
The following code samples show several different ways to create a figure having two rows and three columns of subplots.
The next four code samples all call the method named matplotlib.pyplot.subplots to create a figure with two rows and three columns of subplots. (Note the plural spelling of subplots in the method name. This will become important later.)
The difference in these code samples is in how they handle the return values.
According to the documentation, the subplots method returns a tuple containing a Figure object and either an Axes object or an array of Axes objects depending on whether one or more than one subplot was created.
The first returned value is a reference to the Figure object. This reference is used to set the facecolor of the figure to gray.
The print statements shows that in this case (2 rows by 3 columns), the second returned value is an array of type numpy.ndarray where ndarray stands for n-dimensional array with zero-based indexing.
The code that popluates each of the axes objects with plot data treats the array in a straightforward manner, accessing each element in the array by the row and column index (axes[0,2] for example).
fig,axes = plt.subplots(nrows=2,ncols=3)
#The above statement can also be written as follows:
#fig,axes = plt.subplots(2,3)
fig.set_facecolor('0.75')
print("type of fig =",type(fig))
print("type of axes =",type(axes))
print("type of axes[0,0] =",type(axes[0,0]))
t = np.arange(0, 6.0, 0.05)
axes[0,0].plot(t, np.exp(-t) * np.cos(1*np.pi*t))
axes[0,1].plot(t, np.exp(-t) * np.cos(2*np.pi*t))
axes[0,2].plot(t, np.exp(-t) * np.cos(3*np.pi*t))
axes[1,0].plot(t, np.exp(-t) * np.cos(4*np.pi*t))
axes[1,1].plot(t, np.exp(-t) * np.cos(5*np.pi*t))
axes[1,2].plot(t, np.exp(-t) * np.cos(6*np.pi*t))
plt.tight_layout()
plt.show()
An object of the ndarray class has a method named flatten that can be called to "Return a copy of the array collapsed into one dimension." That method is called in the following code sample,collapsing the 2D array returned by subplots into a 1D array of type ndarray. The flatten method has an optional parameter that can be used to specify the order in which the array is flattened. The default is to flatten the array in row-major order which is the case in this code sample.
Once the array is flattened, it is treated in a straightforward manner by indexing and populating the elements with plot data.
fig,axes = plt.subplots(2,3)
fig.set_facecolor('0.75')
print("type of axes =",type(axes))
t = np.arange(0, 6.0, 0.05)
ax = axes.flatten()
print("type of ax =",type(ax))
ax[0].plot(t, np.exp(-t) * np.cos(1*np.pi*t))
ax[1].plot(t, np.exp(-t) * np.cos(2*np.pi*t))
ax[2].plot(t, np.exp(-t) * np.cos(3*np.pi*t))
ax[3].plot(t, np.exp(-t) * np.cos(4*np.pi*t))
ax[4].plot(t, np.exp(-t) * np.cos(5*np.pi*t))
ax[5].plot(t, np.exp(-t) * np.cos(6*np.pi*t))
plt.tight_layout()
plt.show()
The following code sample flattens the 2D array as in the code sample shown above. However, instead of treating the returned value as a one-dimensional array, the code takes the extra step of unpacking the array into a set of named variables, each of type matplotlib.axes._subplots.AxesSubplot. (This type doesn't appear in the v2.0.2 index but there is an interesting discussion here.) Those named variables are then used to populate each element with plot data.
fig,axes = plt.subplots(2,3)
fig.set_facecolor('0.75')
print("type of axes =",type(axes))
t = np.arange(0, 6.0, 0.05)
ax0,ax1,ax2,ax3,ax4,ax5 = axes.flatten()
print("type of ax0 =",type(ax0))
ax0.plot(t, np.exp(-t) * np.cos(1*np.pi*t))
ax1.plot(t, np.exp(-t) * np.cos(2*np.pi*t))
ax2.plot(t, np.exp(-t) * np.cos(3*np.pi*t))
ax3.plot(t, np.exp(-t) * np.cos(4*np.pi*t))
ax4.plot(t, np.exp(-t) * np.cos(5*np.pi*t))
ax5.plot(t, np.exp(-t) * np.cos(6*np.pi*t))
plt.tight_layout()
plt.show()
As above, his code sample calls plt.subplots. In this case, however, the returned 2D array is immediately unpacked into a tuple for each row where each tuple contains a named variable for each element on the row.
fig,((ax0, ax1, ax2),
(ax3, ax4, ax5)) = plt.subplots(2, 3)
fig.set_facecolor('0.75')
print("type of(ax0, ax1, ax2) =",type((ax0, ax1, ax2)))
print("type of ax0 =",type(ax0))
t = np.arange(0, 6.0, 0.05)
ax0.plot(t, np.exp(-t) * np.cos(1*np.pi*t))
ax1.plot(t, np.exp(-t) * np.cos(2*np.pi*t))
ax2.plot(t, np.exp(-t) * np.cos(3*np.pi*t))
ax3.plot(t, np.exp(-t) * np.cos(4*np.pi*t))
ax4.plot(t, np.exp(-t) * np.cos(5*np.pi*t))
ax5.plot(t, np.exp(-t) * np.cos(6*np.pi*t))
plt.tight_layout()
plt.show()
The following code calls the the matplotlib.pyplot.figure method to create a Figure object. The method has several optional keyword parameters (such as facecolor) by which you can specify various properties of the object. In this case, the facecolor property was set to gray.
Then the code calls the add_subplot method of the Figure object six times in succession to create two rows and three columns of subplots.
The three arguments to the method are interpreted as:
(If there are fewer than ten subplots in the figure, the three arguments can be combined without the separating commas as in 231.)
Unlike zero-based indexing, the plot numbers begin with 1 in the upper-left corner and then proceed from left to right, top to bottom. In this case, the subplot in the upper-left corner is number 1 and the subplot in the bottom right corner is number 6.
This approach is straightforward for a rectangular grid of subplots. However, it can get complicated for more fancy arrangements as you will see later.
#Create a Figure object with a gray facecolor
fig = plt.figure(facecolor='0.75')
#First row
ax1 = fig.add_subplot(2,3,1)
ax2 = fig.add_subplot(2,3,2)
ax3 = fig.add_subplot(2,3,3)
#Second row
ax4 = fig.add_subplot(2,3,4)
ax5 = fig.add_subplot(2,3,5)
ax6 = fig.add_subplot(2,3,6)
print("type of fig =",type(fig))
print("type of ax1 =",type(ax1))
t = np.arange(0, 6.0, 0.05)
ax1.plot(t, np.exp(-t) * np.cos(1*np.pi*t))
ax2.plot(t, np.exp(-t) * np.cos(2*np.pi*t))
ax3.plot(t, np.exp(-t) * np.cos(3*np.pi*t))
ax4.plot(t, np.exp(-t) * np.cos(4*np.pi*t))
ax5.plot(t, np.exp(-t) * np.cos(5*np.pi*t))
ax6.plot(t, np.exp(-t) * np.cos(6*np.pi*t))
plt.tight_layout()
plt.show()
As above, the following code calls the matplotlib.pyplot.figure method to create a Figure object. The method has several optional keyword parameters (such as facecolor) by which you can specify various properties of the object. In this case, the facecolor property was set to gray.
Then the code calls the matplotlib.pyplot.subplot method six times in succession to create two rows and three columns of subplots. (Note the singular spelling of subplot in the method name as opposed to plural subplots in an earlier set of sample code.)
As above, the three arguments to the method are interpreted as:
(If there are fewer than ten subplots in the figure, the three arguments can be combined without the separating commas as in 231.)
Also as above, unlike zero-based indexing, the plot numbers begin with 1 in the upper-left corner and then proceed from left to right, top to bottom. In this case also, the subplot in the upper-left corner is number 1 and the subplot in the bottom right corner is number 6.
This approach is also straightforward for a rectangular grid of subplots. However, it can also get complicated as you will see later.
#Create a Figure object with a gray facecolor
fig = plt.figure(facecolor='0.75')
#First row
ax1 = plt.subplot(2,3,1)
ax2 = plt.subplot(2,3,2)
ax3 = plt.subplot(2,3,3)
#Second row
ax4 = plt.subplot(2,3,4)
ax5 = plt.subplot(2,3,5)
ax6 = plt.subplot(2,3,6)
print("type of fig =",type(fig))
print("type of ax1 =",type(ax1))
t = np.arange(0, 6.0, 0.05)
ax1.plot(t, np.exp(-t) * np.cos(1*np.pi*t))
ax2.plot(t, np.exp(-t) * np.cos(2*np.pi*t))
ax3.plot(t, np.exp(-t) * np.cos(3*np.pi*t))
ax4.plot(t, np.exp(-t) * np.cos(4*np.pi*t))
ax5.plot(t, np.exp(-t) * np.cos(5*np.pi*t))
ax6.plot(t, np.exp(-t) * np.cos(6*np.pi*t))
plt.tight_layout()
plt.show()
So far, all of the arrangements have displayed the subplots on a regular grid, and the code has been relatively straightforward. However, it is possible to create fancy arrangements where the code isn't always straightforward.
You can create fancy arrangements of subplots like the one shown below by calling the add_subplot method if you can figure out the scheme used in the following code. I personally find it very difficult to use this scheme to design fancy arrangements. Fortunately, there is another scheme that I will explain later that uses the matplotlib.pyplot.subplot2grid method. It is just as powerful and easy to use. Therefore, I won't try to explain this scheme. However, if you are interested in pursuing it further, an Internet search should reveal some explanations.
fig = plt.figure(facecolor='0.75')
ax1 = fig.add_subplot(311)
ax2 = fig.add_subplot(334)
ax3 = fig.add_subplot(335)
ax4 = fig.add_subplot(336)
ax5 = fig.add_subplot(337)
ax6 = fig.add_subplot(338)
ax7 = fig.add_subplot(339)
print("type of fig =",type(fig))
print("type of ax1 =",type(ax1))
t = np.arange(0, 6.0, 0.05)
ax1.plot(t, np.exp(-t) * np.cos(1*np.pi*t))
ax2.plot(t, np.exp(-t) * np.cos(2*np.pi*t))
ax3.plot(t, np.exp(-t) * np.cos(3*np.pi*t))
ax4.plot(t, np.exp(-t) * np.cos(4*np.pi*t))
ax5.plot(t, np.exp(-t) * np.cos(5*np.pi*t))
ax6.plot(t, np.exp(-t) * np.cos(6*np.pi*t))
ax7.plot(t, np.exp(-t) * np.cos(7*np.pi*t))
plt.tight_layout()
plt.show()
You can also create fancy arrangements of subplots like the one shown below by calling the plt.subplot method if you can figure out the scheme used in the following code. I also find it very difficult to use this scheme to design fancy arrangements. As mentioned earlier, there is another scheme that I will explain later that is just as powerful and easy to use. Therefore, I won't try to explain this scheme either.
fig = plt.figure(facecolor='0.75')
ax1 = plt.subplot(2,1,1)
ax2 = plt.subplot(2,3,4)
ax3 = plt.subplot(2,3,5)
ax4 = plt.subplot(2,3,6)
t = np.arange(0, 6.0, 0.05)
ax1.plot(t, np.exp(-t) * np.cos(1*np.pi*t))
ax2.plot(t, np.exp(-t) * np.cos(2*np.pi*t))
ax3.plot(t, np.exp(-t) * np.cos(3*np.pi*t))
ax4.plot(t, np.exp(-t) * np.cos(4*np.pi*t))
plt.tight_layout()
plt.show()
The following code calls the matplotlib.pyplot.subplot2grid method to create the fancy arrangement of subplots shown below.
The description of the method in the documentation reads as follows:
subplot2grid(shape, loc, rowspan=1, colspan=1, ...)
Create a subplot in a grid. The grid is specified by shape, at location of loc, spanning rowspan, colspan cells in each direction. The index for loc is 0-based.
Explanation:
The (3,3) argument means that the grid has three rows and three columns.
The combined (3,3),(0,0) arguments mean that the first subplot is positioned at location (0,0) in the grid (the upper-left corner).
The combined (3,3),(0,0),colspan=3 arguments mean that the first subplot extends across all three columns in the first row of the grid.
The combined (3,3),(1,0),colspan=2 arguments mean that the second subplot is located at the left end of the second row and extends across the first two columns.
The combined (3,3),(1, 2),rowspan=2) arguments mean that the third subplot is located at the right end of the second row,is one column wide, and extends downward into the third row.
The arguments for the fourth and fifth subplots simply place them in the first and second columns of the third row.
There is no error message if you create an overlapping subplot conflict with colspan or rowspan. The last of the competing subplots will prevail and the overlapped subplot will disappear.
fig = plt.figure(facecolor='0.75')
ax1 = plt.subplot2grid((3,3),(0,0),colspan=3)
ax2 = plt.subplot2grid((3,3),(1,0),colspan=2)
ax3 = plt.subplot2grid((3,3),(1, 2),rowspan=2)
ax4 = plt.subplot2grid((3,3),(2, 0))
ax5 = plt.subplot2grid((3,3),(2, 1))
print("type of fig =",type(fig))
print("type of ax1 =",type(ax1))
t = np.arange(0, 6.0, 0.05)
ax1.plot(t, np.exp(-t) * np.cos(1*np.pi*t))
ax2.plot(t, np.exp(-t) * np.cos(2*np.pi*t))
ax3.plot(t, np.exp(-t) * np.cos(3*np.pi*t))
ax4.plot(t, np.exp(-t) * np.cos(4*np.pi*t))
ax5.plot(t, np.exp(-t) * np.cos(5*np.pi*t))
plt.tight_layout()
plt.show()
Sometimes when you arrange multiple subplots in a figure, it is cosmetically pleasing to let them share the tick labeling on the x-axis, the y-axis or both. This can be done by setting one or the other or both of the keyword parameters named sharex and sharey to True when you call plt.subplots to create the Figure object. This is illustrated by the following code.
fig,axes = plt.subplots(nrows=2,ncols=3,sharex=True,sharey=True)
#The above statement can also be written as follows:
#fig,axes = plt.subplots(2,3)
fig.set_facecolor('0.75')
t = np.arange(0, 6.0, 0.05)
axes[0,0].plot(t, np.exp(-t) * np.cos(1*np.pi*t))
axes[0,1].plot(t, np.exp(-t) * np.cos(2*np.pi*t))
axes[0,2].plot(t, np.exp(-t) * np.cos(3*np.pi*t))
axes[1,0].plot(t, np.exp(-t) * np.cos(4*np.pi*t))
axes[1,1].plot(t, np.exp(-t) * np.cos(5*np.pi*t))
axes[1,2].plot(t, np.exp(-t) * np.cos(6*np.pi*t))
plt.tight_layout()
plt.show()
My preferred approach for creating arrangements with subplots on a uniform grid is to call the matplotlib.pyplot.subplots method to create a matplotlib.figure.Figure object with the desired rectangular shape.
My preference as to how to handle the return values from that method depends on the overall context of the problem. Sometimes it is best to treat the returned value as an array, such as when you will need to process it in a loop. You should always be able to handle it as an array. In other cases it might be best to unpack the returned array elements into individual named variables pointing to the axes objects. For example, this might lead to more readable code if you use descriptive variable names.
My preferred approach for creating fancy arrangements where the individual subplots may be different sizes and may have different shapes is to call matplotlib.pyplot.subplot2grid to create the arrangement.
The following is a non-exhaustive list of websites containing information on how to arrange subplots in a figure.
--To be continued in the tutorial titled Visualization Exercises Part 2--
Author: Prof. Richard G. Baldwin, Austin Community College, Austin, TX
File: VisualizationPart01.ipynb
Revised: 04/22/18
Copyright 2018 Richard G. Baldwin
-end-