Pandas Merge how Parameter Cheat Sheet
The how parameter in pd.merge() sets the join type, which decides which rows survive: "inner" (the default), "left", "right", "outer", or "cross"; pandas 3.0 and later also accept "left_anti" and "right_anti".
Quick answer: Pass how= to pd.merge(). "inner" (the default) keeps only keys found in both DataFrames, "left" keeps every left row, "right" keeps every right row, "outer" keeps every row from both sides, and "cross" returns every left-right combination without using a key (pandas 3.0+ adds "left_anti" and "right_anti"). Unmatched rows that are kept get NaN in the other side's columns. Add indicator=True to label each row both, left_only, or right_only.
What happens to unmatched rows in a pandas merge?
Unmatched rows are dropped by how="inner", kept from the left side only by "left", from the right side only by "right", and from both sides by "outer", with NaN filling the columns that had no match.
The inner, outer, left, and right examples below all merge these two DataFrames.
import pandas as pd
orders = pd.DataFrame({
"order_id": [1, 2, 3, 4],
"customer_id":[10, 20, 30, 40],
"amount": [99, 50, 75, 20],
})
customers = pd.DataFrame({
"customer_id":[10, 20, 50],
"name": ["Alice", "Bob", "Eve"],
})
Customer 30 and 40 have orders but no profile. Customer 50 has a profile but no orders. Watch what happens to those three rows under each join.
What does how="inner" do in pandas merge?
how="inner" keeps only rows whose key exists in both DataFrames, and it is the default when you leave how out.
pd.merge(orders, customers, on="customer_id", how="inner")
# order_id customer_id amount name
# 0 1 10 99 Alice
# 1 2 20 50 Bob
Customers 30, 40, and 50 are dropped. Use this when missing keys mean "ignore the row."
How do you do a full outer join in pandas?
Pass how="outer" to keep every row from both DataFrames; columns from the side with no match are filled with NaN.
pd.merge(orders, customers, on="customer_id", how="outer")
# order_id customer_id amount name
# 0 1.0 10 99.0 Alice
# 1 2.0 20 50.0 Bob
# 2 3.0 30 75.0 NaN
# 3 4.0 40 20.0 NaN
# 4 NaN 50 NaN Eve
Use it for reconciliation โ finding orders without customers and customers without orders in one pass. Integer columns that gain a NaN turn into floats, which is why order_id now shows 1.0, 2.0, and so on.
how="left" โ Pandas Left Join
Keep every row from the left DataFrame. Right-side columns are NaN when there's no match.
pd.merge(orders, customers, on="customer_id", how="left")
# order_id customer_id amount name
# 0 1 10 99 Alice
# 1 2 20 50 Bob
# 2 3 30 75 NaN
# 3 4 40 20 NaN
Most common in practice โ "enrich orders with customer details, but don't lose orders that lack a profile."
how="right" โ Pandas Right Join
Mirror image of left. Keep every row from the right DataFrame.
pd.merge(orders, customers, on="customer_id", how="right")
# order_id customer_id amount name
# 0 1.0 10 99.0 Alice
# 1 2.0 20 50.0 Bob
# 2 NaN 50 NaN Eve
Equivalent to swapping the inputs and using how="left". Most teams pick one direction and stick with it for readability.
how="cross" โ Pandas Cross Join (Cartesian Product)
Every row on the left paired with every row on the right. No key column.
sizes = pd.DataFrame({"size": ["S", "M", "L"]})
colors = pd.DataFrame({"color": ["red", "blue"]})
pd.merge(sizes, colors, how="cross")
# size color
# 0 S red
# 1 S blue
# 2 M red
# 3 M blue
# 4 L red
# 5 L blue
Result size is len(left) ร len(right). Useful for generating combinations (date ร store, user ร experiment), dangerous on large inputs.
Summary Table
Choose how by whose unmatched rows you must keep: neither side ("inner"), the left ("left"), the right ("right"), or both ("outer"); "cross" ignores keys and returns len(left) ร len(right) rows.
how | rows from left | rows from right | unmatched fill
----------|---------------|----------------|----------------
"inner" | matched only | matched only | (dropped)
"outer" | all | all | NaN
"left" | all | matched only | NaN on right cols
"right" | matched only | all | NaN on left cols
"cross" | all ร all | all ร all | (no key used)
Use the indicator Flag to Debug a Merge
Pass indicator=True to add a _merge column that labels every row 'both', 'left_only', or 'right_only', so you can count unmatched keys before you trust the result.
result = pd.merge(orders, customers, on="customer_id", how="outer", indicator=True)
# Adds a _merge column with values:
# 'left_only' row only in orders
# 'right_only' row only in customers
# 'both' matched
# Quick QA โ count what came from where:
result["_merge"].value_counts()
# _merge
# left_only 2
# both 2
# right_only 1
# Name: count, dtype: int64
# Orders with no matching customer (an anti join):
result[result["_merge"] == "left_only"]
pandas 3.0 and later also accept how="left_anti" and how="right_anti", which return only the unmatched rows from the left or right DataFrame in one step; on pandas 2.x and earlier, filter the _merge column as shown above.
Common Pitfalls
Check for duplicate keys, key columns with different names, and missing keys before you merge; each one can raise an error or silently change the row count.
- Row explosion on duplicates: if the join key isn't unique on both sides, you get a many-to-many product. Check
orders["customer_id"].is_uniquebefore merging. - Different column names: use
left_on=andright_on=instead ofon=. - NaN keys match each other: unlike SQL, pandas pairs a
NaNkey on the left with everyNaNkey on the right, even in an inner join. Fill or drop missing keys first. - Use
validate=to catch bugs:pd.merge(..., validate="one_to_one")raises if your assumption about uniqueness is wrong.
Pro Tip: If you're joining on the index rather than a column, use df.join() โ it defaults to how="left" and skips having to write left_index=True, right_index=True every time.