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

Quick Example

Core Concepts

merge: SQL-Style Joins

Key options:

See SQL joins for join semantics.

Safety: validate and indicator

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

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

References