Python Ranges

It takes a long time to type in the entries of a matrix of any significant size. We do not have time to do that. Fortunately, python has sophisticated ways of filling and manipulating arrays and matrices. These are similar to those in Matlab, and like the Matlab notation, the ideas take some getting used to.

If we want to fill a list with an ordered collection of numbers starting with 0 with increments of 1, this is easy: viz

>>> v=range(10) >>> v [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

There are several important things to note here. First, we see immediately that asking for range(10) gives us ten numbers, but they start at zero, so they end at nine. Second, observe that the result is not an array, not a matrix; instead it is an ordinary low-level python list. This is enough to count objects in a "for" loop or something, but we will often want more structure. The range command is native python - not numpy. Naturally, numpy provides a command similar to range. It is called arange.

>>> from numpy import * >>> v=arange(10) >>> v array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

This time the result is a numpy array. It gives us a bit more structure. The numbers still start at zero. If you actually wanted to start at 1 and go to 10, you would have to tell python explicitly to do that. The arange command can take up to three arguments. If it has two arguments, then the first is interpreted as the starting number, and the second is one greater than the ending number.

>>> u=arange(1,11) >>> u array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

The arange function can be used to fill arrays in other ways as well. For example, if you wanted to create a partition on the interval [-1,1] with uniform spacing of 0.1, that could be done easily by using the third possible argument of arange. That argument specifies the increment. We have seen that by default the increment is one, but if we give that third argument it changes the increment to that value. Note that in this case the second argument becomes one increment greater than the ending number. There is a little bit of room for fudging this. Have a look.

>>> x=arange(-1,1.2,0.2) >>> x array([ -1.00000000e+00, -8.00000000e-01, -6.00000000e-01, -4.00000000e-01, -2.00000000e-01, -2.22044605e-16, 2.00000000e-01, 4.00000000e-01, 6.00000000e-01, 8.00000000e-01, 1.00000000e+00]) >>> x=arange(-1,1.1,0.2) >>> x array([ -1.00000000e+00, -8.00000000e-01, -6.00000000e-01, -4.00000000e-01, -2.00000000e-01, -2.22044605e-16, 2.00000000e-01, 4.00000000e-01, 6.00000000e-01, 8.00000000e-01, 1.00000000e+00]) >>> x=arange(-1,1.01,0.2) >>> x array([ -1.00000000e+00, -8.00000000e-01, -6.00000000e-01, -4.00000000e-01, -2.00000000e-01, -2.22044605e-16, 2.00000000e-01, 4.00000000e-01, 6.00000000e-01, 8.00000000e-01, 1.00000000e+00]) >>> x=arange(-1,1,0.2) >>> x array([ -1.00000000e+00, -8.00000000e-01, -6.00000000e-01, -4.00000000e-01, -2.00000000e-01, -2.22044605e-16, 2.00000000e-01, 4.00000000e-01, 6.00000000e-01, 8.00000000e-01])

What we are looking at here makes the actual algorithm used to generate ranges evident. If there are three arguments s,n,i, then numpy starts at s and appends elements to the array in steps of i until the number is greater than or equal to n. If there are only two arguments, numpy assumes that i=1. If there is only one argument numpy assumes additionally that s=0. Simple...

Along with the ability to create arrays quickly, python allows changes to entire chunks of lists, arrays, or matrices. In this case we can specify ranges for indices using a notation with a colon. It is easier to see an example first.

>>> x=matrix([arange(-1,1.1,0.5)]) >>> A=x.T*x >>> A matrix([[ 1. , 0.5 , 0. , -0.5 , -1. ], [ 0.5 , 0.25, 0. , -0.25, -0.5 ], [ 0. , 0. , 0. , 0. , 0. ], [-0.5 , -0.25, 0. , 0.25, 0.5 ], [-1. , -0.5 , 0. , 0.5 , 1. ]]) >>> A[1:3,1:3]=1 >>> A matrix([[ 1. , 0.5 , 0. , -0.5 , -1. ], [ 0.5 , 1. , 1. , -0.25, -0.5 ], [ 0. , 1. , 1. , 0. , 0. ], [-0.5 , -0.25, 0. , 0.25, 0.5 ], [-1. , -0.5 , 0. , 0.5 , 1. ]])

This illustrates several features of python's matrix handling, some nice, some annoying. We created a rank one matrix A by dropping a range into a matrix (remember the range would have been an array), and then premultiplying it by its transpose. Then we set an entire 2×2 block of that matrix to 1 in a single line. We already knew how to do that one element at a time, but by specifying a collection of indices using the notation [1:3,1:3], we were able to refer to the four elements with indices [1,1], [1,2], [2,1], and [2,2] all at the same time, and set them equal to 1.

When we specify a range using the colon notation, there are a few conventions that apply. These are not entirely intuitive.

• s:n refers to a range of indices starting with s and ending with the greatest integer less than or equal to n-1.
• s: refers to a range of indices starting with s that extends to the last possible index.
• :n refers to a range of indices starting with the first possible index, and extending to the greatest integer less than n-1.
• : refers to a range of all possible indices in that position.
• s:n:i refers to range of indices starting with s and counting in increments of i to the greatest number s+ki that is less than or equal to n-1, where k is a nonnegative integer.
• These rules can be mixed and matched in a somewhat rational way.
Here are some examples of these rules.

>>> A=zeros((5,5)) >>> A array([[ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.]]) >>> A[0,0:5] = arange(5) >>> A array([[ 0., 1., 2., 3., 4.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.]]) >>> A[1,:] = -3 >>> A array([[ 0., 1., 2., 3., 4.], [-3., -3., -3., -3., -3.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.]]) >>> A[2:,4] = pi >>> A array([[ 0. , 1. , 2. , 3. , 4. ], [-3. , -3. , -3. , -3. , -3. ], [ 0. , 0. , 0. , 0. , 3.14159265], [ 0. , 0. , 0. , 0. , 3.14159265], [ 0. , 0. , 0. , 0. , 3.14159265]]) >>> A[2,:4]=pi/2 >>> A array([[ 0. , 1. , 2. , 3. , 4. ], [-3. , -3. , -3. , -3. , -3. ], [ 1.57079633, 1.57079633, 1.57079633, 1.57079633, 3.14159265], [ 0. , 0. , 0. , 0. , 3.14159265], [ 0. , 0. , 0. , 0. , 3.14159265]]) >>> A[3,1:4]=1.0/3 >>> A array([[ 0. , 1. , 2. , 3. , 4. ], [-3. , -3. , -3. , -3. , -3. ], [ 1.57079633, 1.57079633, 1.57079633, 1.57079633, 3.14159265], [ 0. , 0.33333333, 0.33333333, 0.33333333, 3.14159265], [ 0. , 0. , 0. , 0. , 3.14159265]]) >>> A[3:5,:2] = -9 >>> A array([[ 0. , 1. , 2. , 3. , 4. ], [-3. , -3. , -3. , -3. , -3. ], [ 1.57079633, 1.57079633, 1.57079633, 1.57079633, 3.14159265], [-9. , -9. , 0.33333333, 0.33333333, 3.14159265], [-9. , -9. , 0. , 0. , 3.14159265]])

In each of these assignments we put some object into some submatrix of A. In the first case we put a 1D range object into the first (index 0) row. In the second, we put a constant into every entry in the second (index 1) row. After that, we put an approximation for $\pi$ into the last three entries in the last column. Next, $\pi /2$ in the first four entries in the fourth (index 3) row. Another command put an approximation to 1/3 into the middle three entries of the penultimate row. Notice here that we had to make sure to use a floating point representation for either the 1 or the 3 - otherwise python would have supposed that it was doing integer arithmetic, and would have rounded the result to zero. More about that later. Finally, we put the number -9 in the small 2×2 block in the lower left corner. When you understand all these operations, you will be able to manipulate matrices and arrays with speed and subtlety.

A solution for the final is available.

Department of Mathematics, PO Box 643113, Neill Hall 103, Washington State University, Pullman WA 99164-3113, 509-335-3926, Contact Us