← Data detective

A bar chart in text

Count every genre at once with a dictionary, then draw each count as a bar of

Before computers drew charts, people drew them with tally marks. A row of # characters is the same idea, and it is a real bar chart.

Last lesson counted one genre at a time. A dictionary can count every genre at once: one key per genre, and its count as the value. Then each count becomes a bar, because "#" * 5 is #####.

PredictRead the code and say what it will do before you run anything. A wrong guess is fine: it shows you what to look for.

Predict

chart.py
counts = {"pop": 3, "rock": 2}
for genre in counts:
    print(genre.ljust(6) + "#" * counts[genre])

"#" * 3 repeats text. ljust(6) pads a word with spaces until it is 6 characters long.

What does it print?

RunRun it and compare the result with your prediction.

Run

Run it: a bar for each genre, with its count. Which genre has the most songs? Add print(counts) after the first loop to see the whole dictionary.

chart.py
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:
    print(genre.ljust(12) + "#" * counts[genre] + " " + str(counts[genre]))
Try it yourselfYour edits are saved in this browser.

InvestigateTrace how each line of code connects to what you see.

Investigate

chart.py
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:
    print(genre.ljust(12) + "#" * counts[genre] + " " + str(counts[genre]))

What the dictionary holds.

A key that is not there yet.

A count that never starts.

After the first two songs (Jolene, country, then Bohemian Rhapsody, rock), what is counts?

Why is there an if genre in counts: before adding 1?

This should count the genres, but it stops straight away with KeyError: 'country'. Click the line that is wrong.

ModifyChange the code on purpose and check the result.

Modify

Chart the songs by decade (1970, 1980, ...) instead of by genre.

Need a hint?

Use the decade as the key instead of the genre: decade = int(song["year"]) // 10 * 10.

Another hint

// divides and drops the remainder: 1987 // 10 is 198, and 198 * 10 is 1980. A number key needs str() before ljust.

Only draw a bar for genres with 3 or more songs.

Need a hint?

Put the print inside if counts[genre] >= 3:.

Try it yourselfYour edits are saved in this browser.

Chart the songs by decade instead of genre.

    Run your program. This ticks when your code does all of the above.

    Only draw genres with 3 or more songs.

      Run your program. This ticks when your code does all of the above.

      MakeBuild something new using what you have learned. Expect a few tries; that is normal.

      Minutes per genre

      Add up the minutes of each genre, then draw a bar for each genre with one # for every 5 minutes, and the total at the end of the bar. (int() drops the decimals from a number of #s.)

      Try it yourselfYour edits are saved in this browser.

      Total the minutes for each genre and draw a bar for each, one # for every 5 minutes.

        Run your program. This ticks when your code does all of the above.

        Info

        Where this leads

        Real charting tools (spreadsheets, pandas, Gradio's BarPlot in Lesson 5) draw the same bars from the same counts. Next: data that is messy, and how to check your code with test cases.

        Pop has far more songs than folk in this data. What does that tell you about music in general?

        For teachers

        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. About 50 minutes.

        Open the teacher guide: answers, solutions, slides and a worksheet.

        Students will be able to

        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

        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.

        Where it leads

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

        What each step is for

        Now you can
        Next lesson4. Clean it, test itReal data is messy. Write a function that cleans one value, check it with test cases, and skip the rows it cannot fix.