Skip to main content

Lab 4

Objectives

  • Read data from a file into a dictionary of dictionaries
  • Loop over a dictionary’s keys and values with .items()
  • Write data to CSV and JSON files

Part 0: Setup

Download the file lab4.zip by clicking the blue “Download Starter Code” button above; move the zip file to your cs145/labs directory, then extract it (Mac: double-click it; Windows: right-click it and choose “Extract All…”) to create a subfolder called lab4 inside your labs directory. Then delete lab4.zip.

The folder contains the starter file lab4.py and two data files, example.yaml and state_population.yaml. Keep all three in the same folder. The data comes from the US Census Bureau, reformatted for this lab. The examples below use example.yaml because it is short, but your code should also work on state_population.yaml (all 50 states).

lab4.py includes a pretty_print function that prints a dictionary with one entry per line. Use it to check your results. See Testing in the Thonny Shell at the bottom of this page for sample calls of all four functions.

Part 1: Reading a YAML file

YAML is a text file format often used for configuration files. As in Python, indentation matters: it shows nesting. Here is example.yaml:

- name: North Dakota
  pop_2020: 779563
  pop_2024: 796568
- name: Vermont
  pop_2020: 642977
  pop_2024: 648493

The indentation tells us that the pop_2024 on line 3 belongs to North Dakota, not Vermont.

Complete read_yaml(yaml_filename), which reads a file in this format and returns a dictionary of dictionaries. For example.yaml, pretty_print shows:

{
  "North Dakota": {
    "pop_2020": 779563,
    "pop_2024": 796568
  },
  "Vermont": {
    "pop_2020": 642977,
    "pop_2024": 648493
  }
}

The goal is not a complete YAML reader, just one for files in exactly this format. Don’t use the pyyaml library.

  1. Read the docstring for read_yaml. Note the empty dictionary states at the top of the function; you will add to it as you go.

  2. As in class, use with to open the file, then loop through its lines with for line in f and print each line.

  3. Find the lines that start with "- name:". Strings have a method for this:

    >>> "- name: Vermont\n".startswith("- name:")
    True

    For those lines, save just the state name in a variable current_state. "- name: " is 8 characters long, so line.strip()[8:] is the name. Print current_state to check it.

  4. Replace that print with a line that adds current_state to states as a key, with an empty dictionary as its value.

  5. Add an else for every other line, which holds a population. Use .strip() and .split(sep) to split the line into a key (e.g. "pop_2020") and a value (e.g. "642977"). Look at a population line in the file to decide what sep should be.

  6. Add the key and value to the inner dictionary for the current state. That inner dictionary is states[current_state]. Store the population as an int, not a string.

  7. Return states. Test read_yaml on both files before moving on.

Use .strip() rather than slicing off the last character to remove the newline: the last line of state_population.yaml has no newline.

Part 2: Computing population change

Complete compute_population_change(yearly_populations), which takes the dictionary from Part 1 and returns a dictionary from each state to its change in population, 2024 minus 2020. For example.yaml:

{
  "North Dakota": 17005,
  "Vermont": 5516
}
  1. Read the docstring. Note the empty dictionary changes at the top of the function.
  2. Loop through the keys and values of yearly_populations with .items(). For each state, set its value in changes to its "pop_2024" minus its "pop_2020". Don’t change yearly_populations.
  3. Return changes. Some states shrank, so expect a few negative numbers for state_population.yaml.

This function takes about 4 lines.

Part 3: Writing a CSV file

Complete write_population_csv(population_changes, csv_filename), which takes the dictionary from Part 2 and writes a CSV file. For example.yaml, the file looks like this:

state,pop_change
North Dakota,17005
Vermont,5516
  1. Read the docstring. The csv module is already imported at the top of the file.

  2. Make a list of rows for the file. Start it with the header row ["state", "pop_change"], then add one row [state, change] for each state. Print the list to check it. For example.yaml:

    [['state', 'pop_change'], ['North Dakota', 17005], ['Vermont', 5516]]
  3. Open csv_filename in write mode with newline="", as in class, and make a csv.writer for it.

  4. Loop through your list and write each row with writer.writerow. It converts the numbers to strings and adds the commas and newlines.

  5. Remove the print and test the function from the Shell. It returns nothing, but a new file appears in your lab4 folder. Open it in Thonny to check it.

This function takes about 8 lines.

Part 4: Writing a JSON file

Complete write_population_json(population_changes, json_filename), which writes the dictionary from Part 2 to a JSON file. Because JSON looks so much like a Python dictionary, this takes less work than the CSV file. For example.yaml, the file looks like this:

{"North Dakota": 17005, "Vermont": 5516}
  1. Read the docstring. The json module is already imported.
  2. Open json_filename in write mode and use json.dump to write population_changes to it.
  3. Test the function from the Shell, then open the new file in Thonny to check it.

This function takes about 2 lines.

Testing in the Thonny Shell

Here is a sample run of all four functions:

>>> %Run lab4.py
>>> states = read_yaml("example.yaml")
>>> pretty_print(states)
{
  "North Dakota": {
    "pop_2020": 779563,
    "pop_2024": 796568
  },
  "Vermont": {
    "pop_2020": 642977,
    "pop_2024": 648493
  }
}
>>> changes = compute_population_change(states)
>>> pretty_print(changes)
{
  "North Dakota": 17005,
  "Vermont": 5516
}
>>> write_population_csv(changes, "out.csv")
>>> write_population_json(changes, "out.json")

Then try the same calls with "state_population.yaml".

Submitting your work

When you are done, submit lab4.py to Gradescope. It should contain four functions: read_yaml, compute_population_change, write_population_csv, and write_population_json.

Ensure that the upload succeeds and your submission passes all visible tests.