note.nkmk.me

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.

This article describes the following contents.

  • Broadcasting rules in NumPy
  • Broadcasting examples in NumPy
    • Examples of 2D array
    • Examples of 3D array
  • Cases that cannot broadcast
  • 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.

Sponsored Link

Broadcasting rules in NumPy

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.

Broadcasting examples in NumPy

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)
# [[0]
#  [1]
#  [2]]

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)
# [[0]
#  [1]
#  [2]]

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)
# [[0]
#  [1]
#  [2]]

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]]]
Sponsored Link

Cases that cannot broadcast

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)
# [[0]
#  [1]
#  [2]]

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,)

print(np.broadcast_to(a, (3, 3)))
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]

print(type(np.broadcast_to(a, (3, 3))))
# <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)
# [[0]
#  [1]
#  [2]]

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

arrays = np.broadcast_arrays(a, b)

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

print(len(arrays))
# 2

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

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

print(type(arrays[0]))
# <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)

# arrays = np.broadcast_arrays(a, c)
# ValueError: shape mismatch: objects cannot be broadcast to a single shape
Sponsored Link
Share

Related Categories

Related Articles