Pandas merge can match missing keys on both sides, so missing-key acceptance needs an explicit policy in addition to join cardinality.
Pandas merge with null keys: quarantine missing identifiers before matching
Operation contract
The unchecked lookup contains one missing region identifier. A raw merge matches a missing receipt region to that lookup row. The accepted lookup contract rejects missing region keys; a cleaned lookup leaves the receipt’s missing region unmatched for review. Many-to-one validation still checks that lookup keys do not multiply receipt rows.
Failure and ownership boundary
Removing a missing lookup key is not permission to discard its source record without a review policy. Missing values and a real empty-string identifier also differ. Keep unmatched receipts available instead of quietly reporting them under a fabricated region. Pandas joins: cardinality validation and missing lookup keys and Pandas groupby: retain missing keys and define all-null totals apply after the identifier policy is chosen.
Tested environment
Dependency check: this program was executed on CPython 3.14.6 with pandas==3.0.6. Install these versions in a separate virtual environment. The download includes the recorded environment snapshot; no third-party package is part of the website runtime.
Working program
import pandas as pd
def attach_regions(receipts, regions):
if regions["region"].isna().any():
raise ValueError("lookup region cannot be missing")
return receipts.merge(regions, on="region", how="left", validate="many_to_one", indicator=True)
receipts = pd.DataFrame({"region": pd.Series(["DEL", None], dtype="string"), "amount": [125, 75]})
unchecked = pd.DataFrame({"region": pd.Series(["DEL", None], dtype="string"), "label": ["Delhi", "unchecked-missing"]})
print("raw null matched:", receipts.merge(unchecked, on="region", how="left")["label"].iloc[1] == "unchecked-missing")
try:
attach_regions(receipts, unchecked)
except ValueError:
print("missing lookup key rejected")
accepted = unchecked[unchecked["region"].notna()].copy()
joined = attach_regions(receipts, accepted)
print(joined["_merge"].astype(str).tolist())Output
raw null matched: True
missing lookup key rejected
['both', 'left_only']Costs and limits
Join work and retained rows depend on cardinality, key representation and algorithm choice. The many-to-one contract limits output multiplication here but does not cap source bytes or the number of rows. An unmatched row still requires downstream handling.
Common Mistakes
- Null keys can match each other in Pandas.
- Cardinality validation does not supply a missing-identifier policy.
Connected lessons
Pandas joins: cardinality validation and missing lookup keys, Pandas groupby: retain missing keys and define all-null totals, Python aggregation exercise: validate records before returning group totals.
