Python Basics

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.

  1. After reading = "18.5", what is type(reading)? How would you add 2 to its numerical value?
  2. What does 0 <= 105 <= 100 evaluate to?
  3. What values does range(1, 4) produce?
  4. What is stored in answer below?
def double(value):
    print(value * 2)

answer = double(3)
  1. What is the difference between label.upper and label.upper() when label = "north"?
  2. What does NameError: name 'reading' is not defined tell you?
  1. str; use float(reading) + 2, which gives 20.5. Review Variables, Types, and Expressions.
  2. False. Review Conditions and Logic.
  3. 1, 2, 3; the stop value is excluded. Review Loops and Iteration.
  4. The function displays 6, but answer is None because there is no return statement. Review Functions and Return Values.
  5. label.upper retrieves a method; label.upper() calls it and returns "NORTH". Review Objects, Attributes, and Methods.
  6. 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.5
station_count = 3
is_valid = True
station_name = "North"
missing_reading = None

for 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.

reading_text = "18.5"
reading_value = float(reading_text)
print(reading_value + 2)
print(reading_text + "2")
20.5
18.52

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.

temperature_c = 20
temperature_f = temperature_c * 9 / 5 + 32
print(f"{temperature_c} degrees C = {temperature_f} degrees F")

lower_limit, upper_limit = 0, 100
print(lower_limit, upper_limit)
20 degrees C = 68.0 degrees F
0 100

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.

reading_text = "12.5"
adjusted_reading = float(reading_text) + 0.5
print(f"Adjusted reading: {adjusted_reading}")

Conditions and Logic

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 = 105

if reading is None:
    status = "missing"
elif 0 <= 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.

reading = None
is_acceptable = reading is not None and 0 <= reading <= 100
print(is_acceptable)

reading = 105
outside_range = reading < 0 or reading > 100
print(outside_range)
print(not outside_range)
False
True
False

Truth values are not data-quality rules

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.

print(bool(0))
print(bool(None))
print(bool(""))
print(bool("0"))
False
False
False
True

"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?

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.

readings = [18.5, None, 21.0, -2.0, 0.0]
valid_count = 0
valid_total = 0.0

for reading in readings:
    if reading is not None and 0 <= reading <= 100:
        valid_count += 1
        valid_total += reading

print(f"Valid readings: {valid_count}")
print(f"Total: {valid_total}")
if valid_count > 0:
    print(f"Mean: {valid_total / valid_count:.2f}")
Valid readings: 3
Total: 39.5
Mean: 13.17

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?

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.

print(list(range(4)))
print(list(range(1, 4)))
print(list(range(1, 8, 2)))
print(list(range(3, 0, -1)))
[0, 1, 2, 3]
[1, 2, 3]
[1, 3, 5, 7]
[3, 2, 1]

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}")
        break
    print(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 is None:
        continue
    print(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?

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.

# Compute 10! = 1 * 2 * ... * 10.
n = 10
factorial = 1
factor = 1

while factor <= n:
    factorial *= factor
    factor += 1

print(f"{n}! = {factorial}")
10! = 3628800

Diagnose an infinite loop without running it

The following example is displayed for inspection, not included as an executable notebook cell. Do not paste it into a running cell unchanged.

factor = 1
while 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.

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 show_fahrenheit(celsius):
    print(celsius * 9 / 5 + 32)

shown_result = show_fahrenheit(20)
print(f"Stored result: {shown_result}")
68.0
Stored result: None
def celsius_to_fahrenheit(celsius):
    """Return a Celsius temperature converted to Fahrenheit."""
    return celsius * 9 / 5 + 32

converted = 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 is not None and lower <= value <= upper

print(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.

def adjust_reading(value, offset=0):
    return value + offset

print(adjust_reading(18.5))              # 18.5
print(adjust_reading(18.5, offset=0.5))  # 19.0

Local names and global names

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_label

display_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.

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.

def describe_call(*args, **kwargs):
    print(args)
    print(kwargs)

describe_call(18.5, 21.0, unit="C")
# (18.5, 21.0)
# {'unit': 'C'}

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
from statistics import mean
import datetime as dt

print(math.sqrt(81))
print(mean([18.5, 21.0, 0.0]))
observed_at = dt.datetime(2025, 9, 20, 11, 30)
print(observed_at.strftime("%b %d, %Y"))
9.0
13.166666666666666
Sep 20, 2025

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?

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"?

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]:

  1. Write a function that returns whether a reading is present and between 0 and 100 inclusive.
  2. Use a loop to count and total valid readings.
  3. Report their mean, or display "No valid readings" if the count is zero.
  4. Explain why zero is included and None is excluded.
  5. Repeat with [None, 105.0] to check the no-valid-data case.
def is_valid_reading(value, lower=0, upper=100):
    return value is not None and lower <= value <= upper

readings = [18.5, None, 21.0, 0.0, 105.0]
count = 0
total = 0.0
for value in readings:
    if is_valid_reading(value):
        count += 1
        total += value

if 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.

Before moving on, confirm that you can:

Next, review Data Structures, including sequences, mappings, iteration, and shared references. Then continue to Setting Up for Data Science.