# NumPy: Broadcasting rules and examples

Posted: 2021-10-13 / Tags: Python, NumPy

In the operation between NumPy arrays (`ndarray`), each `shape` is automatically converted to be the same by broadcasting.

• Examples of 2D array
• Examples of 3D array
• Functions to get the broadcasted array
• Broadcast an array to a specified shape.: `np.broadcast_to()`
• Broadcast multiple arrays: `np.broadcast_arrays()`

The official documentation explaining broadcast is below.

Use `reshape()` or `np.newaxis` if you want to reshape `ndarray` to any shape you want.

There are the following two rules for broadcasting in NumPy.

1. Make the two arrays have the same number of dimensions.
• If the number of dimensions of the two arrays are different, add new dimensions with size `1` to the head of the array with the smaller dimension.
2. Make each dimension of the two arrays the same size.
• If the sizes of each dimension of the two arrays do not match, dimensions with size `1` are stretched to the size of the other array.
• If there is a dimension whose size is not `1` in either of the two arrays, it cannot be broadcasted and an error occurs.

Note that the number of dimensions of `ndarray` can be obtained with the `ndim` attribute, and the shape with the `shape` attribute.

### Examples of 2D array

#### 2D array and 1D array

The following 2D and 1D arrays are used as examples. To make it easier to understand the result of the broadcast, one of them uses `zeros()` to set all the elements to `0`.

``````import numpy as np

a = np.zeros((3, 3), np.int)
print(a)
# [[0 0 0]
#  [0 0 0]
#  [0 0 0]]

print(a.shape)
# (3, 3)

b = np.arange(3)
print(b)
# [0 1 2]

print(b.shape)
# (3,)
``````

The reason `shape` of 1D array is `(3,)` instead of `(3)` is because tuples with 1 element have a comma at the end.

The result of the addition of these two `ndarray` is as follows.

``````print(a + b)
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]
``````

Let's transform the array with the smaller number of dimensions (1D array `b`) according to the rules described above.

First, according to rule 1, the array is transformed from shape `(3,)` to `(1, 3)` by adding a new dimension of size `1` at the head. The `reshape()` method is used.

``````b_1_3 = b.reshape(1, 3)
print(b_1_3)
# [[0 1 2]]

print(b_1_3.shape)
# (1, 3)
``````

Next, the size of each dimension is stretched according to rule 2. The array is stretched from `(1, 3)` to `(3, 3)`. The stretched part is a copy of the original part. `np.tile()` is used.

``````print(np.tile(b_1_3, (3, 1)))
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]
``````

Note that `reshape()` and `np.tile()` are used here for the sake of explanation, but if you want to get the broadcasted array, there are functions `np.broadcast_to()` and `np.broadcast_arrays()` for that purpose. See below.

#### 2D array and 2D array

The result of addition with the 2D array of `(3, 1)` is as follows.

``````b_3_1 = b.reshape(3, 1)
print(b_3_1)
# [
#  
#  ]

print(b_3_1.shape)
# (3, 1)

print(a + b_3_1)
# [[0 0 0]
#  [1 1 1]
#  [2 2 2]]
``````

In this case, since the number of dimensions is already the same, the array is stretched from `(3, 1)` to `(3, 3)` according to rule 2.

``````print(np.tile(b_3_1, (1, 3)))
# [[0 0 0]
#  [1 1 1]
#  [2 2 2]]
``````

In the previous examples, only one of the arrays is converted, but there are cases where both of the two arrays are converted by broadcasting.

The following is the result of adding arrays whose shapes are `(1, 3)` and `(3, 1)`.

``````print(b_1_3)
# [[0 1 2]]

print(b_1_3.shape)
# (1, 3)

print(b_3_1)
# [
#  
#  ]

print(b_3_1.shape)
# (3, 1)

print(b_1_3 + b_3_1)
# [[0 1 2]
#  [1 2 3]
#  [2 3 4]]
``````

Both `(1, 3)` and `(3, 1)` are stretched to `(3, 3)`.

``````print(np.tile(b_1_3, (3, 1)))
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]

print(np.tile(b_3_1, (1, 3)))
# [[0 0 0]
#  [1 1 1]
#  [2 2 2]]

print(np.tile(b_1_3, (3, 1)) + np.tile(b_3_1, (1, 3)))
# [[0 1 2]
#  [1 2 3]
#  [2 3 4]]
``````

The same applies if one of them is 1D array.

``````c = np.arange(4)
print(c)
# [0 1 2 3]

print(c.shape)
# (4,)

print(b_3_1)
# [
#  
#  ]

print(b_3_1.shape)
# (3, 1)

print(c + b_3_1)
# [[0 1 2 3]
#  [1 2 3 4]
#  [2 3 4 5]]
``````

1D array is converted like `(4,)` -> `(1, 4)` -> `(3, 4)`, and 2D array like `(3, 1)` -> `(3, 4)`.

``````print(np.tile(c.reshape(1, 4), (3, 1)))
# [[0 1 2 3]
#  [0 1 2 3]
#  [0 1 2 3]]

print(np.tile(b_3_1, (1, 4)))
# [[0 0 0 0]
#  [1 1 1 1]
#  [2 2 2 2]]

print(np.tile(c.reshape(1, 4), (3, 1)) + np.tile(b_3_1, (1, 4)))
# [[0 1 2 3]
#  [1 2 3 4]
#  [2 3 4 5]]
``````

