Skip to content
wannacode

Broadcasting: the number, the row and the column

In short: Broadcasting is the rule by which NumPy computes with arrays of different shapes: a number, a row or a column is stretched to the shape of the table when the sizes fit.

Problem

“How much did each product make each week?”

The manager gives you the units sold over the last 4 weeks for three products: white bread, fresh milk and apples. A table with 4 rows (the weeks) and 3 columns (the products). The prices are €1.20, €1.60 and €2.10.

They want the revenue in a table of the same kind: week by product. With Python lists that is two nested for loops, row by row and cell by cell.

In NumPy calculations work on the whole array. But here there are two arrays with different shapes: a 4 × 3 table and a row of 3 prices. How do you multiply one by the other?

The idea

The number and the array

Start with the simplest case. The supplier raises every price by 20%. products['price'].to_numpy() gives the price column as a NumPy array, and the multiplication is by a single number:

Pythondata: the shop tables

25 prices times one number gives 25 new prices: €1.20 becomes €1.44, and the €39 knife set becomes €46.80. The number is “stretched” to the shape of the array: as if a 1.2 stood next to every price.

This stretching is called broadcasting. The number is only the first case. The same happens with a row and a table.

The idea

The row and the table

The units are a (4, 3) table, the prices are a (3,) row. Multiply them directly:

Pythondata: the shop tables

The row of prices is repeated for each of the 4 weeks, and every cell is multiplied by the price of its column. The first week: 30 loaves at €1.20 are €36, 22 milks at €1.60 are €35.20, 14 kg of apples at €2.10 are €29.40. Not a single for.

When does NumPy stretch and when does it stop? There is one rule:

  • Shapes are compared from right to left. (4, 3) and (3,): first 3 and 3, then 4 and the empty place.
  • The sizes must be equal, or one of them must be 1. An empty place counts as 1.
  • The 1 stretches to the other size. Here the (3,) row becomes (1, 3) and stretches to (4, 3).
More precisely, if you are curious

NumPy does not copy the row 4 times in memory. It reads the same 3 prices again for every row. That is why broadcasting is both fast and light on memory, even when the table has millions of rows.

The number from the previous card is an array of shape (), with no sizes. Ones are added in front of it until it is (1,) or (1, 1), and then it stretches. The rule is the same.

See it

How the row fits the table

Here is the same calculation slowly: the 4 × 3 table, the row of 3 prices, the stretch downwards and the multiplication cell by cell.

import numpy as np
# units over 4 weeks: white bread, fresh milk, apples
units = np.array([[30, 22, 14],
[26, 25, 18],
[34, 20, 12],
[28, 27, 20]])
prices = np.array([1.2, 1.6, 2.1])
revenue = units * prices
print(units.shape, prices.shape, revenue.shape)
revenue
Step by step, in words
  1. units is a (4, 3) table: the units for 4 weeks, one column per product.
  2. prices is a (3,) row: €1.20, €1.60, €2.10. One price per product.
  3. Shapes are compared from the right: 3 and 3 are equal. To the left of (3,) there is nothing, which counts as 1, so the row becomes (1, 3).
  4. The 1 stretches to 4: the row of prices repeats for every week and becomes (4, 3).
  5. Now cell by cell. The first week: 30 × 1.2 = 36, 22 × 1.6 = 35.2, 14 × 2.1 = 29.4.
  6. revenue is (4, 3), like units: 12 revenues from one multiplication, without a single for.

Try it

Do the shapes fit?

1. units has shape (4, 3) and prices has shape (3,). What is the shape of units * prices?
2. units has shape (4, 3) and week_bonus has shape (4,). What does units + week_bonus give?
3. Which pair of shapes fits?

The idea

When shapes do not fit

Now the manager asks something else: which product sold above the week’s average in which week. You need to subtract each row’s mean from that same row.

units.mean(axis=1) gives the mean units for each week (axis is from the previous lesson: the size that disappears). That is 4 numbers. Subtract them from the table:

Pythondata: the shop tables

The means are 22, 23, 22 and 25, with shape (4,). NumPy compares from the right: 3 from the table against 4 from the means. They are not equal and neither is 1, so it stops: operands could not be broadcast together with shapes (4,3) (4,).

The error is useful. A row of 4 numbers really cannot lie over 3 columns.

The idea

The column and the table

The 4 means need to be a column: one number per row, stretched to the right across the products. A column has shape (4, 1). There are two ways to get it:

  • week_mean[:, None]: None in second place adds a size of 1;
  • week_mean.reshape(4, 1): the same, with the shape written out.
Pythondata: the shop tables

On the right there are now 3 and 1 (the 1 stretches to 3), then 4 and 4. The result is a (4, 3) table: in the third week bread is 12 units above the average, and apples are 10 below it.

In the second week everything was 10% off, and in the fourth week 20% off. discount holds the discount for each week. Store in after the revenue after the discount, a 4 × 3 table.

Pythondata: the shop tables

The idea

The silent trap: 3 × 3 instead of 3

An error from NumPy shows up at once. It is more dangerous when the shapes fit and the result is something else.

A colleague sends you the new prices, copied from a column in Excel. The array is a (3, 1) column, not a (3,) row. You subtract the old prices to see how much they went up:

Pythondata: the shop tables

You expect 3 differences and get 9. The (3, 1) column stretched to the right, the (3,) row stretched down, and every new price was subtracted from every old one. Only the diagonal (0.1, 0 and 0.2) is the difference you want. The other numbers mean nothing, and NumPy says nothing: the shapes fit the rule.

So after every calculation with different shapes, print .shape. If you expect (3,) and see (3, 3), something stretched without you asking.

Try it

How much did prices go up

new_prices is a (3, 1) column with the new prices. Store in change how much the price of each product went up: 3 numbers, shape (3,).

Pythondata: the shop tables

Exercise

How much more with the new prices

Back to the manager. They want to know how much more each product would have made each week if the new prices had applied already.

units are the units (4, 3), prices the old prices (3,), new_prices the new prices as a (3, 1) column. Store in extra how much higher the revenue would have been: a 4 × 3 table, week by product.

Pythondata: the shop tables

Test

Check yourself

1. What does prices * 1.2 do when prices is an array of 25 prices?
2. week_mean has shape (4,) and units has shape (4, 3). How do you subtract each week’s mean from that week?
3. old has shape (3,) and new has shape (3, 1). What does new - old give?
4. What is the quickest way to catch a silent stretch into a 3 × 3 table?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·