Pandas Merge, Join & Concat
Real analyses combine data from several sources: orders with customers, events with user attributes, prices with exchange rates. Pandas provides three tools for it. merge is a SQL-style join on keys. join is a convenience for joining on indexes. concat stacks DataFrames vertically or side by side. Specialized variants like merge_asof match on the nearest key, which is essential for time series.
Joins are also where silent data corruption creeps in: duplicate keys multiply rows, mismatched dtypes drop matches, and outer joins introduce NaNs that skew aggregates. Pandas has safety features (validate and indicator) that catch most of these problems if you use them.
TL;DR
pd.merge(left, right, on="key", how="left"): SQL-style joins (inner,left,right,outer,cross).- Use
left_on/right_onfor different key names, andsuffixesfor overlapping columns. - Add
validate="many_to_one"(or similar) to assert key uniqueness, andindicator=Trueto see match status. df.join(other)joins on the index;pd.concat([a, b])stacks rows (or columns withaxis=1).pd.merge_asofmatches each row to the nearest earlier (or later) key, for prices, sensor readings, and events.- Watch for row explosions from duplicate keys, and dtype mismatches that prevent matches.
Quick Example
Core Concepts
merge: SQL-Style Joins
Key options:
on="key"oron=["k1", "k2"]when names match;left_on/right_onwhen they differ;left_index/right_indexto use indexes.suffixes=("_order", "_customer")disambiguates overlapping non-key columns (default_x,_y).sort=False(default) keeps the left order for left joins.
See SQL joins for join semantics.
Safety: validate and indicator
validate:"one_to_one","one_to_many","many_to_one", or"many_to_many". Pandas checks key uniqueness and raisesMergeErrorif the relationship is violated. This catches duplicate lookup rows that would silently multiply your data.indicator=True: adds a_mergecolumn showing whether each row matched both sides or only one, which is invaluable for auditing unmatched keys.
Row Explosion
If the right table has duplicate keys, each left row matches multiple right rows, multiplying the row count and inflating sums. Always compare len() before and after a join, and use validate to enforce expectations. Deduplicate lookup tables (drop_duplicates(subset="key")) deliberately, knowing which duplicate you keep.
Key dtypes
Keys must have compatible dtypes and representations: "00123" (string) vs 123 (int), trailing whitespace, case differences, or float IDs (123.0) from columns containing NaNs all prevent matches. Normalize keys before merging (astype, str.strip(), str.lower()), and prefer nullable integer or string dtypes for IDs.
join: Index-Based Convenience
left.join(right, how="left", on=None) joins on the index of right (and the index or a column of left). It's concise when data is already indexed by key, and it can join several frames at once. Under the hood it's merge with index options.
concat: Stacking
- Rows (
axis=0): combine datasets with the same columns, such as monthly files or partitions. Useignore_index=Truefor a fresh index, andkeys=[...]to label sources. - Columns (
axis=1): align side by side on the index. Mismatched indexes produce NaNs, so make sure indexes match. - Columns missing from some frames become NaN. Check
join="inner"to keep only shared columns. - Concatenate a list once, rather than appending in a loop (repeated concat is quadratic).
merge_asof: Nearest-Key Matching
merge_asof performs an ordered "as of" join: for each left row, find the last right row with key ≤ left key (direction="backward"), or the next ("forward"), or the closest ("nearest"). Both sides must be sorted on the key. by= matches exact groups first (per ticker or per currency), and tolerance caps the allowed gap. Typical uses: attaching the price in effect at trade time, the latest sensor calibration, or the most recent user attribute before an event. See time series.
Best Practices
Always Validate Join Cardinality
validate="many_to_one" on every lookup join turns silent row multiplication into an immediate, explicit error. It's the single highest-value habit for correct joins.
Audit Unmatched Keys
Use indicator=True (or an anti-join) to count and inspect unmatched rows. Unexpected mismatches often reveal data quality problems upstream. See data quality management.
Reduce Before Joining
Select only needed columns, and filter rows before merging large frames. It saves memory and time, and avoids suffix clutter.
Consider DuckDB or Polars for Big Joins
For joins on datasets approaching memory limits, DuckDB (SQL on DataFrames and Parquet) or Polars (multithreaded, lazy) are often much faster, with lower memory use. See DuckDB and pandas performance.
Common Mistakes
Left Join That Grew the Table
customers has duplicate IDs. Use validate="many_to_one", and fix or deduplicate the lookup table.
Mismatched Key Types
Joining customer_id as a string on one side and an integer on the other returns zero matches (or raises in recent versions). Align dtypes first.
Appending in a Loop
FAQ
What's the difference between merge and join in pandas?
merge is the general function for SQL-style joins on columns or indexes, with full control over keys, join type, suffixes, and validation. join is a DataFrame method optimized for the common case of joining on the index. It's more concise, but less flexible.
When should I use concat instead of merge?
Use concat to stack DataFrames with the same structure (appending rows) or to place aligned frames side by side. Use merge to combine different datasets by matching key values, like attaching customer attributes to orders.
Why did my merge create duplicate rows?
Because a key appears multiple times on one or both sides. Each combination produces a row. Check key uniqueness with df.key.is_unique, or duplicated(), and enforce expectations with the validate parameter.
What is merge_asof used for?
Joining on the nearest key rather than an exact match, typically timestamps: attaching the most recent price, rate, or status in effect at each event's time. Both inputs must be sorted by the key, and by= restricts matches to the same group.
Related Topics
- Pandas — The library overview
- SQL Joins — Join semantics in SQL
- Pandas GroupBy — Aggregating after joining
- Pandas Time Series — Time-aligned data
- DuckDB — Fast SQL joins on DataFrames and files
- Data Engineering — Joins in pipelines