- 1
-
Matplotlib supports an implicit interface through
pyplot(such asplt.plot(...)) and an explicit object-oriented interface (such asax.plot(...)). Here we usepyplotto create the figure, then call methods on its objects. The olderpylabinterface is discouraged; see the Matplotlib quick start. - 2
-
subplots()creates a Figure, the whole canvas, and an Axes, a plotting region on that canvas. Each ordinary two-dimensional Axes contains an x Axis and a y Axis, which manage scales, ticks, and tick labels. The returned variables refer to objects whose methods we can call to add data or change properties. - 3
-
Here we tell the
axobject to populate itself with ascatterplot of thexandydata. In a Jupyter cell, the optional trailing semicolon (;) suppresses the displayed representation of the returned object. It does not suppressprint()output or warnings.
10 Data Visualization and Other Tricks
Table of contents
So far we have focused on using Python and Numpy for “crunching numbers”…but crunching numbers is not enough! It is necessary to present the results of our efforts so that others can learn from them. In this chapter we’ll take a look at a few ways to make data look good.
Data visualization is actually an active area of academic research. There are always ways to present data that make it easier for a human see things more clearly. Below are some examples.
10.1 Introduction to Matplotlib
Matplotlib is the main package for plotting in Python. There are several other contenders which offer their own perspectives on plotting, but Matplotlib is really all we need.
Matplotlib is part of the Numpy/Scipy ecosystem of packages. It is designed to work with Numpy arrays.
We have seen several examples of Matplotlib graphs already in the course notes, but now we’ll take a deep dive into how we get the plots we want. Matplotlib also offers a gallery of sample plots, with code to get you started.
10.1.1 The Standard Charts
10.1.1.1 Scatter Plots
The scatter plot, or x-y plot, is probably the most useful and commonly used type of plot. It allows us humans to see trends and relationships instantly.
Let’s plot a quadratic curve: \(y = ax^2 + bx + c\).
The concept of a “handle” is common throughout programming. It is not an arcane inscrutable term…it means exactly what it sounds like: it allows you to hold and manipulate an object! It has exactly the same role for a computational object as for objects in the real world. Consider coffee:
Matplotlib has some features for dealing with these handles, like finding one that you’ve lost.
- 1
-
Assume that we don’t have the
axhandle here. - 2
-
This will retrieve the axes handles which are contained within
fig - 3
-
plotdraws connected lines by default, whilescatterdraws separate markers and allows a different size or colour for each point.plot(y)also allows x-data to be omitted, usingrange(len(y))instead. Choose a line when the order of connected observations is meaningful; use a scatter plot to show individual x-y pairs.
- 1
- Assume we don’t have any handles
- 2
-
plt.gcf()returns the current figure, creating one if none exists. The current figure can be an earlier figure that was made active again. - 3
- Then we can grab the Axes objects too
Customizing the formatting of Matplotlib plots can become quite verbose since there are so many options!. Let’s look at a few of the normal options:
- 1
- This code style is allowed by PEP8 and is quite helpful when a function has many arguments. This layout not only allows the coder to see each argument clearly, but it’s also easy to add and remove arguments by just inserting/deleting lines. Even the closing bracket is on its own line so that every argument can be easily adjusted in this way.
- 2
-
These are some common options accepted by
scatter(), not its complete argument list. Additional properties depend on the kind of object the plotting method creates; consult the scatter documentation. Herescontrols marker area in points squared, andlinewidthcontrols the marker edge width in points. - 3
-
Here is an example of applying some formatting to an Axes using the
axhandle. There are a lot of things we can do here, which will also be explored below.
As mentioned above there are many more arguments that can be passed to matplotlib functions. In the following code block we use keyword-unpacking introduced in Chapter 7. We will put all our desired properties into a dict, then pass it to the function:
- 1
-
These properties apply to the collection of markers created by
scatter:linestyleaffects marker outlines andhatchadds a pattern inside them. They do not connect the points. Other plotting methods create different objects and need not accept the same keywords. - 2
-
Here we pass all the values in the
formatting_argsdictusing the**notation which tells Python to assign each “key” to the corresponding keyword argument.
For the sake of completeness, let’s also review the positional argument unpacking as well:
- 1
-
These need to be in the same order as they are expected by the
scatter()method since the*notation indicates these are unpacked as “position arguments”. - 2
-
This conventional order puts unpacked positional arguments before keyword arguments. Ordinary positional arguments must precede keyword arguments; Python also permits some calls with
*argsafter explicit keywords, but this is usually harder to read.
10.1.1.2 Histograms and Bar Charts
A histogram groups numerical observations into bins and counts how many fall in each bin; the bin widths affect the appearance of the distribution. A bar chart instead compares separate categories. We talked briefly in the last chapter about how to use Numpy to generate data following different common distributions.
vals = np.random.normal(loc=1, scale=0.1, size=10000)
fig, ax = plt.subplots()
h = ax.hist(vals, bins=25, edgecolor='k', alpha=.5)Another common use of bar charts is when the ‘x-axis’ is a category rather than a numerical value. Matplotlib lets us specify both an ‘x’ location for the bars,and individual labels:
- 1
-
It is not necessary to store the data in a
dictbut it is obviously quite convenient.
- 2
-
Since the data are in a
dictwe can usekeys()as the x-coord (i.e. category) andvalues()as the y-coord (i.e. height of the bars).
10.1.2 Formatting ax and fig objects
In programming there is a “design pattern” called getters and setters. This means functions which “get” data and functions which “set” data. It is common for these functions to start with the words get and set. In Table 10.1 we can see a selection of set methods on ax objects, but there are also a lot of get methods too.
In fact, when you write data to a dict in Python, like d[item] = value, behind the scenes Python actually runs a hidden function called __setitem__() with item and value as arguments like d.__setitem__(item, value). You can try this yourself! You can also retrieve data using d.__getitem__(item).
In the above sections we saw how to format the “data”, such as marker colors and sizes. We also often want/need to format the graphs themselves, like axis limits and background colors. The ax and fig objects have a lot of methods attached to them which allow us to manipulate the formatting. Of this huge list, the ones that start with set are for “setting” properties. Below is a list of methods on the ax objects. A similar selection of methods can be found on the fig object as well.
Axes object; available methods can vary by Matplotlib version
set |
set_adjustable |
set_agg_filter |
set_alpha |
set_anchor |
set_animated |
set_aspect |
set_autoscale_on |
set_autoscalex_on |
set_autoscaley_on |
set_axes_locator |
set_axis_off |
set_axis_on |
set_axisbelow |
set_box_aspect |
set_clip_box |
set_clip_on |
set_clip_path |
set_facecolor |
set_fc |
set_figure |
set_forward_navigation_events |
set_frame_on |
set_gid |
set_in_layout |
set_label |
set_mouseover |
set_navigate |
set_navigate_mode |
set_path_effects |
set_picker |
set_position |
set_prop_cycle |
set_rasterization_zorder |
set_rasterized |
set_sketch_params |
set_snap |
set_subplotspec |
set_title |
set_transform |
set_url |
set_visible |
set_xbound |
set_xlabel |
set_xlim |
set_xmargin |
set_xscale |
set_xticklabels |
set_xticks |
set_ybound |
set_ylabel |
set_ylim |
set_ymargin |
set_yscale |
set_yticklabels |
set_yticks |
set_zorder |
Let’s see some of these in action:
data = {
'apples': 10,
'pears': 3,
'oranges': 7,
'bananas': 9,
}
fig, ax = plt.subplots()
ax.bar(x=data.keys(), height=data.values())
ax.set_facecolor('grey')
ax.set_yticks([0, 2, 4, 6, 8, 10])
ax.set_yticklabels(('a', 'b', 'c', 'd', 'e', 'f'))
ax.set_ylabel('Some nonsense values')
ax.set_xlabel('The fruit name')
ax.set_xmargin(0.1)
fig.set_facecolor('silver')
fig.set_edgecolor('black')
fig.set_linewidth(10)
fig.set_figwidth(4);Note: This graph does not look very good. This is just a demonstration of how to go about making it look good.
10.1.3 Named Colors and Color Maps
Specifying colors is one of the main ways which we customize plots. Matplotlib has a large list of “named colors” which can be passed to any of the color-related argument like set_facecolor(). These colors are given below:
Matplotlib also has a small subset of colors which work well together called the “tableau palette”. These are shown below:
And here is an example of using the “tableau palette” when combining 4 different data sets.
x1, y1 = np.random.rand(2, 1000)
x2, y2 = np.random.normal(1, 0.1, (2, 1000))
x3, y3 = np.random.weibull(a=2, size=(2, 1000))
x4, y4 = np.random.gamma(scale=1, shape=1, size=(2, 1000))
fig, ax = plt.subplots()
ax.scatter(x4, y4, marker='x', alpha=0.5, c='tab:orange', label='Gamma')
ax.scatter(x3, y3, marker='+', alpha=0.5, c='tab:green', label='Weibull')
ax.scatter(x2, y2, marker='s', alpha=0.25, c='tab:red', label='Normal')
ax.scatter(x1, y1, marker='.', alpha=0.15, c='tab:blue', label='Uniform')
ax.legend();In this talk, Nathaniel Smith, one of the main developers of numpy, explains how the team decided on a new default color map for Matplotlib.
Another way to use colors effectively is assigning each data point some different color based on some additional value other than just “x” and “y” locations.
x, y = np.random.gamma(scale=1, shape=1, size=(2, 1000))
color = (x**2 + y**2)**0.5
1fig, ax = plt.subplots(1, 2, figsize=(9, 4), layout='constrained')
2points0 = ax[0].scatter(x, y, c=color, vmin=0, vmax=color.max())
points1 = ax[1].scatter(x, y, c=color, cmap=plt.cm.turbo,
3 vmin=0, vmax=color.max())
fig.colorbar(points0, ax=ax[0], label='Distance from origin')
fig.colorbar(points1, ax=ax[1], label='Distance from origin');- 1
- Here we create 2 subplots, which we’ll explore in more detail in the next section
- 2
-
We assign the array
colorto thecargument and Matplotlib will color each marker accordingly - 3
-
We can override the default color map by passing a colormap to the
cmapargument. Both panels use the same numerical limits; their colourbars show how colours map to distance. A sequential map such as the defaultviridisis usually easier to interpret for ordered magnitudes than a rainbow-like map such asturbo.
Use categorical colours for distinct groups, sequential colours for ordered magnitudes, and a diverging map for deviations around a meaningful centre such as zero. Labels and marker shapes help readers who cannot distinguish particular colours. There are quite a few colormaps provided by Matplotlib, as shown below:
A good tutorial on the subject of plotting with colors is given here
10.1.4 Multiple Plots
The Matplotlib Gallery contains hundreds of examples to use as a starting point. You can browse this page until you find something that looks close to what you want.
It is pretty common to plot two things side-by-side, or even an N-by-N grid. Using the plt.subplots() function as our default approach allows this easily:
- 1
-
With this 1-by-2 layout,
subplots()returns a one-dimensional array of Axes objects, which we unpack intoax1andax2. A single subplot returns one Axes by default, while a grid with multiple rows and columns returns a two-dimensional array. - 2
-
In this line and the next we add
scatterplots to each of theaxeswe created.
In the above demo we created the exact number of axes when we “unpacked” the collection, but we can also just use the collection directly:
x1, y1 = np.random.rand(2, 1000)
x2, y2 = np.random.normal(1, 0.1, (2, 1000))
x3, y3 = np.random.weibull(a=2, size=(2, 1000))
x4, y4 = np.random.gamma(scale=1, shape=1, size=(2, 1000))
1fig, ax = plt.subplots(2, 2)
2ax[0][0].scatter(x1, y1, marker='.', c='tab:blue')
ax[0][1].scatter(x2, y2, marker='.', c='tab:red')
ax[1][0].scatter(x3, y3, marker='.', c='tab:green')
ax[1][1].scatter(x4, y4, marker='.', c='tab:orange')- 1
-
Here we create a 2-by-2 grid of axes, and catch all of them in
ax - 2
-
To add data to each
Axeswe need to index into theaxcollection according to the “row” and “column” location we want.
There is another version of the subplots() approach called subplot_mosaic() which is interesting since it gives us quite a lot of control over the layout. Instead of just a regular grid, we can do the following:
x1, y1 = np.random.rand(2, 1000)
x2, y2 = np.random.normal(1, 0.1, (2, 1000))
x3, y3 = np.random.weibull(a=2, size=(2, 1000))
x4, y4 = np.random.gamma(scale=1, shape=1, size=(2, 1000))
1fig, ax = plt.subplot_mosaic([['A', 'A', 'A', 'B'],
['A', 'A', 'A', 'C'],
['A', 'A', 'A', 'D']])
2ax['A'].scatter(x1, y1, marker='.', c='tab:blue')
ax['B'].scatter(x2, y2, marker='.', c='tab:red')
ax['C'].scatter(x3, y3, marker='.', c='tab:green')
ax['D'].scatter(x4, y4, marker='.', c='tab:orange')- 1
-
We create a grid of where we want plots
'A'through'D'to appear. Note that we can use any names we want like'Panel A'or'axis 1'.
- 2
-
We then use the names to index into the
axobject to create each individual subplot.
10.1.5 Working with Images
We have already seen several examples of working with images. Images are a very common form of data since engineers use them for many things:
- microscope images of surfaces or tiny features on a part or sample
- aerial photographs of an area like a water treatment plant
- maps with color coding for terrain type or elevation
Let’s take one more look at color images by reading in an image that is often used as a test case:
im = plt.imread('media/astronaut-eileen-collins.png')
print(im.shape)
fig, ax = plt.subplots(2, 2, layout='constrained')
ax[0][0].imshow(im)
ax[0][0].set_title('All Channels')
ax[0][1].imshow(im[..., 0], cmap=plt.cm.Reds)
ax[0][1].set_title('Red Channel')
ax[1][0].imshow(im[..., 1], cmap=plt.cm.Greens)
ax[1][0].set_title('Green Channel')
ax[1][1].imshow(im[..., 2], cmap=plt.cm.Blues)
ax[1][1].set_title('Blue Channel');(512, 512, 4)
PNG stands for “Portable Network Graphics”. Depending on the file, imread can return a grayscale, RGB, or RGBA array. This image has four channels: red, green, blue, and alpha. Alpha controls opacity, with 0 fully transparent and 1 fully opaque in this floating-point array. Transparency is optional; not every PNG has an alpha channel. See the imread documentation.
Let’s look at this in greyscale. A grayscale image stores one intensity per pixel. The range depends on the data type and convention: for example, 0–255 for an unsigned 8-bit image or 0–1 for the floating-point conversion below. A two-dimensional scalar array can also represent elevation or region labels, but those values have their own units or meaning and need an appropriate colour scale.
- 1
-
scikit-imageis a separate image-processing package, imported asskimage. The distribution name used for installation need not match the import name; for example,scikit-learnis imported assklearn. Hyphens are not allowed in identifiers in an ordinary Pythonimportstatement. - 2
-
For this RGBA image,
im[..., :3]selects the RGB channels and discards alpha; it does not composite transparent pixels onto a background. The ellipsis (...) fills in the preceding dimensions, as in NumPy indexing. For this three-dimensional image it is equivalent toim[:, :, :3].rgb2graycombines the RGB channels using unequal weights to approximate perceived brightness.




