Note that the dimension is stretched only when the original size is `1`. Otherwise, it cannot be broadcasted and an error occurs, as described below.

### Examples of 3D array

Rule 1 applies even if the difference in the number of dimensions is two or more.

Using 3D and 1D array as examples, the addition results are as follows.

``````a = np.zeros((2, 3, 4), dtype=np.int)
print(a)
# [[[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]
#
#  [[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]]

print(a.shape)
# (2, 3, 4)

b = np.arange(4)
print(b)
# [0 1 2 3]

print(b.shape)
# (4,)

print(a + b)
# [[[0 1 2 3]
#   [0 1 2 3]
#   [0 1 2 3]]
#
#  [[0 1 2 3]
#   [0 1 2 3]
#   [0 1 2 3]]]
``````

The shape is changed as `(4, )` -> `(1, 1, 4)` -> `(2, 3, 4)`.

``````b_1_1_4 = b.reshape(1, 1, 4)
print(b_1_1_4)
# [[[0 1 2 3]]]

print(np.tile(b_1_1_4, (2, 3, 1)))
# [[[0 1 2 3]
#   [0 1 2 3]
#   [0 1 2 3]]
#
#  [[0 1 2 3]
#   [0 1 2 3]
#   [0 1 2 3]]]
``````

As mentioned above, the dimension is stretched only if the original size is `1`. If the sizes of the dimensions are different and the sizes of both arrays are not `1`, it cannot be broadcasted and an error occurs.

``````a = np.zeros((4, 3), dtype=np.int)
print(a)
# [[0 0 0]
#  [0 0 0]
#  [0 0 0]
#  [0 0 0]]

print(a.shape)
# (4, 3)

b = np.arange(6).reshape(2, 3)
print(b)
# [[0 1 2]
#  [3 4 5]]

print(b.shape)
# (2, 3)

# print(a + b)
# ValueError: operands could not be broadcast together with shapes (4,3) (2,3)
``````

The same applies to the following case.

``````a = np.zeros((2, 3, 4), dtype=np.int)
print(a)
# [[[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]
#
#  [[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]]

print(a.shape)
# (2, 3, 4)

b = np.arange(3)
print(b)
# [0 1 2]

print(b.shape)
# (3,)

# print(a + b)
# ValueError: operands could not be broadcast together with shapes (2,3,4) (3,)
``````

In this example, if a new dimension is added at the end, the array is able to be broadcasted.

``````b_3_1 = b.reshape(3, 1)
print(b_3_1)
# [
#  
#  ]

print(b_3_1.shape)
# (3, 1)

print(a + b_3_1)
# [[[0 0 0 0]
#   [1 1 1 1]
#   [2 2 2 2]]
#
#  [[0 0 0 0]
#   [1 1 1 1]
#   [2 2 2 2]]]
``````

It is easy to understand whether it can be broadcasted or not by right-aligned `shape`.

``````NG
(2, 3, 4)
(      3)

OK
(2, 3, 4)
(   3, 1) -> (1, 3, 1) -> (2, 3, 4)
``````

If the sizes are different when right-aligned and compared vertically, one of them must be `1` to be broadcasted.

For example, in the case of images, a color image is a 3D array whose shape is `(height, width, 3)` (`3` means red, green, and blue), while a grayscale image is a 2D array whose shape is `(height, width)`.

In the case of computing the value of each color in a color image and the value of a grayscale image, it is not possible to broadcast even if the `height` and `width` are the same.

You need to add a dimension to the end of the grayscale image with `np.newaxis`, `np.expand_dims()` and so on.

``````NG
(h, w, 3)
(   h, w)

OK
(h, w, 3)
(h, w, 1) -> (h, w, 3)
``````

## Functions to get the broadcasted array

### Broadcast an array to a specified shape.: `np.broadcast_to()`

Use `np.broadcast_to()` to broadcast `ndarray` with the specified `shape`.

The first argument is the original `ndarray`, and the second argument is a tuple or list indicating `shape`. The broadcasted `ndarray` is returned.

``````a = np.arange(3)
print(a)
# [0 1 2]

print(a.shape)
# (3,)

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

# <class 'numpy.ndarray'>
``````

An error occurs when specifying a shape that cannot be broadcasted.

``````# print(np.broadcast_to(a, (2, 2)))
# ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (3,) and requested shape (2,2)
``````

### Broadcast multiple arrays: `np.broadcast_arrays()`

Use `np.broadcast_arrays()` to broadcast multiple `ndarray`.

Specify multiple arrays separated by commas.A list of `ndarray` is returned.

``````a = np.arange(3)
print(a)
# [0 1 2]

print(a.shape)
# (3,)

b = np.arange(3).reshape(3, 1)
print(b)
# [
#  
#  ]

print(b.shape)
# (3, 1)

print(type(arrays))
# <class 'list'>

print(len(arrays))
# 2

print(arrays)
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]

print(arrays)
# [[0 0 0]
#  [1 1 1]
#  [2 2 2]]

print(type(arrays))
# <class 'numpy.ndarray'>
``````

An error occurs when specifying a combination of arrays that cannot be broadcasted.

``````c = np.zeros((2, 2))
print(c)
# [[0. 0.]
#  [0. 0.]]

print(c.shape)
# (2, 2)