Linear Algebra and Programming Skills¶

Dr Jon Shiach, Department of Computing and Mathematics, Manchester Metropolitan University


Arrays¶

Learning Outcomes¶

On successful completion of this page readers will be able to:

  • Define one- and two-dimensional arrays in Python;
  • index arrays to extract an element or multiple elements;
  • perform arithmetic and matrix operations on arrays.

In mathematics, vectors can be expressed as either a row or column of elements and matrices are a rectangular array of elements. An individual element in the vector $\mathbf{a}$ is identified by an index denoted using a subscript, e.g., $a_i$. The index is the position of the element in the vector starting at 1 for the first element. An individual element in a matrix is identified by two indices denoted in a subscript, e.g., $[A]_{ij}$, the first number corresponding to the row number and the second number corresponding to the column number.

$$\mathbf{a} = \begin{pmatrix} a_1, & a_2, & \cdots & a_n \end{pmatrix}, \qquad \mathbf{b} = \begin{pmatrix} b_1 \\ b_2 \\ \vdots \\ b_n \end{pmatrix}, \qquad A = \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{pmatrix}.$$
In computer programming, a vector or matrix is represented using an array. Arrays can be one-dimensional where they contain a single row or column of elements, similar to a vector, or two-dimensional array similar to a matrix. It is possible to have higher dimensional arrays but this is not recommended as it can over complicate a program.


Defining arrays¶

The NumPy library¶

To work with matrices and arrays in Python we can import the NumPy library. NumPy is short for 'numerical Python' and a library containing functions which are very useful for scientific computing. To import NumPy execute the code cell below so that all other code cells can use NumPy commands.

In [1]:
import numpy as np

The use of this command means we can call the commands from NumPy using the prefix np.

To define a one-dimensional array in Python we can use the np.array command (numpy.array help page).

A = np.array([a1, a2, ... , an ])

It is standard practice (although not necessary) in programming to use an uppercase character for the first character in the array name. This helps to differentiate between arrays and variables.

Example 1¶

