This chapter introduces some of the basic functions which will be used throughout the text.
Before we explore the world of image manipulation as a case-study in array manipulation, we should first define a few terms which we'll use over and over again. Discussions of arrays and matrices and vectors can get confusing due to disagreements on the nomenclature. Here is a brief definition of the terms used in this tutorial, and more or less consistently in the error messages of NumPy.
The python objects under discussion are formally called "multiarray" objects, but informally we'll just call them "array" objects or just "arrays." These are different from the array objects defined in the standard Python array module (which is an older module designed for processing one-dimensional data such as sound files).
These array objects hold their data in a homogeneous block of elements, i.e. their elements all have the same C type (such as a 64-bit floating-point number). This is quite different from most Python container objects, which can contain heterogeneous collections. (You can, however, have an array of Python objects, as discussed later).
Any given array object has a rank, which is the number of "dimensions" or "axes" it has. For example, a point in 3D space [1, 2, 1] is an array of rank 1 - it has one dimension. That dimension has a length of 3.
is an array of rank 2 (it is 2-dimensional). The first dimension has a length of 2, the second dimension has a length of 3. Because the word "dimension" has many different meanings to different folks, in general the word "axis" will be used instead. Axes are numbered just like Python list indices: they start at 0, and can also be counted from the end, so that axis -1 is the last axis of an array, axis -2 is the penultimate axis, etc.
There are two important and potentially unintuitive behaviors of NumPy arrays which take some getting used to. The first is that by default, operations on arrays are performed element-wise. This means that when adding two arrays, the resulting array has as elements the pairwise sums of the two operand arrays. This is true for all operations, including multiplication. Thus, array multiplication using the * operator will default to element-wise multiplication, not matrix multiplication as used in linear algebra. Many people will want to use arrays as linear algebra-type matrices (including their rank-1 versions, vectors). For those users, the Matrix class provides a more intuitive interface. We defer discussion of the Matrix class until later.
The second behavior which will catch many users by surprise is that functions which return arrays which are simply different views at the same data will in fact share their data. This will be discussed at length when we have more concrete examples of what exactly this means.
Now that all of these definitions and warnings are laid out, let's see what we can do with these arrays.
There are many ways to create arrays. The most basic one is the use of the array() function:
The array(numbers, typecode=None, savespace=0) function takes three arguments - the first one is the values, which have to be in a Python sequence object (such as a list or a tuple). The optional second argument is the typecode of the elements. If it is omitted, as in the example above, Python tries to find the one type which can represent all the elements. The third is discussed in Saving space.
Since the elements we gave our example were two floats and one integer, it chose `float' as the type of the resulting array. If one specifies the typecode, one can specify unequivocally the type of the elements - this is especially useful when, for example, one wants to make sure that an array contains floats even though in some cases all of its elements are integers:
>>> a = array([x,y,z]) # integers are enough for 1, 2 and 3
>>> a = array([x,y,z], Float) # not the default type
Pop Quiz: What will be the type of an array defined as follows:
>>> mystery = array([1, 2.0, -3j])
Hint: -3j is an imaginary number.
A very common mistake is to call array with a set of numbers as arguments, as in array(1,2,3,4,5) . This doesn't produce the expected result as soon as at least two numbers are used, because the first argument to array() must be the entire data for the array -- thus, in most cases, a sequence of numbers. The correct way to write the preceding invocation is most likely array((1,2,3,4,5)) .
Possible values for the second argument to the array creator function (and indeed to any function which accepts a so-called typecode for arrays) are:
The meaning of these is as follows:
The last typecode deserves a little comment. Indeed, it seems to indicate that arrays can be filled with any Python objects. This appears to violate the notion that arrays are homogeneous. In fact, the typecode PyObject does allow heterogeneous arrays. However, if you plan to do numerical computation, you're much better off with a homogeneous array with a potentially "large" type than with a heterogeneous array. This is because a heterogeneous array stores references to objects, which incurs a memory cost, and because the speed of computation is much slower with arrays of PyObject 's than with uniform number arrays. Why does it exist, then?
A very useful feature of arrays is the ability to slice them, dice them, select and choose from them, etc. This feature is so nice that sometimes one wants to do the same operations with, e.g., arrays of class instances. In such cases, computation speed is not as important as convenience. Also, if the array is filled with objects which are instances of classes which define the appropriate methods, then NumPy will let you do math with those objects. For example, if one creates an object class which has an __add__ method, then arrays (created with the PyObject typecode) of instances of such a class can be added together.
The following example shows one way of creating multidimensional arrays:
>>> ma = array([[1,2,3],[4,5,6]])
The first argument to array() in the code above is a single list containing two lists, each containing three elements. If we wanted floats instead, we could specify, as discussed in the previous section, the optional typecode we wished:
>>> ma_floats = array([[1,2,3],[4,5,6]], Float)
This array allows us to introduce the notion of `shape'. The shape of an array is the set of numbers which define its dimensions. The shape of the array ma defined above is 2 by 3. More precisely, all arrays have a shape attribute which is a tuple of integers. So, in this case:
Using the earlier definitions, this is a shape of rank 2, where the first axis has length 2, and the seond axis has length 3. The rank of an array A is always equal to len(A.shape) .
Note that shape is an attribute of array objects. It is the first of several which we will see throughout this tutorial. If you're not used to object-oriented programming, you can think of attributes as "features" or "qualities" of individual arrays. The relation between an array and its shape is similar to the relation between a person and their hair color. In Python, it's called an object/attribute relation.
What if one wants to change the dimensions of an array? For now, let us consider changing the shape of an array without making it "grow." Say, for example, we want to make the 2x3 array defined above ( ma ) an array of rank 1:
>>> flattened_ma = reshape(ma, (6,))
One can change the shape of arrays to any shape as long as the product of all the lengths of all the axes is kept constant (in other words, as long as the number of elements in the array doesn't change):
>>> a = array([1,2,3,4,5,6,7,8])
>>> b = reshape(a, (2,4)) # 2*4 == 8
>>> c = reshape(b, (4,2)) # 4*2 == 8
Notice that we used a new function, reshape() . It, like array() , is a function defined in the Numeric module. It expects an array as its first argument, and a shape as its second argument. The shape has to be a sequence of integers (a list or a tuple). Keep in mind that a tuple with a single element needs a comma at the end; the right shape tuple for a rank-1 array with 5 elements is (5,) , not (5) .
One nice feature of shape tuples is that one entry in the shape tuple is allowed to be -1 . The -1 will be automatically replaced by whatever number is needed to build a shape which does not change the size of the array. Thus:
>>> a = reshape(array(range(25)), (5,-1))
The shape of an array is a modifiable attribute of the array. You can therefore change the shape of an array simply by assigning a new shape to it:
>>> a = array([1,2,3,4,5,6,7,8,9,10])
>>> a.shape = (10,1) # second axis has length 1
>>> a.shape = (5,-1) # note the -1 trick described above
As in the rest of Python, violating rules (such as the one about which shapes are allowed) results in exceptions:
ValueError: total size of new array must be unchanged
Sections denoted "For Advanced Users" will be used to indicate aspects of the functions which may not be needed for a first introduction at NumPy, but which should be mentioned for the sake of completeness.
The default printing routine provided by the Numeric module prints arrays as follows:
The remaining axes are printed top to bottom with increasing numbers of separators.
This explains why rank-1 arrays are printed from left to right, rank-2 arrays have the first dimension going down the screen and the second dimension going from left to right, etc.
A final possibility is the resize() function, which takes a "base" array as its first argument and the desired shape as the second argument. Unlike reshape() , the shape argument to resize() can correspond to a smaller or larger shape than the input array. Smaller shapes will result in arrays with the data at the "beginning" of the input array, and larger shapes result in arrays with data containing as many replications of the input array as are needed to fill the shape. For example, starting with a simple array
one can quickly build a large array with replicated data:
and if you imported the view function from the NumTut package, you can do:
>>> view(resize(base, (100,100)))
# grey grid of horizontal lines is shown
>>> view(resize(base, (101,101)))
# grey grid of alternating black and white pixels is shown
The array constructor takes a mandatory data argument, an optional typecode, and optional savespace argument, and an optional copy argument. If the data argument is a sequence, then array creates a new object of type multiarray, and fills the array with the elements of the data object. The shape of the array is determined by the size and nesting arrangement of the elements of data.
If data is not a sequence, then the array returned is an array of shape () (the empty tuple), of typecode 'O' , containing a single element, which is data .
Often, one needs to manipulate arrays filled with numbers which aren't available beforehand. The Numeric module provides a few functions which create arrays from scratch:
zeros() and ones() simply create arrays of a given shape filled with zeros and ones respectively:
Note that the first argument is a shape - it needs to be a list or a tuple of integers. Also note that the default type for the returned arrays is Int , which you can feel free to override using something like:
The arrayrange() function is similar to the range() function in Python, except that it returns an array as opposed to a list.
Combining the arrayrange() with the reshape() function, we can get:
>>> big = reshape(arrayrange(100),(10,10))
>>>
print big
[[ 0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]
[40 41 42 43 44 45 46 47 48 49]
[50 51 52 53 54 55 56 57 58 59]
[60 61 62 63 64 65 66 67 68 69]
[70 71 72 73 74 75 76 77 78 79]
[80 81 82 83 84 85 86 87 88 89]
[90 91 92 93 94 95 96 97 98 99]]
>>> view(reshape(arrayrange(10000),(100,100)))
# array of increasing lightness from top down (slowly) and from left to
arange() is a shorthand for arrayrange() .
One can set the start, stop and step arguments, which allows for more varied ranges:
>>> print arrayrange(10,-10,-2)
An important feature of arrayrange is that it can be used with non-integer starting points and strides:
>>> print arrayrange(0, 1, .2)
If you want to create an array with just one value, repeated over and over, you can use the * operator applied to lists
but that is relatively slow, since the duplication is done on Python lists. A quicker way would be to start with 0's and add 3:
The optional typecode argument can force the typecode of the resulting array, which is otherwise the "highest" of the starting and stopping arguments. The starting argument defaults to 0 if not specified. Note that if a typecode is specified which is "lower" than that which arrayrange would normally use, the array is the result of a precision-losing cast (a round-down, as that used in the astype method for arrays.)
Finally, one may want to create an array with contents which are the result of a function evaluation. This is done using the fromfunction() function, which takes two arguments, a shape and a callable object (usually a function). For example:
... return (x-5)**2+(y-5)**2 # distance from point (5,5) squared
>>> m = fromfunction(dist, (10,10))
[[50 41 34 29 26 25 26 29 34 41]
[41 32 25 20 17 16 17 20 25 32]
[34 25 18 13 10 9 10 13 18 25]
[34 25 18 13 10 9 10 13 18 25]
[41 32 25 20 17 16 17 20 25 32]]
>>> view(fromfunction(dist, (100,100))
# shows image which is dark in topleft corner, and lighter away from it.
>>> m = fromfunction(lambda i,j,k: 100*(i+1)+10*(j+1)+(k+1), (4,2,3))
By examining the above examples, one can see that fromfunction() creates an array of the shape specified by its second argument, and with the contents corresponding to the value of the function argument (the first argument) evaluated at the indices of the array. Thus the value of m[3,4] in the first example above is the value of dist when x=3 and y=4 . Similarly for the lambda function in the second example, but with a rank-3 array.
The implementation of fromfunction consists of:
def fromfunction(function, dimensions):
return apply(function, tuple(indices(dimensions)))
which means that the function function is called with arguments given by the sequence indices(dimensions). As described in the definition of indices, this consists of arrays of indices which will be of rank one less than that specified by dimensions. This means that the function argument must accept the same number of arguments as there are dimensions in dimensions, and that each argument will be an array of the same shape as that specified by dimensions. Furthermore, the array which is passed as the first argument corresponds to the indices of each element in the resulting array along the first axis, that which is passed as the second argument corresponds to the indices of each element in the resulting array along the second axis, etc. A consequence of this is that the function which is used with fromfunction will work as expected only if it performs a separable computation on its arguments, and expects its arguments to be indices along each axis. Thus, no logical operation on the arguments can be performed, or any non-shape preserving operation. Thus, the following will not work as expected:
>>> print fromfunction(buggy,(10,))
Here is how to do it properly. We add a print statement to the function for clarity:
>>> fromfunction(notbuggy,(10,))
array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
We leave it as an excercise for the reader to figure out why the "buggy" example gave the result 1.
We've mentioned the typecodes of arrays, and how to create arrays with the right typecode, but we haven't covered what happens when arrays with different typecodes interact.
The rules followed by NumPy when performing binary operations on arrays mirror those used by Python in general. Operations between numeric and non-numeric types are not allowed (e.g. an array of characters can't be added to an array of numbers), and operations between mixed number types (e.g. floats and integers, floats and omplex numbers, or in the case of NumPy, operations between any two arrays with different numeric typecodes) first perform a coercion of the 'smaller' numeric type to the type of the `larger' numeric type. Finally, when scalars and arrays are operated on together, the scalar is converted to a rank-0 array first. Thus, adding a "small" integer to a "large" floating point array is equivalent to first casting the integer "up" to the typecode of the array:
array([ 12. , 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7, 12.8, 12.9])
The automatic coercions are described in Figure 1. Avoiding upcasting is discussed in Saving space.
One more array constructor is the asarray() function. It is used if you want to have an array of a specific typecode and you don't know what typecode array you have (for example, in a generic function which can operate on all kinds of arrays, but needs them to be converted to complex arrays). If the array it gets as an argument is of the right typecode, it will get sent back unchanged. If the array is not of the right typecode, each element of the new array will be the result of the coercion to the new type of the old elements. asarray() will refuse to operate if there might be loss of information -- in other words, asarray() only casts 'up'.
asarray is also used when you have a function that operates on arrays, but you want to allow people to call it with an arbitrary python sequence object. This gives your function a behavior similar to that of most of the builtin functions that operate on arrays.
The typecodes identifiers ( Float0 , etc.) have as values single-character strings. The mapping between typecode and character strings is machine dependent. An example of the correspondences between typecode characters and the typecode identifiers for 32-bit architectures are shown in Table 3-X.
When dealing with very large arrays of floats and if precision is not important (or arrays of small integers), then it may be worthwhile to cast the arrays to "small" typecodes, such as Int8 , Int16 or Float32 . As the standard Python integers and floats correspond to the typecodes Int32 and Float64 , using them in apparently "innocent" ways will result in up-casting, which may null the benefit of the use of small typecode arrays. For example:
'f' # a.k.a. Float32 on a Pentium
>>> mylargearray = mylargearray + 1 # 1 is an Int64 on a Pentium
>>> mylargearray.typecode() # see Fig. 1 for explanation.
Note that the sizes returned by the itemsize() method are expressed in bytes.
Numeric arrays can be created using an optional, keyworded argument to the constructor, savespace. If savespace is set to 1, Numeric will attempt to avoid the silent upcasting behavior. The status of an array can be queried with the spacesaver() method. If x.spacesaver() is true, x has its space-saving flag set. The flag can be set with the savespace method: x.savespace(1) to set it, x.savespace(0) to clear it.
You may also force NumPy to cast any number array to another number array. For example, to take an array of any numeric type (IntX or FloatX or ComplexX or UnsignedInt8) and convert it to a 64-bit float, one can do:
>>> floatarray = otherarray.astype(Float64)
The typecode can be any of the number typecodes, "larger" or "smaller". If it is larger, this is a cast-up, as if asarray() had been used. If it is smaller, the standard casting rules of the underlying language (C) are used, which means that truncation or loss of precision can occur:
If the typecode used with astype() is the original array's typecode, then a copy of the original array is returned.
If you have a keen eye, you have noticed that some of the previous examples did something new: they added a number to an array. Indeed, most Python operations applicable to numbers are directly applicable to arrays:
Note that the mathematical operators behave differently depending on the types of their operands. When one of the operands is an array and the other is a number, the number is added to all the elements of the array and the resulting array is returned. This is called broadcasting . This also occurs for unary mathematical operations such as sin and the negative sign
[ 0.84147098 0.90929743 0.14112001]
When both elements are arrays with the same shape, then a new array is created, where each element is the sum of the corresponding elements in the original arrays:
If the operands of operations such as addition are arrays which have the same rank but different non-integer dimensions, then an exception is generated:
>>> b = array([4,5,6,7]) # note this has four elements
File ``<stdin>``, line 1, in ?
ArrayError: frames are not aligned
This is because there is no reasonable way for NumPy to interpret addition of a (3,) shaped array and a (4,) shaped array.
Note what happens when adding arrays with different rank
This is another form of broadcasting. To understand this, one needs to look carefully at the shapes of a and b :
Because array a 's last dimension had length 3 and array b 's last dimension also had length 3, those two dimensions were "matched" and a new dimension was created and automatically "assumed" for array a. The data already in a was "replicated" as many times as needed (4, in this case) to make the two shapes of the operand arrays conform. This replication (broadcasting) occurs when arrays are operands to binary operations and their shapes differ and when the following conditions are true:
This algorithm is complex, but intuitive in practice. For more details, consult the Numeric Reference.
Beginning with Python 2.0, Python supports the in-place operators +=, -=, *=, and /=. Numeric supports these operations but you need to be careful. The right-hand side should be of the same typecode. Some violation of this is possible as the following example shows, but in general contortions may be necessary for using the smaller "kinds" of typecodes.
Traceback (most recent call last):
TypeError: return array has incorrect type
Just like other Python sequences, array contents are manipulated with the [] notation. For rank-1 arrays, there are no differences between list and array notations:
>>> print a[0] # get first element
>>> print a[1:5] # get second through fifth element
>>> print a[-1] # get last element
>>> print a[:-1] # get all but last element
The first difference with lists comes with multidimensional indexing. If an array is multidimensional (of rank > 1), then specifying a single integer index will return an array of dimension one less than the original array.
>>> print a[0] # get first row, not first element!
>>> print a[1] # get second row
To get to individual elements in a rank-2 array, one specifies both indices separated by commas:
>>> print a[0,0] # get elt at first row, first column
>>> print a[0,1] # get elt at first row, second column
>>> print a[1,0] # get elt at second row, first column
>>> print a[2,-1] # get elt at third row, last column
Of course, the [] notation can be used to set values as well:
Note that when referring to rows, the right hand side of the equal sign needs to be a sequence which "fits" in the referred array subset (in the code sample below, a 3-element row):
The standard rules of Python slicing apply to arrays, on a per-dimension basis. Assuming a 3x3 array:
>>> a = reshape(arrayrange(9),(3,3))
The plain [:] operator slices from beginning to end:
In other words, [:] with no arguments is the same as [:] for lists - it can be read "all indices along this axis". So, to get the second row along the second dimension:
Note that what was a "column" vector is now a "row" vector -- any "integer slice" (as in the 1 in the example above) results in a returned array with rank one less than the input array.
If one does not specify as many slices as there are dimensions in an array, then the remaining slices are assumed to be "all". If A is a rank-3 array, then
There is one addition to the slice notation for arrays which does not exist for lists, and that is the optional third argument, meaning the ``step size'' also called stride or increment. Its default value is 1, meaning return every element in the specified range. Alternate values allow one to skip some of the elements in the slice:
>>> print a[::2] # return every *other* element
Negative strides are allowed as long as the starting index is greater than the stopping index:
>>> a = reshape(arrayrange(9),(3,3))
If a negative stride is specified and the starting or stopping indices are omitted, they default to "end of axis" and "beginning of axis" respectively. Thus, the following two statements are equivalent for the array given:
>>> print a[::-1] # this reverses only the first axis
>>> print a[::-1,::-1] # this reverses both axes
One final way of slicing arrays is with the keyword ... This keyword is somewhat complicated. It stands for ``however many `:' I need depending on the rank of the object I'm indexing, so that the indices I *do* specify are at the end of the index list as opposed to the usual beginning.``
So, if one has a rank-3 array A , then A[...,0] is the same thing as A[:,:,0] but if B is rank-4, then B[...,0] is the same thing as: B[:,:,:,0] . Only one ... is expanded in an index expression, so if one has a rank-5 array C , then: C[...,0,...] is the same thing as C[:,:,:,0,:] .