Pandas concat vs merge

โฑ๏ธ 40 sec read ๐Ÿ Python

Use pd.concat() to stack DataFrames that share the same columns (or same rows), and pd.merge() to join DataFrames on a key column, SQL-style. Concat glues along an axis; merge matches rows by value.

When Should You Use concat?

Reach for concat when you have multiple chunks of the same shape of data โ€” monthly files, results from a loop, train/test splits โ€” and just need them stacked into one DataFrame. No key matching happens.

import pandas as pd

jan = pd.DataFrame({"sale_id": [1, 2], "amount": [99, 50]})
feb = pd.DataFrame({"sale_id": [3, 4], "amount": [75, 20]})

pd.concat([jan, feb], ignore_index=True)
#    sale_id  amount
# 0        1      99
# 1        2      50
# 2        3      75
# 3        4      20

What Does axis=0 vs axis=1 Do in concat?

axis=0 (default) stacks vertically, adding rows; axis=1 stacks horizontally, adding columns and aligning rows by index. Horizontal concat is index alignment, not key matching โ€” mismatched indexes produce NaN.

prices = pd.DataFrame({"price": [10, 20]}, index=["a", "b"])
stock  = pd.DataFrame({"qty": [5, 8]},   index=["a", "c"])

pd.concat([prices, stock], axis=1)
#    price  qty
# a   10.0  5.0
# b   20.0  NaN
# c    NaN  8.0

What Do ignore_index and keys Do?

ignore_index=True throws away the original indexes and renumbers 0..n-1, which you almost always want when stacking rows. keys= instead labels each source chunk in a MultiIndex so you can tell where rows came from.

combined = pd.concat([jan, feb], keys=["jan", "feb"])
combined.loc["feb"]        # just February's rows

# Without ignore_index you get duplicate index values:
pd.concat([jan, feb]).loc[0]   # returns TWO rows โ€” usually a bug

When Should You Use merge Instead?

Use merge when the two DataFrames hold different columns about the same entities and you need to match rows on a shared key, like joining orders to customers. Which rows survive depends on the how parameter โ€” see the merge how parameter cheat sheet for inner/outer/left/right/cross side by side.

orders    = pd.DataFrame({"customer_id": [10, 20], "amount": [99, 50]})
customers = pd.DataFrame({"customer_id": [10, 20], "name": ["Alice", "Bob"]})

pd.merge(orders, customers, on="customer_id")
#    customer_id  amount   name
# 0           10      99  Alice
# 1           20      50    Bob

What Happened to DataFrame.append?

df.append() was removed in pandas 2.0 โ€” old code using it now raises AttributeError. Replace it with pd.concat, and never concat inside a loop; collect frames in a list and concat once.

# Old (removed in 2.0):  df = df.append(new_rows)
# New:
frames = [process(f) for f in files]
df = pd.concat(frames, ignore_index=True)

Common Pitfalls

Pro Tip: Quick decision rule: same columns, more rows โ†’ concat. Same entities, more columns โ†’ merge. If you're passing on= to concat-style logic or stacking with merge, you've picked the wrong tool.

โ† Back to Python Tips