The commands below defines and prints the array corresponding to the vector $\mathbf{a} = (1, 2, 3)$. Enter them into the code cell below and execute it (don't forget to execute the code cell above to use NumPy commands).

A = np.array([ 1, 2, 3 ])
print(A)
In [27]:
A = np.array([ 1, 2, 3 ])
print(A)
[1 2 3]

Note that the elements in the row vector are contained within square bracket and elements are separated using commas. To define a two-dimensional array (i.e., a matrix) we input multiple row vectors separated by commas.

A = np.array([[ a11, a12, ..., a1n ],
              [ a21, a22, ..., a2n ],
                 :    :         :
              [ am1, am2, ..., amn ]])

Example 2¶

Define arrays for the following matrices

  (i)   $A = \begin{pmatrix} 1 & -2 \\ 0 & 5 \end{pmatrix}$

A = np.array([[ 1, -2 ],
              [ 0, 5 ]])
print(A)
In [26]:
A = np.array([[ 1, -2 ],
              [ 0, 5 ]])
print(A)
[[ 1 -2]
 [ 0  5]]

  (ii)   $B = \begin{pmatrix} 2 \\ -4 \\ 5 \end{pmatrix}$

B = np.array([ [2], [-4], [5] ])
print(B)
In [9]:
B = np.array([ [2], [-4], [5] ])
print(B)
[[ 2]
 [-4]
 [ 5]]

  (iii)   $C = \begin{pmatrix} 1 & 0 & 7 \\ 4 & 7 & 5 \end{pmatrix}$

C = np.array([[ 1, 0, 7 ],
              [ 4, 7, 5 ]])
print(C)
In [10]:
C = np.array([[ 1, 0, 7 ],
              [ 4, 7, 5 ]])
print(C)
[[1 0 7]
 [4 7 5]]

Exercise 1 - Defining arrays¶

  1. Define and print arrays corresponding to the following vectors and matrices:

    (a)   $A = \begin{pmatrix}6, & 2, & 4, & -1 \end{pmatrix}$;

In [12]:
A = np.array([6, 2, 4, -1])
print(A)
[ 6  2  4 -1]

    (b)   $B = \begin{pmatrix} 3 & 5 & -2 \\ -2 & 4 & 3 \\ 7 & 2 & -1 \end{pmatrix}$;

In [14]:
B = np.array([[3, 5, -2], [-2, 4, 3], [7, 2, -1]])
print(B)
[[ 3  5 -2]
 [-2  4  3]
 [ 7  2 -1]]

    (c)   $C = \begin{pmatrix} 2 & 0 & -1 & 4 \\ 7 & -3 & 9 & -5\end{pmatrix}$;

In [15]:
C = np.array([[2, 0, -1, 4], [7, -3, 9, -5]])
print(C)
[[ 2  0 -1  4]
 [ 7 -3  9 -5]]

    (d)   $D = \begin{pmatrix} -4 & 4 & 2 \\ 7 & 5 & -3 \\ 5 & 1 & 6 \end{pmatrix}$.

In [16]:
D = np.array([[-4, 4, 2], [7, 5, -3], [5, 1, 6]])
print(D)
[[-4  4  2]
 [ 7  5 -3]
 [ 5  1  6]]

Indexing arrays¶

In Python the elements of an array are indexed by their position starting at 0 for the first element. The index is written in square brackets following the array name.

A[i]

For two-dimensional arrays, the indices of an element are separated by a comma.

A[i,j]

To index elements at the end of a row or column we can use the index -1

A[-1]

The penultimate element is indexed using -2 and so on.

Example 3¶

Define an array for the matrix $A$ below and print the values of the following elements

$$A = \begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{pmatrix},$$

    (i)   $a_{11}$

A = np.array([[ 1, 2, 3 ],
              [ 4, 5, 6 ],
              [ 7, 8, 9 ]])
print(A[0,0])
In [31]:
A = np.array([[ 1, 2, 3 ],
              [ 4, 5, 6 ],
              [ 7, 8, 9 ]])
print(A[0,0])
1

    (ii)   $a_{32}$

print(A[2,1])
In [30]:
print(A[1,0])
4

    (iii)   the element in the second to last row and last column

print(A[-2,-1])
In [32]:
print(A[-2,-1])
6

Determining the size of an array¶

The number of elements in a one-dimensional array can be determined using

len(A)

The number of rows and columns in an two-dimensional array can be determined using the .shape property of the array (numpy.shape help page).

A.shape

This returns the tuple (rows, columns) for the array A.

Example 4¶

The following commands defines arrays correpsonding to the matrices

$$A = \begin{pmatrix} 1 & 2 & 3 & 4 \end{pmatrix}, \qquad B = \begin{pmatrix} 1 & 3 & 5 \\ 7 & 9 & 11 \\ 13 & 15 & 17 \\ 19 & 21 & 23 \end{pmatrix},$$
and prints their sizes. Enter them into the code cell below and execute it.

A = np.array([ 1, 2, 3, 4 ])
B = np.array([[ 1, 3, 5 ],
              [ 7, 9, 11 ],
              [ 13, 15, 17 ],
              [ 19, 21, 23 ]])

print(f"The array A has {len(A)} elements.")
print(f"The array B has {B.shape[0]} rows and {B.shape[1]} columns.")
In [34]:
A = np.array([ 1, 2, 3, 4 ])
B = np.array([[ 1, 3, 5 ],
              [ 7, 9, 11 ],
              [ 13, 15, 17 ],
              [ 19, 21, 23 ]])

print(f"The array A has {len(A)} elements.")
print(f"The array B has {B.shape[0]} rows and {B.shape[1]} columns.")
The array A has 4 elements.
The array B has 4 rows and 3 columns.

Array slicing¶

Array slicing allows us to return multiple elements from a NumPy array

A[start : stop : step]

If any these are unspecified Python uses default values of start=0, stop=size of dimension, step=1.

Example 5¶

Define an array for the matrix $A$ below and use array slicing to print the following

$$A = \begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{pmatrix}. $$

  (i)   the first row of $A$

A = np.array([[ 1, 2, 3 ],
              [ 4, 5, 6 ],
              [ 7, 8, 9 ]])

print(A[0,:])
In [35]:
A = np.array([[ 1, 2, 3 ],
              [ 4, 5, 6 ],
              [ 7, 8, 9 ]])

print(A[0,:])
[1 2 3]

  (ii)   the second column of $A$

print(A[:,1])
In [36]:
print(A[:,1])
[2 5 8]

Note that Python returned a $1\times 3$ array instead of a $3\times 1$ array. Python always prints one-dimensional arrays as a row vector.

  (iii)   the elements from the second column onwards in the second row of $A$

print(A[1,1:])
In [37]:
print(A[1,1:])
[5 6]

  (iv) the last row of $A$ in reverse order

print(A[-1,::-1])
In [38]:
print(A[-1,::-1])
[9 8 7]

Exercise 2 - Indexing arrays¶

  1. Define an array corresponding to the matrix
$$A = \begin{pmatrix} 6 & -2 & 4 & 0 \\ -4 & 6 & -1 & 2 \\ 4 & -3 & -5 & 6 \end{pmatrix}.$$
In [40]:
A = np.array([[6, -2, 4, 0], [-4, 6, -1, 2], [4, -3, -5, 6]])
print(A)
[[ 6 -2  4  0]
 [-4  6 -1  2]
 [ 4 -3 -5  6]]
  1. Use the array defined in question 2 and array indexing to print:

    (a)   $a_{12}$;

In [41]:
print(A[0,1])
-2

    (b)   $a_{32}$;

In [42]:
print(A[2,1])
-3

    (c)   the first row of $A$;

In [43]:
print(A[0,:])
[ 6 -2  4  0]

    (d)   the middle two elements of the second row of $A$;

In [45]:
print(A[1,1:3])
[ 6 -1]

    (e)   the even columns of $A$;

In [49]:
print(A[:,1::2])
[[-2  0]
 [ 6  2]
 [-3  6]]

    (f)   The matrix $A$ flipped upside-down (i.e., the rows of $A$ in reverse order).

In [50]:
print(A[-1::-1,:])
[[ 4 -3 -5  6]
 [-4  6 -1  2]
 [ 6 -2  4  0]]

Generating special matrices¶

The NumPy library has some commands that can be used to generate special matrices. To generate an array containing all zeros, all ones or the identity matrix we can use the following commands

np.zeros((m, n))
np.ones((m, n))
np.eye(n)

Example 6¶

Use NumPy commands to generate the following arrays.

  (i)   $A = \mathbf{0}_{3\times 3}$

A = np.zeros((3, 3))
print(A)
In [53]:
A = np.zeros((3, 3))
print(A)
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]

  (ii)   $B = \text{a $3 \times 2$ array of 1s}$

