2023-01-23
Any programming language that deals with mathematical problems must have a way to store and manipulate vector data. Python has the list data type, which is adequate for some purposes, but the array data type from NumPy provides much greater functionality and efficiency.
In this lesson, you will learn to create and operate on arrays of data. In particular, you will learn how to
For now, we limit our attention to one-dimensional arrays, but a later lesson will treat two dimensional arrays, which are used to represent matrices.
It is natural to begin with lists, because these are simpler than arrays and use the same indexing conventions. A Python list can be defined by specifying a comma separated sequence of objects, enclosed in square brackets.
For example,
my_list = [3, 2, -1, 5, 9]
The objects in a list don’t have to be numbers, but that is the case of most interest for us. The len function returns the length of a list, so in our example len(my_list) will return 5. In Python, the elements of a list of length n are indexed from 0 to n-1, and the kth element is referenced by appending [k] to the name of the list. Thus, my_list[0] = 3, my_list[1] = 2, my_list[2] = -1, my_list[3] = 5 and my_list[4] = 9.
For a list l of length n, trying to access l[k] when k is larger than n-1 raises IndexError: list index out of range.
An expression of the form l[i:j], for integers i and j, is called a slice of the list l. If i is less than j (the usual case in practice), then this slice is the list
[ l[i], l[i+1], l[i+2], l[i+3], ..., l[j-1] ]
In other words, the slice l[i:j] consists of l[k] for k from i up to but not including j, and len(l[i:j]) = j-i. If i is greater than or equal to j, the l[i:j] is the empty list [].
A more general slice has the form l[i:j:s] where the third integer s is called the stride or step. If i is less than j, and if s is positive, then this slice is the list
[ l[i], l[i+s], l[i+2*s], l[i+3*s], ..., l[i+n*s] ]
where n is the largest integer such that i+n*s<j.
Exercise. Suppose that l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]. Write out by hand each of the following slices, and check your answer using the IPython shell.
l[2:7]
l[1:8:2]
l[6:1]
l[3:9:20]
The notation l[:j] is an abbreviation for l[0:j], and likewise l[i:] is an abbreviation for l[i:n] if n=len(l).
For a list l of length n, the expression l[k] is interpreted as l[k+n] if k is between -n and -1; thus, l[-1] = l[n-1] is the last element, l[-2]=l[n-2] is the second-last element and so on until l[-n]=l[0].
If i is greater than j, and if s is negative, the the slice l[i:j:s] is the list
[ l[i], l[i+s], l[i+2*s], l[i+3*s], ..., l[i+n*s] ]
where n is the largest integer such that i+n*s>j. For example, if l = [0, 1, 2, 3, 4] then l[3:1:-1] is [3, 2]. In general, l[-1::-1] is the list obtained from l by writing out the elements in reverse order.
Exercise. Suppose again that l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]. Write out by hand each of the following slices, and check your answer using the IPython shell.
l[2:9:3]
l[::2]
l[7:-9:-2]
l[3:8:-1]
The NumPy module provides the array function for creating an array.
In the simplest case, we simply provide a list of the elements in the array.
a = array([0, 1, 2, 3, 4, 5, 6])
We can index an array in the same way as a list, so in this example a[3] = 3 and the slice a[2:5] is an array with entries 2, 3, 4. Likewise, the len function works as expected for arrays, so that in our example len(a)=7.
However, other operations on lists work differently for arrays. Try the following experiment.
l1 = [1, 2, 3]
l2 = [4, 5, 6]
l1 + l2
a1 = array(l1)
a2 = array(l2)
a1 + a2
You will see adding the two lists concatenates them to produce [1, 2, 3, 4, 5, 6], whereas adding the two arrays performs elementwise addition to produce an array with entries 1+4=5, 2+5=7, 3+6=9. Now try
3 * l1
3 * a1
You will see that tripling the list produces [1, 2, 3, 1, 2, 3, 1, 2, 3], whereas tripling the array triples each entry to produce an array with entries 3x1=3, 3x2=6, 3x3=9. Thus, arrays behave like vectors under addition and under multiplication by scalars.
Next, try
l1 * l2
a1 * a2
The first statement raises TypeError: can't multiply sequence by non-int of type 'list', whereas the second statement performs elementwise multiplication to produce an array with entries \(1\times4=4\), \(2\times5=10\), \(3\times6=18\). In this respect, lists are more similar to vectors, since ‘multiplication of vectors’ is generally undefined in linear algebra. Nevertheless, we will see in later lessons that elementwise multiplication for arrays turns out to be very useful in contexts other than linear algebra.
Exercise. Look up the docstring for the NumPy function append, and use it to append a2 to a1, obtaining an array with entries 1, 2, 3, 4, 5, 6.
Elementwise subtraction and division of arrays are also defined: a1-a2 produces an array with entries 1-4=-3, 2-5=-3, 3-6=-3, and a1/a2 produces an array with entries 1/4=0.25, 2/5=0.40, 3/6=0.5.
If two arrays have different lengths, then taking their sum raises a ValueError. However, adding a scalar to an array is permitted: the scalar is added to each entry of the array. For example, a1+4 produces an array with entries 1+4=5, 2+4=6, 3+4=7. Adding a scalar to a vector is undefined in linear algebra, but the operation again turns out to be useful for arrays in some contexts.
Another difference between arrays and lists is that every entry in an array must have the same type, but a list can contain different types of entries. For example, given the commands
l=[1, -2.3, 2+4.5j]
a=array(l)
the list l contains an int, a float and a complex, but to create the array the int and float are converted to complex so that a is array([ 1.+0.j, -2.3+0.j, 2.+4.5j]). This homogenous nature of arrays plays an essential role in their efficiency for processing large amounts of numerical data.
Every time you reference an entry of a list or array, Python checks that the indices you provide are within the permitted bounds. For the list l1 above, attempting to access l1[5] will raise IndexError: list index out of range. Similarly, for the array a1, attempting to access a1[5] raises IndexError: index 5 is out of bounds for axis 0 with size 3.
Exercise. Find out what happens when you type l1[2:6] or a1[2:6].
NumPy provides several functions that are frequently used for creating arrays.
zeros(n) creates an array of length n with every entry equal to 0.ones(n) creates an array of length n with every entry equal to 1.arange(n) creates an array of length n with entries \(0\), \(1\), \(2\), …, \(n-1\).linspace(a, b, n) creates an array of length n whose entries are linearly spaced from a to b, i.e., if we let s=(b-a)/n then the entries are a, a+s, a+2s, a+3s, …, a+ns=b.You can also call arange with two or three arguments:
arange(i, j) creates an array of length j-i with entries i, i+1, i+2, …, j-1.arange(i, j, s) creates an array with entries i, i+s, i+2*s, i+3*s, …, i+n*s where n is the largest integer such that i+n*s is less than j , assuming i is less than j and s is positive.A list is an example of a container in Python: it contains other objects, namely it’s entries. A list also said to be mutable because its internal state can be changed. An array is also a mutable container.
Type the following commands in IPython.
l1 = [1, 2, 3, 4]
l2 = l1
l1[2] = -1
l2
Notice that changing the value of l1[2] also changed l2[2], even though we changed l1[2] after we assigned l2 to l1. The assignment l2 = l1 does not create a copy of l1; instead, l2 simply references the same storage as l1. In effect, l2 becomes an alias for l1.
Assignment of arrays works in the same way. In the next lesson, we will see how to make a copy of a list or array.
In this lesson, we have seen how to
The official NumPy documentation includes NumPy: the absolute basics for beginners that provides a quick introduction to arrays, including some topics covered here in later lessons. If you are familiar with Matlab, then NumPy for MATLAB users is recommended.