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.
Read the docstring for
read_yaml. Note the empty dictionarystatesat the top of the function; you will add to it as you go.As in class, use
withto open the file, then loop through its lines withfor line in fand print each line.Find the lines that start with
"- name:". Strings have a method for this:>>> "- name: Vermont\n".startswith("- name:") TrueFor those lines, save just the state name in a variable
current_state."- name: "is 8 characters long, soline.strip()[8:]is the name. Printcurrent_stateto check it.Replace that print with a line that adds
current_statetostatesas a key, with an empty dictionary as its value.Add an
elsefor 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 whatsepshould be.Add the key and value to the inner dictionary for the current state. That inner dictionary is
states[current_state]. Store the population as anint, not a string.Return
states. Testread_yamlon 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
}
- Read the docstring. Note the empty dictionary
changesat the top of the function. - Loop through the keys and values of
yearly_populationswith.items(). For each state, set its value inchangesto its"pop_2024"minus its"pop_2020". Don’t changeyearly_populations. - Return
changes. Some states shrank, so expect a few negative numbers forstate_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
Read the docstring. The
csvmodule is already imported at the top of the file.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. Forexample.yaml:[['state', 'pop_change'], ['North Dakota', 17005], ['Vermont', 5516]]Open
csv_filenamein write mode withnewline="", as in class, and make acsv.writerfor it.Loop through your list and write each row with
writer.writerow. It converts the numbers to strings and adds the commas and newlines.Remove the print and test the function from the Shell. It returns nothing, but a new file appears in your
lab4folder. 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}
- Read the docstring. The
jsonmodule is already imported. - Open
json_filenamein write mode and usejson.dumpto writepopulation_changesto it. - 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.