Advanced Python Arrays - Introducing NumPy
Written by Alex Armstrong   
Sunday, 21 April 2013
Article Index
Advanced Python Arrays - Introducing NumPy
Using the NumPy Array
Integer Indexing

Python arrays are powerful, but they can confuse programmers familiar with other languages. In this follow-on to our first look at Python arrays we examine some of the problems of working with lists as arrays and discover the power of the NumPy array.

Before we move on to more advanced things time for a quick recap of the basics. If you need more information then see Arrays in Python.


python3

Indexing and slicing

In Python arrays are indexed lists. 

myList=[1,2,3,4]

You can access a list element using a simple index as in most other languages:

myList[2]

is the third element as lists are indexed from zero. 

As wel as simple indexing Python allows you to use a slicer notation to specify parts of the list. A slicer has the form

  • start:end    items from start through end-1
  • start:          items from start to the end of the array
  • :end           items from the start to end-1
  • :                 items from start to end

Notice that the only unexpected thing is that the slicer doesn't include the end element. For example myList[0:2] is just elements 0 and 1 and not element 2.

That is, start specifies the first element in the selection and end specifies the first element not in the selection. 

You can also use negative values for start and end and this is interpreted as counting from the end of the list. For example myList[-2:] is [3,4] and myList[-3:-1] is [2,3].

Finally you can include a step size as a third value in the slicer i.e. [start:end:step]. For example myList[0:3:2] is [1,3]. As you can leave out specification an just use : you can write myList[::2] for example to step though the entire list taking every other element. 

It is also worth notiing that is a slicer doesn't specify any elements of the list, for example myList[4:2] then the result is a null list rather than an error.

Slicing notation can be used to extract elements and to store elements i.e. on the left and the right of an assignment.   For example:

myList[1:3]=myList[0:2]

stores elements [0] and [1] in elements [1] and [2].

Loops and comprehensions

Slicing notation is the main way in which Python differs from other languages in its use of lists as arrays. You can also write compact for loops using comprehensions. A comprehension is just a for loop that generates a sequence of values that you can use to generate a list.

For example:

myList=[i for i in range(10)]

creates a list with ten elements containing 0 to 9.  You can see this as a way of writing compact loops but it is also a way of manipulating lists to create new lists.

For example

newList=[myList[i] for i in range(1,3)]

copies elements [1] and [2] into newList.

Notice that you could have written this as;

newList=myList[1:3]

In this case a comprehension is the same as a slice, but this is not always the case.

 

Now we have revised slicing and comprehensions, it is time to take a look at some of the problems and some of the solutions.

Dimensions, slicing and comprehensions

In Python a 2D array is simply a list of lists. For example a 2x2 matrix could be represented as:

 myArray=[[1,2],[3,4]]

which is a list of two lists which represent the rows of the matrix.

You can use slicing and comprehensions on multi-dimensional arrays but they don't always work as you might hope. For example, how could you extract a sub-matrix? The obvious answer is to use slicing.  

Given the 3x3 array:

 myArray=[[1,2,3],[4,5,6],[7,8,9]]

you might imagine that you could extract the 2x2 matrix in its top right-hand corner using:

myArray[0:2][0:2]

However, this doesn't work and what you get is:

[[1, 2, 3], [4, 5, 6]]

If you think about it this is perfectly reasonable. The first slicer specifies the first two elements of the list, and these are the first two rows, i.e. 

myArray[0:2]

is 

[[1, 2, 3], [4, 5, 6]]

Now you can see what goes wrong with the second slicer - it isn't operating on the rows it is stil operating on the outer list. So it returns:

[[1, 2, 3], [4, 5, 6]] [0:2]

which is just the first two rows again.

Slicing notation works perfectly and as advertised, but it is common to think that it will do more.

If you want to extract the sub-matrix then the simplest way is to use a list comprehension or the equivalent for loops:

[[myArray[i][j] for j in range(2) ]
                        for i in range(2)]

which returns:

[[1, 2], [4, 5]]

This is a nice compact way of writing it, but it is not a lot different to a pair of nested index loops.  

Array Functions and Map

If you have used Matlab or Octave you might also expect Python to support matrix functions.

For example to take the square root elementwise you might try:

math.sqrt(myArray)

but this doesn't work. The majority of Python math functions accept a single scalar argument and trying to use them on an array simply generates an error. 

The simplest way around this problem is to use the map function which applies the function specified as its first argument to the list specified as its second argument. 

list(map(math.sqrt,myList))

In Python 3 map returns an iterable which has to be converted to a list - in Python 2 map returns the list directly.  This looks neat but in practice it isn't really much use. For example suppose you want the elementwise square root of a 3x3 matrix then

list(map(math.sqrt,myArray))

fails because sqrt is passed each row of the matrix in turn and it expects a scalar. 

In Python 3 map has been down graded without actually being removed and it is better to write a list comprehension. For example:

[math.sqrt(x) for x in myList]

performs an elementwise square root on the one dimensional myList.  The more than one dimensional case is on a little more complicated:

[[math.sqrt(x) for x in row]
                    for row in myArray]

or if you prefer to use an index loop:

[[math.sqrt(myArray[i][j])
   for j in range(len(myArray)) ]
       for i in range(len(myArray))]

 



Last Updated ( Wednesday, 27 February 2019 )