Beginner: data foundations · Cleaning, exploring and visualising data · topic 19 of 62 · 35 min
Grouping, joining and reshaping
Explanation
Split, apply, combine
Most business questions are about groups. "Which city sells the most?" means: split the rows by city, apply a sum to each group, and combine the answers into one small table. This pattern is called split-apply-combine, and in pandas it is df.groupby("city")["amount"].sum().
sales = pd.DataFrame({
"city": ["Pune", "Delhi", "Pune", "Mumbai", "Delhi"],
"amount": [450, 1200, 780, 2100, 150],
})
print(sales.groupby("city")["amount"].sum())
# city
# Delhi 1350
# Mumbai 2100
# Pune 1230
# Name: amount, dtype: int64Several answers at once with agg
sales.groupby("city")["amount"].agg(["count", "sum", "mean"])
# count sum mean
# city
# Delhi 2 1350 675.0
# Mumbai 1 2100 2100.0
# Pune 2 1230 615.0Joining tables with merge
Data is often split across tables. Orders have a customer_id; the customers table has the name and city for each id. pd.merge(orders, customers, on="customer_id", how="left") brings them together. This is exactly a SQL JOIN.
| how= | Keeps | SQL name |
|---|---|---|
| "inner" | Only rows with a match in both tables | INNER JOIN |
| "left" | All rows of the left table; missing matches become NaN | LEFT JOIN |
| "right" | All rows of the right table | RIGHT JOIN |
| "outer" | All rows from both | FULL OUTER JOIN |
Pivot tables
orders.pivot_table(index="city", columns="month", values="amount", aggfunc="sum", fill_value=0)
# Jan Feb Mar
# Delhi 1200 0 150
# Pune 450 780 0Check every result
After a groupby or merge, always run two quick checks. First, do the totals still add up? The sum of the per-city totals must equal the sum of the whole column. Second, does the row count make sense? A left merge must have at least as many rows as the left table, and exactly as many when the right-hand keys are unique. These checks take seconds and catch most join and grouping bugs before your numbers reach a manager.
To group by more than one column, pass a list: df.groupby(["city", "month"])["amount"].sum() gives one row per city and month. Add .reset_index() to turn the group labels back into normal columns, which is easier to save or merge later.
The same idea in plain Python
from collections import defaultdict
totals = defaultdict(int)
for city, amount in [("Pune", 450), ("Delhi", 1200), ("Pune", 780)]:
totals[city] += amount
print(dict(totals))Common mistakes
- Grouping by a column with messy spellings, so "Pune" and "pune" become two groups. Fix: clean first.
- Using an inner join by accident and silently losing customers with no orders. Fix: think about which rows you must keep; often you want a left join.
- Averaging averages: the mean of city averages is not the overall mean when cities have different numbers of orders. Fix: compute from the raw rows or weight by count.
- Forgetting that groupby drops rows where the key is missing (NaN) by default. Fix: fill the key first or pass dropna=False.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Grouping, joining and reshaping · 1/7
This lesson is about answering "per what" questions: grouping, joining and pivoting.
Prefer a video by a teacher? Find free videos on “pandas groupby merge pivot table tutorial” on YouTube ↗
In real life: Which restaurant partners need help?
A food-delivery company keeps orders in one table and restaurants in another. An analyst joins them on restaurant_id (left join from restaurants, so restaurants with zero orders still appear), groups by city and cuisine, and computes order count, average delivery time and cancellation rate. Restaurants with no orders this month and those with high cancellation rates are sent to the partner team for follow-up.
Practice (0/3 done)
Write group_sum(rows, key, value) where rows is a list of dicts. Return a dict mapping each distinct rows[i][key] to the sum of rows[i][value] for that group (like df.groupby(key)[value].sum()). Example: group_sum([{"city": "Pune", "amount": 450}, {"city": "Pune", "amount": 780}], "city", "amount") returns {"Pune": 1230}.
Write group_mean(rows, key, value) that returns a dict mapping each group to the AVERAGE of value in that group, rounded to 2 decimal places.
Write left_join(left, right, key) for two lists of dicts. For each row in left (in order), find the row in right with the same key and return a merged dict {**left_row, **right_row}. If there is no match, keep the left row as it is (a left join). Assume keys in right are unique.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Exploratory data analysis (EDA)Cleaning, exploring and visualising data · same module
- GROUP BY and HAVINGBackend Development · SQL in depth
- Counting and totals: aggregate functionsBackend Development · Databases and SQL basics
- Joins: combining tablesBackend Development · SQL in depth
- Joining three or more tablesSQL and Databases · JOINs
- Merge sort and quick sortData Structures and Algorithms (DSA) for interviews · Sorting and searching
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does df.groupby("city")["amount"].sum() return?
Answer: The total amount for each city
Split by city, sum each group, combine into one value per city.
You merge customers (left) with orders using how="left". What happens to customers with no orders?
Answer: They are kept, with missing (NaN) order columns
A left join keeps every row of the left table.
After a merge, your 1,000 orders became 3,400 rows. What is the likely cause?
Answer: The key was repeated in both tables, creating many combinations
Many-to-many keys multiply rows. Check the keys are unique on one side.
City A has 2 orders averaging ₹100, city B has 8 orders averaging ₹200. What is the overall average order?
Answer: ₹180
Total = 2 × 100 + 8 × 200 = 1,800 over 10 orders = ₹180. Averaging the averages (₹150) is wrong.
Which tool makes an Excel-style grid with cities as rows and months as columns?
Answer: pivot_table
pivot_table reshapes long data into a grid of summaries.
After customers LEFT JOIN orders, which count shows 0 for a customer with no orders?Related
Answer: COUNT(o.id)
COUNT(column) skips NULLs. The customer's single row has o.id = NULL, so COUNT(o.id) is 0 while COUNT(*) is 1.
From GROUP BY and HAVING
What does SELECT city, SUM(amount) FROM orders GROUP BY city return?Related
Answer: One row per city with its total
GROUP BY city makes one group per city, and SUM runs on each group.
Why is a customer's city stored in a customers table instead of on every order row?Related
Answer: So it is stored once and a change happens in one place
Storing a fact once avoids copies that can disagree. Orders refer to the customer by id.
customers LEFT JOIN orders ... JOIN order_items (an inner join). What happens to Arjun, who has no orders?Related
Answer: He is dropped by the inner join, because his NULL order id matches no order line
A later inner join removes the unmatched rows the LEFT JOIN kept. Use LEFT JOIN for the later joins too.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Cleaning, exploring and visualising data” before its exam: Exploratory data analysis (EDA), Visualising data honestly.
- Module 4 exam: Cleaning, exploring and visualising data · 12 questions · 25 min, without help. 6 topics of this module still to finish first.
- Then the next module, The ML workflow, regression and classification. 62 topics left in the course before the final exam.