← Teacher guide

A bar chart in text

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.

Data detective · Charts and checking · about 50 minutes · Open the student lesson

Classroom resources

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.

Students will be able to

Timing

Predict and Run 10 min · Investigate 15 min · Modify 15 min · Make 10 min

Before class

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.

Running the lesson

  1. Predict: draw the two bars on the board with tally marks first.
  2. Run: add print(counts) after the loop so students see the dictionary itself.
  3. Investigate: act out counting with sticky notes, one per genre, adding a stroke for each song.
  4. Modify: 1987 // 10 * 10 on the board, step by step.
  5. Make: ask what one # stands for, and write it on the chart as a key.

The hard part (on purpose)

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.

When students get stuck

What you will seeA question to askThe 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).

Answers to the checks

  1. Predict: What does it print?
    pop ###, then rock ##. "#" * 3 repeats the text 3 times, and ljust(6) pads each name to 6 characters so the bars start in the same place.
  2. The first two songs: After the first two songs (Jolene, country, then Bohemian Rhapsody, rock), what is counts?
    {"country": 1, "rock": 1}
    Each song's genre is a key. Both genres are new, so each starts at 1. The titles are never keys.
  3. Why the if: Why is there an if genre in counts: before adding 1?
    The first time a genre turns up it has no count yet, so it must start at 1
    counts[genre] + 1 needs counts[genre] to exist. The else starts a new genre at 1.
  4. Spot the bug: This should count the genres, but it stops straight away with KeyError: 'country'. Click the line that is wrong.
    line 9
    The first country song looks up counts["country"] before it exists. Check with if genre in counts: and start it at 1 otherwise.
  5. Where this leads: Pop has far more songs than folk in this data. What does that tell you about music in general?
    Nothing much: it tells you about the songs someone chose to put in this file
    A chart shows the data, and data is a choice. Before claiming anything, ask who collected it, how, and what is missing.

Solutions

One possible solution for each task. Other correct programs are fine.

Modify › Songs by decade

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]))

Modify › Only the big genres

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]))

minutes per 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 and support

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.

Exit ticket

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.

What each step is for

Where it leads

Next lesson cleans messy data and checks code with test cases. Lesson 5 draws the same chart with Gradio's BarPlot.