Three ideas you'll use in every lab from here: what kind of thing a value is, how to make code choose, and how to stop repeating yourself.
Programming for Data Science · University of the Philippines Cebu
What kind of thing is this value — and why 5 ≠ "5".
Making code choose and repeat: if, elif, else, loops.
Name a piece of work once; use it anywhere. return vs print.
Every later lab — pandas, plotting, files — is these three ideas wearing heavier costumes.
Most beginner bugs are one of three mistakes. You'll learn to recognise all three.
Last week you set up Python and ran code others wrote. This week you start writing the decisions — and meeting the errors that come with them.
An error message is not the enemy. It's Python telling you exactly what it couldn't do.
When a cell breaks, read the last line first. It names the problem.
Last week a variable was a name that holds a
value (a piece of data): price = 100. Today’s words are
about what kind of value it is, and how code makes choices.
The kind of value something is. It decides what you can do with it.
e.g. type(5) reports int; type("5") reports str
A whole number, with no decimal point. Used for counts and positions.
e.g. 21, 0, -3
A number with a decimal point. Used for measurements and averages.
e.g. 21.0, 3.5
Text, written inside quotes. Digits in quotes are still text.
e.g. "Cebu", "21"
One of exactly two values, True or False: the answer to a yes/no question.
e.g. 5 > 3 gives True
A symbol that does something to values: add, divide, compare…
e.g. +, /, ==, >=
A yes/no question in code. Its answer is a boolean, and it decides whether a block runs.
e.g. score >= 60
Code that repeats a block: once per item (for), or while a condition stays true (while).
e.g. for h in hours: runs once per number in hours
A function is like a recipe: write the steps once, give it a name, then use it whenever you need it. These words name its parts.
A named, reusable piece of work: you give it inputs, it gives back a result.
e.g. len([3, 5, 4]) gives back 3
Running a function: its name followed by round brackets, with the inputs inside.
e.g. average([3, 5, 4])
The name, in the function’s definition, that will hold an input.
e.g. numbers in def average(numbers):
The actual value you put in the brackets when you call the function.
e.g. [3, 5, 4] in average([3, 5, 4])
Hand a result back to whoever called the function, and stop the function there.
e.g. return total / count
Python’s value for “nothing here”. A function with no return gives back None.
e.g. x = print("hi") leaves x as None
What kind of thing is this value? Get this wrong and everything downstream breaks.
You already store values in variables (Week 1). This part adds the idea that every value has a type, and the type decides what you are allowed to do with it.
5 and "5" as different thingsOne is a number you can do maths with. The other is text that happens to look like a number. Most beginner errors are really this one error in disguise.
Every value has a type: the
kind of value it is. Python writes types with short names — int for
integer, str for string, bool for boolean.
values = [5, "5", …]a list: several values in square brackets, in order (more in Week 3)for v in values:a loop: run the indented line once for each value, with v holding the current oneprint(type(v).__name__)ask for v’s type and print its short nameint whole number · str text · float decimal · bool True/False · list · NoneType the type of None| Type | Example | What it's for |
|---|---|---|
int | 5 | Whole numbers — counts, indices |
float | 5.0 | Numbers with decimals — measurements, averages |
str | "5" | Text — names, labels, anything typed |
bool | True | Yes/no — the result of a comparison |
list | [5] | An ordered collection of values |
NoneType | None | "Nothing here" — a deliberate blank |
You can add two numbers, and you can put a word in capitals — but “capitalise 5” or “add 1 to Cebu” make no sense. Python checks the type before it lets an operation happen. So when something misbehaves, ask “what type is this, really?”
type(x) answers it. Reach for it before you guess.
age holds the text "21", not the number
21. Python refuses to add a number to text — and says so on the last line.
TypeError = an operator got a type it cannot work with+ on text means glue together, and it was handed a numberline 2 — the print line with age + 1+Which is right depends on where the text came from. Data read from a file
often arrives as str — you convert at the point of use.
int(), float(), str() change a value from one
type to another. int("21") reads the text and gives back the number
21; the # 22 comment shows what the line prints.
Python gives you both. / always makes a float; //
throws away the remainder. Pick the one that matches your question.
7 / 2 → 3.5ordinary division; the answer is a float (it has a decimal point) — always, even for 4 / 27 // 2 → 3floor division: how many whole 2s fit in 7; the leftover is thrown away7 % 2 → 1remainder (“modulo”): what is left over. x % 2 == 0 is the test for an even number"Average score" wants /. "How many full teams of 4?" wants //.
Same numbers, different truth.
Data read from a file or a form arrives as text, even when it's all digits. Python takes the quotes literally.
"10" + "5"two strings: + glues them into '105'. The quotes around the answer tell you it is still textint("10") + 5convert the text to the integer 10 first, then add: 15If a "sum" comes out as two numbers stuck together, one of them is secretly a string.
A boolean is True or False
(capital letter, no quotes). Booleans are a kind of integer in Python. Strange at first, but it
makes counting "how many are true?" a one-liner: sum(passed) adds up the list,
counting each True as 1 and each False as 0.
Summing a list of comparisons counts how many rows pass a test — a trick you'll reuse all term.
What does type(3 / 1) report?
intfloatstrB — float, it's 3.0.
The single / always
produces a float, even when the numbers divide evenly. If you needed the integer
3, that's //.
The operator decides the type of the result — not the values you feed it.
Unsure what you're holding? type(x) and print it. No shame,
all speed.
What does "3" + "4" produce?
7"34""7"B — "34".
+ on two strings glues them
together. It only errors when the types disagree (str + int). Same symbol,
different job depending on type.
The operator's meaning depends on the types it sits between.
"What will this do?" always starts with "what types are involved?"
Making code choose, and making it repeat.
You now know what kind of values you are holding. So far, code has run every line once, top to bottom; this part lets it skip lines (a choice) and repeat lines (a loop).
if runs a block only when something is trueA condition is a yes/no question such as
score >= 90 (“is score at least 90?”). elif (short for
“else if”) and else cover the other cases. Python checks them
top to bottom and stops at the first match.
A line ending in a colon : owns the
block of indented lines under it (4 spaces in). Indentation is not
decoration in Python: it says which lines belong to which choice.
if score >= 90:73 ≥ 90? False → skip its blockelif score >= 75:73 ≥ 75? False → skipelif score >= 60:73 ≥ 60? True → run grade = "passing", then jump past the restelse:runs only when every condition above was Falseprint(grade)not indented, so it always runs: prints passingPython takes the first branch that's true, then stops.
score = 75 is ≥ 75, so it never even checks the later, looser conditions.
score = 75 → "good"It matches >= 75 first. The >= 60 branch is true too, but
we never reach it.
If you swapped the branches so >= 60 came first, everything
passing would print "passing" — the tighter grades unreachable.
bool| Operator | Asks | Example → result |
|---|---|---|
== | equal? | 3 == 3 → True |
!= | not equal? | 3 != 4 → True |
> >= | greater (or equal)? | 5 >= 5 → True |
< <= | less (or equal)? | 2 < 1 → False |
and / or | combine conditions | x > 0 and x < 10 |
A single = assigns; a double ==
compares. Mixing them up is the second-most-common beginner bug.
ifis really asking a yes/no question — and the answer is a bool.
and is True only if both sides are; or if at least one is.
A loop repeats a block of code. A for
loop walks a list, running the same block once for each item. Here it keeps a
running total (a variable that starts at 0 and grows each time round)
and counts the non-zero days.
total = 0start both counters at zero, before the loopfor h in hours:repeat the indented block 7 times; h is 3, then 0, then 5…total = total + htake the old total, add this day’s hours, store it backif h > 0:only on days with hours, add 1 to days_worked22 5output: 22 hours in total, spread over 5 days that had any workTotal hours ÷ days worked and total hours ÷ all days are different claims. Choosing the denominator is a judgement, not a formula.
total / days_worked = 22 / 5 = 4.4"On days I worked, I averaged 4.4 hours." Answers a question about work intensity.
total / len(hours) = 22 / 7 ≈ 3.1"Across the whole week, 3.1 hours a day." A different, equally valid claim.
They answer different questions. State which one you mean. (len(hours) counts
the items in the list: 7.)
If every value is 0, then days_worked is
0, and dividing by it crashes. Real data will hand you this case —
so handle it on purpose.
ZeroDivisionError: division by zero — you divided by 0, which has no answerline 2: the division inside printtotal, days_worked = 0, 0 sets both variables at onceAsk "what's the empty version of this input?" before you ship the happy path.
elif stops; separate ifs don'tAn if/elif chain runs at most one branch. Three
separate ifs each get checked — and can all fire.
Swap those to three ifs and a 95 passes every test — ending as
"C", the last one that ran.
You don't need == 0 or len(x) == 0. Python already
treats empty and zero as False in a condition. Values that count as False are called
falsy; everything else is truthy.
0, "" (empty string), [] (empty list),
{} (empty dictionary, Week 3), None — all False. Everything else
is True. So if names: reads “if names has anything in it”.
rangeLooping over items is common; sometimes you need the position too. An item’s
index is its position number, counted from 0.
range(n) hands you 0, 1, … n-1 — n numbers, starting at 0.
range(start, stop, step) can also start elsewhere and jump by step.
range(3) gives three numbers, but the last is 2. Count the
starts, not the end.
enumerate hands you the index and the itemenumerate is a built-in function you wrap around a list
in a for loop. Each time round it gives two things: a counting
number (the index) and the item itself. Write two names before in to catch both.
It is like a teacher calling the register: “number 1, Ana; number 2, Ben” — you hear the number and the name together.
for i, score in enumerate(scores):each time round, i gets the position and score gets the value0 88, 1 92, 2 75the output: positions start at 0, like every index in Pythonstart=1start counting at 1 instead, for lists meant for people: Student 1 scored 88 … Student 3 scored 75while: repeat until something changesUse for when you know how many times; while when you
only know the stopping condition.
while balance > 0:check the condition; while it is True, run the block againbalance = balance - 30100 → 70 → 40 → 10 → −20; now −20 > 0 is False, so it stopsprint(balance)prints -20: the loop only checks at the top, so it can overshootIf the condition never turns False, the loop runs forever. Always change something inside it.
A very common shape: start empty, walk the input, append the
ones you want. Here, only the passing scores.
passing.append(s) is a
method call: a function that belongs to a value, written after a dot.
.append(s) adds s to the end of the list passing. After the
loop, passing is [82, 91].
empty list → loop → conditional append. You'll write it a hundred times.
With the grade ladder from earlier, which
score prints "needs work"?
60597590B — 59.
60 matches >= 60
→ "passing". Only a score below every threshold falls through to the
else. Boundaries are where off-by-one bugs live.
Always test the exact boundary value, not just a number near it.
>= includes the boundary; > excludes it.
Back to the idea that ties the whole course together: functions.
A name for a piece of work you do more than once.
You
can already make code choose and repeat. You have also been using functions all along
— print(), len(), int(). This part shows how to write
your own.
A function is a named, reusable piece of work: you give
it inputs, it gives back a result. Define it once with def;
call it (run it, by writing its name and brackets) as many times as you
like. A docstring is the sentence in triple quotes right under
def; it tells the reader what the function does.
A function is a recipe card. def writes the card;
calling it cooks the dish with whatever ingredients you hand over; return puts
the finished dish in your hands.
def average(numbers):define a function called average with one input, named numbersif len(numbers) == 0:if the list is empty… return 0 hands back 0 and stops right therereturn sum(numbers) / len(numbers)add the values, divide by how many there are, hand the answer backaverage([3, 5, 4]) → 4.0a call; 12 / 3 = 4.0, a float because / always gives a decimalaverage([]) → 0the empty case is caught by the if and gives back 0return hands a value back; print just shows itA function that only prints gives you None back —
you can look at the answer but can't use it. None is Python’s
value for “nothing here”.
To return is to hand
the result back to the code that called the function — like a cashier handing you your
change. print only shows it on a screen, like reading out the amount: you heard it,
but you are holding nothing.
Compute with return. Use print only to look at things.
Remove the if len(numbers) == 0 check and
average([]) divides by zero. A guard is an early check that
catches a bad or empty input before it can cause trouble. The guard turns a crash into a
sensible answer.
average([]) → ZeroDivisionError. The whole program stops.
average([]) → 0. The caller keeps running.
Is 0 the honest answer for "no data"? Sometimes yes — sometimes you'd
rather it complained. (More on that next.)
The parameter is the name in the definition. The argument is the real value you pass in when you call it.
The same function does different work depending on the argument — that's the whole point of naming it.
The parameter is the blank on a form (“Name: ____”); the argument is what you write in the blank this time (“Ana”).
Given nonsense input, a function that returns a meaningless number hides
the bug. One that raises an error stops you before the wrong number spreads.
To raise an error is to stop on purpose with your own message;
the error itself is called an exception (more in Week 4).
isinstance(nums, list)asks “is nums a list?” → True/False; not flips itraise TypeError("expected a list")stop here with our own error type and message...a placeholder: “the rest of the function goes here”File lines: line 6 is the call, line 3, in average is where it stopped. Last line = our messageFail fast, fail clearly. A wrong number that looks fine is the most expensive bug.
1?It should count passing scores. But passing = 0 sits
inside the loop, so it resets every pass. Only the last item's count survives.
Move passing = 0 above the loop, so it accumulates across items.
The position decides which value lands in which parameter. Swap them and the code still runs — with the wrong answer.
Name them at the call — divide(top=10, bottom=2) — when the order isn't
obvious. These are keyword arguments: name=value, so
position no longer matters.
Give a parameter a default (a value written with
= in the definition) and callers may skip it. Fewer required arguments, same
flexibility when you need it.
Defaults come after the required parameters — Python insists on it.
A guard clause deals with the empty or wrong input up
top and returns. The rest of the function can then assume all is well. if not
nums: reads “if nums is empty” (an empty list is falsy).
Deal with the exception first and the happy path reads clean — no nesting, no surprises.
The ZeroDivisionError from the divide-by-zero slide didn't go
away — you just moved it somewhere you can catch it once, for every caller.
You'll guard the same kind of divide-by-zero in percent(part, whole) —
deciding what an answer for “nothing to divide by” even means.
This should count scores ≥ 75. It always
returns 1. Why?
n = 0 is inside the
loop.
It resets to 0 on every pass, so it only
ever remembers the last score. Move n = 0 above the loop and
it counts them all.
Where you initialise a counter is as important as how you increment it.
Paste it into any ▶ Run window and move the line. The lab’s counting task asks you to start a counter in the right place.
Before trusting an input, check it. A clear early error beats a confusing crash five lines later — or worse, a wrong answer that never complains.
Types, control flow, functions — all three meet here: check the type, branch on the edge case, wrap it in a function you can trust.
A function's body is just
print(sum(nums)) — no return. After x = f([1,2,3]),
what is x?
6None[1, 2, 3]B — None.
It printed 6 to the screen, but
a function with no return hands back None. That's why
x holds nothing usable.
Seeing a value on screen is not the same as capturing it in a variable.
No return → the function returns None.
You'll fix the "21" + 1 error, convert text to a number, reorder
the grade ladder, count with a loop, use range and enumerate, give a
function a default, and guard a divide-by-zero. ~45 minutes.
How it works: replace each ____, press
▶ Run to see the output, then Check. Each part starts
with a short explanation of the words it uses.
Every broken cell is a chance to practise the one skill that matters: reading the error.
Make average() fail loudly on bad input, and explain why that's better than
a wrong number.
5 ≠ "5". When something's off, ask what type it really is.
Bug: str + int.
First true branch wins; order matters. = assigns, ==
compares. Bug: wrong branch order / divide by zero.
Name work once. return gives back; print only shows.
Bug: no return → None.
One sentence to keep: most beginner errors are a type confusion, a
branch that never fires, or a missing return.
Reading the last line of the error and naming which of the three it is.
Every new word from today. Week 3 builds on all of them.
213.5"21" is text, not a numberTrue or False; counts as 1 or 0+ / // % == >=+score >= 60:for each item, or while a condition holds0, "", [], Noneaverage([3, 5])numbers[3, 5]bottom=2return givesdef saying what it does.append(x)Finish the Week 2 lab and submit it. If a cell breaks, read the last line, fix it, carry on — that is the exercise.
Python Tutorial §3–4 (numbers, strings, control flow) and §4.7–4.8 (defining
functions) — docs.python.org/3/tutorial.
Everything here is linked on the course page beside this deck.
Type the examples yourself. Reading code and writing code are different skills.
Lists, dictionaries, and reading real files — collections that hold the data you'll spend the rest of the course analysing.
DS 208 · Programming for Data Science