Counting and Grouping
Overview
A dictionary is how you count things, and how you split a table into groups.
What You Will Learn in This Lesson
By the end of this lesson, you will be able to:
- Count how often each value appears with a dictionary and
.get. - Use
collections.Counterfor the same job, and read its most common values. - Group rows into lists by a key with
setdefault. - Summarise each group — the split-apply-combine pattern behind every group-by.
Why This Matters
“How many in each category?” and “what is the average per group?” are the two questions asked of nearly every table. The dictionary pattern in this lesson is exactly what pandas’ groupby does in unit 7: split the rows by a key, apply a summary to each part, combine the answers. Writing it once by hand is what makes group-by stop looking like magic.
Step 1: Counting with a dictionary
The key is the thing being counted; the value is how many times you have seen it.
The first sighting problem
counts[s] += 1 raises KeyError the first time s appears, because there is nothing to add to yet. counts.get(s, 0) + 1 treats an unseen key as zero.
subjects = ["maths", "computing", "maths", "art", "maths"]
counts = {}
for s in subjects:
counts[s] = counts.get(s, 0) + 1
print(counts)
Counter does it for you
collections.Counter is a dictionary built for counting. most_common(n) answers “which values are most frequent?” directly.
from collections import Counter
subjects = ["maths", "computing", "maths", "art", "maths"]
counts = Counter(subjects)
print(counts)
print(counts.most_common(1))
Mini Practice #1: Most common answer
Try It YourselfCount the survey answers with Counter and print the two most common.
Step 2: Grouping is counting with lists
Same shape, except the value is a list you append to instead of a number you add to.
Split the rows by a key
groups.setdefault(key, []) returns the list for that key, creating an empty one on the first sighting. Appending to what it returns fills the group.
rows = [("maths", 92), ("computing", 88), ("maths", 79)]
groups = {}
for subject, score in rows:
groups.setdefault(subject, []).append(score)
print(groups)
Apply a summary to each group
Once the scores are split, each group is an ordinary list and every summary from the last lesson works on it.
groups = {"maths": [92, 79], "computing": [88]}
for subject, scores in groups.items():
print(subject, len(scores), sum(scores) / len(scores))
Combine into one answer
A dictionary comprehension collects the per-group results into a single table: one key per group, one summary per key.
rows = [("maths", 92), ("computing", 88), ("maths", 79)]
groups = {}
for subject, score in rows:
groups.setdefault(subject, []).append(score)
means = {s: sum(v) / len(v) for s, v in groups.items()}
print(means)
Key idea: Split, apply, combine. In unit 7 this whole block becomes df.groupby("subject")["score"].mean(), and the three words are how that line is explained.
Mini Practice #2: Highest per group
Try It YourselfGroup the rows by subject, then print the highest score in each subject.
Step 3: Reading the result honestly
A per-group summary invites comparison, and comparisons need the group sizes next to them.
Report the count with the mean
A group of one student with 95 “beats” a group of forty with 90. Printing the count beside each mean shows which comparison is worth making.
groups = {"maths": [90, 91, 89, 90], "art": [95]}
for subject, scores in sorted(groups.items()):
print(f"{subject}: mean {sum(scores) / len(scores):.1f} from {len(scores)} scores")
Predict, then check: Keep each group’s denominator
Art has two results and maths has one. Predict both averages without combining the groups.
rows = [("art", 0), ("maths", 90), ("art", 60)]
groups = {}
for subject, score in rows:
groups.setdefault(subject, []).append(score)
for subject, scores in groups.items():
print(subject, len(scores), round(sum(scores) / len(scores), 1))
Show the output and explanation
art 2 30.0 maths 1 90.0
Each group has its own total and count. Dividing art’s total by all three rows would give 20.0, which is not its mean. Keeping the count beside the average also shows that maths has only one observation.
Try one change: Add ("maths", 30). Predict the new count and mean for maths; art should stay unchanged.
Go deeper: Keep each group’s state separate
Counting tracks one number per key. Grouping stores the values belonging to each key so you can compute summaries within each group. A per-group mean needs that group’s total and count, not the total number of rows in the dataset.
Trace the example
Predict the output before opening the explanation.
rows = [("A", 2), ("B", 9), ("A", 6)]
groups = {}
for name, score in rows:
groups.setdefault(name, []).append(score)
print(groups["A"])
print(sum(groups["A"]) / len(groups["A"]))
Show the output and trace
[2, 6] 4.0
The first A creates its list with 2. B gets a different list containing 9. The final A appends 6 to A’s existing list. A’s mean divides its sum of 8 by its own count of 2, producing 4.
A mistake to watch for
Dividing A’s total by len(rows) would mix a group total with a whole-dataset count. Replacing groups[name] with [score] on every iteration would lose earlier values instead of accumulating them.
Transfer challenge
Add ("B", 3). Print each group and its mean. Check that changing B’s observations does not change A’s mean.
Change the example below to solve the challenge. Predict the result, run your code, and compare. This practice is ungraded.
Compare with a worked solution
rows = [("A", 2), ("B", 9), ("A", 6), ("B", 3)]
groups = {}
for name, score in rows:
groups.setdefault(name, []).append(score)
for name, scores in groups.items():
print(name, sum(scores) / len(scores))
Run both versions. Explain any difference in their output, then change one input and predict the new result.
Step 4: When to Use It
How many of each
Categories, answers, subjects. Counter or a dictionary with .get gives the frequency table.
Counter(subjects).most_common(3)
A summary per group
When the question says “per” or “by”: split into lists with setdefault, then summarise each list.
When Not to Use It
Do not compare group averages without their sizes. A group of one can top the table by accident — report the count next to every mean.
End-of-Lesson Exercises
Exercise 1: Rows per subject
Write count_subjects(rows) that takes a list of (subject, score) pairs and returns a dictionary of how many rows each subject has. Count every row, including a zero score, and return an empty dictionary for no rows.
Start with an empty dictionary and walk the rows. counts.get(subject, 0) + 1 handles both the first sighting and every one after it.
Exercise 2: Mean per subject
Write mean_by_subject(rows) that takes (subject, score) pairs and returns a dictionary of each subject’s mean score, rounded to one decimal place.
Split first: groups.setdefault(subject, []).append(score). Then combine: {s: round(sum(v) / len(v), 1) for s, v in groups.items()}.