・11 min read

We saw in the previous section how NumPy's universal functions can be used to *vectorize* operations and thereby remove slow Python loops.
Another means of vectorizing operations is to use NumPy's *broadcasting* functionality.
Broadcasting is simply a set of rules for applying binary ufuncs (e.g., addition, subtraction, multiplication, etc.) on arrays of different sizes.

Recall that for arrays of the same size, binary operations are performed on an element-by-element basis:

`import numpy as np`

```
a = np.array([0, 1, 2])
b = np.array([5, 5, 5])
a + b
```

array([5, 6, 7])

Broadcasting allows these types of binary operations to be performed on arrays of different sizes–for example, we can just as easily add a scalar (think of it as a zero-dimensional array) to an array:

`a + 5`

array([5, 6, 7])

We can think of this as an operation that stretches or duplicates the value

`5`

into the array `[5, 5, 5]`

, and adds the results.
The advantage of NumPy's broadcasting is that this duplication of values does not actually take place, but it is a useful mental model as we think about broadcasting.We can similarly extend this to arrays of higher dimension. Observe the result when we add a one-dimensional array to a two-dimensional array:

```
M = np.ones((3, 3))
M
```

array([[ 1., 1., 1.],
[ 1., 1., 1.],
[ 1., 1., 1.]])

`M + a`

array([[ 1., 2., 3.],
[ 1., 2., 3.],
[ 1., 2., 3.]])

Here the one-dimensional array

`a`

is stretched, or broadcast across the second dimension in order to match the shape of `M`

.While these examples are relatively easy to understand, more complicated cases can involve broadcasting of both arrays. Consider the following example:

```
a = np.arange(3)
b = np.arange(3)[:, np.newaxis]
print(a)
print(b)
```

[0 1 2]
[[0]
[1]
[2]]

`a + b`

array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])

Just as before we stretched or broadcasted one value to match the shape of the other, here we've stretched *both*

`a`

and `b`

to match a common shape, and the result is a two-dimensional array!
The geometry of these examples is visualized in the following figure (Code to produce this plot can be found in the appendix, and is adapted from source published in the astroML documentation. Used by permission).The light boxes represent the broadcasted values: again, this extra memory is not actually allocated in the course of the operation, but it can be useful conceptually to imagine that it is.

Broadcasting in NumPy follows a strict set of rules to determine the interaction between the two arrays:

- Rule 1: If the two arrays differ in their number of dimensions, the shape of the one with fewer dimensions is
*padded*with ones on its leading (left) side. - Rule 2: If the shape of the two arrays does not match in any dimension, the array with shape equal to 1 in that dimension is stretched to match the other shape.
- Rule 3: If in any dimension the sizes disagree and neither is equal to 1, an error is raised.

To make these rules clear, let's consider a few examples in detail.

Let's look at adding a two-dimensional array to a one-dimensional array:

```
M = np.ones((2, 3))
a = np.arange(3)
```

Let's consider an operation on these two arrays. The shape of the arrays are

`M.shape = (2, 3)`

`a.shape = (3,)`

We see by rule 1 that the array

`a`

has fewer dimensions, so we pad it on the left with ones:`M.shape -> (2, 3)`

`a.shape -> (1, 3)`

By rule 2, we now see that the first dimension disagrees, so we stretch this dimension to match:

`M.shape -> (2, 3)`

`a.shape -> (2, 3)`

The shapes match, and we see that the final shape will be

`(2, 3)`

:`M + a`