GroupBy
GroupBy Mean
Split a table of rows into groups by a category column, then compute the mean of a value column within each group. Groupby-mean is one of the most common one-liner operations in data analysis.
By hand
Maintain two dicts: totals accumulates the sum per category, counts
accumulates the row count. After the loop, divide to get the mean for each
category. Sort the keys before printing so the output is deterministic.
naive.py
Replay: real traced execution (multi-file project)
cats = ['a', 'b', 'a', 'b', 'a', 'b']
vals = [10, 20, 30, 40, 50, 60]
totals = {}
counts = {}
for cat, val in zip(cats, vals):
totals[cat] = totals.get(cat, 0) + val
counts[cat] = counts.get(cat, 0) + 1
means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}
print('RESULT:', means)
cats ← ['a', 'b', 'a', 'b', 'a', 'b']
1cats = ['a', 'b', 'a', 'b', 'a', 'b']2vals = [10, 20, 30, 40, 50, 60]values this step['a', 'b', 'a', 'b', 'a', 'b']catsvals ← [10, 20, 30, 40, 50, 60]
1cats = ['a', 'b', 'a', 'b', 'a', 'b']2vals = [10, 20, 30, 40, 50, 60]3totals = {}values this step[10, 20, 30, 40, 50, 60]valstotals ← {}
2vals = [10, 20, 30, 40, 50, 60]3totals = {}4counts = {}values this step{}totalscounts ← {}
3totals = {}4counts = {}5for cat, val in zip(cats, vals):values this step{}countscat ← 'a', val ← 10
4counts = {}5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + valvalues this step'a'cat10valtotals ← {'a': 10}
5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 1values this step{} → {'a': 10}totalscounts ← {'a': 1}
6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 18means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}values this step{} → {'a': 1}countscat ← 'b', val ← 20
4counts = {}5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + valvalues this step'a' → 'b'cat10 → 20valtotals ← {'a': 10, 'b': 20}
5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 1values this step{'a': 10} → {'a': 10, 'b': 20}totalscounts ← {'a': 1, 'b': 1}
6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 18means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}values this step{'a': 1} → {'a': 1, 'b': 1}countscat ← 'a', val ← 30
4counts = {}5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + valvalues this step'b' → 'a'cat20 → 30valtotals ← {'a': 40, 'b': 20}
5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 1values this step{'a': 10, 'b': 20} → {'a': 40, 'b': 20}totalscounts ← {'a': 2, 'b': 1}
6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 18means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}values this step{'a': 1, 'b': 1} → {'a': 2, 'b': 1}countscat ← 'b', val ← 40
4counts = {}5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + valvalues this step'a' → 'b'cat30 → 40valtotals ← {'a': 40, 'b': 60}
5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 1values this step{'a': 40, 'b': 20} → {'a': 40, 'b': 60}totalscounts ← {'a': 2, 'b': 2}
6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 18means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}values this step{'a': 2, 'b': 1} → {'a': 2, 'b': 2}countscat ← 'a', val ← 50
4counts = {}5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + valvalues this step'b' → 'a'cat40 → 50valtotals ← {'a': 90, 'b': 60}
5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 1values this step{'a': 40, 'b': 60} → {'a': 90, 'b': 60}totalscounts ← {'a': 3, 'b': 2}
6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 18means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}values this step{'a': 2, 'b': 2} → {'a': 3, 'b': 2}countscat ← 'b', val ← 60
4counts = {}5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + valvalues this step'a' → 'b'cat50 → 60valtotals ← {'a': 90, 'b': 120}
5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 1values this step{'a': 90, 'b': 60} → {'a': 90, 'b': 120}totalscounts ← {'a': 3, 'b': 3}
6 totals[cat] = totals.get(cat, 0) + val7 counts[cat] = counts.get(cat, 0) + 18means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}values this step{'a': 3, 'b': 2} → {'a': 3, 'b': 3}countsfor cat, val in zip(cats, vals):
4counts = {}5for cat, val in zip(cats, vals):6 totals[cat] = totals.get(cat, 0) + valmeans ← {'a': 30.0, 'b': 40.0}
7 counts[cat] = counts.get(cat, 0) + 18means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}9print('RESULT:', means)values this step{'a': 30.0, 'b': 40.0}meansstdout ← RESULT: {'a': 30.0, 'b': 40.0}
8means = {cat: totals[cat] / counts[cat] for cat in sorted(totals)}9print('RESULT:', means)values this stepRESULT: {'a': 30.0, 'b': 40.0}stdout
With Pandas
pd.DataFrame loads the list of dicts into a typed table. groupby('cat')
partitions the rows, ['val'] selects the column, and .mean() reduces
each partition to its mean — all in one chain. The result is a Series
indexed by category.
library.py
import pandas as pd
from dalib.display import set_display
set_display()
data = pd.DataFrame({
'cat': ['a', 'b', 'a', 'b', 'a', 'b'],
'val': [10, 20, 30, 40, 50, 60],
})
means = data.groupby('cat')['val'].mean()
result = {k: float(means[k]) for k in sorted(means.index)}
print('index:', sorted(means.index.tolist()))
print('values:', [float(means[k]) for k in sorted(means.index)])
print('dtype:', means.dtype)
print('RESULT:', result)
index: ['a', 'b']
values: [30.0, 40.0]
dtype: float64
RESULT: {'a': 30.0, 'b': 40.0}