B = np.ones((3, 2))
print(B)
In [57]:
B = np.ones((3, 2))
print(B)
[[1. 1.]
 [1. 1.]
 [1. 1.]]

  (iii)   $I_4$ (the $4 \times 4$ identity matrix)

I = np.eye(4)
print(I)
In [56]:
print(np.eye(4))
[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]

Sequences of numbers¶

A one-dimensional array containing a sequence of numbers can be generated using the np.arange command (numpy.arange help page).

np.arange(end)

This will generate an array containing integer numbers from 0 to end-1. For other sequences we can use

np.arange(start, end, step)

This will generate an array of numbers starting at start and finishing at end-1 where the difference between each number is step. When step isn't specified Python will assume a step of 1.

Example 7¶

Use the np.arange command to define arrays which contain the following sequences of numbers.

  (i)   $0, 1, 2, \ldots, 9$

print(np.arange(10))
In [58]:
print(np.arange(10))
[0 1 2 3 4 5 6 7 8 9]

  (ii)   $2, 5, 8, \ldots, 38$

print(np.arange(2, 40, 3))
In [64]:
print(np.arange(2, 40, 3))
[ 2  5  8 11 14 17 20 23 26 29 32 35 38]

  (iii)   $100, 92, 84, \ldots, 4$

print(np.arange(2, 6))
In [68]:
print(np.arange(100, 0, -8))
[100  92  84  76  68  60  52  44  36  28  20  12   4]

