Giving Measurements a Place
Three thermometers give us three readings. Before we can calculate with them, we need to decide how those readings are stored, which position belongs to which thermometer, and what an operation should do to the collection.
14 sections · 6 questions · 2 coding challenges
The arrow keys move between sections too.
When One Calculation Needed to Work on Many Measurements
BOTIE
The NumPy authors' 2020 account describes an array model that combines compact expressions with compiled numerical operations. The useful idea for us is simple: organise the readings once, then express a calculation for the collection. Our greenhouse readings are invented, not measurements from that paper.
How Do We Give the Computer Our Measurements?
Suppose Botie checks three greenhouses each morning. Today the thermometers read 12, 18, and 24 degrees Celsius. We can write those readings on paper, but a program needs them in a form it can work with.
Let's start with one reading, then work out how to keep all three together. Python gives us several ways to organise information. We will see why a list is useful for keeping entries in order, a dictionary is useful for finding a named reading, and a NumPy array is useful for calculating with the readings.
The greenhouse temperatures are invented; their positions identify the same greenhouses throughout.
Step 1 of 4
Give every greenhouse a position
Keep the same greenhouse order throughout the calculation. A result in the first position still belongs to the first greenhouse.
| Greenhouse | Celsius |
|---|---|
| North | 12 |
| Middle | 18 |
| South | 24 |
North
- Celsius
- 12
Middle
- Celsius
- 18
South
- Celsius
- 24
Step 2 of 4
Apply the conversion to each reading
The conversion needs a scale and an offset. Neither depends on which greenhouse supplied the reading.
| Greenhouse | Celsius | Conversion |
|---|---|---|
| North | 12 | (12 × 1.8) + 32 |
| Middle | 18 | (18 × 1.8) + 32 |
| South | 24 | (24 × 1.8) + 32 |
North
- Celsius
- 12
- Conversion
- (12 × 1.8) + 32
Middle
- Celsius
- 18
- Conversion
- (18 × 1.8) + 32
South
- Celsius
- 24
- Conversion
- (24 × 1.8) + 32
Step 3 of 4
Read the answers in the original order
We now have the same observations in Fahrenheit. Changing the units has not changed the temperature in any greenhouse.
| Greenhouse | Celsius | Fahrenheit |
|---|---|---|
| North | 12 | 53.6 |
| Middle | 18 | 64.4 |
| South | 24 | 75.2 |
North
- Celsius
- 12
- Fahrenheit
- 53.6
Middle
- Celsius
- 18
- Fahrenheit
- 64.4
South
- Celsius
- 24
- Fahrenheit
- 75.2
Step 4 of 4
The container changes what multiplication means
The same two readings can live in a Python list or a NumPy array. Multiplying the list repeats its entries; multiplying the array changes each value. We need to know which object we are using before trusting the result.
| Container | Starting values | After multiplying by 2 |
|---|---|---|
| Python list | [12, 18] | [12, 18, 12, 18] |
| NumPy array | [12, 18] | [24, 36] |
Python list
- Starting values
- [12, 18]
- After multiplying by 2
- [12, 18, 12, 18]
NumPy array
- Starting values
- [12, 18]
- After multiplying by 2
- [24, 36]
The array keeps the readings together and in order
Each wire still carries one entry per greenhouse. The multiplication changes the degree size; the addition changes the reference point.
Can we convert every reading without mixing up the greenhouses?
A conversion is only useful if we still know whose reading it is. Change a Celsius value below and follow that position through the multiplication and addition.
BOTIE
Fahrenheit readings, in greenhouse order
- 053.6
- 164.4
- 275.2
Follow the operations
1. Greenhouse readings in Celsius
- 012
- 118
- 224
2. Set a constant
1.8
3. Multiply
- 021.6
- 132.4
- 243.2
4. Set a constant
32
5. Add
- 053.6
- 164.4
- 275.2
6. Result
- 053.6
- 164.4
- 275.2
This is a unit conversion, not a fitted prediction rule.
1. A Value, a Name, and a Collection
Suppose we only had one thermometer. We could keep its reading under one name and use that name whenever we needed the temperature. Now suppose Botie has a hundred thermometers. Giving every reading its own variable would make even a simple calculation tedious. We need a way to keep related readings together.
A data structure is a way of organising information so that we can find it and work with it. Python gives us several choices. We will start with a list, which keeps items in a sequence. The position of an item is called its index.
Position: 0 1 2
Greenhouse: North Middle South
Celsius: 12 18 24Positions start at zero. Position zero identifies the first reading; it does not mean that the temperature is zero. We have decided that the first position belongs to the north greenhouse.
Start with one reading, then keep three together
The equals sign assigns a value to a name. Here, north refers to one number, which we call a scalar. Square brackets with comma-separated items create a list. Putting square brackets after the list's name retrieves an item instead.
The print function displays what we give it. The len function tells us how many items the list contains. Parentheses hold the information passed to a function.
north = 12
readings = [north, 18, 24]
print(north)
print(readings)
print(readings[0])
print(len(readings))12
[12, 18, 24]
12
3The first line is one number. The second is our collection. The third retrieves the north greenhouse's reading, and the fourth counts the entries. A list can contain other kinds of values too, but this one contains only temperatures.
Correct one entry without replacing the whole report
Suppose the middle thermometer was misread, and Botie also checks a fourth greenhouse. A list is mutable: we can change its contents after creating it. Assigning to an index replaces that item. Calling append adds an item at the end. An operation attached to an object with a dot, such as append, is called a method.
A slice retrieves a range of positions. The starting position is included and the stopping position is excluded. This example asks for positions one and two.
readings = [12, 18, 24]
readings[1] = 19
readings.append(21)
print(readings)
print(readings[1:3])[12, 19, 24, 21]
[19, 24]The corrected reading stays in the middle greenhouse's position. The fourth greenhouse goes at the end. Taking the slice does not remove entries from the original list. Each worked example on these pages starts with its own data, so this correction does not carry into the next example.
2. Apply the Rule One Reading at a Time
Before asking Python to calculate for a whole collection, let's work through one reading. Celsius and Fahrenheit use different sized degrees and different zero points. We first multiply by the scale between their degree sizes. Then we add the offset between their zero points.
North: (12 × 1.8) + 32 = 53.6
Middle: (18 × 1.8) + 32 = 64.4
South: (24 × 1.8) + 32 = 75.2These are three separate conversions. We are changing the units of each observation, so we still need one answer per greenhouse.
Give each step a name
In Python, the asterisk means multiplication. We can store the intermediate result under a name, then use it in the next instruction. This lets us inspect each step before applying the rule to all three readings.
celsius = 12
scaled = celsius * 1.8
fahrenheit = scaled + 32
print(scaled)
print(fahrenheit)21.6
53.6The scaled value is only an intermediate result. We must also shift the zero point before reporting Fahrenheit. Both variables here refer to single numbers, not lists.
3. Tell the Computer Which Kind of Number We Need
Our list keeps the readings together, but Python does not interpret every list operation as arithmetic on its entries. We need a numerical collection whose operations have that meaning. NumPy supplies one: the array, whose Python type is called ndarray.
For the ordinary numerical arrays in these lessons, every entry uses the same data type, or dtype. Integers store whole numbers. Floating-point numbers allow fractional readings, although many decimals are represented approximately. The data type describes how values are stored; it does not record their units.
We will explicitly request floating-point storage because temperatures and their conversions may contain fractions.
Turn the list into a numerical array
Importing NumPy makes its tools available. The name np is a short name we choose for it. The array function takes our Python list and creates an array. The named argument dtype tells it which kind of values to store.
The dtype attribute reports that choice. The tolist method returns an ordinary Python list, which gives us a convenient way to print the entries. It does not change the array into a list in place.
import numpy as np
readings = [12, 18, 24]
celsius = np.array(readings, dtype=float)
print(celsius.tolist())
print(celsius.dtype)[12.0, 18.0, 24.0]
float64The decimal points show the floating-point values in the returned list. The reported float64 is NumPy's 64-bit floating-point type used here. The three positions still belong to the same three greenhouses.
The same multiplication sign can do two different things
Let's multiply a list and an array by the same whole number. Before reading the output, consider what each collection is designed to do: the list supports repeating a sequence; the numerical array supports multiplying its entries.
import numpy as np
readings = [12, 18, 24]
celsius = np.array(readings)
print(readings * 2)
print((celsius * 2).tolist())[12, 18, 24, 12, 18, 24]
[24, 36, 48]Repeating the list gives six entries. Multiplying the array gives three doubled values. If we want to calculate a new temperature for each greenhouse, we need the second behaviour.
Convert the collection and prepare it for display
Now we can apply the two conversion steps to the array. NumPy performs the arithmetic for each entry. NumPy's round function takes the result and a number of decimal places. We keep that rounded result separately, then turn it into a list for printing.
import numpy as np
celsius = np.array([12, 18, 24], dtype=float)
scaled = celsius * 1.8
fahrenheit = scaled + 32
displayed = np.round(fahrenheit, 1)
print(displayed.tolist())
print(celsius.tolist())[53.6, 64.4, 75.2]
[12.0, 18.0, 24.0]We have one converted value per greenhouse. These arithmetic expressions produce new arrays, so the original Celsius readings are still available. Rounding is a display choice; it does not make floating-point arithmetic exact.
4. Check the Meaning as Well as the Numbers
A plausible number is not enough. We need the right units, the right order, and the right number of results. A familiar reference temperature helps us check the conversion.
Freezing point: (0 × 1.8) + 32 = 32
Boiling point at standard atmospheric pressure: (100 × 1.8) + 32 = 212There is another question our array cannot answer on its own: which entry belongs to which greenhouse? We must keep that information with the report.
Use a dictionary when the name is how we find the reading
A dictionary associates a key with a value. Here each key is a greenhouse name, written as a quoted string of text. Its value is the temperature. Curly braces contain the entries, and a colon separates each key from its value.
A list lookup uses a position. This dictionary lookup uses a name. When we later need a numerical array, we can first request the readings in an explicit order.
by_greenhouse = {
"north": 12,
"middle": 18,
"south": 24,
}
print(by_greenhouse["north"])
ordered_readings = [
by_greenhouse["north"],
by_greenhouse["middle"],
by_greenhouse["south"],
]
print(ordered_readings)12
[12, 18, 24]The dictionary is useful for looking up a named reading; the array is useful for numerical calculations across readings. Neither structure knows that these numbers are Celsius. That meaning still belongs in our variable names, labels, and explanation.
5. Why a List and an Array Give Different Answers
Suppose Botie wants to double three temperature differences. We put the numbers in a Python list and multiply by two. That sounds reasonable, but Python repeats the list instead of doubling its entries. Why? A list stores references to objects, and its multiplication operation means repeat the collection.
A NumPy array gives multiplication a different meaning: apply it to each entry. We will see both results below. Lists are useful while collecting entries, including records of different lengths. For a rectangular numerical table, an array lets us give the entries a common data type and calculate across them.
Repeat a collection or double its measurements
The same three differences go through two operations. Compare the lengths as well as the values.
import numpy as np
readings = [2, 3, 5]
print(readings * 2)
array = np.array(readings, dtype=float)
print((array * 2).tolist())
print(array.shape, array.dtype.kind)[2, 3, 5, 2, 3, 5]
[4.0, 6.0, 10.0]
(3,) fThe array still represents three measurements. The letter f identifies a floating-point dtype family; it is not a unit such as degrees Celsius.
Check yourself
Questions on Lists and Array Operations
Question 1 of 1
What does this expression represent?
Use this setup.
readings = [2, 3, 5]. The expression is readings * 2, before converting to NumPy.6. A Second Name Is Not a Second Set of Readings
Botie saves his readings under another variable name, then changes one value. Both names now show the change. Assignment gave the existing array another name; it did not make a second array.
If we want to keep the original while trying a correction, we need copy. Give the saved copy a name that tells us what it holds. Later we will look at views, which can also share data, but here we have only one array with two names.
original = np.array([12., 18.])
alias = original
saved = original.copy()
alias[0] = 99
# original is now [99., 18.]; saved is still [12., 18.]7. What Should One Position Represent?
Before building a table, decide whether a row means a greenhouse, a day, or a sensor event. Keep identifiers alongside it. If a sensor has no reading for one day, silently shortening that row does not produce the same measurement table.
A regular numerical array needs a consistent layout. Represent a missing measurement explicitly, or keep irregular records in a more suitable structure until they can be aligned. The array's shape describes the storage layout. Our record of the row and column meanings gives that layout its interpretation.
Check yourself
Questions on Parts 1 to 7
Question 1 of 5
What does position zero identify in our temperature array?
The greenhouse names and Celsius readings use the same order.
import numpy as np
greenhouses = ["north", "middle", "south"]
readings = np.array([12, 18, 24])Try it with Botie
Build with Botie
Write and run Python