Multi-dimensional arrays

A numpy.ndarray is the more complete term for a numpy array object. Up until this point we have mainly been dealing with and using one dimensional arrays. Numpy arrays (unlike lists) have the concept of shape whicih means they can be multi-dimensional. This means they can represent a grid (2D), a cuboid (3D) and so forth.

The code below generates a numpy.array object containing random numbers. This is a 2D array with a 3 x 3 shape:

We can select one element from this array using the following syntax. We still use square brackets and pass an index value but now we can pass values for each dimension seperated by a comma (,). This index is selecting the third column within the second row:

Using slicing (Start:Stop) syntax you can select an entire dimension at once by omitting both the Start and Stop values and just using :. You can see how this works if you try the slice with just the Start or just the Stop e.g.

Not including a Start index includes values from the beginning of the array/list etc. up to (but not including) the Stop.

Not including a Stop index reads from the Start to the end of the array/list etc.

So just using : with no Start or Stop selects all elements for that dimension.

This following syntax returns first row (first row, every column):

And this would return the first column (every row, first column):

numpy array objects store data in row-major order. Essentially this means for a 2D index this would be the equivalent of [y, x] rather than [x, y].

Basic properties of multi-dimensional arrays

Shape

The shape of a multi-dimensional array is a tuple that describes the size of each dimension. For example, a 2D array with 3 rows and 4 columns has a shape of (3, 4). You can access the shape of an array using the .shape attribute.

We can use many of the array initialisation functions we haver seen for 1d arrays also to create multi-dimensional arrays. For example, we can use np.zeros to create a 2D array of zeros, or np.ones to create a 2D array of ones. We can also use np.random.rand to create a 2D array of random numbers.

Axis and Rank

A multi-dimensional array has multiple dimensions, each of which can be thought of as an axis. The number of dimensions is called the rank of the array. For example, a 2D array has a rank of 2, while a 3D array has a rank of 3. It is accessible via the .ndim attribute.

We typically call a rank 2 array a matrix. A rank 3 array (or higher) is often called a tensor.

We can perform operations along specific axes of a multi-dimensional array. For example, we can sum all the elements along a specific axis using the np.sum function with the axis parameter.

One can do the same also with useful statistical descriptors such as np.mean, np.std, etc.

Slicing

Slicing works similarly to 1D arrays, but you can slice along multiple axes. For example, you can slice a 2D array to get a submatrix or a specific row or column.

Reshaping and flattening

The last example show an inetresting case: we extracted a single column from the matrix, but it is still a 2D array with shape (3, 1).

What if we wanted a truly 1d array (rank 1)? We need to reshape the array.

We can use the np.reshape function to change the shape of an array without changing its data. For example, we can reshape a 2D array into a 1D array or vice versa.

Reshape takes a tuple of the new shape as an argument. If you want to flatten an array (convert it to a 1D array), you can use -1 as one of the dimensions, which tells NumPy to infer the size of that dimension based on the total number of elements.

Another way to cast a multi-dimensional array to a 1D array is to use the np.ravel() function, which returns a flattened view of the array (not a copy).

If we modify the view, we modify the original array as well.

To obtain a completely independent flattened copy of the array, you can use the np.flatten() method, which returns a copy of the array in a 1D format.

We can also do the opposite and increase the rank of an array by reshaping it. For example, we can reshape a 1D array into a 2D array with one column or one row.

Broadcasting

Combining arrays of different shapes is possible in NumPy using a feature called broadcasting. Broadcasting allows NumPy to perform operations on arrays of different shapes by automatically expanding the smaller array to match the shape of the larger one.

You can reshape a 1D array to a column or row vector and use broadcasting to expand it into a large table. For example, to create a table where each row is the original 1D array, or each column is the original array:

Two-dimensional arrays as matrices: some linear algebra

Two-dimensional arrays are often used to represent matrices or images. In a matrix, each element can be accessed using two indices: - one for the row - one for the column.

A matrix is a rectangular array of numbers, symbols, or expressions arranged in rows and columns and is an essential concept in linear algebra.

For examplle, let’s consider the simple system of simultaneous equations:

\[ \begin{align*} 2x + 3y +z &= 5 \\ 4x - y &= 1 \\ 2y +z &= 3 \end{align*} \]