Exercise 3 - Generating arrays¶

  1. Print a $10 \times 10$ array where each element is 1.
In [73]:
print(np.ones((10, 10)))
[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]
  1. Print an $8\times 8$ indentity matrix.
In [74]:
print(np.eye(8))
[[1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1.]]
  1. Print an array containing the first ten multiples of 3.
In [76]:
print(np.arange(3, 33, 3))
[ 3  6  9 12 15 18 21 24 27 30]

Concatenating arrays¶

NumPy arrays can be concantenated (merged) using the np.concatenate command (numpy.concatenate help page)

np.concatenate((first matrix, second matrix), axis=0)

This will form a new matrix where the second matrix is placed below the first matrix. To form a matrix where the second matrix is placed to the right of the first matrix we can use

np.concatenate((first matrix, second matrix), axis=1)

Example 8¶

The commands below define array corresponding to the matrices

$$A = \begin{pmatrix} 1 & 2 \\ 3 & 3 \end{pmatrix}, \qquad B = \begin{pmatrix} 5 & 6 \\ 7 & 8 \end{pmatrix},$$
and concatenates them. Enter them into the code cell below and execute it.

A = np.array([[ 1, 2 ],
              [ 3, 4 ]])
B = np.array([[ 5, 6 ],
              [ 7, 8 ]])

print(np.concatenate((A, B), axis=0), end="\n\n") # merge A and B with A on top of B
print(np.concatenate((A, B), axis=1))             # merge A and B side-by-side
In [79]:
A = np.array([[ 1, 2 ],
              [ 3, 4 ]])
B = np.array([[ 5, 6 ],
              [ 7, 8 ]])

print(np.concatenate((A, B), axis=0), end="\n\n") # merge A and B with A on top of B
print(np.concatenate((A, B), axis=1))             # merge A and B side-by-side
[[1 2]
 [3 4]
 [5 6]
 [7 8]]

[[1 2 5 6]
 [3 4 7 8]]

Matrix and array operations¶

The Python commands for the common operations on matrices and arrays are summarised in the table below.

Operation Name Python code
$A + B$ matrix addition A + B
$A - B$ matrix subtraction A - B
$kA$ scalar multiplication k * A
$A \odot B$ element-wise multiplication A * B
$AB$ matrix multiplication np.dot(A, B)
$ABC$ matrix multiplication of multiple matrices   np.linalg.multi_dot([A, B, C])
$A^{\circ k}$ element-wise power A ** k
$A^k$ matrix power np.linalg.matrix_power(A, k)
$A^T$ matrix transpose A.T
$\det(A)$ determinant of a matrix np.linalg.det(A)
$A^{-1}$ inverse of a matrix np.linalg.inv(A)
$\sin(A)$ $\sin$ of a matrix* np.sin(A)

* Other mathematical functions are treated similarly.

Example 9¶

Define arrays for the matrices $A$ and $B$ below and print the result of the the following operations:

$$A = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}, \qquad B = \begin{pmatrix} 5 & 6 \\ 7 & 8 \end{pmatrix},$$

  (i)   $A + B$

A = np.array([[ 1, 2 ],
              [ 3, 4 ]])
B = np.array([[ 5, 6 ],
              [ 7, 8 ]])

print(A + B)
In [80]:
A = np.array([[ 1, 2 ],
              [ 3, 4 ]])
B = np.array([[ 5, 6 ],
              [ 7, 8 ]])

print(A + B)
[[ 6  8]
 [10 12]]

  (ii)   $3A$

print(3 * A)
In [81]:
print(3 * A)
[[ 3  6]
 [ 9 12]]

  (iii)   $A \odot B$   (element-wise multiplication)

print(A * B)
In [82]:
print(A * B)
[[ 5 12]
 [21 32]]

  (iv)   $AB$

print(np.dot(A, B))
In [84]:
print(np.dot(A, B))
[[19 22]
 [43 50]]

  (v)   $ABA$

