GroupBy
GroupBy Multiple Aggregations
Track both count and running sum per category in a single loop, then derive
the mean from those two accumulators. With pandas, .agg(['count', 'mean'])
applies both aggregations at once and returns a DataFrame with one column per
aggregation — avoiding the need to loop twice or store two separate dicts.
By hand
One loop over zip(cats, vals) fills both counts and totals. A second
loop over sorted keys builds result as a {cat: (count, mean)} dict.
naive.py
Replay: real traced execution (multi-file project)
cats = ['a', 'b', 'a', 'c', 'b', 'a']
vals = [10, 20, 30, 40, 50, 60]
counts = {}
totals = {}
for cat, val in zip(cats, vals):
counts[cat] = counts.get(cat, 0) + 1
totals[cat] = totals.get(cat, 0) + val
result = {}
for k in sorted(counts):
result[k] = (counts[k], round(totals[k] / counts[k], 2))
print('RESULT:', result)
cats ← ['a', 'b', 'a', 'c', 'b', 'a']
1cats = ['a', 'b', 'a', 'c', 'b', 'a']2vals = [10, 20, 30, 40, 50, 60]values this step['a', 'b', 'a', 'c', 'b', 'a']catsvals ← [10, 20, 30, 40, 50, 60]
1cats = ['a', 'b', 'a', 'c', 'b', 'a']2vals = [10, 20, 30, 40, 50, 60]3counts = {}values this step[10, 20, 30, 40, 50, 60]valscounts ← {}
2vals = [10, 20, 30, 40, 50, 60]3counts = {}4totals = {}values this step{}countstotals ← {}
3counts = {}4totals = {}5for cat, val in zip(cats, vals):values this step{}totalscat ← 'a', val ← 10, counts ← {'a': 1}, totals ← {'a': 10}
pass 1 of 64totals = {}5for cat, val in zip(cats, vals):6 counts[cat] = counts.get(cat, 0) + 17 totals[cat] = totals.get(cat, 0) + val8result = {}values this step'a'cat10val{} → {'a': 1}counts{} → {'a': 10}totalsAll 6 passes — pass 1 is the card above pass catvalcountstotals1 'a' 10 {} → {'a': 1} {} → {'a': 10} 2 'a' → 'b' 10 → 20 {'a': 1} → {'a': 1, 'b': 1} {'a': 10} → {'a': 10, 'b': 20} 3 'b' → 'a' 20 → 30 {'a': 1, 'b': 1} → {'a': 2, 'b': 1} {'a': 10, 'b': 20} → {'a': 40, 'b': 20} 4 'a' → 'c' 30 → 40 {'a': 2, 'b': 1} → {'a': 2, 'b': 1, 'c': 1} {'a': 40, 'b': 20} → {'a': 40, 'b': 20, 'c': 40} 5 'c' → 'b' 40 → 50 {'a': 2, 'b': 1, 'c': 1} → {'a': 2, 'b': 2, 'c': 1} {'a': 40, 'b': 20, 'c': 40} → {'a': 40, 'b': 70, 'c': 40} 6 'b' → 'a' 50 → 60 {'a': 2, 'b': 2, 'c': 1} → {'a': 3, 'b': 2, 'c': 1} {'a': 40, 'b': 70, 'c': 40} → {'a': 100, 'b': 70, 'c': 40} for cat, val in zip(cats, vals):
4totals = {}5for cat, val in zip(cats, vals):6 counts[cat] = counts.get(cat, 0) + 1result ← {}
7 totals[cat] = totals.get(cat, 0) + val8result = {}9for k in sorted(counts):values this step{}resultk ← 'a', result ← {'a': (3, 33.33)}
pass 1 of 38result = {}9for k in sorted(counts):10 result[k] = (counts[k], round(totals[k] / counts[k], 2))11print('RESULT:', result)values this step'a'k{} → {'a': (3, 33.33)}resultAll 3 passes — pass 1 is the card above pass kresult1 'a' {} → {'a': (3, 33.33)} 2 'a' → 'b' {'a': (3, 33.33)} → {'a': (3, 33.33), 'b': (2, 35.0)} 3 'b' → 'c' {'a': (3, 33.33), 'b': (2, 35.0)} → {'a': (3, 33.33), 'b': (2, 35.0), 'c': (1, 40.0)} for k in sorted(counts):
8result = {}9for k in sorted(counts):10 result[k] = (counts[k], round(totals[k] / counts[k], 2))stdout ← RESULT: {'a': (3, 33.33), 'b': (2, 35.0), 'c': (1, 40.0)}
10 result[k] = (counts[k], round(totals[k] / counts[k], 2))11print('RESULT:', result)values this stepRESULT: {'a': (3, 33.33), 'b': (2, 35.0), 'c': (1, 40.0)}stdout
With pandas
df.groupby('cat')['val'].agg(['count', 'mean']) returns a DataFrame indexed
by category with count and mean columns. The snapshot shows the columns,
index, and each aggregation's values before the final result dict.
library.py
import pandas as pd
from dalib.display import set_display
set_display()
cats = ['a', 'b', 'a', 'c', 'b', 'a']
vals = [10, 20, 30, 40, 50, 60]
df = pd.DataFrame({'cat': cats, 'val': vals})
agg = df.groupby('cat')['val'].agg(['count', 'mean'])
result = {
k: (int(agg.loc[k, 'count']), round(float(agg.loc[k, 'mean']), 2))
for k in sorted(agg.index)
}
print('columns:', agg.columns.tolist())
print('index:', sorted(agg.index.tolist()))
print('count:', [int(agg.loc[k, 'count']) for k in sorted(agg.index)])
print('mean:', [round(float(agg.loc[k, 'mean']), 2) for k in sorted(agg.index)])
print('RESULT:', result)
columns: ['count', 'mean']
index: ['a', 'b', 'c']
count: [3, 2, 1]
mean: [33.33, 35.0, 40.0]
RESULT: {'a': (3, 33.33), 'b': (2, 35.0), 'c': (1, 40.0)}
Implementation notes
.agg(['count', 'mean'])returns a DataFrame (not a Series), because there is more than one aggregation result per group. The columns are the aggregation names; the index is the group keys.- Passing a dict to
.aggallows renaming and mixing aggregations per column:df.groupby('cat').agg({'val': ['sum', 'mean'], 'score': 'max'}). int()andfloat()convert numpy scalars to Python natives for stable repr.agg.loc[k, 'count']isnp.int64;agg.loc[k, 'mean']isnp.float64— both need conversion for a clean RESULT.- Cross-reference:
group-mean(python-data-basics) for the pure-Python mean-from-accumulators version;groupby-mean(this chapter) for the single-aggregation pandas version.