Before you start
You will be able to
- Construct derived variables (totals, ratios, flags, date parts and durations) and use them to check stored values.
- Compare equal-width, equal-frequency and business-defined bins, and explain what binning throws away.
- Calculate group summaries at a stated granularity, and distinguish the overall average from an average of group averages.
- Reshape a table between wide and long format, and explain which questions need each layout.
- Determine the row count of inner, left and full joins, including the row explosion caused by repeated keys.
- Design a repeatable sequence of transformations that leaves the raw data untouched.
Review first
- Encoding categorical variables — Scaling and encoding changed single columns; this lesson changes whole tables, and it reuses the rule that anything learned from data is fitted on the training set only.
- Datasets: rows, columns, records and variables — You need the unit of observation, primary keys, and wide and long format.
- Multiplying, dividing and averaging; counting the days between two dates.
Fictional case A fictional coffee business founded by Ana Lim. It starts as a catering and barista-training service, opens its own cafés and an online shop for beans, and plans a canned cold brew.
The idea
Fictional case
Lantern Coffee Co. has cleaned the export from its online shop, the lantern-orders file: one row per order from 1 March to 30 June 2026, with columns such as order_date, customer_id, region, product, quantity, unit_price, discount, order_total and subscription. Ana Lim, the founder, brings four questions to a planning meeting. Which regions bring in the most revenue each month? Are subscribers’ orders bigger or smaller than one-off orders? How many orders reach the $40 that a $5 promotion code requires? And what gross margin does each product earn, given the roasting costs kept in a separate spreadsheet? None of them can be answered by reading the clean file row by row.
Each question needs the data in a different form: a new column holding each order’s month, one row per region and month instead of one per order, averages by group, values sorted into bands, or costs brought in from another table. Operations like these are transformations: they don’t correct errors, as the earlier lessons in this course did; they change the form of correct data so that it can answer a question. (In the lesson on skewed data a transformation meant applying a function such as a logarithm to one column; here the word covers any change of form.)
Five transformations do most of the work:
- Derive: compute a new variable from existing ones, such as the expected order total from quantity, price and discount, or the number of nights from two dates.
- Bin: sort the values of a numeric variable into ranges, such as order values “under $25”, “$25 to under $40” and “$40 and over”.
- Aggregate: collapse many rows into one summary row per group, such as total revenue per region per month.
- Reshape: rearrange the same values between a wide layout (months as columns) and a long one (months as rows).
- Join: combine two tables by matching a key, such as attaching each product’s cost to every order of that product.
Each of them can quietly damage good data. A derived column inherits every error in the columns it is built from; bins hide the differences inside each band; an aggregate can answer a different question from the one you asked; a reshape can lose or duplicate values; and a join can multiply or drop rows without any warning. So every transformation in this lesson comes with a check to run straight afterwards, and the most useful check is the simplest: count the rows before and after, and explain the difference.
Derived variables
A derived variable is a new column calculated from other columns of the same row. Lantern’s file stores quantity, unit_price and discount, so the amount each order should have cost can be derived:
Derived variables come in a few families. Arithmetic combinations: line totals, or profit as revenue minus cost. Ratios: the discount rate (discount ÷ (quantity × unit price)) or revenue per night. Flags: yes/no columns that encode a rule, such as promo_eligible = order value of $40 or more. Date parts and durations: the month, weekday or quarter of a date, and the time between two dates, such as nights = check-out date − check-in date for a hotel booking.
Lantern’s file also stores order_total, so deriving the expected total gives you a second, independent value for the same quantity. Comparing the two is a cross-field check: wherever they disagree, at least one of the inputs or the stored total is wrong. Here are six orders from a fictional extract with the same columns, using Lantern’s list prices.
| Order | Qty | Unit price ($) | Discount ($) | Stored total ($) | Derived total ($) |
|---|---|---|---|---|---|
| 1 · House Blend 250g | 2 | 14.00 | 0.00 | 28.00 | 28.00 |
| 2 · Ethiopia Yirgacheffe 250g | 1 | 19.50 | 1.95 | 17.55 | 17.55 |
| 3 · Espresso Roast 250g | 3 | 15.50 | 5.00 | 46.50 | 41.50 |
| 4 · House Blend 1kg | 1 | 46.00 | 4.60 | 41.40 | 41.40 |
| 5 · Colombia Huila 250g | 2 | 17.00 | 0.00 | 43.00 | 34.00 |
| 6 · Cold Brew Pack | 1 | 22.00 | 0.00 | 28.50 | 22.00 |
Orders 3, 5 and 6 disagree, and the size of each difference hints at the cause. Order 3’s stored total is exactly the value before its $5.00 promotion discount. Order 5’s is $9.00 too high, and a difference that divides exactly by 9 is the classic sign of two swapped digits (34 typed as 43). Order 6’s is $6.50 too high, the shipping charge, which belongs in a separate column.
The check finds the problems; it doesn’t say which value to trust. That needs evidence from outside the two columns, such as the payment record or the price list. Compare money with a small tolerance (a difference under half a cent counts as equal), because prices are rounded to cents and computers store most decimals approximately. And a derived value is only as good as its inputs: if a quantity is missing, the derived total must be missing too, not zero.
Dates need particular care. The number of nights is a difference between two dates, not between two day numbers: a guest who checks in on 28 February 2026 and leaves on 3 March 2026 stays 3 nights, because February 2026 has 28 days. Convert both columns to true dates first (the lesson on types and formats shows how), then subtract; most tools return the difference in days.
Binning
Binning (also called discretising or bucketing) replaces a numeric value with the range, or bin, that it falls in. Bins make reports readable (“orders under $25”), turn a business rule into a column (the $5 code from $40), and protect privacy, as when a survey publishes age bands instead of birth dates. There are three common ways to choose the edges. Equal-width bins split the range from the minimum to the maximum into intervals of the same width. Equal-frequency (or quantile) bins put roughly the same number of values in each bin, so their widths vary. Business-defined bands use edges that mean something to the organisation: a promotion threshold, a tax bracket, a credit limit.
Whatever the method, binning throws information away. After binning, a $46 order and a $116 order are both “$40 and over”, while a $39 order and a $40 order land in different bands although they differ by a dollar. Keep the original column next to the binned one, and bin to serve a stated rule or report, not by habit.
Aggregation and granularity
The granularity of a table is the level of detail that one row represents: one order, one customer, one region in one month. It is the unit of observation applied to a table you produce. Aggregation moves a table to a coarser granularity in three moves: it splits the rows into groups that share the values of one or more grouping variables, applies a summary function to each group (sum, mean, count, median, minimum or maximum), and combines the results into one row per group.
Two consequences follow. First, aggregation is one-way: from monthly revenue per region you can never recover the individual orders, so keep the detailed table and aggregate from it each time you need a summary. Second, every average answers a question about a particular granularity. Average order value is total revenue divided by the number of orders; average spend per customer is total revenue divided by the number of customers. Both are averages, and they differ whenever customers place different numbers of orders.
Reshaping
Reshaping changes the layout of a table without changing its values. In long format each row holds one measurement: product, month, revenue. In wide format one variable is spread across several columns: one row per product, with a column for each month. Going from long to wide is a pivot; going from wide to long is a melt, also called an unpivot.
Reports and spreadsheets favour wide tables, because people read across the months. Analysis and charting tools favour long tables, because “month” is a variable and should be a column, the layout called tidy data. You will reshape in both directions, often in the same project.
Joins
A join combines two tables by matching the values of a key column, such as product or customer_id, so that each row of one table gains the matching columns of the other. The key in the table you look things up in should be a primary key, appearing once per value. When it isn’t, every matching row is repeated, a row explosion, and every total computed afterwards is silently inflated. That is why the first and last step of every join is to count rows.
In depth
Rules for derived variables
- Define it before you compute it. Write the formula, its unit and its edge-case rule in the data dictionary. Is the discount per order or per item? Does the night count include the check-out day? (It doesn’t: nights = check-out − check-in.)
- Let missing values propagate. If an input is missing, the result is missing. Arithmetic does this in most tools, but functions that skip missing values, and spreadsheets that treat a blank cell as zero, can turn a missing quantity into a total of $0.
- Guard ratios. A ratio with a zero or missing denominator is undefined, not zero. Decide what to record (missing, with a flag) and say so.
- Fix the clock. An order placed at 11:30 p.m. on 31 March in one time zone is in April in another. Decide which time zone defines “the month” and use it everywhere.
- Test before you use. Where the data already store the value (Lantern’s
order_total, Harborview Hotel’snightsandtotal), count the disagreements and resolve them before deciding which column to keep.
Choosing bin edges
For equal-width bins on values from to , every bin has width . Equal-frequency bins use quantiles as edges: for four bins, the quartiles. The table bins twelve fictional Lantern order values, before discount, three ways: $14, $15.50, $17, $19.50, $22, $28, $33, $39, $46, $58, $98 and $116.
| Method | Bin 1 | Bin 2 | Bin 3 |
|---|---|---|---|
| Equal-width (width $34) | $14 to $48: 9 orders | Over $48 to $82: 1 order | Over $82 to $116: 2 orders |
| Equal-frequency (4 per bin) | $14 to $19.50: 4 orders | $22 to $39: 4 orders | $46 to $116: 4 orders |
| Business bands | Under $25: 5 orders | $25 to under $40: 3 orders | $40 and over: 4 orders |
Equal-width edges are 14, 48, 82 and 116, each bin including its upper edge. The business bands use the $40 promotion threshold.
Each method has a typical failure. Equal-width bins are easy to explain, but a few large values stretch the range so that most values crowd into one bin: here nine of the twelve orders land in the first. Equal-frequency bins always fill evenly, but their edges fall wherever the data happen to put them, and one bin may mix very different values: the third holds a $46 order and a $116 order. Ties make quantiles awkward: if many values are identical, an edge can’t split them, and tools either merge bins or stop (pandas’ qcut raises an error by default when two edges coincide). Business bands answer the business question directly, but only someone who knows the business can set them.
State the boundary rule. A value that sits exactly on an edge must go into one bin only, and conventions differ: pandas’ cut includes the right edge of each bin by default, while many business rules include the left edge (“$40 and over”). Write the rule in the data dictionary. And if the bins will feed a predictive model, compute quantile edges from the training set only and apply the same edges to the test set, for the same reason you fit a scaler on training data: edges computed from all the data let information from the test set leak into training.
Group summaries and what an average means
An aggregation has three parts: the grouping variables (region and month), the variable being summarised (order total) and the function (sum). Write them down before you run it, then check the result: the group counts should add up to the number of rows you started with, and the group sums to the overall sum. If they don’t, rows went missing. A common cause is a missing grouping value: pandas’ groupby leaves out rows whose key is missing unless you ask for them with dropna=False, so orders with a blank region silently vanish from a regional summary.
Know what each function does with missing values. In SQL, count(*) counts rows, while count(rating) counts only the rows where rating is present, and sum adds only the values that are present. An average rating is therefore the average among customers who left a rating, which may be a very different group from all customers.
The commonest error with averages is averaging averages. Suppose you know the mean of each group, , and the number of rows in each group, . The mean of all the rows is not the plain average of the group means but their weighted average, each group mean weighted by its group’s size:
A plain average of group means gives every group the same say, however many rows it has. That answers a different question, such as “what does the typical customer spend per order?” when each group is one customer. Say which one you computed, and never average a column of averages, percentages or ratios without asking what the weights should be.
Aggregating can even turn a comparison around. Suppose Lantern’s ratings looked like the table below. Within each product, subscribers rate higher than one-off buyers; yet across both products subscribers rate lower, because most of their ratings are for the product everyone rates lower. This reversal is known as Simpson’s paradox.
| Product | Subscribers | One-off buyers |
|---|---|---|
| House Blend 250g | 4.0 (80 ratings) | 3.8 (20 ratings) |
| Ethiopia Yirgacheffe 250g | 4.8 (20 ratings) | 4.6 (80 ratings) |
| Both products | 4.16 (100 ratings) | 4.44 (100 ratings) |
Subscribers: (80 × 4.0 + 20 × 4.8) ÷ 100 = 4.16. One-off buyers: (20 × 3.8 + 80 × 4.6) ÷ 100 = 4.44.
Neither summary is wrong; they answer different questions. Before you compare groups on an aggregate, check whether the comparison still holds within the subgroups that matter, and report the subgroup figures when it doesn’t.
Occasionally a question needs a finer granularity than the data have. Harborview Hotel stores one row per booking, but occupancy is counted per night: a booking from 30 January to 2 February 2026 contributes two nights to January and one to February. To count nights per month you expand each booking into one row per night, a deliberate one-to-many transformation, and the check is that the expanded table has exactly as many rows as the bookings have nights in total. You can only go finer when a rule says how to split each row; splitting a monthly total into days without such a rule would be inventing data.
Pivot and melt
Here is revenue for two Lantern products from March to May 2026, as a finance report would show it, and the same values in long format.
| Product | Mar | Apr | May |
|---|---|---|---|
| House Blend 250g | 1,246 | 1,302 | 1,414 |
| Decaf Colombia 250g | 396 | 429 |
| Product | Month | Revenue ($) |
|---|---|---|
| House Blend 250g | Mar | 1,246 |
| House Blend 250g | Apr | 1,302 |
| House Blend 250g | May | 1,414 |
| Decaf Colombia 250g | Mar | 396 |
| Decaf Colombia 250g | Apr | missing |
| Decaf Colombia 250g | May | 429 |
Melting turns the three month columns into two: one for the old headers (month) and one for the cell values (revenue). The wide table holds 2 rows × 3 month columns = 6 values, so the long table has 6 rows; that product is the check. Notice the blank. In the wide table it is easy to read as “no sales”, but the long table forces a decision, because the row needs a value. Is Decaf’s April revenue $0 (nothing sold) or unknown (not recorded)? Only the source can say, and the answer changes Decaf’s total and its monthly average.
Pivoting goes the other way and needs exactly one value for each pair of row label and column label. If the long table holds two rows for House Blend in March, perhaps a duplicate that survived cleaning, there is nowhere to put the second value. Some tools refuse: pandas’ pivot raises an error when a pair has more than one value. Others aggregate: a pivot table sums or averages the values in each cell, depending on the function chosen (pandas’ pivot_table averages unless told otherwise), so a duplicate can disappear into a cell without trace. Check for repeated label pairs before you pivot.
Hadley Wickham’s paper “Tidy data” lists the commonest layouts that need reshaping, and the first is column headers that are values rather than variable names: months, years or age bands across the top of a table. Melting is the fix. In a spreadsheet it is done with a query tool (Excel’s Power Query has an Unpivot Columns command); in pandas it is melt; in SQL a common approach stacks one query per column with UNION ALL.
Join types and their row counts
Call the table you start from the left table and the table you bring in the right table. Join types differ in what they do with a row whose key has no match on the other side:
- Inner join
- Keeps only the rows whose key appears in both tables. Unmatched rows on either side are dropped.
- Left join
- Keeps every row of the left table; where the right table has no match, its columns are left missing. A right join is the same with the tables swapped.
- Full outer join
- Keeps every row of both tables, matching where it can and leaving missing values on whichever side has no match.
Row counts follow from the keys. For each key value , let be its number of rows in the left table and its number in the right. Each left row is paired with every matching right row, so that key produces rows. When the right table’s key is unique (every is 0 or 1), a left join returns exactly as many rows as the left table, which is what you nearly always want when you look up an attribute. When it isn’t, rows multiply. This is the row explosion, and it is easy to miss because nothing fails: the joined table looks normal, but every total computed from it is too large.
The cardinality of a relationship names the pattern. One-to-one: each key at most once on each side. One-to-many or many-to-one: unique on one side only, as with orders (many) and products (one). Many-to-many: repeated on both sides, which usually means that one table is at the wrong granularity or still holds duplicates. Decide which you expect before joining, and test it. pandas’ merge will test it for you: validate='many_to_one' checks that the right table’s keys are unique, and indicator=True adds a column saying whether each row matched on both sides or came from one side only.
Two details differ between tools. In SQL a missing key never matches anything, because comparing a value with NULL gives “unknown” rather than true, so rows with missing keys drop out of an inner join. pandas, by contrast, matches missing keys with each other, which can pair every blank-key row on one side with every blank-key row on the other. Spreadsheet lookups behave differently again: XLOOKUP returns a single match, by default the first it finds searching from the top, so a repeated key in the lookup table doesn’t multiply rows; it silently picks one of the conflicting values. Whatever the tool, resolve missing and repeated keys before you join.
A repeatable sequence
Order matters. Join after removing duplicates, not before, or the duplicates multiply. Derive and validate before you aggregate, because an aggregate hides the rows that produced a bad total. Bin after deciding about outliers, because one extreme value moves equal-width edges. And produce every output from the raw file by the same recorded steps, so that when next month’s export arrives you run them again instead of redoing the work by hand. After each step, run the same four checks:
- Before: count the rows, and write down the granularity and key you expect afterwards.
- After: count again and explain the difference. Deriving should add columns, not rows; melting should multiply the rows by the number of melted columns; a lookup join should leave the row count unchanged.
- Reconcile a total: overall revenue should be the same before and after a reshape, a lookup join or an aggregation.
- Inspect the exceptions: unmatched keys, missing derived values, empty bins, rows with a blank group.
Formulas
Expected order total
- Quantity: number of packs orderedpacks
- Unit price of one pack$
- Discount on the whole order$
Assumes. One product per order row, and the discount recorded per order rather than per item.
Compare it with the stored total using a tolerance of half a cent. A disagreement shows that something is wrong, not which column.
Nights between two dates
- Date of arrival, stored as a true datedate
- Date of departure, stored as a true datedate
- Number of nights stayed; the check-out day is not countednights
Assumes. Both columns are converted to dates first; subtracting day-of-month numbers fails across month ends.
A result of zero or less breaks the rule that check-out comes after check-in.
Width of equal-width bins
- Width of every binsame unit as x
- Largest and smallest valuesame unit as x
- Number of bins
Assumes. Edges are ; a stated rule decides which bin gets a value that sits exactly on an edge.
Equal-frequency bins use quantiles as edges instead, so their widths vary.
Overall mean from group means
- Mean of group gunit of x
- Number of rows in group grows
- Mean of all the rowsunit of x
Assumes. Each group mean was computed over the same kind of row (orders, nights, students) that the overall mean is about.
The plain average of the group means equals this only when the groups are the same size or have the same mean.
Rows produced by inner and left joins
- Rows with key value k in the left tablerows
- Rows with key value k in the right tablerows
- Rows in the joined tablerows
Assumes. The join matches on equal keys; the sums run over every key value that occurs in either table.
If every is 0 or 1, a left join returns exactly as many rows as the left table.
Rows produced by a full outer join
- Rows in the full outer join and in the inner joinrows
- Left rows whose key has no match in the right tablerows
- Right rows whose key has no match in the left tablerows
A left join is the inner join plus the unmatched left rows: .
Worked examples
Fictional figures
A fictional five-booking extract laid out like Harborview Hotel’s harborview-bookings file is shown below. The rules are nights = check-out − check-in and total = nights × rate. Derive the nights from the dates, find the bookings that break a rule, and decide which value is wrong in each.
| Booking | Check-in | Check-out | Nights | Rate ($) | Total ($) |
|---|---|---|---|---|---|
| B1 | 2 Mar 2026 | 5 Mar 2026 | 3 | 140 | 420 |
| B2 | 27 Mar 2026 | 1 Apr 2026 | 6 | 150 | 750 |
| B3 | 26 Feb 2026 | 2 Mar 2026 | 4 | 135 | 540 |
| B4 | 10 Apr 2026 | 12 Mar 2026 | 2 | 160 | 320 |
| B5 | 14 Mar 2026 | 15 Mar 2026 | 1 | 155 | 155 |
- Derive nights by subtracting dates, counting across month ends. B1: 2 to 5 March is . B2: 27 March to 1 April is (the nights of 27, 28, 29, 30 and 31 March). B3: 26 February to 2 March is (26, 27 and 28 February, then 1 March; February 2026 has 28 days). B4: 10 April to 12 March is . B5: .
- Compare with the stored nights. B1, B3 and B5 agree. B2 stores 6 against a derived 5. B4’s derived value is negative, which breaks the rule that check-out comes after check-in.
- Use the third column to decide. For B2, total ÷ rate = , which agrees with the dates, so the stored nights are wrong: 6 is the number of days touched (27 March to 1 April inclusive), one more than the nights. For B4, total ÷ rate = , which agrees with the stored nights, so the check-out date is wrong: two nights from 10 April ends on 12 April, and the month was typed one too low.
- Record the fixes as rules, not hand edits: “where derived nights disagree with stored nights and total ÷ rate agrees with the dates, take the derived nights”, and “where check-out is before check-in and stored nights agree with total ÷ rate, set check-out = check-in + nights”. Log which bookings each rule changed.
Answer. Derived nights are 3, 5, 4, −29 and 1. B2’s stored nights should be 5, not 6; B4’s check-out should be 12 April 2026, not 12 March.
What it means. Two disagreeing values tell you something is wrong; a third, independent value tells you which. Without the total, B2 and B4 could only be flagged. Revenue per night computed from the stored nights would have been understated for B2, and an occupancy count built from B4’s dates would have counted a negative stay.
Fictional figures
Ana asks for Lantern’s “average order value”. A fictional extract of eight orders from three customers is below: C1 is a subscriber receiving one bag of House Blend 250g a month at $12.60 after the 10% subscriber discount. Compute the average per order and the average of the customers’ own averages, explain why they differ, and say which one answers Ana’s question.
| Order | Customer | Product | Qty | Order total ($) |
|---|---|---|---|---|
| 1 | C1 | House Blend 250g | 1 | 12.60 |
| 2 | C2 | House Blend 1kg | 1 | 46.00 |
| 3 | C1 | House Blend 250g | 1 | 12.60 |
| 4 | C2 | Ethiopia Yirgacheffe 250g | 2 | 39.00 |
| 5 | C3 | Gift Box Trio | 2 | 116.00 |
| 6 | C1 | House Blend 250g | 1 | 12.60 |
| 7 | C2 | House Blend 1kg | 1 | 41.00 |
| 8 | C1 | House Blend 250g | 1 | 12.60 |
- Average per order, at the granularity of the table: total revenue ÷ number of orders. Revenue is , so the average is .
- Aggregate to one row per customer first. C1: 4 orders, mean $12.60. C2: 3 orders totalling $126.00, mean $42.00. C3: 1 order, mean $116.00. The plain average of these three means is .
- Reconcile the two with the weighted-average formula, weighting each customer’s mean by their number of orders: . Weighted, the customer means give back the per-order average; unweighted, C3’s single large order counts as much as C1’s four small ones.
- Match each figure to a question. $36.55 answers “what does an order bring in?”, which drives packaging, shipping and revenue forecasts from order counts. $56.87 answers “what is the typical customer’s usual order size?”, with every customer counting once, and it is pulled up by one gift buyer.
Answer. Average order value is $36.55 per order. The average of the three customers’ averages is $56.87 (rounded), a different statistic that gives each customer equal weight.
What it means. Ana asked about orders, so $36.55 is the figure to report, stated with its definition (“total revenue ÷ number of orders”) and the number of orders behind it. The gap also says something useful: subscribers place many small orders and gift buyers few large ones, so a single average hides two different kinds of customer, and the report should show the groups separately.
Fictional figures
To review gross margins, Lantern’s analyst joins a fictional extract of eight orders (revenue $250.00) to the roasting-cost spreadsheet. The cost sheet has six rows: House Blend 250g twice ($5.20 from 1 January 2026 and $5.60 from 1 May 2026), and one row each for Decaf Colombia 250g ($6.40), Espresso Roast 250g ($5.90), Colombia Huila 250g ($6.80) and Sumatra Mandheling 250g ($7.10). Cold Brew Pack is new and not on the sheet yet. Predict the row counts, find the damage a naive left join on product does, and fix it.
| Order | Date | Product | Qty | Revenue ($) |
|---|---|---|---|---|
| 1 | 12 Mar 2026 | House Blend 250g | 2 | 28.00 |
| 2 | 15 Mar 2026 | Decaf Colombia 250g | 1 | 16.50 |
| 3 | 20 Apr 2026 | House Blend 250g | 1 | 14.00 |
| 4 | 22 Apr 2026 | Espresso Roast 250g | 3 | 46.50 |
| 5 | 18 May 2026 | House Blend 250g | 4 | 56.00 |
| 6 | 25 May 2026 | Colombia Huila 250g | 2 | 34.00 |
| 7 | 2 Jun 2026 | Decaf Colombia 250g | 2 | 33.00 |
| 8 | 9 Jun 2026 | Cold Brew Pack | 1 | 22.00 |
- Count keys on both sides. Orders: House Blend 250g 3, Decaf 2, Espresso Roast 1, Huila 1, Cold Brew 1 (8 rows). Costs: House Blend 250g 2, Decaf 1, Espresso Roast 1, Huila 1, Sumatra 1 (6 rows). The cost sheet’s key is not unique, so a join on
productalone is many-to-many for House Blend. - Predict the counts with . Inner join: rows (Cold Brew and Sumatra drop out). Left join: rows (Cold Brew kept, with no cost). Full outer join: rows (Sumatra added, with no order).
- Measure the damage. In the 11-row left join every House Blend order appears twice, so summing revenue gives , an overstatement of . An inner join would also drop Cold Brew’s $22.00 and report , with no warning about either error.
- Fix the granularity. The cost sheet has one row per product per cost period, so match on product and on the order date falling inside the cost period. Orders 1 and 3 take $5.20 and order 5 takes $5.60. Each order now matches at most one cost: the join returns 8 rows and revenue reconciles to $250.00. Test it, for example with a many-to-one validation.
- Compute margins only where a cost exists. Cost of goods: House Blend ; Decaf ; Espresso Roast ; Huila ; total 88.50. Revenue on the seven costed orders is , so gross profit is , a margin of .
Answer. A naive left join returns 11 rows and overstates revenue by $98.00 (39.2%); an inner join returns 10 rows and hides the missing Cold Brew cost. Joining on product and cost period returns 8 rows, reconciles to $250.00 of revenue, and gives a gross profit of $139.50 (61.2%) on the seven costed orders, with Cold Brew Pack reported as lacking a cost.
What it means. Nothing in the naive join failed: it produced a plausible table that would have shown House Blend selling twice what it did. Counting rows before and after, and reconciling revenue, catches the explosion in seconds. The margin review can go ahead for four products, but Cold Brew’s cost has to be added to the sheet before the full range is compared.
Explore
Common mistakes
“A left join can’t change the number of rows.”
A left join can’t lose left rows, but it can add them: every left row is repeated once for each matching right row. If the lookup table holds a key twice, every order with that key appears twice. Check that the lookup key is unique and compare row counts before and after.
“The average of the group averages is the overall average.”
Only when the groups are the same size or share the same average. Otherwise the overall mean is the weighted average of the group means, weighted by the number of rows in each. Averaging three channel averages gives a small channel as much say as a large one.
“Binning tidies up noisy numbers.”
Binning doesn’t remove errors; it hides them inside a band, together with real differences. A $46 order and a $116 order become the same category. Bin to serve a stated rule or report, keep the original column, and compute data-driven edges on training data only.
“A blank cell in a wide table means zero.”
A blank can mean zero, not recorded or not applicable. When you melt a wide table, every blank becomes a row that needs a value, and choosing 0 or missing changes totals and averages. Find out from the source which it is and record the answer in the data dictionary.
“Counting rows after a join counts the customers.”
A join can change the granularity. After joining student records (one row per student) to enrolments (one row per course taken), each row is an enrolment, so counting rows counts enrolments, and a mean GPA over the rows weights each student by the number of courses they take. Count distinct keys, or aggregate back to one row per student first.
“It’s quicker to fix the numbers in the raw file.”
A hand edit can’t be rerun, reviewed or undone with confidence, and the next export will bring the same problems back. Keep the raw data untouched and express every change as a step in a script, a query or a recorded recipe.
Guided practice
Work through these with hints when you need them. Every hint and every worked solution is there to be used.
Loading four questions…
On your own
No hints this time. Answer, check, and read the feedback; the solution appears once you have answered or if you ask for it. Your first answers count toward mastery.
Loading six questions…
In practice
Back to Ana’s four questions. For revenue per region per month, Lantern’s analyst, Jun Park, derives month from order_date (only after converting the export’s mixed date formats, or some orders land in the wrong month), groups by region and month, sums the derived total, and checks two things: that the group counts add up to the number of orders, and that the regional sums add up to the revenue of the whole file. A region spelled two ways, or left blank, shows up immediately as an extra or missing group. Only then does he pivot the months into columns for the board pack, because people read across months, while keeping the long table for charts and for the next analysis.
For the subscriber question he compares average order value (total revenue ÷ number of orders) for subscription and one-off orders, with the number of orders behind each figure, and adds each group’s median, because a few bulk and gift orders pull the mean up. For the promotion question he bins order values at $40, stating that the threshold applies before discount. For the margin question he joins on product and cost period, as in the applied example, and lists the products without a cost instead of letting an inner join drop them.
The same habits appear wherever data are summarised for a decision. A hotel’s revenue manager reports what the industry calls the average daily rate: room revenue divided by rooms sold. At Harborview Hotel that is a ratio of two sums over the whole month, not the average of each booking’s nightly rate, which would give a one-night stay the same weight as a two-week one. Occupancy by month needs bookings expanded into nights, because a stay that crosses a month end belongs to both months.
At Northfield University, the planning office joins student records to an enrolment table to see how many students take each course. Done carelessly, the join makes every count a count of enrolments, and the university appears to have several times as many students as it does. The office therefore counts distinct student numbers and writes the expected granularity of each table at the top of its query. In finance the matching habit is reconciliation: a figure in a report should trace back to a total in the source system, and analysts state that it does.
None of these transformations was difficult; each became dangerous only when it ran unchecked. Professionals build the checks into the step. The next lesson turns those checks, and every cleaning decision in this course, into a report that others can trust and repeat.
Knowledge check
Six questions, one attempt each, no hints. Score 80% or more to complete the lesson; you can take it again at any time.
Loading six questions…
Summary
- Transformations change the form of correct data so it can answer a question: derive, bin, aggregate, reshape and join. Each needs a check, starting with the row count before and after.
- A derived variable inherits its inputs’ errors and missing values. Where the data also store the value, the derived one is a cross-field check, and a third column can show which value is wrong.
- Binning throws information away. Equal-width bins are stretched by extreme values, equal-frequency bins have data-driven edges (fit them on training data only), and business bands need a stated boundary rule.
- Aggregation moves to a coarser granularity and can’t be undone. The overall mean is the weighted average of the group means, not their plain average, and grouping can even reverse a comparison (Simpson’s paradox).
- Melt wide to long when column headers are values; pivot long to wide only when every label pair is unique, or a duplicate will either stop the pivot or vanish into an aggregate.
- Inner joins drop unmatched rows, left joins keep every left row, full joins keep both sides. A key found a times on the left and b times on the right yields a × b rows, so make the lookup key unique and reconcile totals.
- Keep the raw data untouched and produce every transformed table by steps you can rerun.
Key formulas
- Expected order total
- Nights between two dates
- Width of equal-width bins
- Overall mean from group means
- Rows produced by inner and left joins
- Rows produced by a full outer join
Terms introduced
- Derived variable
- A new variable calculated from other variables of the same row, such as expected total = quantity × unit price − discount, or nights = check-out date − check-in date.
- Binning
- Replacing a numeric value with the interval (bin) it falls in. Bin edges can be equal-width, equal-frequency (quantiles) or set by a business rule.
- Granularity
- The level of detail that one row of a table represents, such as one order, one customer or one region in one month.
- Aggregation
- Collapsing groups of rows into one summary row per group by applying a function such as sum, mean or count. It moves a table to a coarser granularity.
- Weighted average
- An average in which each value counts in proportion to a weight. The mean of all rows equals the average of the group means weighted by group size.
- Reshaping
- Changing the layout of a table between wide and long format without changing its values.
- Pivot
- Reshaping from long to wide format: the values of one column become column headers. It needs exactly one value for each pair of row label and column label.
- Melt
- Reshaping from wide to long format, also called unpivoting: several columns become one column of former headers and one column of values.
- Join
- Combining two tables by matching the values of a key column, so that each row gains the matching columns of the other table.
- Inner join
- A join that keeps only the rows whose key appears in both tables.
- Left join
- A join that keeps every row of the left table, leaving the right table’s columns missing where there is no match.
- Full outer join
- A join that keeps every row of both tables, matched where possible.
- Row explosion
- The multiplication of rows in a join when a key value appears more than once in the other table. Totals computed from the joined table are inflated.
Sources
- Wickham, H. (2014). Tidy data. Journal of Statistical Software, 59(10), 1–23. jstatsoft.org/article/view/v059i10
- pandas development team. pandas.merge (API reference: join types, the
validateandindicatoroptions, and the matching of missing keys). pandas documentation. pandas.pydata.org/docs/reference/api/pandas.merge.html - pandas development team. Reshaping and pivot tables (user guide: pivot, pivot_table and melt). pandas documentation. pandas.pydata.org/docs/user_guide/reshaping.html
- pandas development team. Group by: split-apply-combine (user guide, including the handling of missing group keys). pandas documentation. pandas.pydata.org/docs/user_guide/groupby.html
- PostgreSQL Global Development Group. PostgreSQL 18 Documentation, section 7.2.1.1, “Joined Tables”. postgresql.org/docs/current/queries-table-expressions.html
- Weinberger, N. Simpson’s paradox. The Stanford Encyclopedia of Philosophy, first published 24 March 2021, substantive revision 6 June 2026. plato.stanford.edu/entries/paradox-simpson
