Business Analytics & Data Science

B02 · Introductory

Data Cleaning and Preparation

12 lessons

B02 · Module 4, lesson 3 · Lesson 11 of 12

Transforming and reshaping data

Clean data rarely arrives in the shape a question needs. Learn to derive new variables, sort values into bins, aggregate, reshape between wide and long, and join tables, and to check every step so that it adds information rather than errors.

About 105 minutes

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:

expected total=quantity×unit price−discount\text{expected total} = \text{quantity} \times \text{unit price} - \text{discount}

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.

Stored order totals checked against the derived total (fictional extract)
OrderQtyUnit price ($)Discount ($)Stored total ($)Derived total ($)
1 · House Blend 250g214.000.0028.0028.00
2 · Ethiopia Yirgacheffe 250g119.501.9517.5517.55
3 · Espresso Roast 250g315.505.0046.5041.50
4 · House Blend 1kg146.004.6041.4041.40
5 · Colombia Huila 250g217.000.0043.0034.00
6 · Cold Brew Pack122.000.0028.5022.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’s nights and total), count the disagreements and resolve them before deciding which column to keep.

Choosing bin edges

For kk equal-width bins on values from xmin⁡x_{\min} to xmax⁡x_{\max}, every bin has width w=(xmax⁡−xmin⁡)/kw = (x_{\max} - x_{\min}) / k. 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.

Twelve order values binned three ways (fictional)
MethodBin 1Bin 2Bin 3
Equal-width (width $34)$14 to $48: 9 ordersOver $48 to $82: 1 orderOver $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 bandsUnder $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, xˉg\bar{x}_g, and the number of rows in each group, ngn_g. 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:

xˉ=∑gng xˉg∑gng\bar{x} = \frac{\sum_g n_g\,\bar{x}_g}{\sum_g n_g}

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.

Mean rating by product and type of customer (fictional)
ProductSubscribersOne-off buyers
House Blend 250g4.0 (80 ratings)3.8 (20 ratings)
Ethiopia Yirgacheffe 250g4.8 (20 ratings)4.6 (80 ratings)
Both products4.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.

