Applying Math with Python - Second Edition

By Sam Morley
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Free Chapter
    Chapter 2: Mathematical Plotting with Matplotlib
About this book
The updated edition of Applying Math with Python will help you solve complex problems in a wide variety of mathematical fields in simple and efficient ways. Old recipes have been revised for new libraries and several recipes have been added to demonstrate new tools such as JAX. You'll start by refreshing your knowledge of several core mathematical fields and learn about packages covered in Python's scientific stack, including NumPy, SciPy, and Matplotlib. As you progress, you'll gradually get to grips with more advanced topics of calculus, probability, and networks (graph theory). Once you’ve developed a solid base in these topics, you’ll have the confidence to set out on math adventures with Python as you explore Python's applications in data science and statistics, forecasting, geometry, and optimization. The final chapters will take you through a collection of miscellaneous problems, including working with specific data formats and accelerating code. By the end of this book, you'll have an arsenal of practical coding solutions that can be used and modified to solve a wide range of practical problems in computational mathematics and data science.
Publication date:
December 2022


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

Technical requirements

The main plotting package for Python is Matplotlib, which can be installed using your favorite package manager, such as pip:

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

import matplotlib.pyplot as plt

Many of the recipes in this chapter also require NumPy, which, as usual, is imported under the np alias.

The code for this chapter can be found in the Chapter 02 folder of the GitHub repository at


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.

Getting ready

Most commonly, the data that you wish to plot will be stored in two separate NumPy arrays, which we will label x and 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 x and 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 y values:

  1. The linspace routine 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
  2. Once we have created x values, we can generate y values:
    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
  3. To plot the data, we first need to create a new figure and attach axes objects, which can be achieved by calling the plt.subplots routine 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 "x" and "y":

ax.set_title("Plot of the functions f, g, and h")

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 function to make the plot appear:

If you use, 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

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 figure and 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.legend(["f", "g", "h"])
plt.text(0.4, 2.0, "Intersection")

If there are currently no Figure or 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 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.


The 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 routine should be used to display a figure, rather than the show method on a Figure object.

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 r, g, b, c, y, m, k, and w, respectively.

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

Figure 2.2 - Plot of three sets of data, each plotted using a different marker style

The set_title, set_xlabel, and 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, y1, y2, and then y3.

There are a number of keyword arguments that can be supplied to the set_title, set_xlabel, and 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.

The 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.

There’s more…

The 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 0, 1, 2, and so on.

The 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. The 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 0 and 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 c for color and lw for linewidth.

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 scatter.

Other aspects of the plot can be customized by using methods on the Axes object. The axes ticks can be modified using the set_xticks and 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 (semilogx or 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 serif, sans-serif, or monospace, which will choose the appropriate built-in font. A complete list of modifiers can be found in the Matplotlib documentation for the matplotlib.text.Text class.

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.


Adding subplots

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.

Getting ready

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

  1. We use the subplots routine to create a new figure and references to all of the Axes objects in each subplot, arranged in a grid with one row and two columns. We also set the tight_layout keyword argument to True to 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
  2. Once Figure and Axes objects are created, we can populate the figure by calling the relevant plotting method on each Axes object. For the first plot (displayed on the left), we use the plot method on the ax1 object, which has the same signature as the standard plt.plot routine. We can then call the set_title, set_xlabel, and set_ylabel methods on ax1 to set the title and the x and y labels. We also use TeX formatting for the axes labels by providing the usetex keyword argument; you can ignore this if you don’t have TeX installed on your system:
    ax1.plot(iterates, "kx")
    ax1.set_xlabel("$i$", usetex=True)
    ax1.set_ylabel("$x_i$", usetex=True)
  3. Now, we can plot the error values on the second plot (displayed on the right) using the ax2 object. 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 plot method. Again, we set the axes labels and the title. Again, the use of usetex can be left out if you don’t have TeX installed:
    ax2.semilogy(errors, "kx") # plot y on logarithmic scale
    ax2.set_xlabel("$i$", usetex=True)

The result of this sequence of commands is shown here:

Figure 2.3 - Multiple subplots on the same Matplotlib figure

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

A 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 loglog method.

There’s more...

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 Figure object.

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 sharex and 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.

See also

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.

Getting ready

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 figure and 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 yerr argument:

    measurements, yerr=err, fmt="kx", 

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")

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

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…

The 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.

There’s more...

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 xerr and yerr arguments are keywords only). We could have plotted the data from the recipe as a bar chart using the following commands:, 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

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.

Getting ready

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:

  1. 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.plot(x, y)
    ax.set_title("Graph of $y = x^2$", usetex=True)
    ax.set_xlabel("$x$", usetex=True)
    ax.set_ylabel("$y$", usetex=True)
  2. Then, we use the savefig method on fig to 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:
    fig.savefig("savingfigs.png", dpi=300)

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

The 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.

There’s more...

The 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 quality and optimize. A dictionary of image metadata can be passed to the metadata keyword, which will be written as image metadata when saving.

See also

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.

Getting ready

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:

  1. We first use np.linspace to generate a reasonable number of points in these ranges:
    X = np.linspace(-5, 5)
    Y = np.linspace(-5, 5)
  2. Now, we need to create a grid on which to create our values. For this, we use the np.meshgrid routine:
    grid_x, grid_y = np.meshgrid(X, Y)
  3. 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)
  4. 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
  5. 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")
  6. Now, we can call the plot_surface method 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")
  7. 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 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

Figure 2.6 - A three-dimensional surface plot produced with Matplotlib

  1. Contour plots do not require the mplot3d toolkit, and there is a contour routine in the pyplot interface that produces contour plots. However, unlike the usual (two-dimensional) plotting routines, the contour routine requires the same arguments as the plot_surface method. 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

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

The 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.

The 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 X and Y arrays and creates from them a grid consisting of all the possible combinations of values in X and 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 plot_surface method.

There’s more...

The routines described in the preceding section, contour and 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:

  1. 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, 
  2. 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_trisurf method on the three-dimensional axes to plot the approximated surface, and the tricontour method 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)
    ax1.set_title("Approximate surface")
  3. 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)
    ax2.set_title("Approximate contours")

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

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 plot (or 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.

See also

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 (, 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.

Getting ready

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 azim and elev keyword arguments along with the projection="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 vmin and vmax arguments:

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")

The resulting plot is shown in Figure 2.9:

Figure 2.9 - Customized 3D surface plot with modified normalization and an initial viewing angle

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 vmin and 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 0 and 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 vmin and vmax keyword arguments to artificially change the value that is mapped to 0 and 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 ( 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.

There’s more...

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 LogNorm and 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 LightSource object:

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 "ortho".


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.)

Getting ready

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(

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 and 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 dx and 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

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.

There’s more…

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

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 quiver method.


Further reading

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

About the Author
  • Sam Morley

    Sam Morley is an experienced lecturer in mathematics and a researcher in pure mathematics. He is currently a research software engineer at the University of Oxford working on the DataSig project. He was previously a lecturer in mathematics at the University of East Anglia and Nottingham Trent University. His research interests lie in functional analysis, especially Banach algebras. Sam has a firm commitment to providing high-quality, inclusive, and enjoyable teaching, with the aim of inspiring his students and spreading his enthusiasm for mathematics.

    Browse publications by this author
Applying Math with Python - Second Edition
Unlock this book and the full library FREE for 7 days
Start now