2.1 Why programming belongs in the learning process
A mathematical formula describes a calculation. A program makes the calculation repeatable, checks its inputs, and records its outputs. If a walking trial covers 12 meters in 10 seconds, its average speed is \(12/10=1.2\) meters per second. Calculating this once is easy. Calculating it consistently for many trials, identifying invalid durations, and retaining the association between a trial and its result requires a procedure.
Python is the language used for executable examples in this book. We begin with ordinary values and control structures so that later numerical code has a visible meaning. You do not need to learn every language feature before beginning machine learning. You do need to recognize what a program stores, how its steps are ordered, and how to investigate a result that looks wrong.
2.1.1 Learning objectives and prerequisites
After this chapter, you should be able to:
Store numbers, text, truth values, and collections using descriptive names.
Write decisions, repeated calculations, and small documented functions.
Read text records, convert text to numbers, and handle expected invalid input.
Separate a numerical procedure into inputs, calculations, checks, and output.
Explain the difference between a syntax error, an exception, and a logical error.
Read Chapter 1 first. Arithmetic is the only mathematical prerequisite. Every executable block below can run independently in a fresh Python session; repeated definitions are intentional. In the book, Quarto executes blocks in chapter order. Running a small block independently is also useful while debugging.
2.2 Step 1: Store values with names and units
A variable name refers to a value. In duration_seconds = 10.0, the equals sign means assignment: evaluate the expression on the right and bind its result to the name on the left. It does not state a mathematical identity that Python will maintain forever. After a reassignment, subsequent calculations use the new value.
Our names include units because 10 alone does not tell us whether a duration is in seconds, milliseconds, or minutes. A wrong unit can produce a plausible number without causing any language error.
Store the quantities used by the calculation.
Calculate speed from distance and duration.
Print the result with its unit and inspect the stored type.
# Step 1: Describe one synthetic walking trial using explicit units.# Measurements needed to calculate average speed.# Distance traveled during the trial, measured in meters.distance_meters =12.0# Elapsed time for the same trial, measured in seconds.duration_seconds =10.0# Step 2: Divide distance by duration to obtain meters per second.# This result summarizes motion over the complete trial.average_speed_meters_per_second = distance_meters / duration_seconds# Step 3: Display both the result and its Python type.print("Average speed (m/s):", average_speed_meters_per_second)print("Stored type:", type(average_speed_meters_per_second).__name__)
Average speed (m/s): 1.2
Stored type: float
The result is 1.2 with type float. The calculation is an example of a derived feature: a measurement calculated from other measurements. It is not a trained model.
2.2.1 Types give values meaning
A type determines the operations a value supports. Common types are int for whole numbers, float for approximate real-number calculations, str for text, and bool for True or False. None indicates the absence of a value; it is different from zero. The text "10.0" is a string, whereas 10.0 is a floating-point number. Convert numeric text deliberately with float(...) when a calculation requires a number.
Python uses +, -, *, /, and ** for addition, subtraction, multiplication, division, and powers. Parentheses make grouping explicit. / performs true division; // performs floor division. == tests equality, while = assigns a name. These basic operations and the language’s indexing conventions are described in the Python introduction.
Names are case sensitive: trial_count and Trial_Count are different. Prefer names such as sampling_frequency_hertz to abbreviations whose meaning you will forget. Do not name a variable list, sum, or float; those names already identify useful Python tools.
2.3 Step 2: Organize several values
A collection stores related items. Choose the structure according to the relationship you need to preserve.
Structure
Example role
Key property
list
Trial durations in acquisition order
Ordered and changeable
tuple
A fixed pair of coordinates
Ordered; its item references cannot be replaced
dict
One trial with named fields
Maps keys to values
set
Distinct participant identifiers
Unique elements; do not rely on display order
A list index is a position starting at zero. For a list with three items, valid nonnegative indices are 0, 1, and 2. A slice values[0:2] selects positions 0 and 1; the stop position is excluded. A dictionary key is a field name, such as "duration_seconds", rather than a row position.
# Step 1: Keep three synthetic durations in trial order.# Each list item is one duration in seconds.trial_durations_seconds = [10.0, 12.0, 8.0]# Step 2: Represent one record using named fields.# Named fields reduce confusion when a record contains different quantities.trial_record = {"participant_id": "P01","distance_meters": 12.0,"duration_seconds": 10.0,}# Step 3: Inspect position-based and name-based access.print("First duration (s):", trial_durations_seconds[0])print("First two durations (s):", trial_durations_seconds[0:2])print("Participant:", trial_record["participant_id"])# Step 4: Collect unique identifiers without assuming their order.# Repeated participant IDs should count as one distinct participant.participant_ids = ["P01", "P01", "P02"]# A set removes repeated identifiers for this counting task.unique_participant_ids =set(participant_ids)print("Number of trials:", len(participant_ids))print("Number of distinct participants:", len(unique_participant_ids))
First duration (s): 10.0
First two durations (s): [10.0, 12.0]
Participant: P01
Number of trials: 3
Number of distinct participants: 2
Three records from two people do not provide three independent people. A useful data structure helps preserve that distinction, which later matters when splitting a dataset for evaluation. It does not by itself solve statistical dependence.
Lists are mutable: operations such as append change the existing object. Assignment of a list to another name does not copy its contents. Use original_list.copy() when you need a separate shallow list of simple numeric values. Dictionaries and nested lists also require attention to shared objects. The Python data structures tutorial explains collection methods and copying behavior.
2.4 Step 3: Make decisions before calculating
An if statement runs an indented block only when its condition is true. elif checks another condition when previous ones were false; else covers the remaining case. Four spaces identify the block in this book. Indentation is part of Python’s syntax, not decoration.
A speed calculation needs a positive duration. Zero causes division by zero. A negative duration may be arithmetically usable but has no meaning for this measurement. We should decide what valid input means before calculating.
# Step 1: Store a deliberately invalid synthetic duration.# Measurements for a demonstration of input checking.# The distance is physically nonnegative.distance_meters =12.0# A zero duration cannot support an average-speed calculation.duration_seconds =0.0# Step 2: Reject invalid values before performing division.if duration_seconds <=0:print("Cannot calculate speed: duration must be positive.")elif distance_meters <0:print("Cannot calculate speed: distance must be nonnegative.")else:# This branch is reached only when both input checks have passed. average_speed_meters_per_second = distance_meters / duration_secondsprint("Average speed (m/s):", average_speed_meters_per_second)
Cannot calculate speed: duration must be positive.
and, or, and not combine ordinary Boolean conditions. For example, a duration is in the interval from 5 through 20 seconds when duration_seconds >= 5 and duration_seconds <= 20. In later NumPy examples, arrays require different elementwise operators; do not transfer scalar and directly to an array.
2.5 Step 4: Repeat the same calculation with a loop
A for loop visits each item of a collection in order. A running total is a variable updated after each item. Before the loop, choose its initial value. After the loop, use the number of items to turn the total into a mean.
For durations \(10\), \(12\), and \(8\) seconds, the sum is \(30\) seconds and the arithmetic mean is \(30/3=10\) seconds. Here the loop makes that sequence visible.
# Step 1: Define a nonempty list of synthetic trial durations.# Durations in seconds for three trials.trial_durations_seconds = [10.0, 12.0, 8.0]# Step 2: Accumulate the duration total one item at a time.# Start at zero because no measurements have yet been added.total_duration_seconds =0.0for duration_seconds in trial_durations_seconds:# Add this trial to the total from the preceding trials. total_duration_seconds = total_duration_seconds + duration_seconds# Step 3: Divide by the item count to obtain the mean.# The count is needed because the total alone depends on dataset size.number_of_trials =len(trial_durations_seconds)# This example has three items, so division by the count is valid.mean_duration_seconds = total_duration_seconds / number_of_trialsprint("Total duration (s):", total_duration_seconds)print("Mean duration (s):", mean_duration_seconds)# Step 4: Attach readable trial numbers while inspecting the values.# enumerate supplies a counter and an item; start=1 is only for display.for trial_number, duration_seconds inenumerate(trial_durations_seconds, start=1):print("Trial", trial_number, "duration (s):", duration_seconds)
range(3) supplies the integers 0, 1, and 2 when positions are necessary. Iterating directly over measurements is clearer when only their values matter. A while loop instead repeats while a condition remains true. Its body must change the relevant state or otherwise reach a stopping rule. For example, an optimization loop might stop when improvement becomes small or a maximum iteration count is reached. Do not use an unlimited while True loop as a first attempt at numerical optimization. See the official control flow tutorial for these constructs.
2.6 Step 5: Give a reusable calculation a function
A function packages a task behind a name. Its parameters are local names for inputs. return sends a result back to the caller. Printing displays information but does not return that information for further arithmetic.
The following function defines a small contract: finite distance in meters, finite positive duration in seconds, and a returned speed in meters per second. Checking finiteness excludes nan (not a number) and infinity. Type hints document intended numerical inputs; Python does not automatically enforce those hints. The function raises ValueError when a numeric input violates the measurement assumptions.
Describe the function’s purpose and assumptions in a docstring.
Check the input values.
Compute and return the speed.
Call the function using names that show the units.
# Step 1: Import a standard-library check for finite numbers.from math import isfinite# Step 2: Define the reusable calculation and its input contract.def calculate_average_speed(distance_meters: float, duration_seconds: float) ->float:"""Return average speed in meters per second. Parameters are finite numeric distance in meters and duration in seconds. Distance must be nonnegative and duration must be positive. Raise ValueError if these physical assumptions are violated. """# Reject undefined or infinite measurements before applying the formula.ifnot isfinite(distance_meters) ornot isfinite(duration_seconds):raiseValueError("Distance and duration must be finite numbers.")if distance_meters <0:raiseValueError("Distance must be nonnegative.")if duration_seconds <=0:raiseValueError("Duration must be positive.")# The unit calculation is meters divided by seconds. average_speed_meters_per_second = distance_meters / duration_secondsreturn average_speed_meters_per_second# Step 3: Call the function with named arguments to make units visible.# Store the returned number for later calculations or inspection.trial_speed_meters_per_second = calculate_average_speed( distance_meters=12.0, duration_seconds=10.0,)print("Returned speed (m/s):", trial_speed_meters_per_second)
Returned speed (m/s): 1.2
Variables defined inside a function normally belong to that call. A function should receive measurements through parameters rather than silently depend on unrelated variables outside it. This makes its behavior easier to check. Small functions also isolate errors: a failed speed calculation is easier to investigate than a single function that loads data, trains a model, plots everything, and writes a report.
2.7 Step 6: Read and write text without modifying a dataset
A file is a sequence of bytes or text that a program can read. A file-like object provides similar methods without necessarily using a disk file. StringIO creates a text stream in memory. This lets us practice reading and writing without overwriting a real dataset. Its methods follow Python’s text stream interface.
Comma-separated values (CSV) store rows as text. A number read by the default CSV reader is initially text and requires conversion. The standard-library csv module understands CSV quoting; manually splitting every line at commas is unreliable when a field itself contains a quoted comma. See the CSV module reference.
# Step 1: Import tools for a CSV file held entirely in memory.import csvfrom io import StringIO# Step 2: Store synthetic records with an explicit column header.# This text represents a tiny CSV file; no real participant data are used.csv_text ="trial_id,duration_seconds\nT01,10.0\nT02,12.0\n"# An empty list will collect converted numeric durations.loaded_durations_seconds = []# Step 3: Read records and convert each duration from text to a number.# The with block closes the stream automatically after use.with StringIO(csv_text) as input_stream:# DictReader uses the header row as dictionary keys. csv_reader = csv.DictReader(input_stream)for trial_record in csv_reader:# Convert this field so arithmetic will use a number, not text. duration_seconds =float(trial_record["duration_seconds"]) loaded_durations_seconds.append(duration_seconds)# Step 4: Write a short summary into a second in-memory stream.with StringIO(newline="") as output_stream:# csv.writer handles delimiters and line endings for the output rows. csv_writer = csv.writer(output_stream) csv_writer.writerow(["number_of_trials", "total_duration_seconds"]) csv_writer.writerow([len(loaded_durations_seconds), sum(loaded_durations_seconds)])# Retrieve the produced text before the with block closes the stream. summary_csv_text = output_stream.getvalue()print("Loaded durations (s):", loaded_durations_seconds)print(summary_csv_text)
For an actual text file, the corresponding pattern is with open(path, "r", encoding="utf-8") as input_stream:. Mode "r" reads. Mode "w" writes and can replace an existing file, so choose destinations deliberately. Relative paths are resolved from the program’s current working directory, which may differ from the folder you see in an editor. See Python input and output for file modes and context managers.
2.8 Step 7: Handle expected errors and investigate unexpected ones
A syntax error means Python cannot parse the program: examples include a missing colon or mismatched bracket. An exception occurs during execution, such as converting "missing" to a number. A logical error produces a result that violates the intended task even though execution succeeds, such as dividing seconds by meters while labeling the result meters per second.
Use try and except around the operation expected to fail. Catch the specific exception you understand and decide how the program should respond. Catching every exception and continuing can conceal damaged data or a programming mistake. The Python exceptions tutorial describes exception handling and raising errors.
# Step 1: Keep raw duration fields exactly as they might arrive from text.# The middle field deliberately contains an invalid numeric value.raw_duration_fields = ["10.0", "missing", "8.0"]# Accepted measurements will be stored separately from rejected records.valid_durations_seconds = []# Count rejected fields so their disappearance is visible in the output.rejected_field_count =0# Step 2: Convert each field while reporting expected conversion failures.for raw_duration_field in raw_duration_fields:try:# float raises ValueError for the deliberately invalid text. duration_seconds =float(raw_duration_field)exceptValueError: rejected_field_count = rejected_field_count +1print("Rejected nonnumeric duration:", repr(raw_duration_field))else:# Keep successfully converted values; validity checks would follow. valid_durations_seconds.append(duration_seconds)# Step 3: Inspect both accepted values and the rejection count.print("Converted durations (s):", valid_durations_seconds)print("Rejected fields:", rejected_field_count)
Successful conversion alone does not guarantee a valid measurement: float("nan") is allowed, and a negative value is numeric. Apply the measurement contract after conversion. Also retain record identifiers and rejection reasons in real workflows. Simply discarding rows can change the population represented by the data.
When an unexpected error occurs, use this debugging sequence:
Read the final exception type and message in the traceback.
Find the last relevant line in your code, then inspect the values entering it.
Check types, collection lengths, units, and assumptions.
Reproduce the problem with the smallest relevant input.
Correct the cause and rerun from a fresh session.
For numerical results, calculate one tiny example by hand. A program can run perfectly while solving the wrong problem. Later chapters add numerical assertions; these verify specific expectations but do not replace reasoning about the experiment.
2.9 Common mistakes and how to recognize them
Symptom
Likely cause
First check
NameError
Misspelled or undefined name
Definition and execution order
TypeError during arithmetic
Text and numbers mixed
type(...) and conversion
IndexError
Position outside a list
Length and zero-based positions
KeyError
Dictionary field absent
Available keys and spelling
Unexpectedly changed list
Two names share one object
Whether a copy was intended
Plausible value with wrong magnitude
Wrong units or formula
Hand calculation and units
Different result after restarting
Hidden session state
Independent inputs and imports
Do not repair a missing field by inserting an arbitrary zero. Zero is a real measurement with a meaning; missingness has a different meaning. The data chapter develops that distinction.
2.10 Exercises and worked answers
2.10.1 1. Assignment versus equality
Set duration_seconds = 10.0, calculate speed for 12 meters, then reassign duration_seconds = 6.0. Does the previously calculated speed change automatically?
Answer: No. The stored speed remains 1.2 until the division is executed again. Recomputing produces 2.0 m/s. Assignment stores a result at that point in the program; it is not a spreadsheet dependency relationship.
2.10.2 2. Mean speed versus speed from totals
Two trials each cover 12 meters and take 6 and 12 seconds. Compare the mean of trial speeds with total distance divided by total time.
Answer: Trial speeds are \(2\) and \(1\) m/s, so their unweighted mean is \(1.5\) m/s. Combined speed is \(24/18=4/3\) m/s. The former gives each trial equal weight. The latter weights trial speeds by duration. Choose the quantity that matches the question before writing code.
2.10.3 3. A reusable function
Design a function that returns the mean of a nonempty list of finite durations. What should it do for an empty list?
Answer: It should reject the empty input with a clear ValueError, rather than dividing by zero. It should check each duration, add them to a running total, and divide by the list length. For [10.0, 12.0, 8.0], the result is 10.0 seconds. If the contract requires positive durations, zero and negative values also need rejection.
2.10.4 4. Do not hide errors
Why is replacing every failed conversion by 0.0 risky?
Answer: It converts an unknown duration into a known zero, distorts summaries, and may cause later divisions to fail. Record the problem and choose a justified missing-data procedure instead. A clear failure is often more useful than a silent incorrect result.
2.10.5 5. Interview question: print or return?
Why should a calculation function usually return a number rather than only print it?
Answer: A returned value can be checked, combined with other results, stored, or plotted. A printed string is primarily for display. Keeping calculation and display separate makes code easier to reuse.
2.11 One-page revision summary
Start with the quantity you want, its inputs, and its units.
Assignment binds a name to the result of an expression; == compares values.
Numbers, text, Boolean values, and None represent different kinds of information.