Ask Each Row a Question
Botie needs the greenhouse readings that reached a temperature limit. A yes-or-no decision for each reading lets us select the relevant observations without losing their order.
10 sections · 4 questions · 2 coding challenges
The arrow keys move between sections too.
Selecting Observations Without Rewriting the Table
BOTIE
NumPy documents Boolean indexing as a way to use those decisions. This lesson uses invented greenhouse readings to show the mechanism. It teaches selection, not a claim that all unusual observations should be discarded.
Sources for this history
Which Readings Need Our Attention?
A greenhouse report includes comfortable temperatures and readings that need attention. We want to see only the readings at or above 25 degrees, while keeping the original collection available.
We can ask the same question of every entry. The answers form another array, containing true or false at each position. This array of decisions is called a mask.
Keep the full report
Each position belongs to one greenhouse reading. The threshold includes the boundary itself.
Reading
- 20
- 27
- 25
- 18
What we can now calculate
The comparison makes the decisions; selection applies those decisions to the original temperatures.
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
- 027
- 125
Follow the operations
1. Greenhouse temperatures
- 020
- 127
- 225
- 318
2. Alert temperature
25
3. Threshold gate
- 00
- 11
- 21
- 30
4. Select real entries
- 027
- 125
5. Result
- 027
- 125
The limit is supplied by the task. It is not a learned or medically meaningful threshold. Editing this example does not change the workshop tests.
1. Build One Decision per Observation
Botie needs to know which greenhouses have readings at or above the alert temperature. We could inspect each reading ourselves, but a program needs an explicit decision rule. A comparison asks a question about a value and returns a Boolean: either True or False.
Applying the comparison to an array produces another array with a Boolean at each position. This array of decisions is called a mask. Its entries answer whether to keep a reading; they are not temperatures.
20 ≥ 25 → false
27 ≥ 25 → true
25 ≥ 25 → true
18 ≥ 25 → falseOur boundary counts. A strictly greater-than comparison would omit the reading exactly at the threshold.
Compare once, then compare at every position
The Python operator below means greater than or equal to. First we use it on one number, then on an array. The array's dtype is bool because the stored entries are decisions.
import numpy as np
print(27 >= 25)
readings = np.array([20, 27, 25, 18])
mask = readings >= 25
print(mask.tolist())
print(mask.dtype)
print(mask.shape)True
[False, True, True, False]
bool
(4,)The mask has four entries because there are four readings to decide about. We have only created the decisions so far. The original temperatures are still in readings.
2. Use the Decisions to Select Values
We now have two aligned collections: the temperatures and the decisions. Putting the Boolean mask inside the readings' square brackets tells NumPy to keep the positions marked True. For this one-dimensional selection, the mask must have one decision per reading.
A Boolean mask and an array of integer indices are different structures for making a selection. The Boolean array asks yes or no at each position. The integer array names the positions to retrieve, in the order requested.
The workshop displays decisions as zero and one. In NumPy we must actually use Boolean values for a mask. A numerical array containing zeros and ones would instead be interpreted as positions.
Decisions are different from numbered positions
Both selections below are valid Python, but only the first applies our alert rule. In the second, each zero requests the first reading and each one requests the second reading.
import numpy as np
readings = np.array([20, 27, 25, 18])
mask = np.array([False, True, True, False])
positions = np.array([0, 1, 1, 0])
print(readings[mask].tolist())
print(readings[positions].tolist())[27, 25]
[20, 27, 27, 20]The mask selects two readings. The index array retrieves four entries, including repeats. Using the right values with the wrong data type can therefore ask an entirely different question.
3. Summarise the Selected Group
After selecting the readings, we can count them or calculate their average. Both the total and the count must come from the selected group. The original report's count would give us the wrong denominator.
Selected total = 27 + 25 = 52
Selected count = 2
Selected mean = 52 / 2 = 26An empty selection still has a count of zero, but it has no mean. There are no temperatures to average, so a displayed zero would wrongly suggest an observed temperature.
The selection is another numerical array
The selected array has the same numerical data type as the readings. It can have fewer entries, so inspect its size before treating it as the whole report.
In the last print, an f before a quoted string lets us put an expression inside braces. Python evaluates the expression before the colon. The .1f after the colon asks for its value with one digit after the decimal point.
import numpy as np
readings = np.array([20, 27, 25, 18])
selected = readings[readings >= 25]
print(selected.tolist())
print(selected.size)
print(f"{selected.mean():.1f}")[27, 25]
2
26.0The list shows which observations contributed. The next line counts them, and the last summarises them. Boolean selection here creates a copy, so changing this selected array later would not edit readings.
Keep an empty result distinguishable from a measured zero
On this cooler day, no reading meets the rule. The result is still an array, just one with no entries. The if statement chooses what to do when the count is zero. The double equals sign compares two values; it does not assign a value to a name.
import numpy as np
readings = np.array([19, 21])
selected = readings[readings >= 25]
print(selected.tolist())
print(selected.shape)
if selected.size == 0:
print("No readings met the alert rule.")
else:
print(selected.mean())[]
(0,)
No readings met the alert rule.The empty list display and the zero-length shape both say that no items were selected. The indented instruction under if runs when the comparison is true; the instruction under else runs otherwise. We never ask for an average of the empty array.
4. A Filter Changes the Question
The mean of readings above the limit does not describe the whole report without qualification. We deliberately chose warmer readings. That is useful for inspecting alerts, but someone reading the result needs to know which observations contributed.
Another Python structure, the set, keeps distinct items. It is useful when we care whether a value occurred, but it discards repeated occurrences and does not give us positions to index like a list. That would lose information if we used it to store repeated measurements.
Let's distinguish asking which temperatures occurred from asking how many readings triggered an alert.
Distinct temperatures are not the same as individual readings
These are three separate alert readings, two of which happen to have the same value. Constructing a set keeps that value only once. We use sorted to produce a list for predictable display; the set itself has no sequence order to rely on. The in operator checks membership.
alert_readings = [27, 27, 25]
distinct_temperatures = set(alert_readings)
print(len(alert_readings))
print(sorted(distinct_temperatures))
print(len(distinct_temperatures))
print(27 in distinct_temperatures)3
[25, 27]
2
TrueThere were three readings but only two distinct temperatures. A set is appropriate for the second question. Keep the list or numerical array for calculations in which each observation should count.
Check yourself
Questions on Parts 1 to 4
Question 1 of 4
The reading at exactly 25 qualifies under our rule.
The alert rule includes readings exactly at the limit.
import numpy as np
readings = np.array([20, 27, 25, 18])
limit = 25
mask = readings >= limitTry it with Botie
Build with Botie
Write and run Python