This chapter reviews the Python programming foundations from STAT 201 that you will use in STAT 303-1. Use your existing STAT 201 Python setup: the executable examples need only Python and its standard library. No data science packages are required yet.
Begin with the diagnostic. If you can explain your answers, skim the corresponding sections; otherwise, work through the examples and checks. For a fuller explanation of prerequisite material, return to the STAT 201 book.
By the end of this review, you should be able to trace assignments, choose conditions and loops, write functions that return reusable results, import a standard-library module, distinguish attributes from methods, and interpret common error messages.
Check Your Starting Point
Without running the code, predict each result. Then test your predictions in a scratch cell.
After reading = "18.5", what is type(reading)? How would you add 2 to its numerical value?
What is the difference between label.upper and label.upper() when label = "north"?
What does NameError: name 'reading' is not defined tell you?
NoteDiagnostic answers and where to review
str; use float(reading) + 2, which gives 20.5. Review Variables, Types, and Expressions.
False. Review Conditions and Logic.
1, 2, 3; the stop value is excluded. Review Loops and Iteration.
The function displays 6, but answer is None because there is no return statement. Review Functions and Return Values.
label.upper retrieves a method; label.upper() calls it and returns "NORTH". Review Objects, Attributes, and Methods.
Python cannot find that name in the current scope. Check spelling and whether the assignment has run. Review Understanding Python Errors.
Variables, Types, and Expressions
A variable name refers to an object. Assignment binds a name to an object; later assignment can bind the same name to a different object, even one of a different type.
Use descriptive snake_case names such as temperature_c and valid_count. Names are case-sensitive, cannot start with a digit, and cannot be reserved keywords such as for. Letters, digits, and underscores cover the names we will use; Python also permits many Unicode letters. Avoid hiding built-ins with names such as list, str, or sum.
temperature_c =18.5station_count =3is_valid =Truestation_name ="North"missing_reading =Nonefor value in [temperature_c, station_count, is_valid, station_name, missing_reading]:print(repr(value), type(value).__name__)
18.5 float
3 int
True bool
'North' str
None NoneType
Here, int, float, bool, and str are types; None is a special value whose type is NoneType. It commonly represents the absence of a value. A numeric zero can be a valid measurement and should not automatically be treated as missing.
Lists, tuples, sets, and dictionaries organize collections of values. Strings are immutable text sequences. We will review these structures in the next chapter.
Convert deliberately
Text that looks numeric is still text. float() and int() convert suitable values; str() creates a text representation. Conversion can fail when the content does not match the requested type.
The first operation adds numbers; the second joins strings. float("unknown") would raise ValueError. Likewise, int("18.5") fails: that text is not an integer literal. Decide what a value means before converting it.
Operators and assignment
Expression
Meaning
a + b, a - b, a * b
Addition, subtraction, multiplication
a / b
Division
a // b, a % b
Floor division and remainder
a ** b
Exponentiation
a == b, a != b
Equality and inequality
a < b, a <= b, a > b, a >= b
Ordered comparisons
= assigns a value; == compares values. Use parentheses to make a calculation’s intended order clear.
An f-string inserts values into text using {...}. The last assignment unpacks two values into two names. Ordinary unpacking requires matching counts; starred unpacking is reviewed in Data Structures.
Check: Set reading_text = "12.5". Convert it to a number, add 0.5, and display Adjusted reading: 13.0.
A condition determines which code runs. Indent the body consistently with four spaces. Configure your editor to insert spaces when you press Tab; mixing tabs and spaces can cause errors.
reading =105if reading isNone: status ="missing"elif0<= reading <=100: status ="within the expected range"else: status ="needs investigation"print(status)
needs investigation
Python checks branches in order and runs the first matching branch. The else branch runs if none match. The range here is an example rule, not a universal definition of valid data: a real analysis needs a rule appropriate to the measurement.
Use is None to check for the special missing-value placeholder. Use == to compare ordinary values. Checking for None first avoids trying to compare it with a number.
Combine conditions
and: both conditions must hold.
or: at least one condition must hold.
not: reverses a truth value.
Python short-circuits and and or: it evaluates the second expression only when needed.
Common false values include False, None, numeric zeros, and empty strings and collections. Other objects can define their own truth behavior; this is not an exhaustive list. bool() is a built-in function for checking truth values.
"0" is nonempty text, so it is true. For numerical measurements, if reading: cannot distinguish a valid zero from None. Use an explicit check that matches your question. Later, arrays and tables will require their own tools for combining conditions and checking missing data.
Predict → run → explain: Change reading in the first branching example to None, 0, and 101. Which branch runs each time? Why would if not reading: be unsuitable for detecting only missing readings?
NoteCheck your reasoning
None is missing, 0 is within the expected range, and 101 needs investigation. Both None and 0 are false in a truth test, so if not reading: groups together two different situations.
Loops and Iteration
Start with a for loop
A for loop visits each item in an iterable, such as a list, string, or range. You do not need a counter just to access each item.
The loop includes the valid zero. It excludes missing and out-of-range readings using an explicit rule. Checking the count before division handles the possibility of no valid readings.
Modify: Replace readings with [None, -2.0]. What should the program report? What happens if the guard before division is removed?
NoteExpected result
The count is 0 and the total is 0.0; no mean is printed. Without the guard, division by zero raises ZeroDivisionError. For a report, you could add an else branch displaying "No valid readings".
Use range() when you need integer steps
The stop value is always excluded. A positive step moves upward; a negative step moves downward. If the direction cannot reach any value before the stopping boundary, the range is empty. A step of zero is invalid.
For an item and its position, enumerate() is usually clearer than indexing through range(len(...)). Data Structures reviews that pattern in more detail.
break and continue
break exits the nearest enclosing loop. continue skips the remaining statements in the current iteration and starts the next iteration.
# Stop at the first reading that needs investigation.for reading in [18.5, 105.0, 21.0]:if reading >100:print(f"Investigate: {reading}")breakprint(f"Checked: {reading}")
Checked: 18.5
Investigate: 105.0
# Skip missing entries while keeping valid zeros.for reading in [18.5, None, 0.0]:if reading isNone:continueprint(f"Recorded: {reading}")
Recorded: 18.5
Recorded: 0.0
Predict: Why is 21.0 not checked in the first example? Why is 0.0 printed in the second?
NoteExplanation
break ends the first loop when 105.0 is encountered. In the second loop, only None triggers continue; zero remains a valid entry.
Use a while loop for a stopping condition
A while loop repeats while its condition is true. Identify what changes on each iteration and why the loop will eventually stop.
The following example is displayed for inspection, not included as an executable notebook cell. Do not paste it into a running cell unchanged.
factor =1while factor <=10:print(factor)# The value of factor never changes.
Repair it: Which line belongs inside the loop? If a loop runs unexpectedly, interrupt execution using your notebook’s stop/interrupt control, then inspect the condition and update before running it again.
NoteRepair
Add factor += 1 inside the loop. The condition eventually becomes false when factor reaches 11. A condition such as while factor > 0 would still not stop when repeatedly increasing a positive factor.
Functions and Return Values
A function gives a reusable operation a name. It can accept zero or more arguments. Defining a function does not run its body; calling it does.
return versus print
print() displays information. return sends a value back to the caller so another calculation can use it. A function that reaches its end without returning a value returns None.
def celsius_to_fahrenheit(celsius):"""Return a Celsius temperature converted to Fahrenheit."""return celsius *9/5+32converted = celsius_to_fahrenheit(20)print(f"Converted: {converted}")print(f"Five degrees higher: {converted +5}")
Converted: 68.0
Five degrees higher: 73.0
Parameters, keyword arguments, and defaults
A parameter is a name in a function definition. An argument is a value supplied in a call. Arguments may be supplied by position or by keyword. A parameter with a default may be omitted from the call.
def is_valid_reading(value, lower=0, upper=100):"""Return whether a numeric reading is present and within the limits."""return value isnotNoneand lower <= value <= upperprint(is_valid_reading(18.5))print(is_valid_reading(None))print(is_valid_reading(105, upper=120))print(is_valid_reading(value=-2, lower=-10, upper=40))
True
False
True
True
This function expects a number or None; it does not convert arbitrary text. Documenting that expectation helps the caller use it correctly. Keyword arguments make choices such as upper=120 visible at the call site.
Write: Define adjust_reading(value, offset=0) to return value + offset. Call it once with the default and once using a keyword argument.
Parameters and names assigned inside a function are normally local to that function. A function can read names from its module’s global scope, but passing required values as arguments makes dependencies easier to see.
station_label ="North"# Global name in this notebook.def display_station():print(station_label) # Reads the global name; does not declare a local one.def make_station_label(name): cleaned_label = name.strip().title() # Local name.return cleaned_labeldisplay_station()print(make_station_label(" south "))
North
South
cleaned_label is not made available outside make_station_label; its value is made available through return. Assignment to a name inside a function normally creates or updates a local binding rather than rebinding a global name. Shared mutable objects are a separate issue, reviewed in Data Structures.
NoteOptional review: flexible argument lists
In documentation, *args collects extra positional arguments into a tuple and **kwargs collects extra keyword arguments into a dictionary. The * and ** syntax gives these names their roles; the names themselves are conventions.
For this review, prioritize ordinary parameters, returned values, and keyword arguments. You do not need to design flexible interfaces before starting STAT 303-1.
Built-ins and Imports
Tool
Where it comes from
How you use it
len, sum, type, print
Built into Python
Call directly
math, datetime, statistics
Python standard library
Import; no separate package installation
NumPy, Pandas, Matplotlib, Seaborn
Third-party packages
Install in a project environment, then import
An import makes a module or selected names available to your program; it does not install a package.
import math imports a module, from statistics import mean imports a particular name, and import datetime as dt gives a module an alias. %b is the abbreviated month name in the current locale, so its displayed language can vary.
We will install third-party packages after the review, in Project Environments, Packages, and File Paths. Different projects can need different packages and versions; that motivates keeping their environments separate.
Objects, Attributes, and Methods
Names refer to objects. An object’s attributes are accessed using a dot; its methods are operations associated with the object. A method is itself an attribute that you can call.
observed_at = dt.datetime(2025, 9, 20, 11, 30)print(observed_at.year) # Read an attribute.print(observed_at.strftime("%Y-%m-%d")) # Call a method.station_label ="north"print(station_label.upper())print(station_label) # Strings are immutable.
2025
2025-09-20
NORTH
north
Parentheses call the method. station_label.upper() returns a new string; it does not change the existing string. Later, library objects will also expose attributes and methods. Learn to check what an operation returns and whether it changes its object.
Explain: Why does observed_at.year() raise TypeError?
NoteExplanation
observed_at.year is an integer attribute, not a callable method. Retrieve its value without parentheses. The error says that an int object is not callable.
Understanding Python Errors
Read the error type and message, then locate the indicated line in your code. Use the surrounding values and types to explain the cause before changing anything.
The following examples are shown as text so that running all executable cells does not intentionally fail.
Example
Error
What to investigate
print(undefined_reading)
NameError
Has that name been assigned? Is it spelled correctly?
"18.5" + 2
TypeError
Are these types compatible with the operation?
float("missing")
ValueError
Is this text a valid representation of a number?
10 / 0
ZeroDivisionError
Can the denominator be zero?
if True without a colon
SyntaxError
Is the statement structurally valid?
A notebook remembers earlier executed assignments. If a result seems inconsistent, restart the kernel and run cells from the beginning. This revised chapter’s executable cells are designed to complete in order without interactive input or intentional infinite loops.
Debug: A measurement arrives as reading_text = "18.5". The expression reading_text + 0.5 fails. Explain the cause and repair it. Would the same repair work for the text "missing"?
NoteExplanation and repair
The expression mixes a string and a float. For numeric text, use float(reading_text) + 0.5, which gives 19.0. float("missing") raises ValueError; the program needs an explicit rule for missing text rather than assuming every string is numeric.
Readiness Check
Try this task without looking at the solution. Use only ordinary Python.
For readings = [18.5, None, 21.0, 0.0, 105.0]:
Write a function that returns whether a reading is present and between 0 and 100 inclusive.
Use a loop to count and total valid readings.
Report their mean, or display "No valid readings" if the count is zero.
Explain why zero is included and None is excluded.
Repeat with [None, 105.0] to check the no-valid-data case.
NoteOne solution
def is_valid_reading(value, lower=0, upper=100):return value isnotNoneand lower <= value <= upperreadings = [18.5, None, 21.0, 0.0, 105.0]count =0total =0.0for value in readings:if is_valid_reading(value): count +=1 total += valueif count >0:print(f"Count: {count}; mean: {total / count:.2f}")else:print("No valid readings")
The first list produces Count: 3; mean: 13.17. The second produces No valid readings. Zero meets the range rule; None represents an absent value and is checked before numerical comparisons.