print(np.linalg.multi_dot([A, B, A]))
In [89]:
print(np.linalg.multi_dot([A, B, A]))
[[ 85 126]
 [193 286]]

  (vi)   $A^{\circ 3}$   (element-wise power)

print(A ** 3)
In [90]:
print(A ** 3)
[[ 1  8]
 [27 64]]

  (vii)   $A^3$   (matrix power)

print(np.linalg.matrix_power(A, 3))
In [91]:
print(np.linalg.matrix_power(A, 3))
[[ 37  54]
 [ 81 118]]

  (viii)   $A^\mathrm{T}$   (transpose of a matrix)

print(A.T)
In [94]:
print(A.T) 
[[1 3]
 [2 4]]

  (ix)   $\det(A)$   (the determinant of $A$)

print(np.linalg.det(A))
In [93]:
print(np.linalg.det(A))
-2.0000000000000004

  (x)   $A^{-1}$   (inverse of a matrix)

print(np.linalg.inv(A))
In [95]:
print(np.linalg.inv(A))
[[-2.   1. ]
 [ 1.5 -0.5]]

  (xi)   $\sin(A)$

print(np.sin(A))
In [96]:
print(np.sin(A))
[[ 0.84147098  0.90929743]
 [ 0.14112001 -0.7568025 ]]

Exercise 4 - Matrix and array operations¶

  1. Define arrays corresponding to
$$A = \begin{pmatrix} 1 & 4 & -2 \\ 0 & 5 & 7 \\ 4 & 1 & -9 \end{pmatrix}, \qquad B = \begin{pmatrix} 5 & 1 & 8 \\ -4 & -2 & 0 \\ 5 & 11 & 3 \end{pmatrix}.$$
In [97]:
A = np.array([[1, 4, -2], [0, 5, 7], [4, 1, -9]])
B = np.array([[5, 1, 8], [-4, -2, 0], [5, 11, 3]])

print(A)
print()
print(B)
[[ 1  4 -2]
 [ 0  5  7]
 [ 4  1 -9]]

[[ 5  1  8]
 [-4 -2  0]
 [ 5 11  3]]
  1. Use the arrays you defined in question 7 to calculate:

    (a)   $A - 3B$  

In [98]:
print(A - 3 * B)
[[-14   1 -26]
 [ 12  11   7]
 [-11 -32 -18]]

    (b)   $A \odot B$

In [99]:
print(A * B)
[[  5   4 -16]
 [  0 -10   0]
 [ 20  11 -27]]

    (c)   $AB$

In [100]:
print(np.dot(A, B))
[[-21 -29   2]
 [ 15  67  21]
 [-29 -97   5]]

    (d)   $BA$

In [101]:
print(np.dot(B, A))
[[ 37  33 -75]
 [ -4 -26  -6]
 [ 17  78  40]]

    (e)   $ABA$

In [104]:
print(np.linalg.multi_dot([A, B, A]))
[[ -13 -227 -179]
 [  99  416  250]
 [  -9 -596 -666]]

    (f)   $B^{\circ 4}$

In [105]:
print(B ** 4)
[[  625     1  4096]
 [  256    16     0]
 [  625 14641    81]]

    (g)   $B^3$

In [106]:
print(np.linalg.matrix_power(B, 3))
[[ 261  583  680]
 [-220 -364 -192]
 [ 161  503  115]]

    (h)   $A^T$

In [107]:
print(A.T)
[[ 1  0  4]
 [ 4  5  1]
 [-2  7 -9]]

    (i)   $\det(A)$

In [108]:
print(np.linalg.det(A))
99.99999999999996

    (j)   $A^{-1}$

In [109]:
print(np.linalg.inv(A))
[[-0.52  0.34  0.38]
 [ 0.28 -0.01 -0.07]
 [-0.2   0.15  0.05]]

    (k)   $\cos(B)$

In [110]:
print(np.cos(B))
[[ 0.28366219  0.54030231 -0.14550003]
 [-0.65364362 -0.41614684  1.        ]
 [ 0.28366219  0.0044257  -0.9899925 ]]