Wide: one row per product, one column per month (fictional, $)
ProductMarAprMay
House Blend 250g1,2461,3021,414
Decaf Colombia 250g396429
Long: one row per product and month (the same values)
ProductMonthRevenue ($)
House Blend 250gMar1,246
House Blend 250gApr1,302
House Blend 250gMay1,414
Decaf Colombia 250gMar396
Decaf Colombia 250gAprmissing
Decaf Colombia 250gMay429

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 kk, let aka_k be its number of rows in the left table and bkb_k its number in the right. Each left row is paired with every matching right row, so that key produces ak×bka_k \times b_k rows. When the right table’s key is unique (every bkb_k 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:

  1. Before: count the rows, and write down the granularity and key you expect afterwards.
  2. 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.
  3. Reconcile a total: overall revenue should be the same before and after a reshape, a lookup join or an aggregation.
  4. Inspect the exceptions: unmatched keys, missing derived values, empty bins, rows with a blank group.

Formulas

Expected order total

expected total=q×p−d\text{expected total} = q \times p - d
qq
Quantity: number of packs orderedpacks
pp
Unit price of one pack$
dd
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

nights=check-out date−check-in date\text{nights} = \text{check-out date} - \text{check-in date}
check-in date\text{check-in date}
Date of arrival, stored as a true datedate
check-out date\text{check-out date}
Date of departure, stored as a true datedate
nights\text{nights}
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

w=xmax⁡−xmin⁡kw = \dfrac{x_{\max} - x_{\min}}{k}
ww
Width of every binsame unit as x
xmax⁡, xmin⁡x_{\max},\ x_{\min}
Largest and smallest valuesame unit as x
kk
Number of bins

Assumes. Edges are xmin⁡, xmin⁡+w, …, xmax⁡x_{\min},\ x_{\min} + w,\ \dots,\ x_{\max}; 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

xˉ=∑gng xˉg∑gng\bar{x} = \dfrac{\sum_g n_g\,\bar{x}_g}{\sum_g n_g}
xˉg\bar{x}_g
Mean of group gunit of x
ngn_g
Number of rows in group grows
xˉ\bar{x}
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

Ninner=∑kak bkNleft=∑kakmax⁡(bk, 1)N_{\text{inner}} = \sum_k a_k\, b_k \qquad N_{\text{left}} = \sum_k a_k \max(b_k,\, 1)
aka_k
Rows with key value k in the left tablerows
bkb_k
Rows with key value k in the right tablerows
NN
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 bkb_k is 0 or 1, a left join returns exactly as many rows as the left table.

Rows produced by a full outer join

Nfull=Ninner+UL+URN_{\text{full}} = N_{\text{inner}} + U_L + U_R
Nfull, NinnerN_{\text{full}},\ N_{\text{inner}}
Rows in the full outer join and in the inner joinrows
ULU_L
Left rows whose key has no match in the right tablerows
URU_R
Right rows whose key has no match in the left tablerows

A left join is the inner join plus the unmatched left rows: Nleft=Ninner+ULN_{\text{left}} = N_{\text{inner}} + U_L.

Worked examples

A first example: Deriving nights and checking the stored values

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.

Five bookings (fictional)
BookingCheck-inCheck-outNightsRate ($)Total ($)
B12 Mar 20265 Mar 20263140420
B227 Mar 20261 Apr 20266150750
B326 Feb 20262 Mar 20264135540
B410 Apr 202612 Mar 20262160320
B514 Mar 202615 Mar 20261155155
  1. Derive nights by subtracting dates, counting across month ends. B1: 2 to 5 March is 33. B2: 27 March to 1 April is 55 (the nights of 27, 28, 29, 30 and 31 March). B3: 26 February to 2 March is 44 (26, 27 and 28 February, then 1 March; February 2026 has 28 days). B4: 10 April to 12 March is −29-29. B5: 11.
  2. 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.
  3. Use the third column to decide. For B2, total ÷ rate = 750÷150=5750 \div 150 = 5, 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 = 320÷160=2320 \div 160 = 2, 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.
  4. 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.

A harder example: Two averages from the same eight orders

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.

Eight orders from three customers (fictional)
OrderCustomerProductQtyOrder total ($)
1C1House Blend 250g112.60
2C2House Blend 1kg146.00
3C1House Blend 250g112.60
4C2Ethiopia Yirgacheffe 250g239.00
5C3Gift Box Trio2116.00
6C1House Blend 250g112.60
7C2House Blend 1kg141.00
8C1House Blend 250g112.60
  1. Average per order, at the granularity of the table: total revenue ÷ number of orders. Revenue is 4×12.60+(46.00+39.00+41.00)+116.00=50.40+126.00+116.00=292.404 \times 12.60 + (46.00 + 39.00 + 41.00) + 116.00 = 50.40 + 126.00 + 116.00 = 292.40, so the average is 292.40÷8=36.55292.40 \div 8 = 36.55.
  2. 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 (12.60+42.00+116.00)÷3=170.60÷3≈56.87(12.60 + 42.00 + 116.00) \div 3 = 170.60 \div 3 \approx 56.87.
  3. Reconcile the two with the weighted-average formula, weighting each customer’s mean by their number of orders: (4×12.60+3×42.00+1×116.00)÷8=292.40÷8=36.55(4 \times 12.60 + 3 \times 42.00 + 1 \times 116.00) \div 8 = 292.40 \div 8 = 36.55. 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.
  4. 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.

Applied: a business decision: Bringing in product costs without inflating revenue

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.

Eight orders (fictional)
OrderDateProductQtyRevenue ($)
112 Mar 2026House Blend 250g228.00
215 Mar 2026Decaf Colombia 250g116.50
320 Apr 2026House Blend 250g114.00
422 Apr 2026Espresso Roast 250g346.50
518 May 2026House Blend 250g456.00
625 May 2026Colombia Huila 250g234.00
72 Jun 2026Decaf Colombia 250g233.00
89 Jun 2026Cold Brew Pack122.00
  1. 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 product alone is many-to-many for House Blend.
  2. Predict the counts with ∑akbk\sum a_k b_k. Inner join: 3×2+2×1+1×1+1×1=103 \times 2 + 2 \times 1 + 1 \times 1 + 1 \times 1 = 10 rows (Cold Brew and Sumatra drop out). Left join: 10+1=1110 + 1 = 11 rows (Cold Brew kept, with no cost). Full outer join: 11+1=1211 + 1 = 12 rows (Sumatra added, with no order).
  3. Measure the damage. In the 11-row left join every House Blend order appears twice, so summing revenue gives 250.00+(28.00+14.00+56.00)=250.00+98.00=348.00250.00 + (28.00 + 14.00 + 56.00) = 250.00 + 98.00 = 348.00, an overstatement of 98.00÷250.00=39.2%98.00 \div 250.00 = 39.2\%. An inner join would also drop Cold Brew’s $22.00 and report 348.00−22.00=326.00348.00 - 22.00 = 326.00, with no warning about either error.
  4. 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.
  5. Compute margins only where a cost exists. Cost of goods: House Blend 3×5.20+4×5.60=38.003 \times 5.20 + 4 \times 5.60 = 38.00; Decaf 3×6.40=19.203 \times 6.40 = 19.20; Espresso Roast 3×5.90=17.703 \times 5.90 = 17.70; Huila 2×6.80=13.602 \times 6.80 = 13.60; total 88.50. Revenue on the seven costed orders is 250.00−22.00=228.00250.00 - 22.00 = 228.00, so gross profit is 228.00−88.50=139.50228.00 - 88.50 = 139.50, a margin of 139.50÷228.00≈61.2%139.50 \div 228.00 \approx 61.2\%.

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

Open Harborview’s bookings. Derive the nights from check_in and check_out, and nights × rate, and compare each with the stored column. For every disagreement, notice how the third column tells you which value to trust.

Loading the interactive…

Change how many orders are for products with repeated cost rows, and how many rows each repeated product has. Watch the left join grow past the size of the orders table, and see what an inner join would silently drop.

Loading the interactive…

Set the size and average of two groups of orders. The overall average follows the bigger group; the plain average of the two averages ignores group size. Find settings where the two agree, and explain why.

Loading the interactive…

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

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

  1. Wickham, H. (2014). Tidy data. Journal of Statistical Software, 59(10), 1–23. jstatsoft.org/article/view/v059i10
  2. pandas development team. pandas.merge (API reference: join types, the validate and indicator options, and the matching of missing keys). pandas documentation. pandas.pydata.org/docs/reference/api/pandas.merge.html
  3. pandas development team. Reshaping and pivot tables (user guide: pivot, pivot_table and melt). pandas documentation. pandas.pydata.org/docs/user_guide/reshaping.html
  4. 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
  5. PostgreSQL Global Development Group. PostgreSQL 18 Documentation, section 7.2.1.1, “Joined Tables”. postgresql.org/docs/current/queries-table-expressions.html
  6. 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