Python for Data 3: if/elif/else, for and while Loops, and Comprehensions
These are the notes for Data Analytics with Python, episode 6. Until now every program ran each line once, top to bottom. Here programs learn to decide (if, elif, else) and to repeat (for and while loops, range, enumerate, zip, break, continue), to loop over dictionaries, and to build whole lists and dicts in one line with comprehensions and the conditional expression. Every term is defined the first time it appears and collected in the Glossary. Every program below was run for real (Python 3.13); outputs are unedited.
Making decisions: if
Ramesh's target is 300 cups. He wants a warning on slow days and silence on good days. A program that runs every line every time can't do that: it needs to choose.
A condition is any expression that gives True or False (like cups < 300). The if statement runs a block of code only when its condition is True. A block is a group of lines indented (pushed right, by convention 4 spaces) under a line that ends with a colon.
cups = 280
if cups < 300:
print("Slow day: try a samosa offer")
print("Report done")
Slow day: try a samosa offer
Report done
With cups = 410 the output is only Report done: the condition is False, so the indented block is skipped. The last line isn't indented, so it always runs.
Indentation is grammar in Python, not decoration. Two classic errors:
cups = 280
if cups < 300
print("Slow day")
File "main.py", line 2
if cups < 300
^
SyntaxError: expected ':'
cups = 280
if cups < 300:
print("Slow day")
File "main.py", line 3
print("Slow day")
^^^^^
IndentationError: expected an indented block after 'if' statement on line 2
if, elif, else
if: test the first condition.elif(short for "else if"): tested only if every condition above it wasFalse.else: runs when none of the conditions wereTrue.
Python checks top to bottom and runs exactly one block: the first one whose condition is True. Order conditions from strictest to loosest.
cups = 450
if cups >= 400:
label = "busy"
elif cups >= 300:
label = "normal"
else:
label = "slow"
print(cups, label)
450 busy
With cups = 300: 300 normal. If the >= 300 test came first, 450 would wrongly be labelled normal.
for loops
Copying an if block for 7 days (or 365) is unreadable and error-prone. A for loop runs the same block once for every item in a collection, like a ticket checker who has one instruction ("check the ticket") and repeats it for every passenger. Each run is an iteration; the loop variable (cups below) holds the current item.
week = [320, 280, 410, 300, 350, 390, 450]
for cups in week:
if cups < 300:
print(cups, "slow")
else:
print(cups, "ok")
print("Loop finished")
320 ok
280 slow
410 ok
300 ok
350 ok
390 ok
450 ok
Loop finished
The print lines are indented twice: they belong to the if/else, which belongs to the loop. Loop finished isn't indented, so it runs once, after the loop. A for loop works on any iterable: lists, tuples, strings (letter by letter), sets, dicts.
range
range(stop), range(start, stop) and range(start, stop, step) make a sequence of whole numbers, starting at start (default 0) and stopping before stop, like slices.
for n in range(5):
print(n, end=" ")
print()
print(list(range(1, 8)))
print(list(range(0, 50, 10)))
0 1 2 3 4
[1, 2, 3, 4, 5, 6, 7]
[0, 10, 20, 30, 40]
end=" " makes print end with a space instead of a new line; print() alone prints a new line. list(range(...)) turns the range into a list so you can see it.
enumerate and zip instead of index loops
Looping over positions with range(len(...)) invites off-by-one bugs:
week = [320, 280, 410]
for i in range(len(week) + 1):
print(week[i])
320
280
410
Traceback (most recent call last):
File "main.py", line 3, in <module>
print(week[i])
~~~~^^^
IndexError: list index out of range
Cleaner tools:
zip(a, b)walks two (or more) lists side by side, giving one pair (a tuple) at a time.enumerate(items, start=1)gives a counter with each item.
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
cups = [320, 280, 410, 300, 350, 390, 450]
for day, c in zip(days, cups):
print(day, c)
for n, day in enumerate(days[:3], start=1):
print(f"Day {n}: {day}")
Mon 320
Tue 280
Wed 410
Thu 300
Fri 350
Sat 390
Sun 450
Day 1: Mon
Day 2: Tue
Day 3: Wed
for day, c in ... unpacks each pair into two variables. zip stops at the shortest list.
Accumulators: totals inside a loop
An accumulator is a variable that collects a result across a loop (a running total or count). x += 5 is augmented assignment, shorthand for x = x + 5 (also -=, *=).
cups = [320, 280, 410, 300, 350, 390, 450]
weather = ["sunny", "rainy", "sunny", "rainy",
"sunny", "sunny", "sunny"]
rain_total = 0
rain_days = 0
for c, w in zip(cups, weather):
if w == "rainy":
rain_total += c
rain_days += 1
print("Rainy days:", rain_days)
print("Rainy average:", rain_total / rain_days)
Rainy days: 2
Rainy average: 290.0
while loops
"How many months to save Rs 25,000 at Rs 2,000 a month?" has no list to walk and an unknown number of rounds. A while loop repeats its block as long as its condition is True, checking the condition before every round.
savings = 0
months = 0
while savings < 25000:
savings += 2000
months += 1
print(f"{months} months, saved Rs {savings}")
13 months, saved Rs 26000
After 12 months he has 24,000 (still short), so one more month.
Infinite loop: if nothing inside the loop moves the condition towards False (say you forget savings += 2000), the loop never ends. Stop it with the notebook's stop button / "Interrupt kernel", or Ctrl+C in a terminal. Use for when you know what you're looping over; while when you only know when to stop.
break and continue
breakleaves the loop immediately.continueskips the rest of this iteration and jumps to the next item.
week = [320, 280, 410, 300, 350, 390, 450]
for day, c in enumerate(week, start=1):
if c > 400:
print(f"First 400+ day: day {day} ({c} cups)")
break
log = [320, "N/A", 410, "N/A", 350]
total = 0
for c in log:
if c == "N/A":
continue
total += c
print("Total of real days:", total)
First 400+ day: day 3 (410 cups)
Total of real days: 1080
Without continue, total += "N/A" would raise TypeError: unsupported operand type(s) for +=: 'int' and 'str'. Skipping unusable values is a first taste of data cleaning.
Looping over a dict
A plain for over a dict gives its keys. Use .items() to get key-value pairs and unpack them.
menu = {"chai": 12, "samosa": 15, "bun maska": 25}
for item in menu:
print(item)
for item, price in menu.items():
print(f"{item}: Rs {price}")
chai
samosa
bun maska
chai: Rs 12
samosa: Rs 15
bun maska: Rs 25
.values() loops over just the values.
List comprehensions
Building a filtered list takes four lines with a loop. A list comprehension does it in one:
cups = [320, 280, 410, 300, 350, 390, 450]
quiet = []
for c in cups:
if c < 350:
quiet.append(c)
print(quiet)
quiet = [c for c in cups if c < 350]
print(quiet)
in_hundreds = [c / 100 for c in cups]
print(in_hundreds)
[320, 280, 300]
[320, 280, 300]
[3.2, 2.8, 4.1, 3.0, 3.5, 3.9, 4.5]
Read [c for c in cups if c < 350] in three parts:
| Part | Meaning |
|---|---|
c |
what to keep (any expression, e.g. c / 100) |
for c in cups |
where the items come from |
if c < 350 |
optional filter |
Use a comprehension when it stays short and readable; use a normal loop for long logic. This filter idea becomes pandas filtering later in the course.
The conditional expression
A conditional expression (also called a ternary expression) picks one of two values in a single line: value_if_true if condition else value_if_false.
cups = [320, 280, 410, 300, 350, 390, 450]
c = 280
label = "slow" if c < 300 else "ok"
print(label)
labels = ["slow" if c < 300 else "ok" for c in cups]
print(labels)
slow
['ok', 'slow', 'ok', 'ok', 'ok', 'ok', 'ok']
Note the position: a filter if goes at the end of a comprehension (and drops items); a conditional expression goes at the front (and keeps every item, choosing its value).
Dict comprehensions
{key: value for ... in ...} builds a dict.
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
cups = [320, 280, 410, 300, 350, 390, 450]
by_day = {d: c for d, c in zip(days, cups)}
print(by_day)
menu = {"chai": 12, "samosa": 15, "bun maska": 25}
new_menu = {item: p + 2 for item, p in menu.items()}
print(new_menu)
busy = {d: c for d, c in by_day.items() if c >= 400}
print(busy)
{'Mon': 320, 'Tue': 280, 'Wed': 410, 'Thu': 300, 'Fri': 350, 'Sat': 390, 'Sun': 450}
{'chai': 14, 'samosa': 17, 'bun maska': 27}
{'Wed': 410, 'Sun': 450}
(dict(zip(days, cups)) also builds the first dict.)
Mini project: a weekly report
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
cups = [320, 280, 410, 300, 350, 390, 450]
weather = ["sunny", "rainy", "sunny", "rainy",
"sunny", "sunny", "sunny"]
for day, c, w in zip(days, cups, weather):
if c >= 400:
tag = "BUSY"
elif c >= 300:
tag = "normal"
else:
tag = "slow"
print(f"{day} {c:>3} {w:<5} {tag}")
rainy = [c for c, w in zip(cups, weather) if w == "rainy"]
sunny = [c for c, w in zip(cups, weather) if w == "sunny"]
print(f"Rainy avg {sum(rainy) / len(rainy):.0f}, sunny avg {sum(sunny) / len(sunny):.0f}")
Mon 320 sunny normal
Tue 280 rainy slow
Wed 410 sunny BUSY
Thu 300 rainy normal
Fri 350 sunny normal
Sat 390 sunny normal
Sun 450 sunny BUSY
Rainy avg 290, sunny avg 384
In the f-string, :>3 right-aligns in 3 characters and :<5 left-aligns in 5, so the columns line up. Rainy days average 290 cups vs 384 on sunny days, about 94 fewer, based on one week (a real analysis would use a full year).
Common mistakes
| Code | Real error | Why | Fix |
|---|---|---|---|
if cups < 300 |
SyntaxError: expected ':' |
if/elif/else/for/while lines end with : |
if cups < 300: |
| Block not indented | IndentationError: expected an indented block after 'if' statement on line 2 |
Indentation defines the block | Indent 4 spaces |
for i in range(len(week) + 1) |
IndexError: list index out of range |
Loop runs past the end | Loop over items, or use enumerate/zip |
while savings < 25000: with no update |
(no error: never ends) | The condition never becomes False |
Update the variable inside the loop |
total += "N/A" |
TypeError: unsupported operand type(s) for +=: 'int' and 'str' |
Text in a number list | continue past it, or clean first |
| Conditions in the wrong order | (no error: wrong label) | The first True branch wins |
Strictest condition first |
x = 5 in an if instead of == |
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? |
= assigns, == compares |
if x == 5: |
Practice
1. Label each value in screen = [3.5, 5.2, 4.1, 2.0, 6.3] (hours) as "high" if over 4, else "ok", with a loop and with a comprehension.
Solution
labels = []
for h in screen:
if h > 4:
labels.append("high")
else:
labels.append("ok")
labels2 = ["high" if h > 4 else "ok" for h in screen]
Both give ['ok', 'high', 'high', 'ok', 'high'].
2. With while, how many years until Rs 10,000 doubles at 8% a year?
Solution
money, years = 10000, 0
while money < 20000:
money *= 1.08
years += 1
print(years)
Prints 10 (after 9 years it is about 19,990; after 10, about 21,589).
3. Sum log = [320, "N/A", 410, "N/A", 350] two ways.
Solution
Loop with continue (shown above), or sum([c for c in log if c != "N/A"]). Both give 1080.
4. Print the first day (name) with fewer than 300 cups using zip and break.
Solution
for day, c in zip(days, cups):
if c < 300:
print(day)
break
Prints Tue.
5. Build {item: price} only for menu items cheaper than Rs 20.
Solution
{k: v for k, v in menu.items() if v < 20} → {'chai': 12, 'samosa': 15}.
Quick quiz
cups = [320, 280, 410, 300]
print([c for c in cups if c >= 300 and c < 400])
A. [320, 410, 300] B. [320, 300] C. [280] D. [320, 280, 300]
Answer
B. 320 and 300 pass both tests; 280 is under 300 and 410 is not under 400.
Interview questions
forvswhile?foriterates over a known collection;whilerepeats until a condition becomesFalse(unknown number of rounds).breakvscontinue?breakexits the loop;continueskips to the next iteration.- Why use
enumerateandzip? They avoid manual index handling and off-by-one bugs, and read more clearly. - What is a list comprehension? A one-line way to build a list:
[expr for item in iterable if condition]. - Filter
ifvs conditional expression in a comprehension? A trailingifdrops items;a if cond else bat the front keeps every item and chooses its value. - How does Python know which lines belong to an
if? Indentation: the block is the indented lines after the colon.
Glossary
| Term | Meaning | Example |
|---|---|---|
| Condition | An expression that is True or False |
cups < 300 |
| if statement | Runs a block only if its condition is True |
if cups < 300: |
| elif | "else if": the next condition, tested only if the ones above were False |
elif cups >= 300: |
| else | Runs when no condition above was True |
else: |
| Block | Indented lines that belong together under a : line |
the body of an if |
| Indentation | Spaces at the start of a line (4 by convention) | print(...) |
| IndentationError | Error for missing or inconsistent indentation | expected an indented block |
| Loop | Code that repeats a block | for, while |
| for loop | Repeats a block once per item of an iterable | for c in cups: |
| Iteration | One run of a loop's block | 7 iterations for a week |
| Loop variable | Holds the current item in a for loop |
c |
| Iterable | Anything a for loop can go through |
list, str, dict, range |
| range() | A sequence of whole numbers (stop excluded) | range(1, 8) |
| end= | print setting for what goes at the end of the line |
print(n, end=" ") |
| enumerate() | Gives a counter with each item | enumerate(days, start=1) |
| zip() | Walks several lists side by side | zip(days, cups) |
| Unpacking | Splitting a pair/tuple into separate variables | for day, c in ... |
| Accumulator | A variable that collects a result across a loop | rain_total |
| Augmented assignment | x += y means x = x + y |
total += c |
| while loop | Repeats while its condition is True |
while savings < 25000: |
| Infinite loop | A loop whose condition never becomes False |
forgetting savings += 2000 |
| break | Leaves the loop immediately | stop at the first match |
| continue | Skips to the next iteration | skip "N/A" |
| .items() | A dict's key-value pairs, for looping | for k, v in menu.items(): |
| List comprehension | One-line list builder | [c for c in cups if c < 350] |
| Dict comprehension | One-line dict builder | {d: c for d, c in zip(days, cups)} |
| Conditional expression | a if condition else b in one line |
"slow" if c < 300 else "ok" |
| Format alignment | :>3 right-align, :<5 left-align in an f-string |
f"{c:>3}" |
Summary
- if / elif / else choose exactly one path; lines end with
:and blocks are indented. - for loops repeat a block for every item; range makes number sequences.
- enumerate and zip replace index loops; accumulators with
+=build totals and counts. - while repeats until a condition is
False; avoid infinite loops. - break stops a loop; continue skips an item;
.items()loops over a dict. - Comprehensions build lists and dicts in one line; the conditional expression picks a value.
- Next episode: Python for data, part 4: functions, errors and files.