Skip to main content

Homework 5

Objectives

  • Read data from a CSV file, skipping its header
  • Compute summary statistics from a list of numbers without changing the list
  • Group data into a dictionary of lists
  • Write results to a JSON file
  • Interpret what the numbers say about real data

Background

In this assignment you will analyze ten years of daily weather from Cornwall, Vermont, just southwest of Middlebury. weather.csv has one line for each day from 2016 through 2025: the date, the high and low temperatures (°F), and the snowfall (inches). A few days with missing readings were left out, so there are 3,631 days in all. The first lines of the file are:

weather.csv
date,high,low,snow
2016-01-01,38,32,0.0
2016-01-02,38,27,0.0
2016-01-03,33,30,0.0

The homework builds on Lecture 14 (reading CSV files and writing JSON files), Lecture 12 (building a dictionary in a loop, like letter_counts), Lecture 9 (sorted), and Lecture 13 (references).

Read the whole writeup before you start. Then write one function at a time, and test each one before moving on.

NoteHow the pieces fit together
Function Part Gives back
read_column(filename, column) 1 returns one column of the file as a list of numbers
median(nums) 2 returns the median of a list of numbers
summarize(nums) 3 returns a dictionary of statistics, using median
by_month(filename, column) 4 returns a dictionary that groups a column’s values by month
save_monthly_summary(filename, column, out_filename) 5 writes a JSON file, using by_month and summarize

Part 0: Setup

Click “Download Starter Code” above to download hw5.zip. Move it to your cs145/homework folder, then extract it (Mac: double-click it; Windows: right-click it and choose “Extract All…”) to create a hw5 folder with four files:

  • hw5.py is where you write your code. At the bottom, inside if __name__ == "__main__":, it has a commented-out test for each function, with the expected output. Uncomment each test once you’ve written that function, and keep the tests inside that block.
  • weather.csv is the full data set.
  • tiny.csv has 7 days from weather.csv, so you can check your results by hand.
  • empty.csv has only the header line.

Keep all four files in the same folder. Your hw5.py file is nearly empty: you will write each function and its docstring yourself.

tiny.csv
date,high,low,snow
2025-01-30,38,2,0.0
2025-01-31,27,1,0.0
2025-02-01,35,12,3.0
2025-02-02,13,-11,0.0
2025-02-03,23,-10,2.0
2025-03-20,72,37,0.0
2025-03-21,69,30,0.5

Part 1: read_column

read_column(filename, column) returns the values in one column of a CSV file as a list of floats, in order. column is the column’s header, such as "high". If the file has only a header line, it returns an empty list.

>>> read_column("tiny.csv", "high")
[38.0, 27.0, 35.0, 13.0, 23.0, 72.0, 69.0]
>>> read_column("tiny.csv", "snow")
[0.0, 0.0, 3.0, 0.0, 2.0, 0.0, 0.5]
>>> read_column("empty.csv", "high")
[]
  • Read the rows with csv.reader, as in Lecture 14. The first row is the header, a list such as ['date', 'high', 'low', 'snow'].
  • Don’t assume which column column is. The list method index finds it: ["date", "high", "low", "snow"].index("low") is 2.
  • Each value in a row is a string. Convert it with float.

Part 2: median

median(nums) returns the median of a list of numbers: the middle value once the numbers are in order. If there is an even number of values, the median is the average of the two middle values. If nums is empty, it returns None.

>>> nums = [38.0, 27.0, 35.0]
>>> median(nums)
35.0
>>> nums
[38.0, 27.0, 35.0]
>>> median([38.0, 27.0])
32.5
>>> print(median([]))
None

median must not change nums. As we saw in Lecture 9, nums.sort() sorts nums itself, while sorted(nums) returns a new sorted list and leaves nums alone. Since nums refers to the caller’s list (Lecture 13), nums.sort() would change the caller’s list too.

Part 3: summarize

summarize(nums) returns a dictionary with five statistics about a list of numbers:

Key Value
"count" how many numbers there are
"min" the smallest number
"max" the largest number
"mean" the average, rounded to 2 decimal places
"median" the median, from your median function

If nums is empty, it returns {"count": 0}, since the other statistics don’t exist.

>>> summarize([38.0, 27.0, 35.0, 13.0, 23.0, 72.0, 69.0])
{'count': 7, 'min': 13.0, 'max': 72.0, 'mean': 39.57, 'median': 35.0}
>>> summarize([])
{'count': 0}

You may use len, min, max, sum, and round here. round(39.5714, 2) is 39.57.

Part 4: by_month

