Students count every genre at once with a dictionary and draw the counts as a text bar chart, then chart by decade, filter the bars, and total minutes per genre.
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. The counting pattern: counts = {}, then for each row, if the key is in counts add 1, otherwise start it at 1 (the bug task leaves out the start, giving a KeyError). Looping over a dictionary (for genre in counts:) gives its keys. "#" * n repeats text; ljust(12) pads text with spaces to 12 characters so the bars line up. The decade task uses // (divide and drop the remainder): 1987 // 10 * 10 is 1980. The Where this leads question is about where data comes from: this file is a hand-picked set of well-known songs, so it says nothing about music in general.
Counting with a dictionary: the new key that has no count yet. Ask what counts holds after the first song, on paper, before they change code.
| What you will see | A question to ask | The fix |
|---|---|---|
| KeyError: 'country'. | Does the genre have a count before you add 1 to it? | if genre in counts: add 1, else: start it at 1. |
| AttributeError: 'int' object has no attribute 'ljust'. | Is the key a number now? | str(decade).ljust(12). |
| TypeError: can't multiply sequence by non-int of type 'float'. | Is the number of | "#" * int(minutes / 5). |
One possible solution for each task. Other correct programs are fine.
import csv
with open("music.csv") as f:
songs = list(csv.DictReader(f))
counts = {}
for song in songs:
decade = int(song["year"]) // 10 * 10
if decade in counts:
counts[decade] = counts[decade] + 1
else:
counts[decade] = 1
for decade in counts:
print(str(decade).ljust(12) + "#" * counts[decade] + " " + str(counts[decade]))
import csv
with open("music.csv") as f:
songs = list(csv.DictReader(f))
counts = {}
for song in songs:
genre = song["genre"]
if genre in counts:
counts[genre] = counts[genre] + 1
else:
counts[genre] = 1
for genre in counts:
if counts[genre] >= 3:
print(genre.ljust(12) + "#" * counts[genre] + " " + str(counts[genre]))
import csv
with open("music.csv") as f:
songs = list(csv.DictReader(f))
minutes = {}
for song in songs:
genre = song["genre"]
if genre in minutes:
minutes[genre] = minutes[genre] + float(song["minutes"])
else:
minutes[genre] = float(song["minutes"])
for genre in minutes:
bar = "#" * int(minutes[genre] / 5)
print(genre.ljust(12) + bar + " " + str(round(minutes[genre])) + " min")
Stretch: Print the bars in order, biggest first (sorted(counts, key=counts.get, reverse=True) is one way to look up).
Support: Give students the counting loop and ask them only to write the loop that draws the bars.
Why does counting need if genre in counts:?
The first time a genre appears it has no count yet, so counts[genre] + 1 would fail; it starts at 1 instead.
Next lesson cleans messy data and checks code with test cases. Lesson 5 draws the same chart with Gradio's BarPlot.