Lab 7: NumPy and Matrix Row Reduction¶
In earlier labs, we wrote functions like scalar_mult(s, v), vector_add(v, w), and dot_product(v, w). In order to avoid having to write these functions every time, people like Travis Oliphant (a BYU alumnus) created a python library we can use called NumPy.
In this lab you will learn how to use NumPy to write functions that perform basic operations related to row-reducing a matrix. You will learn about:
importing NumPy
vectors and matrices in NumPy
accessing NumPy arrays using slicing
NumPy¶
Importing¶
To start using NumPy import it using:
>>> import numpy as np
As mentioned in Lab 5, every time we use a function from NumPy, we need to prefix it with np. So calling a function like cos (for cosine) would look like:
>>> np.cos(0)
1.0
Note
np is the commonly understood nickname for NumPy so we don’t have to type out “numpy” every time.
Vectors and Matrices¶
Recall that in NumPy, we represent matrices and vectors as a special kind of array called a NumPy array. To define
a NumPy array, we use the function np.array(). For example, to create the vector
as a NumPy array, we take the Python list [1,2,-1] and plug it into the
function np.array().
>>> my_vect=np.array([1,2,-1])
We can add vectors and multiply by scalars in a straightforward way. Think back to vector_add(v, w) and scalar_mult(c, v) from Lab 5.
>>> array1=np.array([1,2,3])
>>> array2=np.array([0,7,4])
>>> array1 + array2 # adding vectors
array([1, 9, 7])
>>> my_vect=np.array([1,2,-1])
>>> 3 * my_vect # multiplying by scalars
array([3, 6, -3])
Note
While the vectors,
mean different things when we write them out, NumPy represents row and column vectors in the exact same way:
>>> np.array([1, 2, -1])
array([ 1, 2, -1])
Both row and column vectors will be created with a one dimensional NumPy array.
NumPy makes working with vectors very easy. They even have dot() which replaces our dot_product() function.
>>> array1=np.array([1,2,3])
>>> array2=np.array([4,5,6])
>>> np.dot(array1, array2)
32
Note
Note that if you attempt to add vectors of different lengths or perform the dot product of vectors of different sizes, NumPy will throw an error.
To define matrices in NumPy, we define them as “lists of lists”. Then we can plug that into the function np.array(). For example, to define the matrix
we would create a list with three elements. The first element will be the list [1, 2, 3, 4], which we consider the first row of the matrix. The second element in our list will be [-5, -6, -7, -8], representing the second row, and so on.
Note
In NumPy, both arrays and matrices are created using np.array(). np.array() returns a special object called a np.ndarray which will be explained more later.
>>> my_matrix = np.array([[1, 2, 3, 4],[-5, -6, -7, -8],[1, 5, 2, 3]])
>>> print(my_matrix)
[[ 1 2 3 4]
[-5 -6 -7 -8]
[ 1 5 2 3]]
Matrix Multiplication with @, np.dot(), and np.matmul()
There are a few different ways to multiply matrices in Python. @ is native to python, while np.dot() and np.matmul() come with NumPy.
>>> import numpy as np
>>> a = np.array([[1, 2, 3],
[3, 4, 5]])
>>> b = np.array([[2, 3],
[4, 5],
[6, 7]])
>>> a @ b
[[28 34]
[52 64]]
>>> np.dot(a, b)
[[28 34]
[52 64]]
>>> np.matmul(a, b)
[[28 34]
[52 64]]
Each of these operations returns the same thing for 2d matrices. Each has different rules for NumPy arrays that are not two dimensional, so be careful of that. You can see more information here.
Accessing NumPy Arrays with Slicing¶
We can access elements of a NumPy array the same way we access elements in a list, by
specifying indices. Recall that Python lists (and NumPy arrays) begin at
index 0. So if an element of a list or array has index 3, that really means it’s the 4th element
in the list or array.
>>> v=np.array([4,1,-5,3,-2,1,0,9])
>>> print(v[3])
3
If we want more specific indexing, we can use slicing. This is where we specify a range of indices. It looks like:
>>> my_array[start:stop]
We can think of it like \([start,stop)\) in mathematics. So from the array above,
>>> print(v[3:7])
[ 3 -2 1 0]
There is another optional parameter in slicing called step.
>>> my_array[start:stop:step]
step determines how many elements we skip over. So
>>> print(v[3:7:2])
[3 1]
will start at index 3 and select every second element until it reaches (but does not include) 7.
Here are some more examples:
>>> v=np.array([4,1,-5,3,-2,1,0,9])
>>> print(v[2:6]) # [2,6)
[-5 3 -2 1]
>>> print(v[3:]) # [3,end)
[3 -2 1 0 9]
>>> print(v[:4]) # [beginning,4)
[4 1 -5 3]
>>> print(v[::2]) # [beginning,end) stepping every second element
[ 4 -5 -2 0]
We can access the entries in a matrix in a similar way to accessing elements of a list. However for matrices, we need to provide two indices (or ranges of indices), to specify the location of the row(s) and/or column(s) in which we are interested.
>>> my_matrix=np.array([[1, 2, 3, 4],[-5, -6, -7, -8],[1, 5, 2, 3]])
>>> print(my_matrix)
[[ 1 2 3 4]
[-5 -6 -7 -8]
[ 1 5 2 3]]
>>> print(my_matrix[1,2]) # row index 1, column index 2
-7
>>> print(my_matrix[2,1:3]) # row index 2, column indices 1 through 3
[5 2]
>>> print(my_matrix[:,3]) # all the rows, column index 3
[4 -8 3]
>>> print(my_matrix[1]) # row index 1 (2nd row)
[-5 -6 -7 -8]
Warning
Remember in python, indexing starts at 0!
Note
my_matrix[1,2] will do the same thing as my_matrix[1][2] for np.ndarrays. For two dimensional python lists, only my_matrix[1][2] is valid.
Other NumPy Tools¶
When you make a NumPy array, it isn’t an int, str, float, or bool, it is something called an object of type numpy.ndarray (which stands for n-dimensional array). Objects in python are just one more way to represent data. When an object is made, it has attributes that contain different information about the object. We get attributes with the . notation. We will learn more about objects later, but for now you just need to know how to use object attributes. As an example, if our array is named array1, then:
array1.ndimwill tell you the number of dimensions of the arrayarray1.sizewill tell you how many elements are in the arrayarray1.shapewill give you a tuple with each element represents the number of elements in each dimension of the array (a one dimensional array would be(n,), a two dimensional array would be(n, m)and so on)
NumPy arrays also have functions associated with them. These functions have a special name because they only work on np.ndarrays. These special functions are called methods. We call them in the exact same way we would a normal function. Here are some of the most useful ones:
array1.sum()returns the sum of all the elements in the arrayarray1.mean()returns the mean of all the elements in the arrayarray1.max()returns the maximum value of the arrayarray1.min()returns the minimum value of the array
Warning
Attributes are not functions so we don’t call them with ().
The main difference between methods and attributes is that methods are calculated on the fly, while attributes are stored with the object.
NumPy also has built-in functions to create NumPy arrays. These are important to know about, but you don’t need to know all the details right now.
np.zeros(shape)creates an array full of 0snp.ones(shape)creates an array full of 1snp.empty(shape)creates an array filled with uninitialized (potentially random) numbers faster thanzeros()orones()np.full(shape, fill_value)creates an array filled with the specified valuenp.arange(start, stop, step)works just likerange(), but it creates an array with all the valuesnp.linspace(start, stop, num)creates an array from start to end (inclusive) of evenly spaced numbers (specified bynum)
NumPy also has a set of functions that you access with numpy.linalg.
It includes things like matrix multiplication, eigenvalues, the transpose of a matrix, and lots of other useful functions.
Again, these are good to know about, but you don’t need to know all the details yet.
Application: Matrix Row Reduction¶
Now, we will write functions to perform basic matrix operations related to row-reduction. These aren’t directly built into NumPy, but we can use NumPy to make writing them easier! These functions will be very useful for future labs as well.
Requirements
The functions you write for this lab should work for matrices of any size.
All inputs and outputs for this lab should be NumPy arrays.
Task 1¶
Write a function row_swap(A, i, j) which takes as input a matrix A, and two indices i and j. Your function should return the matrix obtained from A with rows i and j swapped.
>>> row_swap(np.array([[1, -1, 1], [0, 1, 3], [2, -2, 0]]), 0, 2)
array([[ 2, -2, 0],
[ 0, 1, 3],
[ 1, -1, 1]])
>>> row_swap(np.array([[2, -1, 3], [1, 2, 3]]), 0, 1)
array([[ 1, 2, 3],
[ 2, -1, 3]])
Task 2¶
Write a function row_mult(A, i, c) which takes as input a matrix A, one index i, and a scalar c. Your function should return the matrix obtained from A with row i multiplied by c.
>>> row_mult(np.array([[1, 1], [2, 3]]), 1, 3)
array([[ 1, 1],
[ 6, 9]])
>>> row_mult(np.array([[1, 1], [6, 9]]), 0, 0)
array([[ 0, 0],
[ 6, 9]])
Task 3¶
Write a function row_add(A, i, j, c) which takes as input a matrix A, two indices i and j, and a scalar c. Your function should return the matrix obtained from A with row i replaced with itself plus c times row j.
>>> row_add(np.array([[0, 1, 1], [1, -1, 3], [1, 3, 2]]), 2, 0, -3)
array([[ 0, 1, 1],
[ 1, -1, 3],
[ 1, 0, -1]])
>>> row_add(np.array([[2, 1], [1, -2]]), 0, 1, 0)
array([[ 2, 1],
[ 1, -2]])
Challenge¶
Write a function that determines whether or not a matrix is in echelon form.
Write a function that row-reduces a matrix to echelon form. The difficult part of this problem is determining when to swap rows.