Broadcasting Practice

Computation on Arrays: Broadcasting)


2019-03-20 19:44:47


In [1]:
import numpy as np

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


Out[2]:
array([5, 6, 7])

In [4]:
a, b


Out[4]:
(array([0, 1, 2]), array([5, 5, 5]))

In [5]:
a + 5


Out[5]:
array([5, 6, 7])

In [6]:
M = np.ones((3,3)); M


Out[6]:
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])

In [7]:
M + a


Out[7]:
array([[1., 2., 3.],
       [1., 2., 3.],
       [1., 2., 3.]])

In [8]:
a = np.arange(3)
b = np.arange(3)[:, np.newaxis]

print(a); print(b)


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

In [9]:
a + b


Out[9]:
array([[0, 1, 2],
       [1, 2, 3],
       [2, 3, 4]])

Rules of Broadcasting

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 doesn't 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.

Example 1


In [10]:
M = np.ones((2,3))
a = np.arange(3)
  • M.shape = (2, 3)
  • `a.shape = (3,)

By Rule 1, a is padded on the left w/ ones:

  • M.shape → (2, 3)
  • a.shape → (1, 3)

By Rule 2, the first dimension now disagrees, so its stretched to match:

  • M.shape → (2, 3)
  • a.shape → (2, 3)

The shapes now match, and the final shape will be (2, 3).


In [12]:
M + a


Out[12]:
array([[1., 2., 3.],
       [1., 2., 3.]])

Example 2


In [13]:
a = np.arange(3).reshape((3, 1))
b = np.arange(3)

In [20]:
print('a.shape',a.shape); print('b.shape',b.shape)


a.shape (3, 1)
b.shape (3,)

Rule 1: pad b with ones:

  • a.shape -> (3,1)
  • b.shape -> (1,3)

Rule 2: upgrade each of these ones to match the corresp. size of the other array:

  • a.shape -> (3,3)
  • b.shape -> (3,3)

The result matches so the shapes are compatible.


In [21]:
a + b


Out[21]:
array([[0, 1, 2],
       [1, 2, 3],
       [2, 3, 4]])

Example 3


In [22]:
M = np.ones((3,2))
a = np.arange(3)

Matrix M is transposed from above.

  • M.shape = (3,2)
  • a.shape = (3,)

Rule 1: pad the shape of a w/ ones:

  • M.shape = (3,2)
  • a.shape = (1,3)

Rule 2: first dim of a stretched to match that of M:

  • M.shape = (3,2)
  • a.shape = (3,3)

Rule 3 is triggered: the final shapes don't match so these arrays are incompatible:


In [23]:
M + a


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-23-8cac1d547906> in <module>
----> 1 M + a

ValueError: operands could not be broadcast together with shapes (3,2) (3,) 

You could manually 1-pad a from the right:


In [24]:
a[:, np.newaxis].shape


Out[24]:
(3, 1)

In [25]:
M + a[:, np.newaxis]


Out[25]:
array([[1., 1.],
       [2., 2.],
       [3., 3.]])

These rules apply to any binary ufunc (universal function):


In [26]:
np.logaddexp(M, a[:, np.newaxis])


Out[26]:
array([[1.31326169, 1.31326169],
       [1.69314718, 1.69314718],
       [2.31326169, 2.31326169]])

logaddexp(a,b) computes log(exp(a) + exp(b)) w/ more precision than the naïve approach.

See: Computation on NumPy Arrays: Universal Functions

More practice

Centering an array


In [32]:
# 10 observations, ea. w/ 3 vals
X = np.random.random((10, 3))

Calculate the mean of ea. feature using the mean aggregate across the first dimension:


In [33]:
Xmean = X.mean(0)
Xmean


Out[33]:
array([0.48743874, 0.46067178, 0.39392076])

In [35]:
# center X by subtracting mean (broadcasting op)
X_centered = X - Xmean

In [36]:
# check centered array has ≈ 0 mean
X_centered.mean(0)


Out[36]:
array([-6.66133815e-17, -2.22044605e-17, -5.55111512e-17])

Mean is now zero, to within machine precision.

Plotting a two-dimensional function


In [43]:
# x and y have 50 steps from 0 to 5
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]

z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)

In [44]:
%matplotlib inline
import matplotlib.pyplot as plt

In [45]:
plt.imshow(z, origin='lower', extent=[0,5,0,5],cmap='viridis')
plt.colorbar();


A visualization of the two-dimensional function.


In [ ]: