Broadcasting and shape manipulation

NumPy operations are mostly done element-wise, which requires two arrays in an operation to have the same shape; however, this doesn't mean that NumPy operations can't take two differently shaped arrays (refer to the first example we looked at with scalars). NumPy provides the flexibility to broadcast a smaller-sized array across a larger one. But we can't broadcast the array to just about any shape. It needs to follow certain constrains; we will be covering them in this section. One key idea to keep in mind is that broadcasting involves performing meaningful operations over two differently shaped arrays. However, inappropriate broadcasting might lead to an inefficient use of memory that slows down computation.

Broadcasting rules

The general rule for broadcasting is to determine whether two arrays are compatible with dimensioning. There are two conditions that need to be met:

  • Two arrays should be of equal dimensions
  • One of them is 1

If the preceding conditions are not met, a ValueError exception will be thrown to indicate that the arrays have incompatible shapes. Now, we are going through three examples to take a look at how broadcasting rules work:

In [35]: x = np.array([[ 0, 0, 0], 
   ....:               [10,10,10], 
   ....:               [20,20,20]]) 
In [36]: y = np.array([1, 2, 3]) 
In [37]: x + y 
Out[37]: 
array([[ 1,  2,  3], 
       [11, 12, 13], 
       [21, 22, 23]]) 

Let's make the preceding code into a graph to help us understand broadcasting. The x variable has a shape of (3, 3), while y only has a shape of 3. But in NumPy broadcasting, the shape of y is translated to 3 by 1; therefore, the second condition of the rule has been met. y has been broadcast to the same shape of x by repeating it. The+ operation can apply element-wise.

Broadcasting rules
Numpy broadcasting on different shapes of arrays, where x(3,3) + y(3)

Next, we are going to show you the result of broadcasting both arrays:

In [38]: x = np.array([[0], [10], [20]]) 
In [39]: x 
Out[39]: 
array([[ 0], 
       [10], 
       [20]]) 
In [40]: x + y 
Out[40]: 
array([[ 1,  2,  3], 
       [11, 12, 13], 
       [21, 22, 23]]) 

The preceding example shows you how both x and y are broadcast. x is broadcast by the column, while y is broadcast by the row since both of them have dimension that are equal to 1 in terms of their shape. The second broadcasting condition has been met, and the new result array is a 3 by 3 array.

Broadcasting rules

Let's take a look of our last example, which two arrays can't meet the requirement of broadcasting rules:

In [41]: x = np.array([[ 0, 0, 0], 
   ....:               [10,10,10], 
   ....:               [20,20,20]]) 
In [42]: y = np.arange(1,5) 
In [43]: x + y 
ValueError: operands could not be broadcast together with shapes (3,3) (4) 

In the third example, broadcasting can't be performed due to x and y as they have different shapes in the row dimension and none of them are equal to 1. Thus, none of the broadcasting conditions can be met. NumPy throws ValueError, telling you that the shape is incompatible.

Broadcasting rules

Reshaping NumPy Arrays

After understanding the broadcasting rules, another important concept here is to reshape your NumPy Arrays, especially when you are dealing with multidimensional arrays. It's common for you to create a NumPy Array in just one dimension, reshaping it to a multidimension later, or vice versa. A key idea here is that you can change the shape of your arrays, but the number of elements should not be changed; for example, you can't reshape a 3 by 3 array to a 10 by 1 array. The total number of elements (or a so-called data buffer in the ndarray internal organization) should be consistent before and after reshaping. Or ,you might need to resize, but that's another story. Now, let's look at some shape manipulations:

In [44]: x = np.arange(24) 
In [45]: x.shape = 2, 3, -1 
In [46]: x 
Out[46]: 
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]]]) 

The basic reshaping technique changes the numpy.shape attribute. In the preceding example, we have an array whose shape is (24,1), and after altering the shape attribute, we obtain an array of the same size but the shape has been changed to 2 by 3 by 4. Note that -1 in a shape means the remaining shape size of the transferred array.

In [47]: x = np.arange(1000000) 
In [48]: x.shape = 100, 100, 100 
In [49]: %timeit x.flatten() 
1000 loops, best of 3: 1.14 ms per loop 
In [50]: %timeit x.ravel() 
1000000 loops, best of 3: 330 ns per loop 

The preceding example is to reshape a 100 by 100 by 100 array back to just one dimension; here, we apply two functions, numpy.flatten() and numpy.ravel(), to collapse the array, and at the same time, we also compare the execution time. We notice that the speed difference between numpy.flatten() and numpy.ravel() is huge, but both of them are much faster than three layers of Python looping. The difference in performance between the two functions is that np.flatten() creates a copy from the original array, while np.ravel() just changes the view (if you don't remember the difference between copies and views, go back a bit to Chapter 2The NumPy ndarray Object).

This example simply shows you that NumPy offers many functions and some of them can produce same results; pick up the function that satisfies your purpose and, at the same time, provides you with optimized performance.

Vector stacking

Reshaping changes the shape of one array, but how do we construct a two or multidimensional array by equally-sized row vectors? NumPy provides a solution for this called vector stacking; here, we are going to go through three examples using three different stacking functions to achieve the combination of two arrays based on different dimensions:

In [51]: x = np.arange (0, 10, 2) 
In [52]: y = np.arange (0, -5, -1) 
In [53]: np.vstack([x, y]) 
Out[53]: 
array([[ 0,  2,  4,  6,  8], 
          [ 0, -1, -2, -3, -4]]) 

Numpy.vstack() constructs the new array by vertically stacking two input arrays. The new array is two-dimensional:

In [54]: np.hstack([x, y]) 
Out[54]: array([ 0,  2,  4,  6,  8,  0, -1, -2, -3, -4]) 

While numpy.hstack() combines the two arrays horizontally, the new array is still one-dimensional:

In [55]: np.dstack([x, y]) 
Out[55]: 
array([[[ 0,  0], 
        [ 2, -1], 
        [ 4, -2], 
        [ 6, -3], 
        [ 8, -4]]]) 

numpy.dstack() is a bit different: it stacks the arrays in sequence depth-wise along the third dimension so that the new array is three-dimensional.

In the following code, if you change the array size using numpy.resize(), you are enlarging the array, and it will repeat itself until it reaches the new size; otherwise, it will truncate the array to the new size. A point to note here is that ndarray also has the resize() operation, so you can also use it to change the size of your array by typing x.resize(8) in this example; however, you will notice that the enlarging part is filled with zero, not repeating the array itself. Also, you can't use ndarray.resize() if you have assigned the array to another variable. Numpy.resize() creates a new array with specified shapes, which have fewer limitations than ndarray.resize(), and is a more preferable operation to use to change the size of your NumPy Array if necessary:

In [56]: x = np.arange(3) 
In [57]: np.resize(x, (8,)) 
Out[57]: array([0, 1, 2, 0, 1, 2, 0, 1]) 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.3.175