by_month(filename, column) reads a weather CSV file and returns a dictionary that groups one column’s values by month. Each key is a month number (an int from 1 to 12), and its value is the list of that column’s values on days in that month, in the order they appear in the file. Only months that appear in the file are keys. If the file has only a header line, it returns an empty dictionary.

>>> by_month("tiny.csv", "high")
{1: [38.0, 27.0], 2: [35.0, 13.0, 23.0], 3: [72.0, 69.0]}
>>> by_month("tiny.csv", "snow")
{1: [0.0, 0.0], 2: [3.0, 0.0, 2.0], 3: [0.0, 0.5]}
>>> by_month("empty.csv", "high")
{}
  • Read the file and find the column as in Part 1. The date is always in the first column. You can’t use read_column for the dates, since they aren’t numbers, so it’s fine to repeat the lines that read the file.
  • The month is the middle part of a date such as "2025-02-03". split it, and convert the month with int.
  • Build the dictionary the way letter_counts did in Lecture 12: the first time you see a month, it isn’t a key yet, so give it an empty list. Then append the value to that month’s list.

Part 5: save_monthly_summary

save_monthly_summary(filename, column, out_filename) groups a column by month and writes a JSON file named out_filename. The JSON file holds a dictionary that maps each month to the summarize dictionary for that month’s values. It doesn’t return anything.

Use by_month and summarize, and write the file with json.dump, as in Lecture 14. Pass indent=2 to json.dump so that the file is easy to read. After running save_monthly_summary("tiny.csv", "high", "tiny_summary.json"), open tiny_summary.json in Thonny. It should start like this:

tiny_summary.json
{
  "1": {
    "count": 2,
    "min": 27.0,
    "max": 38.0,
    "mean": 32.5,
    "median": 32.5
  },
  "2": {
    "count": 3,

JSON keys are always strings, so json.dump writes the month 1 as "1". That’s expected.

Part 6: Questions about the data

Run the last two tests in hw5.py, which write high_summary.json and snow_summary.json for the full data set. Use them to answer these questions in the docstring at the top of hw5.py. A short answer is enough for each part.

  1. Spread of the daily highs
    1. Which two months have the biggest spread between their warmest high and their coldest high (max minus min)? What are those spreads?
    2. Which two months have the smallest spread? What are those spreads?
  2. Snowfall
    1. What is the median snowfall for each month?
    2. Why is the median the same for every month?
    3. Which statistic, mean or median, shows better how snowy a month is?
    4. Using that statistic, about how many inches of snow fall in a typical February?

Specification

At a minimum, your hw5.py should have:

  1. A function named read_column with two parameters, a file name and a column header, that returns that column’s values as a list of floats.
  2. A function named median with one parameter, a list of numbers, that returns their median (or None for an empty list) without changing the list.
  3. A function named summarize with one parameter, a list of numbers, that returns the dictionary described in Part 3.
  4. A function named by_month with two parameters, a file name and a column header, that returns a dictionary mapping each month number to a list of that column’s values.
  5. A function named save_monthly_summary with three parameters, a file name, a column header, and an output file name, that writes the JSON file described in Part 5.
  6. Answers to the two questions in Part 6.

Style and Design

  • You must add your code to the provided starter file.
  • At the top of the file, include your name (Author:), the resources you used, and your answers to Part 6 (included in the starter file).
  • Each function needs a docstring with its purpose, parameters, and return value. Spell out the “otherwise” cases, such as what median returns for an empty list.
  • Use the functions you’ve already written instead of repeating their code: summarize calls median, and save_monthly_summary calls by_month and summarize.
  • Follow the Guidelines for Code Quality.

Common issues

FileNotFoundError: [Errno 2] No such file or directory: 'tiny.csv'? The data files must be in the same folder as hw5.py. Keep all four files in your hw5 folder.

ValueError: could not convert string to float: 'high'? You’re converting the header line. Skip the first row.

ValueError: 'hgih' is not in list? The column header you passed isn’t in the file. Check its spelling.

nums is sorted after you call median? Use sorted(nums) instead of nums.sort() (see Part 2).

KeyError in by_month? You appended to a month’s list before that month was a key. Check whether the month is in the dictionary first.

Submitting your work

When you are done, submit hw5.py to Gradescope. You don’t need to submit the data files or the JSON files.

Ensure that the upload succeeds and your submission passes all visible tests. Passing all of the visible tests does not guarantee that your submission correctly satisfies all of the requirements of the assignment. Some tests are hidden until after the grace period; they use other data files. We encourage you to submit early and often so you have time to resolve any issues.