Wednesday, March 11th, 2026¶

On Monday, we saw how to create figures with several subplots.

In [ ]:
import numpy as np
import matplotlib.pyplot as plt

Subplots with matplotlib (cont.)¶

We last discussed using the plt.axes function to reactivate a previous subplot for situations where we may want to go back and add more features.

In [ ]:
t = np.linspace(0,2*np.pi, 1000)
x = np.cos(t)
y = np.sin(t)

ax = plt.subplot(2,2,1)
plt.plot(t,x)

plt.subplot(2,2,2)
plt.plot(t,y)
plt.title('$y(t) = \cos(t)$')
plt.xlabel('$t$')
plt.ylabel('$y$')

plt.subplot(2,1,2, aspect='equal')
plt.plot(x,y)
plt.plot(x+ 3,y)
plt.title('Cirle plot')
plt.xlabel('$x$')
plt.ylabel('$y$')

plt.tight_layout()

plt.axes(ax)
plt.title('$x(t) = \cos(t)$')
plt.xlabel('$t$')
plt.ylabel('$x$')

The plt.axes function can also be used to insert an axes object with arbitrary position and size. To use plt.axes in this way, we supply a list input [x_left, y_bottom, width, height] where

  • x_left is the left edge of the axes object (measured between 0 and 1 where 0 is the at the far left and 1 is at the far right),
  • y_bottom is the bottom edge of the axes object (measured between 0 and 1 where 0 is at the bottom and 1 is at the top),
  • width is the width of the axes object (measured between 0 and 1 as a proportion of the full figure width,
  • height is the height of the axes object (measured between 0 and 1 as a proportion of the full figure height).
In [ ]:
 
In [ ]:
 

More with numpy¶

We have started looking at using numpy arrays to perform iterative calculations quickly. For example, we've used np.linspace to generate an array of values that can be plugged into a function that performs some arithmetic element-by-element. For example, earlier today we generated an array of $t$-values and evaluated $\sin t$ and $\cos t$ using numpy.

However, numpy arrays are far more flexible.

Multi-dimensional arrays¶

It is often very useful to represent higher-dimensional data as a numpy array. There are many ways to generate multi-dimensional arrays with numpy. For example, we can reshape a 1D array into a compatible 2D shape using the .reshape method. The .reshape method takes in a sequence of integers that define the shape of the output array. For example, calling .reshape(2,5) on an array of length 10 will create an array containing 2 rows and 5 columns.

In [ ]:
 

Just like with 1D arrays, we can perform arithmetic operations elementwise on 2D arrays.

In [ ]:
 
In [ ]:
 
In [ ]:
 

Each array has an attribute .shape that stores the shape (i.e. number of rows, columns, etc.) of the array.

In [ ]:
 

We can also use np.ones or np.zeros to generate multi-dimensional arrays:

In [ ]:
 
In [ ]:
 

We can use plt.array to convert a list of lists into an array. This only works if each "inner" list has the same length. For example, consider the a list of Pythagorean triples, which can be thought of us a $2D$ array.

In [ ]:
 
In [ ]:
 

With multi-dimensional arrays, we can slice across any axis of the array. This works just like regular list/array slicing, except that we can separately slice through a row, columm, or any other axis. We use commas inside the slice to separate between different dimensions of the array. For example, taking [:,0] will produce a slice consisting of elements from every row (the first dimension), but only the first column (e.g. the index-0 entry of the second dimension, i.e. the columns).

In [ ]:
 
In [ ]:
 

As an example, consider a list of lists of Pythagorean triples $(a,b,c)$.

In [ ]:
ptriples = [[3, 4, 5],
[4, 3, 5],
[5, 12, 13],
[6, 8, 10],
[8, 6, 10],
[8, 15, 17],
[9, 12, 15],
[12, 5, 13],
[12, 9, 15],
[12, 16, 20],
[15, 8, 17],
[15, 20, 25],
[16, 12, 20],
[20, 15, 25]]

We've previously discussed how we may want to select just the $a$ and $b$ parts of each triple so that we can plot them against one another. This can be done using list comprehension or by initializing empty lists and building them up. What if we converted this list of lists into a numpy array?

In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 

Visualizing 2-dimensional arrays¶

Consider the following 2D-array, filled with integers 0 up to 99.

In [ ]:
A = np.arange(100).reshape(10,10)
print(A)

For this array, it is small enough that we can simply print the array to understand its contents. For other larger arrays, this becomes infeasable. The plt.imshow function is a very useful tool for visualizing 2D arrays of any size. The basic syntax is plt.imshow(<some 2D array>).

In [ ]:
plt.imshow(A)

When calling plt.imshow on a 2D array, the graph places a colored block at each row & column index pair. The color of the block is determined by the value of the array in that row & column position. The colors are assigned according to a designated colormap.

The colors are assigned so that the lowest value in the array is assigned the "lowest" color in the colormap (dark blue by default) and the highest value of the array is assigned the "highest" color in the colormap (yellow by default). Intermdiate values are assigned intermediate colors between the "lowest" and "highest" colors in the colormap.

Let's make some changes to the A array and see how the output of plt.imshow is affected. For example, suppose we want to set the values in rows 3 or 4 and columns 6 or 7 to be 120.

In [ ]:
 
In [ ]:
 

Note: Just like the plt.plot function, we can take manual control of the figure and axes generation using plt.figure and plt.subplot. We can use plt.imshow within a subplot in the same way that we use plt.plot. There are also many optional arguments that can be included to modify the default behavior of plt.imshow. For example:

  • We can supply a value vmin that the "darkest" color will be assigned to. Any values in the array that are below this minimum value will also be mapped to the "darkest" color.
  • We can supply a value vmax that the "brightest" color will be assigned to. Any values in the array that are above this maximum value will also be mapped to the "brightest" color.
In [ ]:
 

By default, plt.imshow will not interpolate colors between neighboring array positions (unless your screen's resolution is too low to display all of the colored blocks). That is, we can see a discrete set of boxes of colors. We can modify this behavior by using the interpolation keyword. Some available interpolation methods include:

  • 'nearest': each pixel is colored according to the closest colored block to that pixel
  • 'bilinear': each pixel is colored according to a bilinear interpolation (both horizontally and vertically) between nearby colored blocks
  • 'bicubic': each pixel is colored according to a bicubic interpolation (both horizontally and vertically) between nearby colored blocks
  • 'lanczos': each pixel is colored according to Lancsoz interpolation (both horizontally and vertically) between nearby colored blocks
In [ ]:
 
In [ ]:
 
In [ ]:
 

We can also modify the color choices used by plt.imshow. These choices are called colormaps. A collection of colormaps can be found on the Matplotlib documentation page.

The cmap keyword argument can be used to change colormaps:

In [ ]:
 

Note: The figures produced above do not indicate what the actual values in the array are. We can call plt.colorbar to add a colorbar that indicates which values correspond to which colors.

In [ ]:
 

We can also use colormaps in plt.plot to assign colors to our plots. The colormaps can be found in the cm submodule of matplotlib.

In [ ]:
import matplotlib.cm as cm
In [ ]:
cm.plasma

Each colormap works as a function that takes in a float, where:

  • any input 0 or less returns the color at the left-end of the colormap,
  • any input 1 or more returns the color at the right-end of the colormap,
  • any input between 0 and 1 returns the color that is proportionally between the left-end and right-end of the colormap.
In [ ]:
 

What does the output actually look like when we plug a float into a colormap?

In [ ]:
 

RGB(A) values¶

The output from the colormap above is an Red/Green/Blue/Alpha value, or RGBA value. That is, we have a tuple (R, G, B, A) where:

  • R is the amount of red in the color (R=1 means full red, R=0 means no red),
  • G is the amount of green in the color (G=1 means full green, G=0 means no green),
  • B is the amount of blue in the color (B=1 means full blue, B=0 means no blue),
  • A is the transparency of the color (A=1 means fully opaque, A=0 means fully transparent).

If the transparency channel is omitted (i.e. if we work with a pure RGB triple), the color is assume to be fully opaque. We can also supply our own RGB(A) tuples in plt.plot to specify colors.

In [ ]:
 
In [ ]:
 

The plt.imshow function can also work with RGB(A) values. Instead of supplying a 2D-array, we can supply a 3D-array where:

  • The first axis corresponds to the row of the array,
  • The second axis corresponds to the column of the array,
  • The third axis corresponds to the RGB(A) values of the array.

For example, let's define a 10 by 20 array with 3 color channels (RGB) called RGB_array.

In [ ]:
 
In [ ]:
 
In [ ]:
 

For the above array, we will think of RGB_array[:,:,0] as a $2D$-array containing the red data. Similarly, RGB_array[:,:,1] contains the green data, and RGB_array[:,:,2] contains the blue data. Let's add some colored stripes to the array.

Note: We can take slices through the rows/columns of an (m,n,3) array of RGB values. For example, RGB_array[:4, 3:7] will give a (4,4,3) array consisting of the RGB triples in rows 0, 1, 2, 3 and columns 3, 4, 5, 6. If we set RGB_array[:4, 3:7] = (.1, .7, .9), then every RGB triple in these rows/columns will be set to (.1, .7, .9). This is called broadcasting, and is a very useful feature of NumPy.

In [ ]:
 
In [ ]:
 
In [ ]:
 

Most computer images are stored as arrays of RGB(A) values. We can read an image file into an RGB(A) array using plt.imread. The syntax is: plt.imread(<path to some image file>). For example, download the image mario.png from the course webpage and place it into the same folder as this Jupyter notebook.

In [ ]:
 
In [ ]:
 

We can look at the shape of the array to see if it contains RGB or RGBA values. In this case, the mario.png image file includes a transparency channel.

In [ ]:
 
In [ ]:
 

Exercise: Use NumPy slicing to remove the transparency channel from the mario array.Exercise: Use NumPy slicing and plt.imshow to zoom in on Mario (in the lower-left corner).

In [ ]:
 
In [ ]:
 

Exercise: Use NumPy slicing and plt.imshow to zoom in on Mario (in the lower-left corner).

In [ ]:
 
In [ ]:
 

Exercise: Create an array blueless_mario where the blue channel information from mario has been removed (i.e. set to 0) and plot using plt.imshow.

In [ ]:
 
In [ ]:
 

Exercise: Create an array mixed_mario where:

  • the red channel of mixed_mario matches the green channel of mario,
  • the green channel of mixed_mario matches the blue channel of mario,
  • the blue channel of mixed_mario matches the red channel of mario.
In [ ]:
 
In [ ]:
 

Note: For the next project, we will be working with integer-valued RGB triples. The integer-values will range from 0 to 255. An integer 0 means no color while an integer 255 means full color (equivalent to a float of 1).

Exercise: Convert the mario array to an integer-type array mario_RGB_int containing RGB triples with values between 0 and 255.

In [ ]:
 
In [ ]:
 

Exercise: Convert the mario_RGB_int array into a float-type array containing RGB triples with values between 0 and 1.

In [ ]:
 
In [ ]: