One Reference for Many Rows
Each greenhouse has its own target temperature. We want to compare every day's readings with those three targets without copying the targets into a new table by hand.
10 sections · 4 questions · 2 coding challenges
The arrow keys move between sections too.
Repeating a Calculation Without Repeating Its Data
BOTIE
NumPy's broadcasting rules specify when smaller shapes can be reused across larger ones. Our greenhouse example illustrates one such case. It is not a claim that NumPy understands the meaning of the columns; matching the references to the right columns remains our responsibility.
Sources for this history
Do We Need to Repeat the Same Reference Every Day?
Suppose the north, middle, and south greenhouses have target temperatures of 10, 20, and 30 degrees. Botie checks them on two days. Each day needs the same three references, in the same order.
We could write those targets beside every row. Instead, we can tell the array calculation that this one target row applies to every day. NumPy calls this matching of compatible shapes broadcasting.
Start with two days of readings
The greenhouse order is north, middle, south in every row.
Monday
- 12
- 18
- 24
Tuesday
- 14
- 22
- 36
What we can now calculate
Each target stays attached to its column as the rows pass through the calculation.
Explore the full example
Choose an example, then change a value. Follow the calculation below to see which result changes and why. These are small teaching examples; the construction later asks you to choose the operations yourself.
BOTIE
Result from these values
| 2 | -2 | -6 |
| 4 | 2 | 6 |
Follow the operations
1. Days by greenhouses
12 18 24 14 22 36 2. Target for each greenhouse
- 010
- 120
- 230
3. Subtract a vector from every row
2 -2 -6 4 2 6 4. Result
2 -2 -6 4 2 6
The workshop tool handles a reference vector across rows. It does not implement every possible NumPy broadcasting pattern. Editing this example does not change the workshop tests.
1. Match the Reference to Its Meaning
Each greenhouse needs its own target. We could keep those three targets as a one-dimensional array, as one row of a table, or as three rows with one column. Those arrangements contain the same numbers, but they do not tell NumPy to align them in the same way.
A one-dimensional array has positions along one axis. It has no separate row and column axes, even though printing it may put the entries on one line.
Let's make all three arrangements explicitly before deciding which one belongs beside our readings.
The same values in three different shapes
A flat list creates one axis. A list containing one inner list creates one row. Three inner lists containing one item each create three rows and one column. Printing the shapes makes this distinction visible.
import numpy as np
targets = np.array([10, 20, 30])
target_row = np.array([[10, 20, 30]])
target_column = np.array([[10], [20], [30]])
print(targets.shape)
print(target_row.shape)
print(target_column.shape)
print(target_row.tolist())
print(target_column.tolist())(3,)
(1, 3)
(3, 1)
[[10, 20, 30]]
[[10], [20], [30]]Our greenhouse targets belong to the columns of the readings table. Both the one-dimensional target array and the one-row version can align those three entries with the three columns. The column version would arrange them along the wrong direction for this example.
2. Subtract the Same Targets From Every Day
We want each target to stay with its greenhouse as Botie moves from Monday to Tuesday. That means subtracting the first target from the first greenhouse on both days, then doing the same for the other greenhouses.
Monday: [12 - 10, 18 - 20, 24 - 30] = [2, -2, -6]
Tuesday: [14 - 10, 22 - 20, 36 - 30] = [4, 2, 6]A positive difference means the reading was above target; a negative difference means it was below. We still need a difference for every reading.
Keep the reference array small
The subtraction below reuses the targets for both rows. This is broadcasting: NumPy aligns compatible shapes so an operation can use a smaller input across a larger one. We do not need to write a second copy of the targets.
import numpy as np
readings = np.array([
[12, 18, 24],
[14, 22, 36],
])
targets = np.array([10, 20, 30])
differences = readings - targets
print(differences.tolist())
print(differences.shape)[[2, -2, -6], [4, 2, 6]]
(2, 3)The two day rows and three greenhouse columns survive. Broadcasting has reused a reference, not combined observations into a summary.
3. Read the Shape Rule From the Right
NumPy compares axis lengths starting from the right of each shape. Two lengths can work together if they match or if one is one. A missing leading axis acts like an axis of length one. This lets our three targets apply to both day rows.
Now suppose the reference is the outside temperature for each day. Each reference must apply across a whole row. We therefore need two rows with one entry each. A flat two-entry array would instead try to align with our three greenhouse columns.
Greenhouse targets: (2, 3) with (3,) → (2, 3)
Day references: (2, 3) with (2, 1) → (2, 3)
Wrong alignment: (2, 3) with (2,) → incompatibleThe computer checks the lengths, not the meaning. A square table can conceal an alignment mistake because its row and column counts happen to match.
Give each day's reference a column axis
We already used a colon to keep all positions along an axis. In the indexing expression below, None adds a new axis of length one. It does not insert an unknown temperature. The comma separates these two indexing instructions.
We keep the two day entries, give them a one-column axis, and inspect the result before subtracting. These illustrative outside readings differ from the ones you will use in the challenge.
import numpy as np
readings = np.array([
[12, 18, 24],
[14, 22, 36],
])
outside = np.array([6, 10])
outside_column = outside[:, None]
print(outside.shape)
print(outside_column.shape)
print(outside_column.tolist())
print((readings - outside_column).tolist())(2,)
(2, 1)
[[6], [10]]
[[6, 12, 18], [4, 12, 26]]The first outside temperature now aligns with Monday and the second with Tuesday. Adding the axis gives a new view of the same two stored values. It does not copy either temperature three times.
A square table can accept the wrong interpretation
Both the day count and greenhouse count are two here. Subtracting a flat outside array therefore succeeds, but applies the two references to columns. Adding the one-column axis instead attaches them to days.
import numpy as np
readings = np.array([[12,18],[14,22]])
outside = np.array([6,10])
print((readings - outside).tolist())
print((readings - outside[:, None]).tolist())[[6, 8], [8, 12]]
[[6, 12], [4, 12]]Both outputs have the same shape. Only the second uses each day's outside reading across that day's whole row. Understanding the input meaning is still necessary after a program stops reporting shape errors.
4. Keep Reuse Separate From Reduction
Subtracting a target and averaging the resulting differences answer two different questions. The subtraction asks how far each reading is from its target. The average asks for a summary across several readings.
Follow the shapes as well as the values. Broadcasting can preserve our table, whereas a reduction combines entries along the axis we name.
The workshop asks for the full table of differences. This next example goes a step further to show what would happen if Botie also requested one summary per greenhouse.
One table of differences, then one summary per column
We subtract first, then average over days. The first shape still describes a day-by-greenhouse table. The second describes one entry per greenhouse.
import numpy as np
readings = np.array([[12, 18, 24], [14, 22, 36]])
targets = np.array([10, 20, 30])
differences = readings - targets
average_difference = differences.mean(axis=0)
print(differences.shape)
print(average_difference.shape)
print(average_difference.tolist())(2, 3)
(3,)
[3.0, 0.0, 0.0]An average difference of zero does not tell us that every reading was on target. Above-target and below-target readings can cancel. Keep the individual differences when that distinction matters.
Check yourself
Questions on Parts 1 to 4
Question 1 of 4
Subtracting our three targets should leave two rows and three columns.
Rows are days and columns are greenhouses. Each target belongs to a greenhouse.
import numpy as np
readings = np.array([[12, 18, 24], [14, 22, 36]])
targets = np.array([10, 20, 30])
differences = readings - targetsTry it with Botie
Build with Botie
Write and run Python