Students clean a messy survey with a function that returns a number or None, check it with assert test cases, then tidy genres, skip repeated rows and chart the cleaned genres.
Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.
Predict and Run 10 min · Investigate 15 min · Modify 15 min · Make 10 min
Read the unit primer first. survey.csv is made up (no real students) with deliberate mess: capitals and spaces in genres, a blank genre, a blank hours answer, "three", -1, 400 (a week has 168 hours) and a repeated row. clean_hours returns a number, or None ("no value") when the answer cannot be used; hours is None checks for it (is is the usual way to compare with None). try / except ValueError is introduced here in one sentence (the Predict): try a line, and if it causes that error, run the except lines instead of stopping. Deeper Python's errors lesson goes further. Test cases are assert lines: silent when true, and the program stops with AssertionError when false. The bug task joins the two range checks with and, which can never be true; the tests catch it. Talk about honesty: a report says how many answers were used.
Writing a test case before the fix. When a student is stuck, ask what the function should give for the failing input, and have them write that assert first.
| What you will see | A question to ask | The fix |
|---|---|---|
| AssertionError on a test line. | What does the function give for that input, and what does the test expect? | Print clean_hours("400") to see what it gives, then fix the function, not the test. |
| ZeroDivisionError at the average. | Did any row get counted? | Check that count goes up in the else, for the rows that are kept. |
| The repeated student is still counted twice. | When is the student added to seen? | seen.append(...) when a row is used, and check if row["student"] in seen first. |
One possible solution for each task. Other correct programs are fine.
import csv
def clean_hours(text):
try:
hours = float(text)
except ValueError:
return None
if hours < 0 or hours > 168:
return None
return hours
def clean_genre(text):
return text.strip().lower()
assert clean_hours("5") == 5
assert clean_hours("4.5") == 4.5
assert clean_hours("three") is None
assert clean_hours("") is None
assert clean_hours("-1") is None
assert clean_genre(" Hip Hop ") == "hip hop"
assert clean_genre("POP") == "pop"
print("All tests pass")
with open("survey.csv") as f:
rows = list(csv.DictReader(f))
for row in rows:
print(row["student"] + ": " + clean_genre(row["genre"]))
import csv
def clean_hours(text):
try:
hours = float(text)
except ValueError:
return None
if hours < 0 or hours > 168:
return None
return hours
assert clean_hours("5") == 5
assert clean_hours("4.5") == 4.5
assert clean_hours("three") is None
assert clean_hours("") is None
assert clean_hours("-1") is None
print("All tests pass")
with open("survey.csv") as f:
rows = list(csv.DictReader(f))
total = 0
count = 0
seen = []
for row in rows:
hours = clean_hours(row["hours"])
if row["student"] in seen:
print("Skipped " + row["student"] + ": a repeat")
elif hours is None:
print("Skipped " + row["student"] + ": '" + row["hours"] + "'")
else:
total = total + hours
count = count + 1
seen.append(row["student"])
print("Average: " + str(round(total / count, 1)) + " hours of music a week, from " + str(count) + " answers")
import csv
def clean_genre(text):
return text.strip().lower()
assert clean_genre(" Hip Hop ") == "hip hop"
assert clean_genre("POP") == "pop"
print("All tests pass")
with open("survey.csv") as f:
rows = list(csv.DictReader(f))
counts = {}
for row in rows:
genre = clean_genre(row["genre"])
if genre != "":
if genre in counts:
counts[genre] = counts[genre] + 1
else:
counts[genre] = 1
for genre in counts:
print(genre.ljust(12) + "#" * counts[genre] + " " + str(counts[genre]))
Stretch: Write clean_year(text) for the year_level column (only 7 to 12 allowed) with test cases, and print the average hours for each year level.
Support: Give students clean_genre written out, and ask them to write the two test cases for it.
What does a test case such as assert clean_hours("400") is None prove?
That the function rejects an impossible answer; if someone breaks the function later, the program stops on that line and says so.
Next lesson puts the data behind a Gradio app with pandas, which has its own cleaning tools. Test cases come back in every later course.