Which Direction Are We Adding?
A table can hold several greenhouses across several days. Adding across a row answers a different question from adding down a column. We need to keep that distinction when we ask an array to calculate.
12 sections · 5 questions · 2 coding challenges
The arrow keys move between sections too.
Keeping the Meaning of a Scientific Table
BOTIE
NumPy's documented reduction operations make the chosen axis explicit. Our two-day greenhouse table is an invented example of why that choice matters. We are using an established array convention, not attributing the invention of tables or averages to NumPy.
Which Average Did We Actually Ask For?
Botie now has two days of temperatures for three greenhouses. We could ask for the average temperature of each greenhouse, or the average across all greenhouses on each day. Both are reasonable questions, but they have different answers.
The entries alone cannot tell the computer which question we mean. We need to describe the table's layout and say which direction the calculation should combine.
Rows are days and columns are greenhouses in this illustrative report.
Step 1 of 4
Name both directions
Rows are days; columns are greenhouses. Keep these labels in view before reducing anything.
| Day | North | Middle | South |
|---|---|---|---|
| Monday | 10 | 20 | 30 |
| Tuesday | 14 | 22 | 36 |
Monday
- North
- 10
- Middle
- 20
- South
- 30
Tuesday
- North
- 14
- Middle
- 22
- South
- 36
Step 2 of 4
Keep the greenhouses separate
Combine the two days within each column. We should end with three answers.
| Greenhouse | Monday | Tuesday |
|---|---|---|
| North across days | 10 | 14 |
| Middle across days | 20 | 22 |
| South across days | 30 | 36 |
North across days
- Monday
- 10
- Tuesday
- 14
Middle across days
- Monday
- 20
- Tuesday
- 22
South across days
- Monday
- 30
- Tuesday
- 36
Step 3 of 4
Or keep the days separate
Combining each row gives a daily summary instead. That leaves two answers.
Monday = (10 + 20 + 30) / 3 = 20 Tuesday = (14 + 22 + 36) / 3 = 24
Step 4 of 4
One overall average can hide the greenhouse differences
We can also average every reading together. That gives one report-wide number, but it loses the distinction between the cooler and warmer greenhouses. The right summary depends on what Botie needs to decide.
| Summary | Result | What remains identifiable |
|---|---|---|
| One average per greenhouse | 12, 21, 33 degrees Celsius | The greenhouse |
| One average for the whole report | 22 degrees Celsius | Only the report |
One average per greenhouse
- Result
- 12, 21, 33 degrees Celsius
- What remains identifiable
- The greenhouse
One average for the whole report
- Result
- 22 degrees Celsius
- What remains identifiable
- Only the report
The axis decides which question the average answers
Swapping rows and columns puts one greenhouse's readings together. Each row average then keeps that greenhouse separate.
Are we averaging days or greenhouses?
Each greenhouse has several readings, and each day has several greenhouses. Before running the calculation, decide which observations must contribute to one greenhouse's average.
BOTIE
Average temperature for each greenhouse
- 012
- 121
- 233
Follow the operations
1. Days by greenhouses
10 20 30 14 22 36 2. Swap rows and columns
10 14 20 22 30 36 3. Mean within each row
- 012
- 121
- 233
4. Result
- 012
- 121
- 233
All days receive equal weight, and every greenhouse has a reading on every day.
1. Give Each Axis a Job
One list was enough for one morning. With several mornings, a flat list would make us remember where each day begins and ends. Instead, let's keep one list per day and put those lists inside another list. This is a nested list.
We can use this arrangement to create a two-dimensional NumPy array. Its first axis follows the days and its second follows the greenhouses. An axis is a direction in the organisation of an array.
North Middle South
Monday 10 20 30
Tuesday 14 22 36A scalar is one value. A one-dimensional array is a sequence of values. Our two-dimensional array is a table of values, which we can also interpret as a matrix. The number of axes is different from the number of entries.
Build the table one row at a time
The outer list contains two items, each of which is itself a list. The first lookup chooses Tuesday's list. The second lookup chooses the middle greenhouse within that list. NumPy accepts these equally long rows as a rectangular numerical array.
import numpy as np
days = [
[10, 20, 30],
[14, 22, 36],
]
print(days[1])
print(days[1][1])
temperatures = np.array(days, dtype=float)
print(temperatures.tolist())[14, 22, 36]
22
[[10.0, 20.0, 30.0], [14.0, 22.0, 36.0]]The double square brackets in the final output preserve the rows: this is a list containing lists. A Python nested list could have unequal row lengths, but those rows would not form the regular numerical table we need here.
Read the shape as a tuple of axis lengths
A tuple is an ordered collection whose entries cannot be replaced after it is created. Python commonly writes tuples with parentheses. NumPy reports an array's shape as a tuple: one length for each axis, in axis order.
The ndim attribute counts axes. The size attribute counts all entries. These describe different things, so let's print each one separately.
import numpy as np
temperatures = np.array([
[10, 20, 30],
[14, 22, 36],
])
shape = temperatures.shape
print(shape)
print(shape[0])
print(shape[1])
print(temperatures.ndim)
print(temperatures.size)(2, 3)
2
3
2
6There are two day rows, three greenhouse columns, two axes, and six temperatures. The shape tuple records the lengths; it contains none of the temperatures. We can index that tuple to read a length, but we cannot replace one of its entries as we could with a list.
Ask for one reading, one day, or one greenhouse
In a two-dimensional array, the comma inside square brackets separates the row selection from the column selection. A colon by itself means all positions along that axis. So we can choose one row and one column, all columns of a row, or all rows of a column.
import numpy as np
temperatures = np.array([
[10, 20, 30],
[14, 22, 36],
])
print(temperatures[1, 1])
print(temperatures[0, :].tolist())
print(temperatures[:, 1].tolist())22
[10, 20, 30]
[20, 22]The first result is Tuesday's middle greenhouse reading. The second is all of Monday. The third follows the middle greenhouse across both days. Selecting one row or one column this way leaves a one-dimensional array.
2. Remove the Direction We Combine
To find one mean per greenhouse, we need to combine the days while keeping the greenhouses separate. The days occupy axis zero, so that is the axis we reduce. A reduction combines several entries into a summary.
North: (10 + 14) / 2 = 12
Middle: (20 + 22) / 2 = 21
South: (30 + 36) / 2 = 33
Result shape: (3,)The day axis disappears because its readings have been combined. The remaining entries belong to the three greenhouses.
Keep one answer per greenhouse
The mean method calculates an average. Its axis argument says which direction to combine. The result's shape is a one-entry tuple. The trailing comma is how Python distinguishes a one-entry tuple from parentheses around a single number.
import numpy as np
temperatures = np.array([
[10, 20, 30],
[14, 22, 36],
])
per_greenhouse = temperatures.mean(axis=0)
print(per_greenhouse.tolist())
print(per_greenhouse.shape)[12.0, 21.0, 33.0]
(3,)The result has one axis of length three. It is not a table with three rows and an unspecified number of columns. It has no second axis.
3. Ask the Other Question
For one mean per day, we combine the greenhouse axis instead. Reducing axis one leaves the day axis. The same six temperatures now answer a different question.
Monday: (10 + 20 + 30) / 3 = 20
Tuesday: (14 + 22 + 36) / 3 = 24
Result shape: (2,)Neither summary is inherently better. The useful one depends on whether Botie needs a greenhouse comparison or a daily report.
Compare a daily summary with a single overall value
Providing axis one gives a result for each day. Leaving out axis asks mean to combine every entry into one scalar. That single number no longer keeps either day or greenhouse separate.
import numpy as np
temperatures = np.array([
[10, 20, 30],
[14, 22, 36],
])
per_day = temperatures.mean(axis=1)
print(per_day.tolist())
print(per_day.shape)
print(temperatures.mean())[20.0, 24.0]
(2,)
22.0The list has two daily means. The final number averages all six readings. Before writing a reduction, say what one answer should represent and how many answers you need.
4. Predict the Shape Before Running the Code
We now have a way to check whether an operation left us the right kind of result: inspect its shape. That check is useful before reading any particular number. The right shape does not prove the numbers are correct, but the wrong shape can reveal that we answered a different question.
There is one more detail to understand before editing a selected row. A NumPy slice can be a view, meaning it refers to part of the original array's storage. Giving that view a new variable name does not give it independent data.
If Botie wants to try a correction without altering the saved readings, we need a copy.
A view shares readings; a copy keeps its own
We select Monday's row twice. One selection remains a view. For the other, we call copy to give it its own numerical storage. Then we change the first entry of each selection.
import numpy as np
temperatures = np.array([
[10, 20, 30],
[14, 22, 36],
])
monday_view = temperatures[0, :]
monday_copy = temperatures[0, :].copy()
monday_view[0] = 11
monday_copy[0] = 99
print(temperatures[0, :].tolist())
print(monday_copy.tolist())[11, 20, 30]
[99, 20, 30]Changing the view changed Monday in the original table. Changing the copy did not. The workshop will summarise the readings, and the challenges will calculate both kinds of mean without modifying their inputs.
5. Keep the Greenhouse Axis When Subtracting Its Mean
We have each greenhouse's average. Now we want to subtract it from that greenhouse's reading on every day. There is a small layout problem: a reduction normally removes the axis it combines.
Using keepdims=True leaves that axis in place with length one. Here we combine the days and keep the greenhouse columns. The result is one row of averages that NumPy can reuse down the table, with each average still in the correct column.
Daily readings relative to each greenhouse
Rows are days and columns are north and south greenhouses.
import numpy as np
readings = np.array([[10., 20.], [14., 26.], [12., 20.]])
reference = readings.mean(axis=0, keepdims=True)
print(reference.shape)
print((readings - reference).tolist())(1, 2)
[[-2.0, -2.0], [2.0, 4.0], [0.0, -2.0]]Each column now measures departure from its own reference. Reducing axis one would answer a different question: the average across greenhouses on each day.
6. Give a Third Axis a Meaning Before Reducing It
Suppose the report contains two weeks, seven days per week, and three greenhouses. Its axes can mean week, day and greenhouse, in that order. Averaging over days leaves one row of greenhouse averages per week. Averaging over week and day together leaves one average per greenhouse.
There is no universal rule that axis zero means observations. That is a convention we choose for a particular array. Write the axis meanings beside new data, especially when a library expects channels or batches in a particular position.
readings.shape = (2, 7, 3)
readings.mean(axis=1).shape = (2, 3)
readings.mean(axis=(0, 1)).shape = (3,)Check yourself
Questions on Parts 1 to 6
Question 1 of 5
Which axis must disappear to leave one mean per greenhouse?
Rows are Monday and Tuesday. Columns are north, middle, and south.
import numpy as np
temperatures = np.array([
[10, 20, 30],
[14, 22, 36],
])Try it with Botie
Build with Botie
Write and run Python