Keep Enough of a Calculation to Run It Again
A printed table is not a complete experiment record. We will save arrays with their shapes and types, keep related arrays together, and check what survives when a report moves through a text file.
12 sections · 4 questions · 2 coding challenges
The arrow keys move between sections too.
Keeping Numerical Results Usable by the Next Program
BOTIE
NumPy's NPY format records an array's dtype and shape with its values. NPZ archives collect named arrays, while text readers and writers serve simpler interchange needs. These formats preserve stored data, not the scientific meaning we forget to record.
Sources for this history
What Will Tomorrow's Program Need From Today's Work?
Botie has prepared a feature table, fitted coefficients, and kept the greenhouse names. Tomorrow's program must load those pieces in the same order. Saving only a screenshot or a rounded list of predictions would not be enough to repeat the calculation.
NumPy can save one array in its native format, keep several arrays in a named archive, or exchange numbers as text. We will try each route and check both the values and the information needed to interpret them.
This illustration begins after loading; the Python examples perform the save and load.
Step 1 of 3
Keep the model inputs
Observation rows must remain attached to their feature columns.
| Greenhouse | Temperature | Humidity |
|---|---|---|
| North features | 10 | 40 |
| South features | 20 | 60 |
North features
- Temperature
- 10
- Humidity
- 40
South features
- Temperature
- 20
- Humidity
- 60
Step 2 of 3
Keep the rule and the names
Coefficients and labels explain how the table is used.
| Saved item | Meaning | Contents |
|---|---|---|
| Weights | Temperature, then humidity | [2, 0.1] |
| Names | Observation order | [north, south] |
Weights
- Meaning
- Temperature, then humidity
- Contents
- [2, 0.1]
Names
- Meaning
- Observation order
- Contents
- [north, south]
Step 3 of 3
Recompute after loading
A successful round trip restores the data and lets the same calculation run.
| Greenhouse | Weighted sum |
|---|---|
| North | 24 |
| South | 46 |
North
- Weighted sum
- 24
South
- Weighted sum
- 46
Restoring values includes restoring what their positions mean
The restored coefficients produce one score per row, and the restored names retain the row identity.
Can the restored pieces still make the same prediction?
The feature table, coefficients and names have already been loaded. Follow one row through the calculation and check that its name still identifies the resulting score.
BOTIE
Restored scores and their greenhouse names
[
[
24,
46
],
[
"north",
"south"
]
]Follow the operations
1. Restored feature table
10 40 20 60 2. Restored coefficients
- 02
- 10.1
3. Matrix times vector
- 024
- 146
4. Restored names in row order
- 0north
- 1south
5. Collect two results
[ [ 24, 46 ], [ "north", "south" ] ]6. Result
[ [ 24, 46 ], [ "north", "south" ] ]
The workshop starts after loading and checks how the restored data is used. The Python examples and challenges perform the actual file-format round trips in memory.
1. Save the Array, Not Only Its Printed Appearance
np.save writes one array in NPY format. np.load reads it back, including the dtype and shape. A printed string is intended for people; a saved numerical array is intended for another calculation.
These browser examples use BytesIO, a binary file held in memory. It follows the same read/write interface as a file without writing to your computer. Its contents are temporary and disappear when the Python runtime is reset. For a local Python program, a file path can be supplied instead.
Write an array and read it back
BytesIO comes from Python's io module. Writing advances the file position, so seek(0) returns to the beginning before loading. We disallow pickle because these ordinary numerical arrays do not need Python-object deserialisation.
import numpy as np
from io import BytesIO
readings = np.array([[12.,18.],[14.,22.]], dtype=np.float64)
file = BytesIO()
np.save(file, readings, allow_pickle=False)
file.seek(0)
restored = np.load(file, allow_pickle=False)
print(restored.tolist())
print(restored.shape)
print(restored.dtype)
print(np.array_equal(restored, readings))[[12.0, 18.0], [14.0, 22.0]]
(2, 2)
float64
TrueThe restored array has the same values, shape, and numerical representation. That does not tell it whether a column was temperature or humidity; metadata must supply those meanings.
2. Keep Related Arrays Under Explicit Names
An NPZ archive can hold several arrays. Keyword arguments to savez choose names by which they can be retrieved. This is useful when a feature table, a coefficient vector, and observation labels belong to the same calculation.
The archive is a resource that should be closed after reading. A with statement keeps it open for the indented block and closes it when the block ends. Keep any arrays needed later by assigning them within that block.
Restore the pieces before recomputing the result
The labels are a regular NumPy string array, not arbitrary Python objects. Dictionary-like key lookup retrieves each named array from the archive.
import numpy as np
from io import BytesIO
X = np.array([[10.,40.],[20.,60.]])
weights = np.array([2.,0.1])
names = np.array(["north","south"])
file = BytesIO()
np.savez(file, features=X, weights=weights, names=names)
file.seek(0)
with np.load(file, allow_pickle=False) as saved:
restored_X = saved["features"]
restored_weights = saved["weights"]
restored_names = saved["names"]
print(restored_names.tolist())
print((restored_X @ restored_weights).tolist())['north', 'south']
[24.0, 46.0]Names remain aligned with the rows. savez_compressed writes a compressed NPZ archive when reducing stored size is useful, with additional work for compression and decompression.
3. Read a Small Text Table Deliberately
Text is convenient for inspecting and sharing rows, but a reader must know the delimiter, which lines are headers, and what type to use. loadtxt is suitable for a regular numeric table with the stated format. genfromtxt provides additional handling for missing entries.
StringIO is the text counterpart to BytesIO. It lets this example read a tiny CSV entirely in memory. On disk, the reader can take a path instead.
Keep the header separate from the numerical rows
Three quotes enclose a string spanning several lines. The delimiter says that commas separate fields. skiprows skips the one header line; it does not turn those names into automatic labels on the returned array.
import numpy as np
from io import StringIO
csv = StringIO("""temperature,humidity
12,50
18,60
""")
features = np.loadtxt(csv, delimiter=",", skiprows=1, ndmin=2)
print(features.tolist())
print(features.shape)[[12.0, 50.0], [18.0, 60.0]]
(2, 2)The feature names still need to be stored separately. ndmin=2 keeps a table-shaped result even if a future report contains just one numerical row.
4. Record What a File Format Cannot Infer
A round-trip check should compare the loaded shape, dtype, values, and label ordering with the originals. Units, feature meanings, random settings, and library versions belong in the experiment record too. A saved array cannot reconstruct assumptions that were never recorded.
When writing text with savetxt, the format controls precision. A small number of decimal places may be fine for a human report, but it deliberately discards information needed for an exact numerical replay.
Show the difference between a report and an exact record
We write two decimal places to a text file in memory, then reload it. allclose can accept the stated display error, while exact equality correctly reports that the original values were not fully preserved.
import numpy as np
from io import StringIO
original = np.array([[12.345,50.],[18.765,60.]])
text_file = StringIO()
np.savetxt(text_file, original, delimiter=",", fmt="%.2f")
text_file.seek(0)
restored = np.loadtxt(text_file, delimiter=",", ndmin=2)
print(restored.shape == original.shape)
print(np.array_equal(restored, original))
print(np.allclose(restored, original, rtol=0, atol=0.01))True
False
TrueThat loss of precision was a choice made by the writer. Use a native array file when exact preservation of the stored numerical entries is required.
5. Loading Successfully Is Only the First Check
A file can load and still be the wrong version, have the wrong column order, or contain the wrong dtype. Compare a saved and loaded array's shape, dtype and values. Then check its meaning against the metadata that names its columns and units.
For numerical arrays, prefer formats that preserve these numerical properties directly. Avoid object arrays when plain numeric or string arrays express the data. Loading pickled objects requires trust in the file and is unnecessary for the measurements here.
Check a numerical round trip in memory
BytesIO stands in for a file so this example works in the browser without choosing a local path.
import numpy as np
import io
original = np.array([[12., 50.], [18., 65.]], dtype=np.float64)
buffer = io.BytesIO()
np.save(buffer, original, allow_pickle=False)
buffer.seek(0)
restored = np.load(buffer, allow_pickle=False)
print(restored.shape == original.shape)
print(restored.dtype == original.dtype)
print(bool(np.array_equal(restored, original)))True
True
TrueThese checks establish numerical preservation. A companion description must still say that the columns are temperature and humidity, in that order.
6. Save the Information Needed to Use the Array Tomorrow
Imagine opening this dataset in a fresh Python process a month from now. Would we still know which column is temperature, what its units are, and how a missing reading was recorded? Keep those details beside the arrays, along with the period the records came from. For a fitted transformation, save the training reference in its feature order too.
Give changes to the file layout a version that the loader can check. If a version is unsupported, report that explicitly. Guessing the meaning of an unfamiliar two-column file could give us convincing numbers for the wrong calculation.
Check yourself
Questions on Parts 1 to 6
Question 1 of 4
What must happen before reading this in-memory file from its start?
Use this setup. The code is given; its result is for you to work out.
import numpy as np
from io import BytesIO
file = BytesIO()
np.save(file, np.array([12.,18.]), allow_pickle=False)Try it with Botie
Build with Botie
Write and run Python