Section 3: The numpy Library (part 2)

 Table of Contents > Chapter 7 > Section 3 

In this section we consider multi-dimensional numpy arrays.  While arrays with dimension greater than 3 are possible, we will restrict our attention to arrays of no more than 3 dimensions.  That said, the concepts that we will learn extend naturally to higher dimensional arrays.

Two-dimensional Arrays
A 2-dimensional array is an array of arrays.  It can be used to represent information that is organized into a table of rows and columns:
2d array
We can use a Python list-of-lists to initialize a numpy 2D array.  We can then provide a row index to access the array that represents a particular row of information or we can provide a row and column index to access the element in a particular row and column:

>>> import numpy as np
>>> z = np.array([[1, 2, 3], [4, 5, 6]])
>>> z[0]     # the first element of z is a 1D array
array([1, 2, 3])

>>> z[1]     # the second element of z is also a 1D array
array([4, 5, 6])

>>> z[0, 0]  # the element at row 0, column 0
1

>>> z[1, 2]  # the element at row 1, column 2
6


To retrieve a particular column or some other sub-array, we can use slicing operations.  Recall that such operations provide a view onto the array:

>>> import numpy as np
>>> z = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

>>> z[:,0]         # slice of elements in all rows of column 0
array([1, 4, 7])

>>> z[0:2,1:] = 0  # slice of elements lying in rows 0 up to
                   # but not including 2 and from column 1 to
                   # the last column
array([[1, 0, 0],
       [4, 0, 0],
       [7, 8, 9]])


Fancy indexing operations can also be applied to 2D arrays.  To specify the row and column index of entries in the view, we create a tuple with the row indexes as the first component and corresponding column indexes as the second:

>>> import numpy as np
>>> z = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> zi = (np.array([0, 1, 2]), np.array([2, 1, 0]))
>>> z[zi]
array([3, 5, 7])

>>> z[zi] = 0
>>> z
array([[1, 2, 0],
       [4, 0, 6],
       [0, 8, 9]])

In the example above, zi is a tuple that represents the following indexes into the array: [0, 2], [1, 1] and [2, 0] - these are the entries lying on the diagonal from top-right to bottom-left.  Note that the statement z[zi] = 0 assigns the value 0 to each of these entries.

You can also use a 2D array of Booleans as a fancy index into a 2D array.  As for a 1D array, we obtain a view onto an array that consists of only those values that correspond to True in the Boolean array:

>>> import numpy as np
>>> z = np.array([[1, 2, 3], [4, 5, 6], [7,8,9]])
>>> zb = np.array([[False, False, True],
...                [False, True, False],
...                [True, False, False]])
>>> z[zb] = 0
>>> z
array([[1, 2, 0],
       [4, 0, 6],
       [0, 8, 9]])


The ndarray class
The ndarray class has the following attributes that allow us to determine the number of dimensions, size and shape of an array

ndim - the number of dimensions
shape - the shape of the array (the size of each dimension) as a tuple
size - the total number of elements in the array

>>> import numpy as np
>>> z = np.array([[1, 2, 3], [4, 5, 6]])
>>> z.ndim
2              # z is a 2D array
>>> z.shape
(2, 3)         # z has 2 rows and 3 columns
>>> z.size
6              # z has a total of 6 elements


The class also has a large number of methods for performing operations on an ndarray.  We mention just a few of them here:

reshape - produces a view whose shape is as specified by the parameters (reshaped array must have same size as original)
ravel - produces a view as a 1D array

>>> import numpy as np
>>> z = np.array([[1, 2, 3], [4, 5, 6]])
>>> z.reshape(3, 2)        # a reshaped view onto z
array([[1, 2],
       [3, 4],
       [5, 6]])

>>> z.ravel()              # a 1D view onto z
array([1, 2, 3, 4, 5, 6])

>>> z                      # the shape of z is still (2, 3)
array([[1, 2, 3],
       [4, 5, 6]])


Operations along axes
An n-dimensional array has n-axes labelled 0 through n-1 – one for each of the dimensions of the array. 

Many of the ndarray methods and numpy functions that operate on arrays consume an optional axis argument.   If this argument is not provided, the operation is performed across all elements of the array, regardless of the shape of the array. If the axis argument is provided, the calculation is performed over each 1-dimensional sub-array that lies parallel to the given axis. 

Axis 0 is the axis corresponding to the first index into the array.  As we increase the first index, leaving the other indexes fixed, we move through the array in the direction of axis 0.  Similarly, as we increase the second index, leaving the other indexes fixed, we move through the array in the direction of axis 1, etc. 

The following figure depicts the alignment of axes for the array:

np.array([[1, 2, 3], [4, 5, 6]]):
 2d array, axis alignment 

We can now perform computations along 1D arrays that lie parallel to a specified axis (reference the diagram above to verify the values produced by each of the following function calls):

>>> import numpy as np
>>> z = np.array([[1, 2, 3], [4, 5, 6]])  # array pictured above
>>> np.max(z, axis=0)
array([4, 5, 6])        # maximum along each 1D array
                        # parallel to axis 0

>>> np.max(z, axis=1)
array([3, 6])           # maximum along each 1D array
                        # parallel to axis 1



Similarly, the following figure depicts the alignment of axes for the array:

np.array([[[ 0,  1,  2,  3], [ 4,  5,  6,  7], [ 8,  9, 10, 11]],
          [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]])


3d array axis alignment
  
Just as for 2D arrays, we can perform computations along 1D arrays aligned with a specified axis (again, reference the diagram above to verify the value produced by each of the following function calls):

>>> z = np.arange(24).reshape(2,3,4)   # array pictured above
>>> np.max(z, axis=0)
array([[12, 13, 14, 15],               # maximum along each
       [16, 17, 18, 19],               # 1D array parallel
       [20, 21, 22, 23]])              # to axis 0

>>> np.max(z, axis=1)                  # maximum along each
array([[ 8,  9, 10, 11],               # 1D array parallel
       [20, 21, 22, 23]])              # to axis 1

>>> np.max(z, axis=2)                  # maximum along each
array([[ 3,  7, 11],                   # 1D array parallel
       [15, 19, 23]])                  # to axis 2