The CFO loved the channel grouping from yesterday. Now she wants a second tab: total spend per channel, total leads per channel, and average CPL per channel. In a pivot table you'd drag spend and leads into the values area and set them to Sum. In Python, where do you even start?
We already have group_by_channel — that splits campaigns into a dict where each key is a channel and each value is a list of campaign dicts. But a list of dicts is not the same as aggregated totals.
Exactly the right starting point. The grouped dict gives you the raw material — now you need to walk it. The key move is .items(), which hands you both the channel name and its campaign list at the same time. Once you have the list, summing spend is just a loop inside a loop:
grouped = group_by_channel(campaigns)
for channel, campaign_list in grouped.items():
total_spend = sum(c["spend"] for c in campaign_list)
total_leads = sum(c["leads"] for c in campaign_list)
print(f"{channel}: ${total_spend:,.2f} spend, {total_leads} leads")I keep getting tangled — grouped.items() gives me the channel string and the list together? And then I loop that inner list separately? I'm losing track of which loop I'm in.
Think of it in two rings. The outer loop is your pivot row — one pass per channel. The inner sum is your pivot value — one accumulation per row in that channel's list. When you're inside the outer loop, campaign_list is just a plain list of dicts: campaign_list[0]["spend"] is the spend of the first campaign in that channel. Once you add the CPL calculation and guard for zero leads, the whole thing collapses into a clean output dict:
def channel_summary(campaigns: list) -> dict:
grouped = group_by_channel(campaigns)
result = {}
for channel, campaign_list in grouped.items():
total_spend = sum(c["spend"] for c in campaign_list)
total_leads = sum(c["leads"] for c in campaign_list)
avg_cpl = total_spend / total_leads if total_leads > 0 else 0.0
result[channel] = {
"total_spend": total_spend,
"total_leads": total_leads,
"avg_cpl": avg_cpl
}
print(f"Summarised channel: {channel}")
return resultThat's a pivot table. group_by_channel is the row grouping, sum(c["spend"] ...) is the Sum aggregation, and the output dict is the resulting values area. I built a pivot table in Python.
You did. And before you ask — yes, pandas does this in one line. df.groupby("channel").agg({"spend": "sum", "leads": "sum"}). We'll get there. But you just wrote the primitive that pandas compiles down to internally.
Knowing the primitive makes pandas less magic. I'd rather understand what the shortcut is actually doing.
That's the right instinct. The pattern you built — outer loop over groups, inner accumulation per item, guarded division — shows up every time you go from raw rows to a summary. The shape of the output dict mirrors what you'd hand to a chart library or drop into a report. That's the real lesson: nested data in, flat summary out.
A dict of lists (from group_by_channel) feeds into a dict of dicts (your summary). .items() is the bridge.
for key, items in grouped.items():
total = sum(x["field"] for x in items)
result[key] = {"total": total, ...}
| Pattern | Handles |
|---|---|
x / y if y > 0 else 0.0 | Zero denominator |
sum(c["spend"] for c in lst) | Inner accumulation |
grouped["email"] → list of campaign dicts for emailgrouped["email"][0]["spend"] → spend of first email campaignThe CFO loved the channel grouping from yesterday. Now she wants a second tab: total spend per channel, total leads per channel, and average CPL per channel. In a pivot table you'd drag spend and leads into the values area and set them to Sum. In Python, where do you even start?
We already have group_by_channel — that splits campaigns into a dict where each key is a channel and each value is a list of campaign dicts. But a list of dicts is not the same as aggregated totals.
Exactly the right starting point. The grouped dict gives you the raw material — now you need to walk it. The key move is .items(), which hands you both the channel name and its campaign list at the same time. Once you have the list, summing spend is just a loop inside a loop:
grouped = group_by_channel(campaigns)
for channel, campaign_list in grouped.items():
total_spend = sum(c["spend"] for c in campaign_list)
total_leads = sum(c["leads"] for c in campaign_list)
print(f"{channel}: ${total_spend:,.2f} spend, {total_leads} leads")I keep getting tangled — grouped.items() gives me the channel string and the list together? And then I loop that inner list separately? I'm losing track of which loop I'm in.
Think of it in two rings. The outer loop is your pivot row — one pass per channel. The inner sum is your pivot value — one accumulation per row in that channel's list. When you're inside the outer loop, campaign_list is just a plain list of dicts: campaign_list[0]["spend"] is the spend of the first campaign in that channel. Once you add the CPL calculation and guard for zero leads, the whole thing collapses into a clean output dict:
def channel_summary(campaigns: list) -> dict:
grouped = group_by_channel(campaigns)
result = {}
for channel, campaign_list in grouped.items():
total_spend = sum(c["spend"] for c in campaign_list)
total_leads = sum(c["leads"] for c in campaign_list)
avg_cpl = total_spend / total_leads if total_leads > 0 else 0.0
result[channel] = {
"total_spend": total_spend,
"total_leads": total_leads,
"avg_cpl": avg_cpl
}
print(f"Summarised channel: {channel}")
return resultThat's a pivot table. group_by_channel is the row grouping, sum(c["spend"] ...) is the Sum aggregation, and the output dict is the resulting values area. I built a pivot table in Python.
You did. And before you ask — yes, pandas does this in one line. df.groupby("channel").agg({"spend": "sum", "leads": "sum"}). We'll get there. But you just wrote the primitive that pandas compiles down to internally.
Knowing the primitive makes pandas less magic. I'd rather understand what the shortcut is actually doing.
That's the right instinct. The pattern you built — outer loop over groups, inner accumulation per item, guarded division — shows up every time you go from raw rows to a summary. The shape of the output dict mirrors what you'd hand to a chart library or drop into a report. That's the real lesson: nested data in, flat summary out.
A dict of lists (from group_by_channel) feeds into a dict of dicts (your summary). .items() is the bridge.
for key, items in grouped.items():
total = sum(x["field"] for x in items)
result[key] = {"total": total, ...}
| Pattern | Handles |
|---|---|
x / y if y > 0 else 0.0 | Zero denominator |
sum(c["spend"] for c in lst) | Inner accumulation |
grouped["email"] → list of campaign dicts for emailgrouped["email"][0]["spend"] → spend of first email campaignAmir's CFO wants a single-function view of campaign performance by channel: total spend, total leads, and average CPL for each channel in the dataset. Write `channel_summary(campaigns)` that takes a list of campaign dicts (each with `"name"`, `"channel"`, `"spend"`, and `"leads"` keys), calls `group_by_channel` to split by channel, then for each channel computes `total_spend`, `total_leads`, and `avg_cpl` (guarding against zero leads with `0.0`). Return a dict keyed by channel name, where each value is a dict with those three keys — for example, `{"email": {"total_spend": 2500.0, "total_leads": 100, "avg_cpl": 25.0}}`.
Tap each step for scaffolded hints.
No blank-editor panic.