Mathematical Plotting with Matplotlib
Plotting is a fundamental tool in all of mathematics. A good plot can reveal hidden details, suggest future directions, verify results, or reinforce an argument. It is no surprise, then, that the scientific Python stack features a powerful and flexible plotting library called Matplotlib.
In this chapter, we will plot functions and data in a variety of styles and create figures that are fully labeled and annotated. We will create three-dimensional plots, customize the appearance of figures, create figures that contain multiple plots using subplots, and save figures directly to files for applications that are not running in an interactive environment.
Plotting is one of the most important aspects covered in this book. Plotting data, functions, or solutions can often help you gain an understanding of a problem that can really help to reason about your methods. We will see plotting again in every chapter of this book.
In this chapter, we will cover the following recipes:
- Basic plotting with Matplotlib
- Adding subplots
- Plotting with error bars
- Saving Matplotlib figures
- Surface and contour plots
- Customizing three-dimensional plots
- Plotting vector fields with quiver plots
The main plotting package for Python is Matplotlib, which can be installed using your favorite package manager, such as
python3.10 -m pip install matplotlib
This will install the most recent version of Matplotlib, which, at the time of writing this book, is version 3.5.2.
Matplotlib contains numerous sub-packages, but the main user interface (UI) is the
matplotlib.pyplot package, which, by convention, is imported under the
plt alias. This is achieved using the following
import matplotlib.pyplot as plt
Many of the recipes in this chapter also require NumPy, which, as usual, is imported under the
The code for this chapter can be found in the
Chapter 02 folder of the GitHub repository at https://github.com/PacktPublishing/Applying-Math-with-Python-2nd-Edition/tree/main/Chapter%2002.
Basic plotting with Matplotlib
Plotting is an important part of understanding behavior. So much can be learned by simply plotting a function or data that would otherwise be hidden. In this recipe, we will walk through how to plot simple functions or data using Matplotlib, set the plotting style, and add labels to a plot.
Matplotlib is a very powerful plotting library, which means it can be rather intimidating to perform simple tasks with it. For users who are used to working with MATLAB and other mathematical software packages, there is a state-based interface called
pyplot. There is also an object-oriented interface (OOI), which might be more appropriate for more complex plots. In either case, the
pyplot interface is a convenient way to create basic objects.
Most commonly, the data that you wish to plot will be stored in two separate NumPy arrays, which we will label
y for clarity (although this naming does not matter in practice). We will demonstrate plotting the graph of a function, so we will generate an array of
x values and use the function to generate the corresponding
y values. We’re going to plot three different functions over the range on the same axes:
def f(x): return x*(x - 2)*np.exp(3 – x) def g(x): return x**2 def h(x): return 1 - x
Let’s plot these three functions in Python using Matplotlib.
How to do it...
Before we can plot the function, we must generate
y data to be plotted. If you are plotting existing data, you can skip these commands. We need to create a set of
x values that cover the desired range, and then use the function to create
linspaceroutine from NumPy is ideal for creating arrays of numbers for plotting. By default, it will create 50 equally spaced points between the specified arguments. The number of points can be customized by providing an additional argument, but 50 is sufficient for most cases:
x = np.linspace(-0.5, 3.0) # 50 values between -0.5 and 3.0
- Once we have created
xvalues, we can generate
y1 = f(x) # evaluate f on the x points
y2 = g(x) # evaluate g on the x points
y3 = h(x) # evaluate h on the x points
- To plot the data, we first need to create a new figure and attach axes objects, which can be achieved by calling the
plt.subplotsroutine without any arguments:
fig, ax = plt.subplots()
Now, we use the
plot method on the
ax object to plot the first function. The first two arguments are and coordinates to be plotted, and the third (optional) argument specifies that the line color should be black:
ax.plot(x, y1, "k") # black solid line style
To help distinguish the plots for the other functions, we plot those with a dashed line and a dot-dash line:
ax.plot(x, y2, "k--") # black dashed line style ax.plot(x, y3, "k.-") # black dot-dashed line style
Every plot should have a title and axis labels. In this case, there isn’t anything interesting to label the axes with, so we just label them
ax.set_title("Plot of the functions f, g, and h") ax.set_xlabel("x") ax.set_ylabel("y")
Let’s also add a legend to help you distinguish between the different function plots without having to look elsewhere to see which line is which:
ax.legend(["f", "g", "h"])
Finally, let’s annotate the plot to mark the intersection between the functions and with text:
ax.text(0.4, 2.0, "Intersection")
This will plot the
y values against the
x values on a new figure. If you are working within IPython or with a Jupyter notebook, then the plot should automatically appear at this point; otherwise, you might need to call the
plt.show function to make the plot appear:
If you use
plt.show, the figure should appear in a new window. We won’t add this command to any further recipes in this chapter, but you should be aware that you will need to use it if you are not working in an environment where plots will be rendered automatically, such as an IPython console or a Jupyter Notebook. The resulting plot should look something like the plot in Figure 2.1:
Figure 2.1 – Three functions on a single set of axes, each with a different style, with labels, legend, and an annotation
If you are using a Jupyter notebook and the
subplots command, you must include the call to
subplots within the same cell as the plotting commands or the figure will not be produced.
How it works…
Here, we’re using the OOI because it allows us to keep track of exactly which figure and axes object we’re plotting on. This isn’t so important here where we have only a single
axes, but one can easily envisage situations where you might have two or more figures and axes concurrently. Another reason to follow this pattern is to be consistent when you add multiple subplots—see the Adding subplots recipe.
You can produce the same plot as in the recipe via the state-based interface by using the following sequence of commands:
plt.plot(x, y1, "k", x, y2, "k--", x, y3, "k.-") plt.title("Plot of the functions f, g, and h") plt.xlabel("x") plt.ylabel("y") plt.legend(["f", "g", "h"]) plt.text(0.4, 2.0, "Intersection")
If there are currently no
Axes objects, the
plt.plot routine creates a new
Figure object, adds a new
Axes object to the figure, and populates this
Axes object with the plotted data. A list of handles to the plotted lines is returned. Each of these handles is a
Lines2D object. In this case, this list will contain a single
Lines2D object. We could use this
Lines2D object to further customize the appearance of the line later.
Notice that in the preceding code, we combined all the calls to the
plot routine together. This is also possible if you use the OOI; the state-based interface is passing the arguments to the axes method on the set of axes that it either retrieves or creates.
The object layer of Matplotlib interacts with a lower-level backend, which does the heavy lifting of producing the graphical plot. The
plt.show function issues an instruction to the backend to render the current figure. There are a number of backends that can be used with Matplotlib, which can be customized by setting the
MPLBACKEND environment variable, modifying the
matplotlibrc file, or by calling
matplotlib.use from within Python with the name of an alternative backend. By default, Matplotlib picks a backend that is appropriate for the platform (Windows, macOS, Linux) and purpose (interactive or non-interactive), based on which backends are available. For example, on the author’s system, the
QtAgg backend is the default. This is an interactive backend based on the Anti-Grain Geometry (AGG) library. Alternatively, one might want to use the
QtCairo backend, which uses the Cairo library for rendering.
plt.show function does more than simply call the
show method on a figure. It also hooks into an event loop to correctly display the figure. The
plt.show routine should be used to display a figure, rather than the
show method on a
The format string used to quickly specify the line style has three optional parts, each consisting of one or more characters. The first part controls the marker style, which is the symbol that is printed at each data point; the second controls the style of the line that connects the data points; the third controls the color of the plot. In this recipe, we only specified the line style. However, one could specify both line style and marker style or just marker style. If you only provide the marker style, no connecting lines are drawn between the points. This is useful for plotting discrete data where no interpolation between points is necessary.
Four line-style parameters are available: a solid line (
-), a dashed line (
--), a dash-dot line (
-.), or a dotted line (
:). Only a limited number of colors can be specified in the format string; they are red, green, blue, cyan, yellow, magenta, black, and white. The character used in the format string is the first letter of each color (with the exception of black), so the corresponding characters are
In the recipe, we saw three examples of these format strings: the single
k format string only changed the color of the line and kept the other settings at default (small point markers and unbroken blue line); the
k.- format strings both changed the color and the line style. For an example of changing the point style, see the There’s more... section and Figure 2.2:
Figure 2.2 - Plot of three sets of data, each plotted using a different marker style
set_ylabel methods simply add the text argument to the corresponding position of the
Axes object. The
legend method, as called in the preceding code, adds the labels to the datasets in the order that they were added to the plot—in this case,
y2, and then
There are a number of keyword arguments that can be supplied to the
set_ylabel routines to control the style of the text. For example, the
fontsize keyword can be used to specify the size of the label font in the usual
pt point measure.
annotate method on the
Axes object adds arbitrary text to a specific position on the plot. This routine takes two arguments—the text to display as a string and the coordinates of the point at which the annotation should be placed. This routine also accepts keyword arguments that can be used to customize the style of the annotation.
plt.plot routine accepts a variable number of positional inputs. In the preceding code, we supplied two positional arguments that were interpreted as
x values and
y values (in that order). If we had instead provided only a single array, the
plot routine would have plotted the values against their position in the array; that is, the
x values are taken to be
2, and so on.
plot method also accepts a number of keyword arguments that can also be used to control the style of a plot. Keyword arguments take precedence over format string parameters if both are present, and they apply to all sets of data plotted by the call. The keyword to control the marker style is
marker, the keyword for the line style is
linestyle, and the keyword for color is
color keyword argument accepts a number of different formats to specify a color, which includes RGB values as a
(r, g, b) tuple, where each character is a float between
1 or is a hex string. The width of the line plotted can be controlled using the
linewidth keyword, which should be provided with a
float value. Many other keyword arguments can be passed to
plot; a list is given in the Matplotlib documentation. Many of these keyword arguments have a shorter version, such as
In the this recipe, we plotted a large number of coordinates generated by evaluating functions on a selection of values. In other applications, one might have data sampled from the real world (as opposed to generated). In these situations, it might be better to leave out the connecting lines and simply plot the markers at the points. Here is an example of how this might be done:
y1 = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) y2 = np.array([1.2, 1.6, 3.1, 4.2, 4.8]) y3 = np.array([3.2, 1.1, 2.0, 4.9, 2.5]) fig, ax = plt.subplots() ax.plot(y1, 'o', y2, 'x', y3, '*', color="k")
The result of these commands is shown in Figure 2.2. Matplotlib has a specialized method for producing scatter plots such as this, called
Other aspects of the plot can be customized by using methods on the
Axes object. The axes ticks can be modified using the
set_yticks methods on the
Axes object, and the grid appearance can be configured using the
grid method. There are also convenient methods in the
pyplot interface that apply these modifications to the current axes (if they exist).
For example, we modify the axis limits, set the ticks at every multiple of
0.5 in both the and direction, and add a grid to the plot by using the following commands:
ax.axis([-0.5, 5.5, 0, 5.5]) # set axes ax.set_xticks([0.5*i for i in range(9)]) # set xticks ax.set_yticks([0.5*i for i in range(11)]) # set yticks ax.grid() # add a grid
Notice how we set the limits slightly larger than the extent of the plot. This is to avoid markers being placed on the boundary of the plot window.
Matplotlib has many other plotting routines besides the
plot routine described here. For example, there are plotting methods that use a different scale for the axes, including the logarithmic or axes separately (
semilogy, respectively) or together (
loglog). These are explained in the Matplotlib documentation. The
scatter plotting routine may be useful if you wish to plot discrete data on axes without connecting the points with a line. This allows more control over the style of the marker. For example, you can scale the marker according to some additional information.
We can use a different font by using the
fontfamily keyword, the value of which can be the name of a font or
monospace, which will choose the appropriate built-in font. A complete list of modifiers can be found in the Matplotlib documentation for the
Text arguments can also be rendered using TeX for additional formatting by supplying
usetex=True to the routine. We’ll demonstrate the use of TeX formatting of labels in Figure 2.3 in the following recipe. This is especially useful if the title or axis label contains a mathematical formula. Unfortunately, the
usetex keyword argument cannot be used if TeX is not installed on the system—it will cause an error in this case. However, it is still possible to use the TeX syntax for formatting mathematical text within labels, but this will be typeset by Matplotlib, rather than by TeX.
Occasionally, it is useful to place multiple related plots within the same figure side by side but not on the same axes. Subplots allow us to produce a grid of individual plots within a single figure. In this recipe, we will see how to create two plots side by side on a single figure using subplots.
You will need the data to be plotted on each subplot. As an example, we will plot the first five iterates of Newton’s method applied to the function with an initial value of on the first subplot, and for the second, we will plot the error of the iterate. We first define a generator function to get the iterates:
def generate_newton_iters(x0, number): iterates = [x0] errors = [abs(x0 - 1.)] for _ in range(number): x0 = x0 - (x0*x0 - 1.)/(2*x0) iterates.append(x0) errors.append(abs(x0 - 1.)) return iterates, errors
This routine generates two lists. The first list contains iterates of Newton’s method applied to the function, and the second contains the error in the approximation:
iterates, errors = generate_newton_iters(2.0, 5)
How to do it...
The following steps show how to create a figure that contains multiple subplots:
- We use the
subplotsroutine to create a new figure and references to all of the
Axesobjects in each subplot, arranged in a grid with one row and two columns. We also set the
tight_layoutkeyword argument to
Trueto fix the layout of the resulting plots. This isn’t strictly necessary, but it is in this case as it produces a better result than the default:
fig, (ax1, ax2) = plt.subplots(1, 2,
#1 row, 2 columns
Axesobjects are created, we can populate the figure by calling the relevant plotting method on each
Axesobject. For the first plot (displayed on the left), we use the
plotmethod on the
ax1object, which has the same signature as the standard
plt.plotroutine. We can then call the
ax1to set the title and the
ylabels. We also use TeX formatting for the axes labels by providing the
usetexkeyword argument; you can ignore this if you don’t have TeX installed on your system:
- Now, we can plot the error values on the second plot (displayed on the right) using the
ax2object. We use an alternative plotting method that uses a logarithmic scale on the axis, called
semilogy. The signature for this method is the same as the standard
plotmethod. Again, we set the axes labels and the title. Again, the use of
usetexcan be left out if you don’t have TeX installed:
ax2.semilogy(errors, "kx") # plot y on logarithmic scale
The result of this sequence of commands is shown here:
Figure 2.3 - Multiple subplots on the same Matplotlib figure
The left-hand side plots the first five iterates of Newton’s method, and the right-hand side is the approximation error plotted on a logarithmic scale.
How it works...
Figure object in Matplotlib is simply a container for plot elements, such as
Axes, of a certain size. A
Figure object will usually only hold a single
Axes object, which occupies the entire figure area, but it can contain any number of
Axes objects in the same area. The
subplots routine does several things. It first creates a new figure and then creates a grid with the specified shape in the figure area. Then, a new
Axes object is added to each position of the grid. The new
Figure object and one or more
Axes objects are then returned to the user. If a single subplot is requested (one row and one column, with no arguments) then a plain
Axes object is returned. If a single row or column is requested (with more than one column or row, respectively), then a list of
Axes objects is returned. If more than one row and column are requested, a list of lists, with rows represented by inner lists filled with
Axes objects, will be returned. We can then use the plotting methods on each of the
Axes objects to populate the figure with the desired plots.
In this recipe, we used the standard
plot method for the left-hand side plot, as we have seen in previous recipes. However, for the right-hand side plot, we used a plot where the axis had been changed to a logarithmic scale. This means that each unit on the axis represents a change of a power of 10 rather than a change of one unit so that
0 represents ,
1 represents 10,
2 represents 100, and so on. The axes labels are automatically changed to reflect this change in scale. This type of scaling is useful when the values change by an order of magnitude, such as the error in an approximation, as we use more and more iterations. We can also plot with a logarithmic scale for only by using the
semilogx method, or both axes on a logarithmic scale by using the
There are several ways to create subplots in Matplotlib. If you have already created a
Figure object, then subplots can be added using the
add_subplot method of the
Figure object. Alternatively, you can use the
subplot routine from
matplotlib.pyplot to add subplots to the current figure. If one does not yet exist, it will be created when this routine is called. The
subplot routine is a convenience wrapper of the
add_subplot method on the
In the preceding example, we created two plots with differently scaled axes. This demonstrates one of the many possible uses of subplots. Another common use is for plotting data in a matrix where columns have a common
x label and rows have a common
y label, which is especially common in multivariate statistics when investigating the correlation between various sets of data. The
plt.subplots routine for creating subplots accepts the
sharey keyword parameters, which allows the axes to be shared among all subplots or among a row or column. This setting affects the scale and ticks of the axes.
Matplotlib supports more advanced layouts by providing the
gridspec_kw keyword arguments to the
subplots routine. See the documentation for
matplotlib.gridspec for more information.
Plotting with error bars
It is quite common that the values that we gather from the real world carry some uncertainty; no measurement of a real-world quantity is perfectly accurate. For example, if we measure a distance with a tape measure, there is a certain amount of accuracy that we can assume in our results, but beyond this accuracy, we cannot be sure that our measurement is valid. For such a situation, we can probably be confident of our accuracy up to about 1 millimeter or a little less than 1/16 inch. (This is, of course, assuming that we are measuring perfectly.) These values are the smallest subdivisions on typical tape measures. Let’s assume that we have collected such a set of 10 measurements (in centimeters) and we wish to plot these values along with the accuracy that we are confident about. (The range of values that lie above or below the measurement by the accuracy amount is called the error.) This is what we address in this recipe.
As usual, we have the Matplotlib
pyplot interface imported under the alias
plt. We first need to generate our hypothetical data and the assumed accuracy in NumPy arrays:
measurement_id = np.arange(1, 11) measurements = np.array([2.3, 1.9, 4.4, 1.5, 3.0, 3.3, 2.9, 2.6, 4.1, 3.6]) # cm err = np.array([0.1]*10) # 1mm
Let’s see how to use plotting routines in Matplotlib to plot these measurements with error bars to indicate the uncertainty in each measurement.
How to do it…
The following steps show how to plot measurements along with accuracy information on a figure.
First, we need to generate a new
axis object as usual:
fig, ax = plt.subplots()
Next, we use the
errorbar method on the axis object to plot the data along with the error bars. The accuracy information (the error) is passed as the
ax.errorbar(measurement_id, measurements, yerr=err, fmt="kx", capsize=2.0)
As usual, we should always add meaningful labels to the axes and a title to the plot:
ax.set_title("Plot of measurements and their estimated error") ax.set_xlabel("Measurement ID") ax.set_ylabel("Measurement(cm)")
Since Matplotlib will not produce
xlabel ticks at every value by default, we set the x-tick values to the measurement IDs so that they are all displayed on the plot:
The resulting plot is shown in Figure 2.4. The recorded value is shown at the
x markers, and the error bar extends above and below that value by an accuracy of 0.1 cm (1 mm):
Figure 2.4 - Plot of a set of 10 sample measurements (in centimeters) with their measurement error shown
We can see here that each of the markers has a vertical bar that indicates the range in which we expect the true measurement (-value) to lie.
How it works…
errorbar method works in a similar way to other plotting methods. The first two arguments are the and coordinates of the points to be plotted. (Note that both must be provided, which is not the case for other plotting methods.) The
yerr argument indicates the size of the error bars to be added to the plot and should all be positive values. The form of the value(s) passed to this argument determines the nature of the error bars. In the recipe, we provided a flat NumPy array with 10 entries—one for each measurement—which leads to error bars above and below each point with the same size (the corresponding value from the argument). Alternatively, we could have specified a 2-by-10 array, where the first row contains the lower error and the second row contains the upper error. (Since all our errors are the same, we could also have provided a single float containing the common error for all measurements.)
In addition to the data arguments, there are the usual format arguments, including the
fmt format string. (We used this here as a keyword argument because we named the
yerr argument that precedes it.) In addition to the formatting of lines and points found in other plotting methods, there are special arguments for customizing the look of error bars. In the recipe, we used the
capsize argument to add “caps” to either end of the error bars so that we could easily identify the ends of those bars; the default style is a simple line.
In the recipe, we only plotted errors in the axis because the values were simply ID values. If both sets of values have uncertainty, you can also specify the error values using the
xerr argument. This argument functions in the same way as the
yerr argument used previously.
If you are plotting a very large number of points that follow some kind of trend, you might wish to plot error bars more selectively. For this, you can use the
errorevery keyword argument to instruct Matplotlib to add error bars at every
nth data point rather than at all of them. This can be either a positive integer—indicating the “stride” to use to select points that will have errors—or a tuple containing an offset from the first value and a stride. For example,
errorevery=(2, 5) would place error bars every five data points, starting from the second entry.
You can also add error bars to bar charts in the same way (except here, the
yerr arguments are keywords only). We could have plotted the data from the recipe as a bar chart using the following commands:
ax.bar(measurement_id, measurements, yerr=err, capsize=2.0, alpha=0.4)
If this line is used instead of the call to
errorbar in the recipe, then we would get a bar chart, as shown in Figure 2.5:
Figure 2.5 - Bar chart of measurements with error bars
As before, the measurement bar is capped with an indicator of the range in which we expect the true measurement to lie.
Saving Matplotlib figures
When you work in an interactive environment, such as an IPython console or a Jupyter notebook, displaying a figure at runtime is perfectly normal. However, there are plenty of situations where it would be more appropriate to store a figure directly to a file, rather than rendering it on screen. In this recipe, we will see how to save a figure directly to a file, rather than displaying it on screen.
You will need the data to be plotted and the path or file object in which you wish to store the output. We store the result in
savingfigs.png in the current directory. In this example, we will plot the following data:
x = np.arange(1, 5, 0.1) y = x*x
Let’s see how to plot this curve using Matplotlib and save the resulting plot to a file (without needing to interact with the plot GUI).
How to do it...
The following steps show how to save a Matplotlib plot directly to a file:
- The first step is to create a figure, as usual, and add any labels, titles, and annotations that are necessary. The figure will be written to the file in its current state, so any changes to the figure should be made before saving:
fig, ax = plt.subplots()
ax.set_title("Graph of $y = x^2$", usetex=True)
- Then, we use the
figto save this figure to a file. The only required argument is the path to output to or a file-like object that the figure can be written to. We can adjust various settings for the output format, such as the resolution, by providing the appropriate keyword arguments. We’ll set the Dots per Inch (DPI) of the output figure to
300, which is a reasonable resolution for most applications:
Matplotlib will infer that we wish to save the image in the Portable Network Graphics (PNG) format from the extension of the file given. Alternatively, a format can be explicitly provided as a keyword argument (by using the
format keyword), or it will fall back to the default from the configuration file.
How it works...
savefig method chooses the appropriate backend for the output format and then renders the current figure in that format. The resulting image data is written to the specified path or file-like object. If you have manually created a
Figure instance, the same effect can be achieved by calling the
savefig method on that instance.
savefig routine takes a number of additional optional keyword arguments to customize the output image. For example, the resolution of the image can be specified using the
dpi keyword. The plots in this chapter have been produced by saving the Matplotlib figures to the file.
The output formats available include PNG, Scalable Vector Graphics (SVG), PostScript (PS), Encapsulated PostScript (EPS), and Portable Document Format (PDF). You can also save to JPEG format if the Pillow package is installed, but Matplotlib does not support this natively since version 3.1. There are additional customization keyword arguments for JPEG images, such as
optimize. A dictionary of image metadata can be passed to the
metadata keyword, which will be written as image metadata when saving.
The examples gallery on the Matplotlib website includes examples of embedding Matplotlib figures into a GUI application using several common Python GUI frameworks.
Surface and contour plots
Matplotlib can also plot three-dimensional data in a variety of ways. Two common choices for displaying data such as this are using surface plots or contour plots (think of contour lines on a map). In this recipe, we will see a method for plotting surfaces from three-dimensional data and how to plot contours of three-dimensional data.
To plot three-dimensional data, it needs to be arranged into two-dimensional arrays for the , , and components, where both the and components must be of the same shape as the component. For the sake of this demonstration, we will plot the surface corresponding to the following function:
For 3D data, we can’t just use the routines from the
pyplot interface. We need to import some extra functionality from another part of Matplotlib. We’ll see how to do this next.
How to do it...
We want to plot the function on the and range. The first task is to create a suitable grid of pairs on which to evaluate this function:
- We first use
np.linspaceto generate a reasonable number of points in these ranges:
X = np.linspace(-5, 5)
Y = np.linspace(-5, 5)
- Now, we need to create a grid on which to create our values. For this, we use the
grid_x, grid_y = np.meshgrid(X, Y)
- Now, we can create values to plot, which hold the value of the function at each of the grid points:
z = np.exp(-((grid_x-2.)**2 + (
grid_y-3.)**2)/4) - np.exp(-(
(grid_x+3.)**2 + (grid_y+2.)**2)/3)
- To plot three-dimensional surfaces, we need to load a Matplotlib toolbox,
mplot3d, which comes with the Matplotlib package. This won’t be used explicitly in the code, but behind the scenes, it makes the three-dimensional plotting utilities available to Matplotlib:
from mpl_toolkits import mplot3d
- Next, we create a new figure and a set of three-dimensional axes for the figure:
fig = plt.figure()
# declare 3d plot
ax = fig.add_subplot(projection="3d")
- Now, we can call the
plot_surfacemethod on these axes to plot the data (we set the colormap to gray for better visibility in print; see the next recipe for a more detailed discussion):
ax.plot_surface(grid_x, grid_y, z, cmap="gray")
- It is extra important to add axis labels to three-dimensional plots because it might not be clear which axis is which on the displayed plot. We also set the title at this point:
ax.set_title("Graph of the function f(x, y)")
You can use the
plt.show routine to display the figure in a new window (if you are using Python interactively and not in a Jupyter notebook or on an IPython console) or
plt.savefig to save the figure to a file. The result of the preceding sequence is shown here:
Figure 2.6 - A three-dimensional surface plot produced with Matplotlib
- Contour plots do not require the
mplot3dtoolkit, and there is a
contourroutine in the
pyplotinterface that produces contour plots. However, unlike the usual (two-dimensional) plotting routines, the
contourroutine requires the same arguments as the
plot_surfacemethod. We use the following sequence to produce a plot:
fig = plt.figure() # Force a new figure
plt.contour(grid_x, grid_y, z, cmap="gray")
plt.title("Contours of f(x, y)")
The result is shown in the following plot:
Figure 2.7 - Contour plot produced using Matplotlib with the default settings
The peak and basin of the function are shown clearly here by the rings of concentric circles. In the top right, the shading is lighter, indicating that the function is increasing, and in the bottom left, the shade is darker, indicating that the function is decreasing. The curve that separates the regions in which the function is increasing and decreasing is shown between them.
How it works...
mplot3d toolkit provides an
Axes3D object, which is a three-dimensional version of the
Axes object in the core Matplotlib package. This is made available to the
axes method on a
Figure object when the
projection="3d" keyword argument is given. A surface plot is obtained by drawing quadrilaterals in the three-dimensional projection between nearby points in the same way that a two-dimensional curve is approximated by straight lines joining adjacent points.
plot_surface method needs the values to be provided as a two-dimensional array that encodes the values on a grid of pairs. We created a range of and values that we are interested in, but if we simply evaluate our function on the pairs of corresponding values from these arrays, we will get the values along a line and not over a grid. Instead, we use the
meshgrid routine, which takes the two
Y arrays and creates from them a grid consisting of all the possible combinations of values in
Y. The output is a pair of two-dimensional arrays on which we can evaluate our function. We can then provide all three of these two-dimensional arrays to the
The routines described in the preceding section,
plot_surface, only work with highly structured data where the , , and components are arranged into grids. Unfortunately, real-life data is rarely so structured. In this case, you need to perform some kind of interpolation between known points to approximate the value on a uniform grid, which can then be plotted. A common method for performing this interpolation is by triangulating the collection of pairs and then using the values of the function on the vertices of each triangle to estimate the value on the grid points. Fortunately, Matplotlib has a method that does all of these steps and then plots the result, which is the
plot_trisurf routine. We briefly explain how this can be used here:
- To illustrate the use of
plot_trisurf, we will plot a surface and contours from the following data:
x = np.array([ 0.19, -0.82, 0.8 , 0.95, 0.46, 0.71,
-0.86, -0.55, 0.75,-0.98, 0.55, -0.17, -0.89,
-0.4 , 0.48, -0.09, 1., -0.03, -0.87, -0.43])
y = np.array([-0.25, -0.71, -0.88, 0.55, -0.88, 0.23,
0.18,-0.06, 0.95, 0.04, -0.59, -0.21, 0.14, 0.94,
0.51, 0.47, 0.79, 0.33, -0.85, 0.19])
z = np.array([-0.04, 0.44, -0.53, 0.4, -0.31,
0.13,-0.12, 0.03, 0.53, -0.03, -0.25, 0.03,
-0.1 ,-0.29, 0.19, -0.03, 0.58, -0.01, 0.55,
- This time, we will plot both the surface and contour (approximations) on the same figure as two separate subplots. For this, we supply the
projection="3d"keyword argument to the subplot that will contain the surface. We use the
plot_trisurfmethod on the three-dimensional axes to plot the approximated surface, and the
tricontourmethod on the two-dimensional axes to plot the approximated contours:
fig = plt.figure(tight_layout=True) # force new figure
ax1 = fig.add_subplot(1, 2, 1, projection="3d") # 3d axes
ax1.plot_trisurf(x, y, z)
- We can now plot the contours for the triangulated surface using the following command:
ax2 = fig.add_subplot(1, 2, 2) # 2d axes
ax2.tricontour(x, y, z)
We include the
tight_layout=True keyword argument with the figure to save a call to the
plt.tight_layout routine later. The result is shown here:
Figure 2.8 - Approximate surface and contour plots generated from unstructured data using triangulation
In addition to surface plotting routines, the
Axes3D object has a
plot3D) routine for simple three-dimensional plotting, which works exactly as the usual
plot routine but on three-dimensional axes. This method can also be used to plot two-dimensional data on one of the axes.
Matplotlib is the go-to plotting library for Python, but other options do exist. We’ll see the Bokeh library in Chapter 6. There are other libraries, such as Plotly (https://plotly.com/python/), that simplify the process of creating certain types of plots and adding more features, such as interactive plots.
Customizing three-dimensional plots
Contour plots can hide some detail of the surface that they represent since they only show where the “height” is similar and not what the value is, even in relation to the surrounding values. On a map, this is remedied by printing the height onto certain contours. Surface plots are more revealing, but the problem of projecting three-dimensional objects into 2D to be displayed on a screen can itself obscure some details. To address these issues, we can customize the appearance of a three-dimensional plot (or contour plot) to enhance the plot and make sure the detail that we wish to highlight is clear. The easiest way to do this is by changing the colormap of the plot, as we saw in the previous recipe. (By default, Matplotlib will produce surface plots with a single color, which makes details difficult to see in printed media.) In this recipe, we look at some other ways we can customize 3D surface plots, including changing the initial angle of the display and changing the normalization applied for the colormap.
In this recipe, we will further customize the function we plotted in the previous recipe:
We generate points at which this should be plotted, as in the previous recipe:
t = np.linspace(-5, 5) x, y = np.meshgrid(t, t) z = np.exp(-((x-2.)**2 + (y-3.)**2)/4) - np.exp( -((x+3.)**2 + (y+2.)**2)/3)
Let’s see how to customize a three-dimensional plot of these values.
How to do it...
The following steps show how to customize the appearance of a 3D plot:
As usual, our first task is to create a new figure and axes on which we will plot. Since we’re going to customize the properties of the
Axes3D object, we’ll just create a new figure first:
fig = plt.figure()
Now, we need to add a new
Axes3D object to this figure and change the initial viewing angle by setting the
elev keyword arguments along with the
"3d" keyword argument that we have seen before:
ax = fig.add_subplot(projection="3d", azim=-80, elev=22)
With this done, we can now plot the surface. We’re going to change the bounds of the normalization so that the maximum value and minimum value are not at the extreme ends of our colormap. We do this by changing the
ax.plot_surface(x, y, z, cmap="gray", vmin=-1.2, vmax=1.2)
Finally, we can set up the axes labels and the title as usual:
ax.set_title("Customized 3D surface plot") ax.set_xlabel("x") ax.set_ylabel("y") ax.set_zlabel("z")
The resulting plot is shown in Figure 2.9:
Figure 2.9 - Customized 3D surface plot with modified normalization and an initial viewing angle
Comparing Figure 2.6 with Figure 2.9, we can see that the latter generally contains darker shades compared to the former, and the viewing angle offers a better view of the basin where the function is minimized. The darker shade is due to the normalization applied to the values for the colormap, which we altered using the
vmax keyword arguments.
How it works...
Color mapping works by assigning an RGB value according to a scale—the colormap. First, the values are normalized so that they lie between
1, which is typically done by a linear transformation that takes the minimum value to
0 and the maximum value to
1. The appropriate color is then applied to each face of the surface plot (or line, in another kind of plot).
In the recipe, we used the
vmax keyword arguments to artificially change the value that is mapped to
1, respectively, for the purposes of fitting the colormap. In effect, we changed the ends of the color range applied to the plot.
Matplotlib comes with a number of built-in colormaps that can be applied by simply passing the name to the
cmap keyword argument. A list of these colormaps is given in the documentation (https://matplotlib.org/tutorials/colors/colormaps.html) and also comes with a reversed variant, which is obtained by adding the
_r suffix to the name of the chosen colormap.
The viewing angle for a 3D plot is described by two angles: the Azimuthal angle, measured within the reference plane (here, the --plane), and the elevation angle, measured as the angle from the reference plane. The default viewing angle for
Axes3D is -60 Azimuthal and 30 elevation. In the recipe, we used the
azim keyword argument of
plot_surface to change the initial Azimuthal angle to -80 degrees (almost from the direction of the negative axis) and the
elev argument to change the initial elevation to 22 degrees.
The normalization step in applying a colormap is performed by an object derived from the
Normalize class. Matplotlib provides a number of standard normalization routines, including
PowerNorm. Of course, you can also create your own subclass of
Normalize to perform the normalization. An alternative
Normalize subclass can be added using the
norm keyword of
plot_surface or other plotting functions.
For more advanced uses, Matplotlib provides an interface for creating custom shading using light sources. This is done by importing the
LightSource class from the
matplotlib.colors package, and then using an instance of this class to shade the surface elements according to the value. This is done using the
shade method on the
from matplotlib.colors import LightSource light_source = LightSource(0, 45) # angles of lightsource cmap = plt.get_cmap("binary_r") vals = light_source.shade(z, cmap) surf = ax.plot_surface(x, y, z, facecolors=vals)
Complete examples are shown in the Matplotlib gallery should you wish to learn more about how this.
In addition to the viewing angle, we can also change the type of projection used to represent 3D data as a 2D image. The default is a perspective projection, but we can also use an orthogonal projection by setting the
proj_type keyword argument to
Plotting vector fields with quiver plots
A vector field is a function that assigns to each point in a region a vector—it is a vector-valued function defined on a space. These are especially common in the study of (systems of) differential equations, where a vector field typically appears as the right-hand side of the equation. (See the Solving systems of differential equations recipe from Chapter 3 for more details.) For this reason, it is often useful to visualize a vector field and understand how the function will evolve over space. For now, we’re simply going to produce a plot of a vector field using a quiver plot, which takes a set of and coordinates and a set of and vectors, and produces a plot on which each point has an arrow in the direction and whose length is the length of this vector. (Hopefully, this will become more clear when we actually create the said plot.)
As usual, we import the Matplotlib
pyplot interface under the alias
plt. Before we start, we need to define a function that takes a point and produces a vector; we’ll use this later to generate and data that will be passed to the plotting function.
For this example, we’re going to plot the following vector field:
For this example, we’ll plot the vector field over the region where and .
How to do it…
The following steps show how to visualize the aforementioned vector field over the specified region.
First, we need to define a Python function that evaluates our vector field at points:
def f(x, y): v = x**2 +y**2 return np.exp(-2*v)*(x+y), np.exp( -2*v)*(x-y)
Next, we need to create our grid of points covering the region. For this, we first create a temporary
linspace routine with values between
1. Then, we use
meshgrid to generate a grid of points:
t = np.linspace(-1., 1.) x, y = np.meshgrid(t, t)
Next, we use our function to generate
dy values that describe the vectors at each grid point:
dx, dy = f(x, y)
Now, we can create a new figure and axis and use the
quiver method to generate a plot:
fig, ax = plt.subplots() ax.quiver(x, y, dx, dy)
The resulting plot is shown in Figure 2.10:
Figure 2.10 - Visualization of a vector field using a quiver plot
In Figure 2.10, we can see the value represented as an arrow based at each coordinate. The size of the arrow is determined by the magnitude of the vector field. At the origin, the vector field has , so the arrows nearby are very small.
How it works…
Our example from the recipe is a mathematical construction rather than something that might arise from real data. For this particular case, the arrows describe how some quantity might evolve if it flows according to the vector field we specified.
Each point in the grid is the base of an arrow. The direction of the arrow is given by the corresponding value, and the length of the arrow is normalized by length (so, a vector with smaller components produces a shorter arrow). This can be customized by changing the
scale keyword argument. Many other aspects of the plot can be customized too.
If you want to plot a set of trajectories that follow a vector field, you can use the
streamplot method. This will plot trajectories starting at various points to indicate the general flow in different parts of the domain. Each streamline has an arrow to indicate the direction of flow. For example, Figure 2.11 shows the result of using the
streamplot method with the vector field in the recipe:
Figure 2.11 – Plot of the trajectories described by the vector field from the recipe
In a different scenario, you might have data about wind speed (or similar quantities) at a number of coordinates—on a map, say—and you want to plot these quantities in the standard style for weather charts. Then, we can use the
barbs plotting method. The arguments are similar to the
The Matplotlib package is extensive, and we can scarcely do it justice in such a short space. The documentation contains far more detail than is provided here. Moreover, there is a large gallery of examples (https://matplotlib.org/gallery/index.html#) covering many more of the capabilities of the package than in this book.
Other packages build on top of Matplotlib to offer high-level plotting methods for specific applications. For example, the Seaborn libraries provide routines for visualizing data (https://seaborn.pydata.org/).