Python from zero — values, collections, control flow, functions, files and errors
Everything an analyst needs from core Python before pandas — explained with business examples and real outputs.
This lesson builds on: Aggregations, GROUP BY and HAVING.
- Use variables and the core data types (int, float, str, bool)
- Choose between lists, tuples, dictionaries and sets
- Control flow with if/elif/else, for and while loops
- Write functions with parameters and return values; import modules
- Read and write files safely and handle errors with try/except
Setting up
Install Python 3.11+ (python.org or Anaconda) and use VS Code or Jupyter notebooks. Every code block below was executed; the box underneath shows its real output. You can paste blocks into a notebook and run them yourself — download the Orion CSV files from the Datasets page into a folder and run your notebook from that folder.
Variables and data types
A variable is a name that refers to a value. Python infers the type from the value.
store = "Baku Central" # str (text)
orders = 1250 # int (whole number)
avg_order_value = 89.95 # float (decimal)
is_open = True # bool (True/False)
revenue = orders * avg_order_value
print(type(store), type(orders), type(avg_order_value), type(is_open))
print("Revenue:", revenue)
print(f"{store} revenue: {revenue:,.2f}")
<class 'str'> <class 'int'> <class 'float'> <class 'bool'>
Revenue: 112437.5
Baku Central revenue: 112,437.50
#starts a comment.- An f-string (
f"...") inserts values;:,.2fformats with thousands separators and 2 decimals. - Floats are binary approximations — for money in production code use
decimal.Decimalor integer cents.
print(0.1 + 0.2)
print(round(0.1 + 0.2, 2))
print(7 / 2, 7 // 2, 7 % 2, 2 ** 10)
0.30000000000000004
0.3
3.5 3 1 1024
/ is true division, // integer division, % remainder, ** power.
Strings
city = " baku "
print(city.strip().title())
email = "aylin.aliyev12@example.com"
print(email.split("@")[1])
print(email.upper().startswith("AYLIN"), len(email))
print("Orion" + "Stats", "-" * 10)
Baku
example.com
True 26
OrionStats ----------
Strings are immutable: methods return new strings. Indexing starts at 0; email[0:5] is a slice of characters 0–4.
Lists, tuples, dictionaries and sets
| Type | Example | Ordered | Mutable | Typical use |
|---|---|---|---|---|
| list | [120, 95, 143] |
yes | yes | sequence of values |
| tuple | ("Baku", "East") |
yes | no | fixed record, dict keys |
| dict | {"Baku": 621, "Ganja": 794} |
insertion order | yes | key → value lookup |
| set | {"card", "cash"} |
no | yes | unique values, membership |
monthly_orders = [868, 682, 1120, 938]
monthly_orders.append(1109)
print(monthly_orders, len(monthly_orders), sum(monthly_orders), max(monthly_orders))
print(monthly_orders[0], monthly_orders[-1], monthly_orders[1:3])
store_region = {"Baku Central": "East", "Ganja": "West"}
store_region["Tbilisi"] = "West"
print(store_region["Ganja"], store_region.get("Ankara", "unknown"))
for name, region in store_region.items():
print(name, "->", region)
methods = ["card", "cash", "card", "wallet", "card"]
print(set(methods), "bnpl" in set(methods))
[868, 682, 1120, 938, 1109] 5 4717 1120
868 1109 [682, 1120]
West unknown
Baku Central -> East
Ganja -> West
Tbilisi -> West
{'card', 'cash', 'wallet'} False
.get(key, default) avoids a KeyError when the key is missing. Sets remove duplicates instantly — a quick way to count distinct values.
Conditions
def classify(attainment):
if attainment >= 1.0:
return "On or above plan"
elif attainment >= 0.95:
return "Slightly below plan"
else:
return "Below plan"
for a in [1.107, 0.968, 0.947]:
print(a, classify(a))
1.107 On or above plan
0.968 Slightly below plan
0.947 Below plan
Indentation (4 spaces) defines blocks — it is syntax, not style. Like SQL's CASE, the first true branch wins.
Loops
targets = {"Sumqayit": 71900, "Ankara": 124900, "Tbilisi": 103200}
actuals = {"Sumqayit": 79626, "Ankara": 118288, "Tbilisi": 99884}
below = []
for store, plan in targets.items():
pct = actuals[store] / plan
if pct < 1:
below.append(store)
print(f"{store:<10} {pct:6.1%}")
print("Below plan:", below)
# list comprehension: build a list in one expression
pct_list = [round(actuals[s] / targets[s] * 100, 1) for s in targets]
print(pct_list)
Sumqayit 110.7%
Ankara 94.7%
Tbilisi 96.8%
Below plan: ['Ankara', 'Tbilisi']
[110.7, 94.7, 96.8]
These figures are the real 2025 plan and actuals for three Orion stores. while loops repeat until a condition is false — rarely needed in analysis, where you loop over data or (better) use pandas vectorised operations.
Functions and modules
A function packages logic so it can be reused and tested:
def net_revenue(quantity, unit_price, discount_pct=0.0):
"""Net revenue of an order line."""
if quantity < 0 or not 0 <= discount_pct < 1:
raise ValueError("invalid quantity or discount")
return quantity * unit_price * (1 - discount_pct)
print(net_revenue(2, 50))
print(net_revenue(2, 50, discount_pct=0.2))
import math, statistics
from datetime import date, timedelta
print(math.sqrt(144), statistics.median([3, 1, 4, 1, 5]))
print(date(2025, 3, 1) - timedelta(days=1))
100.0
80.0
12.0 3
2025-02-28
- Parameters can have defaults (
discount_pct=0.0); call with keyword arguments for clarity. - The docstring documents the function.
importloads modules from the standard library (math, statistics, datetime, csv, json, pathlib) or installed packages (pandas, numpy).
Files
import csv
from pathlib import Path
path = Path("retail/stores.csv")
with path.open(newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
print(len(rows), "stores")
print(rows[0]["store_name"], rows[0]["city"])
out = Path("/tmp/open_stores.txt")
with out.open("w", encoding="utf-8") as f:
for r in rows:
if not r["close_date"]:
f.write(r["store_name"] + "\n")
print(out.read_text().splitlines()[:3])
9 stores
Orion Baku Central Baku
['Orion Baku Central', 'Orion Baku Riverside', 'Orion Ganja']
with closes the file automatically, even if an error occurs. pathlib.Path handles paths on Windows, macOS and Linux.
Error handling
def safe_ratio(a, b):
try:
return a / b
except ZeroDivisionError:
return None
except TypeError as e:
print("Bad input:", e)
return None
print(safe_ratio(10, 4), safe_ratio(10, 0), safe_ratio("10", 2))
Bad input: unsupported operand type(s) for /: 'str' and 'int'
2.5 None None
Catch specific exceptions you can handle; let unexpected ones surface. A bare except: that hides all errors is the Python version of IFERROR(…, 0).
In data pipelines, raise ValueError(f"Missing column {col} in {file}") at the point of failure saves hours compared with a cryptic error ten steps later.
Practice
What does 7 // 2 evaluate to in Python?
regions = {"Baku": "East"}. What does regions.get("Ankara", "unknown") return?
Given m = [868, 682, 1120, 938, 1109], what is m[-1]? (Enter the number.)
Why is a bare except: that returns 0 on any error a bad idea in a data pipeline?
Summary
- Core types:
int,float,str,bool; format with f-strings; beware float rounding. - Lists (ordered), tuples (fixed), dicts (lookup), sets (unique).
if/elif/else,forloops and comprehensions control flow; indentation is syntax.- Functions with defaults and docstrings; modules via
import. - Use
withfor files and catch specific exceptions.