Join semantics: inner, left, right, outer
What it is
how= decides which non-matching rows survive. A left join keeps every row of the left frame and fills unmatched right-hand columns with NaN.
Why it matters
Choosing inner where you meant left silently drops the rows that failed to match — frequently the exact rows worth investigating.
In the real world: Attaching customer details to every order with a left join, so orders from guest checkouts are kept rather than silently dropped.
Visual example
JoinsA left join keeps every left row; where the right side has no match, the new columns are NaN.
Rows kept by each join
how="left" → 2 rows
how="inner" → 1 row
Why this matters
The unmatched rows are usually the interesting ones — orders from customers you have no record of. An inner join hides them.
Common misconception
Wrong idea: a merge only adds columns.
Correct idea: an inner join can silently drop rows, and a left join adds NaN. Compare len() before and after.
- 1Match keysc1 finds Ann; c9 finds nothing.
- 2Keep left rowsWith
how="left"every order stays. - 3Fill the gapNo match means NaN in the new columns.
before = len(orders)
joined = orders.merge(customers, on="customer_id", how="left")
assert len(joined) == before # a left join must not change the count
joined["customer_name"].isna().sum() # how many orders had no customer rowReading a shrunken row count as 'the join worked'. Compare counts before and after, deliberately.
Practise it: Attach Customer Names to Orders, Join Without Letting a Dirty Dimension Table Multiply Your Rows, Pad Each Store's Own Calendar, Not the Global One