CS170: Programming for the World Around Us - Hands-On Data Analysis with Python
Activity Goals
The goals of this activity are:- To read a small CSV data file using plain Python
- To store data in lists and dictionaries for analysis
- To compute summary statistics (count, total, average, minimum, and maximum) with loops
- To group and count data by category with a dictionary
- To produce a simple, readable text report from data
Supplemental Reading
Feel free to visit these resources for supplemental background reading material.The Activity
Directions
Consider the activity models and answer the questions provided. First reflect on these questions on your own briefly, before discussing and comparing your thoughts with your group. Appoint one member of your group to discuss your findings with the class, and the rest of the group should help that member prepare their response. Answer each question individually from the activity, and compare with your group to prepare for our whole-class discussion. After class, think about the questions in the reflective prompt and respond to those individually in your notebook. Report out on areas of disagreement or items for which you and your group identified alternative approaches. Write down and report out questions you encountered along the way for group discussion.Model 1: Reading a CSV File into Parallel Lists
Questions
- Why do we skip the first row of the file? What would happen to
float(parts[2])if we didn't? - What does
line.strip()remove, and what could go wrong without it? - These three lists are called parallel lists:
names[2],categories[2], andprices[2]all describe the same snack. What is at index 1 of each list?
Model 2: Computing Statistics with a Loop
Questions
- Trace this loop by hand: what is
totalafter each of the five iterations? - Modify the loop to also find the largest price, and use parallel lists to print the name of that most expensive snack.
Model 3: Grouping and Counting with a Dictionary
Questions
- What does this program print? Trace the dictionary's contents after each loop iteration to check yourself.
- Why do we need the
if category in countscheck? What error do you get without it?
Notes and Walkthrough
You have learned about variables, conditionals, loops, functions, lists, and dictionaries — today they all come together to do the single most common thing people use Python for in the real world: answering questions about data. Biologists tally species observations, athletic trainers summarize workout logs, businesses total their sales, and journalists fact-check claims — all with programs shaped exactly like the one you’ll build in this activity. No new language features are required; the skill we’re practicing is composing the pieces you already know.
The Shape of Every Data Analysis Program
Nearly every small data analysis program has the same three-part shape:
- Read: open the file and load its values into lists (converting text to numbers as needed).
- Compute: loop over the lists to count, total, average, find the biggest and smallest, or group by category.
- Report: print the answers in a way a human can read.
Keep this shape in mind and even a blank editor won’t feel intimidating: you always know what the first section of your program will do.
Our Dataset
We’ll analyze a tiny snack bar sales file, snacks.csv. Create it in your editor by making a new file with exactly these lines (or invent your own snacks — the program won’t mind!):
name,category,price
Pretzels,salty,2.50
Apple,fruit,0.75
Trail Mix,salty,3.25
Banana,fruit,1.00
Chocolate Bar,candy,2.00
The first line is the header: it names the columns for humans. Every other line is one record: a snack’s name, its category, and its price, separated by commas. This is a CSV (“comma separated values”) file — the humble format behind an enormous amount of the world’s data, and one you can open in any spreadsheet program too.
Step 1: Read — From Text File to Lists
The first model above reads the file into three parallel lists. Two conversions happen along the way, and they trip up everyone at first:
- Each
linearrives as a string ending in an invisible newline character;line.strip()removes it. Without it, the last column of every row would be"2.50\n"— and printing your data would look mysteriously double-spaced. - Everything read from a file is text, even the numbers!
"2.50" + "0.75"is"2.500.75"(string gluing), not3.25. That’s why we convert withfloat(parts[2])before appending.
Here is a trace of the reading loop for the first three lines of the file:
| Iteration | line (after strip) |
rownum at loop top |
What happens | prices afterward |
|---|---|---|---|---|
| 1 | name,category,price |
0 | Header: the if is False, nothing appended |
[] |
| 2 | Pretzels,salty,2.50 |
1 | Split into 3 parts; 2.50 becomes the float 2.5 |
[2.5] |
| 3 | Apple,fruit,0.75 |
2 | Split; 0.75 becomes 0.75 |
[2.5, 0.75] |
After the whole file, names has 5 strings, categories has 5 strings, and prices has 5 floats — and index i of each list describes the same snack.
Step 2: Compute — Asking the Data Questions
How many snacks? That’s just len(names).
What do they cost on average? Total them with the accumulator pattern — start a running total at 0 and add each value:
total = 0
for price in prices:
total = total + price
average = total / len(prices)
This program prints (given our file):
Average price: 1.9
What’s the most expensive snack? Track the best-so-far index, so the parallel lists can tell you its name:
biggest = 0 # index of the priciest snack seen so far
for i in range(len(prices)):
if prices[i] > prices[biggest]:
biggest = i
print("Most expensive:", names[biggest], "at $" + str(prices[biggest]))
Trace it against our data — prices is [2.5, 0.75, 3.25, 1.0, 2.0]:
i |
prices[i] |
prices[biggest] before |
Is it bigger? | biggest after |
|---|---|---|---|---|
| 0 | 2.5 | 2.5 | no (tie with itself) | 0 |
| 1 | 0.75 | 2.5 | no | 0 |
| 2 | 3.25 | 2.5 | yes | 2 |
| 3 | 1.0 | 3.25 | no | 2 |
| 4 | 2.0 | 3.25 | no | 2 |
The loop ends with biggest = 2, so it prints Most expensive: Trail Mix at $3.25.
How many snacks per category? Categories are labels, so counting them calls for a dictionary mapping each label to its count — the third model above. Trace its loop over ["salty", "fruit", "salty", "candy", "fruit"]:
| Iteration | category |
Already in counts? |
counts afterward |
|---|---|---|---|
| 1 | salty |
no | {'salty': 1} |
| 2 | fruit |
no | {'salty': 1, 'fruit': 1} |
| 3 | salty |
yes | {'salty': 2, 'fruit': 1} |
| 4 | candy |
no | {'salty': 2, 'fruit': 1, 'candy': 1} |
| 5 | fruit |
yes | {'salty': 2, 'fruit': 2, 'candy': 1} |
Step 3: Report — Presenting the Answers
Raw numbers dumped to the screen make readers work too hard. A few print calls turn your analysis into a report:
print("SNACK BAR SALES REPORT")
print("======================")
print("Snacks on the menu:", len(names))
print("Average price: $" + str(round(average, 2)))
print("Most expensive:", names[biggest])
print()
print("By category:")
for category in counts:
print(" " + category + ": " + str(counts[category]))
This program prints:
SNACK BAR SALES REPORT
======================
Snacks on the menu: 5
Average price: $1.9
Most expensive: Trail Mix
By category:
salty: 2
fruit: 2
candy: 1
Notice the loop at the bottom: looping over a dictionary visits its keys, and counts[category] looks up each one’s value.
Common Mistakes to Avoid
- Forgetting to skip the header — your program crashes with
ValueError: could not convert string to float: 'price'. Now you know exactly what that message means! - Forgetting
float(...)— no crash, but totals come out as strangely glued-together text, or you get aTypeErrorwhen dividing. - Dividing by
len(...)of an empty list — if your file path is wrong or the file is empty, you’ll divide by zero. Printlen(names)early to confirm the data actually loaded. - Using a new dictionary key without creating it —
counts[category] = counts[category] + 1on a first-time key raises aKeyError; that’s what theif category in countscheck prevents. - Mixing up index and value —
prices[biggest](withbiggestan index) versusbiggestitself. If your “maximum price” prints as2, you printed the index!
Practice Exercises
Work through these in order — each builds on the last. Choose your own dataset if you like: instead of snacks, make a CSV of your music library (title, genre, minutes), a sports team (player, position, points), books you’ve read (title, genre, pages) — anything with one text column, one category column, and one numeric column will work with exactly the same code.
Exercise 1 (warm-up): Count and Total
Read snacks.csv (or your own file) into parallel lists and print how many records it contains and the total of the numeric column. Expected output for the snack data: 5 snacks totaling $9.5.
Click to reveal a solution to Exercise 1
```python names = [] prices = [] f = open("snacks.csv") rownum = 0 for line in f: if rownum > 0: parts = line.strip().split(",") names.append(parts[0]) prices.append(float(parts[2])) rownum = rownum + 1 f.close() total = 0 for price in prices: total = total + price print(str(len(names)) + " snacks totaling $" + str(total)) ``` The reading loop is the same pattern as the model; the totaling loop is the accumulator pattern.Exercise 2: Minimum and Maximum
Print the name of the cheapest and most expensive snack. Expected output: Cheapest: Apple and Most expensive: Trail Mix.
Click to reveal a solution to Exercise 2
```python # (read the file into names and prices as in Exercise 1 first) cheapest = 0 biggest = 0 for i in range(len(prices)): if prices[i] < prices[cheapest]: cheapest = i if prices[i] > prices[biggest]: biggest = i print("Cheapest:", names[cheapest]) print("Most expensive:", names[biggest]) ``` Both searches track an *index* so the parallel `names` list can supply the answer's name.Exercise 3: Group Averages
Using a dictionary, compute the average price per category. Hint: you’ll need two dictionaries (or a dictionary whose values are lists): one accumulating each category’s total, one counting its members. Expected output includes fruit: 0.875.
Click to reveal a solution to Exercise 3
```python # (read the file into categories and prices first) totals = {} counts = {} for i in range(len(prices)): category = categories[i] if category in totals: totals[category] = totals[category] + prices[i] counts[category] = counts[category] + 1 else: totals[category] = prices[i] counts[category] = 1 for category in totals: print(category + ": " + str(totals[category] / counts[category])) ``` Two parallel dictionaries share the same keys, just as parallel lists share the same indices.Exercise 4: Filter and Report
Ask the user for a price limit with input(), then print a formatted report of only the snacks at or under that limit, followed by how many matched. Remember that input() gives you a string!
Click to reveal a solution to Exercise 4
```python # (read the file into names and prices first) limit = float(input("Price limit: $")) print("Snacks within your budget:") matches = 0 for i in range(len(prices)): if prices[i] <= limit: print(" " + names[i] + " ($" + str(prices[i]) + ")") matches = matches + 1 print(str(matches) + " snack(s) found.") ``` The `float(...)` around `input()` matters: comparing a string to a float raises a `TypeError`.Exercise 5 (challenge): Write the Report to a File
Combine everything into a full report — count, average, cheapest, most expensive, and per-category counts — and write it to report.txt instead of the screen, using open("report.txt", "w") and file.write(...). Open the result in your editor to admire it. (Remember: write does not add newlines for you — end each line with "\n".)
Click to reveal a solution to Exercise 5
```python # (read the file and compute total, average, biggest, counts as above) report = open("report.txt", "w") report.write("SNACK BAR SALES REPORT\n") report.write("======================\n") report.write("Snacks on the menu: " + str(len(names)) + "\n") report.write("Average price: $" + str(round(average, 2)) + "\n") report.write("Most expensive: " + names[biggest] + "\n") report.write("By category:\n") for category in counts: report.write(" " + category + ": " + str(counts[category]) + "\n") report.close() print("Report written to report.txt") ``` Forgetting `report.close()` can leave the file empty — Python may not flush your writes to disk until the file is closed.Where You’ll Use This Next
This read-compute-report shape is exactly the skeleton of the Respiratory Tracker assignment (reading sensor data and computing breathing rates), and it’s a natural backbone for many final projects. If today’s activity clicked, you already know how to start both.