Revenue by category in pandas
The bitshop data lives in three tables: orders, order_items and products. Revenue sits on the
line items, the category sits on the product, and the status sits on the order. You need all three.
Write revenue_by_category(orders, items, products). Each argument is a pandas DataFrame loaded from
the matching bitshop CSV:
orders:order_id, customer_id, order_date, statusitems:order_id, product_id, quantity, unit_priceproducts:product_id, name, category, price
Return a DataFrame with exactly two columns, category and revenue:
revenueis the sum ofquantity * unit_priceover line items whose order is notcancelled. Returned orders still count.- Use
unit_pricefrom the line item, not the product’s list price. - One row per category that has revenue. Leave out categories with no qualifying items.
- Sort by
revenuedescending, thencategoryascending. Don’t round.
>>> revenue_by_category(orders, items, products).head(2)
category revenue
0 Hardware 157421.34
1 Courses 58614.63
Hints get more specific as you go. Reveal one, try again, then come back.
Filter first: orders[orders["status"] != "cancelled"]. Fewer rows to join.
Two inner joins: items with the kept orders on order_id, then with products[["product_id", "category"]] on product_id. Inner joins drop items from cancelled orders for free.
Add a column with df["quantity"] * df["unit_price"], then
df.groupby("category", as_index=False)["revenue"].sum().
sort_values(["revenue", "category"], ascending=[False, True]), then reset_index(drop=True) and
select the two columns in order.
Try one more hint first? The solution is always here, but working it out is what makes it stick.
Approach
Filter, join, compute, group, sort.
import pandas as pd
def revenue_by_category(orders, items, products):
live = orders[orders["status"] != "cancelled"][["order_id"]]
df = items.merge(live, on="order_id").merge(products[["product_id", "category"]], on="product_id")
df = df.assign(revenue=df["quantity"] * df["unit_price"])
out = df.groupby("category", as_index=False)["revenue"].sum()
out = out.sort_values(["revenue", "category"], ascending=[False, True])
return out.reset_index(drop=True)[["category", "revenue"]]
Selecting [["order_id"]] before the join keeps customer_id, order_date and status out of the
result. Selecting [["product_id", "category"]] keeps the product’s list price out, so there’s no
chance of summing the wrong price column.
Complexity
The joins are hash joins: O(orders + items + products). Grouping is O(rows), and sorting the
handful of categories costs nothing.
Why this matters
This is the same query as SELECT category, SUM(quantity * unit_price) ... GROUP BY category in SQL,
and pandas runs it the same way: build a hash table on one side of each join, probe it with the
other, then hash-aggregate. The status filter is a business rule, and it’s the part that gets lost
when the same metric is computed in three dashboards three ways. Writing it once, as a tested
function, is the cheap fix.
Per-problem discussion threads are coming. Until then, questions and better solutions are welcome on LinkedIn or by email at hello@builtfrombits.com.
Press ⌘ ↵ or Run to check the visible tests. Submit runs the hidden ones too. Everything runs in your browser.