This can be represented in matrix form as: \[ \begin{bmatrix} 2 & 3 &1 \\ 4 & -1 &0 \\ 0 & 2 &1 \end{bmatrix} \begin{bmatrix} x \\ y \\ z \end{bmatrix} = \begin{bmatrix}5 \\ 1\\ 3 \end{bmatrix} \]

And if we call \(A\) the matrix of coefficients, \(\mathbf{x}\) the vector of variables, and \(b\) the vector of constants, we can write this as: \[ A \mathbf{x} = \mathbf{b} \]

where \[A = \begin{bmatrix} 2 & 3 &1 \\ 4 & -1 &0 \\ 0 & 2 &1 \end{bmatrix}, \quad \mathbf{x} = \begin{bmatrix} x \\ y\\ z \end{bmatrix}, \quad \mathbf{b} = \begin{bmatrix} 5 \\ 1\\ 3 \end{bmatrix} \]

A key result of linear algebra is that if \(A\) is invertible, we can solve for \(\mathbf{x}\) by multiplying both sides of the equation by the inverse of \(A\): \[ \mathbf{x} = A^{-1} \mathbf{b} \]

where \(A^{-1}\) is the inverse of matrix \(A\).

NumPy has a dedicated linear algebra submodule called numpy.linalg that provides functions for performing various linear algebra operations, including matrix inversion, solving systems of equations, and computing eigenvalues and eigenvectors.

The linear algebra submodule has a function called solve which can be used to solve the above equation efficiently:

But we can use numpy to verify that this is correct. We can use the symbol @ to perform matrix multiplication in numpy.

We can also directly calculate the inverse of a matrix using the inv function from the numpy.linalg and use it to solve the equation

All the most common linear algebra operations are available in the numpy.linalg submodule:

  • tranpose
  • scalar (dot) product (which takes two vectors and returns a scalar)
  • cross product

Linear algebra applications are beyond the scope of this course (so, there will be no assessment of these), but they are widely used in various fields such as physics, computer science, and engineering. For example, they are essential in computer graphics for transformations, in machine learning for optimization, and in physics for solving systems of equations. So it is important for you to know that all these can be implemented efficiently using numpy.

Matrices as images

A two-dimensional table of numbers can also be used to represent an image. Each number in the table corresponds to a pixel in the image, and the value of the number represents the color or intensity of that pixel.

matplotlib provides a convenient way to visualize 2D arrays as images. The matshow function can be used to display a 2D matrix as an image, where the values in the array are mapped to colors.

Notice that the indices of the y-axis increase as we go down. These are the indices of the rows in the matrix.

For a matrix of a given shape we can get the indices using the np.indices function, which returns a grid of indices for each dimension. This can be useful for creating masks or selecting specific regions of the matrix.

An alternative function is imshow, which is more general and can be used for both 2D arrays and images.

Here we can set the origin of the axis:

The main difference between matshow and imshow is that matshow is specifically designed for displaying matrices, while imshow is more general and can be used for both 2D arrays and images. matshow automatically adjusts the aspect ratio to make the matrix square, while imshow does not. Also the interpolation method used by matshow is different from that used by imshow, which can affect the appearance of the image.

Images are represneted as 3d arays: every entry is a the intensity of a pixel (if the image is grayscale) or the intensity of a colour (e.g. red, green or blue) if the image is in colour.

Let’s take a colour image

This is no longer just a 2d array, it has a third dimension for the colour channels (red, green, blue). We can access the individual colour channels by slicing the array along the third dimension.

We can slice the array to get the various channels (notice that we specify the colormap cmap argument to display the channels in the appropriate colour):

If we want to subsample regions of an image, we can simply slice the array further in its rows and columns.

We can also use boolean indexing to filter the image based on conditions.

For example, we can binarise it by applying a threshold

We can even perform logical operations using numpy

  • AND with &
  • OR with |
  • NOT with ~ or np.logical_not

Pair programming

The following exercise will allow you explore multi-dimensional arrays by working in pairs. One person will write the code, while the other will explain what the code does. You can switch roles after each exercise.

There are two parts so, you can switch roles after each part.