본문으로 건너뛰기
김신건의 로그

[Pandas] merge

· 수정 · 📖 약 2분 · 585자/단어 #python #pandas #merge #join #sql
Pandas merge, DataFrame.merge, pandas join, SQL join in pandas, pd.merge, pandas DataFrame 결합

정의

pandas.merge(left, right, ...) 는 두 DataFrame 을 SQL join 처럼 결합. 4 가지 join 타입 (inner / left / right / outer) 지원.

사용 상황

  • 두 테이블 키 기반 결합: users + orders 테이블을 user_id 로 병합
  • 다중 키 결합: ['region', 'date'] 같은 복합 키로 결합
  • 데이터 보강: 기본 DataFrame 에 코드 테이블, 마스터 테이블 붙이기
  • join 타입 선택: left (기준 유지), inner (교집합), outer (합집합)

join 타입 선택 시각화

flowchart TD
    START["두 DataFrame 결합"] --> Q1{"매칭 키만 필요?"}
    Q1 -->|"Yes"| INNER["how='inner'"]
    Q1 -->|"No"| Q2{"왼쪽 DataFrame 기준?"}
    Q2 -->|"Yes"| LEFT["how='left'"]
    Q2 -->|"No"| Q3{"오른쪽 DataFrame 기준?"}
    Q3 -->|"Yes"| RIGHT["how='right'"]
    Q3 -->|"No"| OUTER["how='outer'"]

4 가지 join 타입

how설명SQL
inner (기본)양쪽 모두 있는 키만INNER JOIN
left왼쪽 모두 + 오른쪽 매칭LEFT JOIN
right오른쪽 모두 + 왼쪽 매칭RIGHT JOIN
outer양쪽 합집합, NaN 채움FULL OUTER JOIN
cross모든 조합CROSS JOIN

기본

python
import pandas as pd
users = pd.DataFrame({'id': [1, 2, 3], 'name': ['Alice', 'Bob', 'Charlie']})
orders = pd.DataFrame({'user_id': [1, 1, 2, 4], 'amount': [100, 200, 150, 50]})

inner = pd.merge(users, orders, left_on='id', right_on='user_id', how='inner')
print(inner)
결과
   id   name  user_id  amount
0   1  Alice        1     100
1   1  Alice        1     200
2   2    Bob        2     150
idnameuser_idamount
01Alice1100
11Alice1200
22Bob2150

users 3 명 중 Alice (id=1, 주문 2개), Bob (id=2, 주문 1개) 만 매칭. Charlie 와 orders 의 user_id=4 는 제외.

left join

pd.merge(users, orders, left_on='id', right_on='user_id', how='left')
# Charlie 도 포함, amount 는 NaN

outer join + indicator

pd.merge(a, b, on='key', how='outer', indicator=True)
# _merge 컬럼이 추가됨: left_only, right_only, both

매칭 여부를 시각화하기에 유용.

on / left_on, right_on / left_index, right_index

pd.merge(a, b, on='id')                            # 양쪽 컬럼 이름 같을 때
pd.merge(a, b, left_on='id', right_on='uid')       # 다른 이름
pd.merge(a, b, left_index=True, right_index=True)  # index 기반
pd.merge(a, b, left_on='id', right_index=True)     # 혼합

다중 키

pd.merge(a, b, on=['region', 'date'])
pd.merge(a, b, left_on=['region', 'date'], right_on=['reg', 'dt'])

suffixes

같은 이름의 컬럼이 양쪽에 있으면 suffix 가 붙는다.

pd.merge(a, b, on='id')
# 양쪽에 'name' 있으면 -> name_x, name_y
pd.merge(a, b, on='id', suffixes=('_a', '_b'))
# -> name_a, name_b

validate (1:1, 1:m, m:1, m:m 검증)

pd.merge(a, b, on='id', validate='one_to_one')
pd.merge(a, b, on='id', validate='many_to_one')
# 가정이 깨지면 MergeError

데이터 가정을 명시적으로 검증, 디버깅에 매우 유용. 실무에서 validate 를 항상 붙이는 습관을 들이면 데이터 품질 버그를 조기에 잡을 수 있다.

실전 패턴

코드 테이블 보강

import pandas as pd

# 트랜잭션 데이터에 상품 마스터 붙이기
txn = pd.DataFrame({
    'product_code': ['P001', 'P002', 'P001', 'P003'],
    'amount': [100, 200, 150, 300],
})
products = pd.DataFrame({
    'code': ['P001', 'P002', 'P003'],
    'name': ['Widget', 'Gadget', 'Doohickey'],
    'category': ['A', 'B', 'A'],
})

enriched = pd.merge(
    txn,
    products,
    left_on='product_code',
    right_on='code',
    how='left',
    validate='many_to_one',
)

outer + indicator 로 누락 탐색

# 어느 쪽에만 있는 키 찾기
merged = pd.merge(
    users, orders,
    left_on='id', right_on='user_id',
    how='outer', indicator=True
)

# 주문이 없는 유저
no_orders = merged[merged['_merge'] == 'left_only']
# 유저 없는 주문 (고아 레코드)
orphan_orders = merged[merged['_merge'] == 'right_only']

날짜 범위 join (merge_asof)

# 정확한 날짜가 아니라 가장 가까운 날짜로 매핑
pd.merge_asof(
    trades.sort_values('time'),
    quotes.sort_values('time'),
    on='time',
    by='ticker',
    direction='backward',  # 직전 시세
)

다중 키 + suffix 패턴

# 이번 달 vs 지난 달 비교
this_month = df[df['month'] == '2025-07'].rename(columns={'sales': 'sales_cur'})
last_month = df[df['month'] == '2025-06'].rename(columns={'sales': 'sales_prev'})

comparison = pd.merge(
    this_month[['region', 'product', 'sales_cur']],
    last_month[['region', 'product', 'sales_prev']],
    on=['region', 'product'],
    how='outer',
)
comparison['growth'] = comparison['sales_cur'] / comparison['sales_prev'] - 1

성능

방법속도비고
pd.merge()기준hash join 기반
DataFrame.join()빠름index 기반, 내부적으로 merge 사용
pd.merge_asof()빠름sort 된 키 필요, 시계열에 특화
중복 키 많은 outer join느림결과 크기 폭증 주의
# 대용량에서 dtype 맞추면 빠름
users['id'] = users['id'].astype('int32')
orders['user_id'] = orders['user_id'].astype('int32')

# sort 된 키 + merge_asof 는 매우 빠름
pd.merge_asof(a.sort_values('key'), b.sort_values('key'), on='key')

함정

1. 다대다 폭증

# users: id 가 1, 1 (중복)
# orders: user_id 가 1, 1, 1 (중복)
pd.merge(users, orders, ...)
# 2 x 3 = 6 행 생성, 의도 아닐 가능성

# 해법: validate 로 사전 검증
pd.merge(users, orders, ..., validate='many_to_one')  # MergeError 로 경고

2. dtype 불일치

users['id']        # int64
orders['user_id']  # object (문자열로 저장됨)
pd.merge(...)      # 매칭 안 됨!

# 해법
orders['user_id'] = orders['user_id'].astype('int64')

3. NaN 끼리 매칭 안 됨

# 양쪽에 NaN key 가 있어도 매칭 안 됨: NaN != NaN
# 필요하면 fillna 후 merge
a['key'] = a['key'].fillna('UNKNOWN')
b['key'] = b['key'].fillna('UNKNOWN')
pd.merge(a, b, on='key')

4. 결과에 불필요한 컬럼 잔류

pd.merge(a, b, left_on='id', right_on='user_id')
# 결과에 'id' 와 'user_id' 가 모두 남음
# 원하면 merge 후 drop
merged = pd.merge(...).drop(columns=['user_id'])

CAUTION

validate 없이 merge 하면 다대다 폭증이 조용히 발생한다. 특히 ETL 파이프라인에서 중복 데이터가 대량 생성될 수 있다. 항상 validate 를 붙이는 습관을 들여라.

merge vs join vs concat

동작함수
key 기반 결합merge
index 기반 결합 (간편 버전)DataFrame.join
단순 위/옆 붙이기concat

관련 위키

이 글의 용어 (5개)
[Pandas] Boolean Indexingpandas
정의 Boolean Indexing 은 True/False 의 Series 를 인덱서로 전달 해 행을 선택하는 패턴. pandas 의 가장 흔한 필터링 방법. 마스크 생성과 적용…
[Pandas] concatpandas
정의 는 여러 DataFrame/Series 를 세로 (axis=0) 또는 가로 (axis=1) 로 단순 연결. SQL 의 (axis=0) 또는 column-level conca…
[Pandas] DataFramepandas
정의 은 2차원 레이블 테이블. 각 열이 , 모든 열이 같은 (행 라벨) 를 공유. SQL 테이블 / Excel 시트 / R data.frame 의 Python 대응체. 구조 시…
[Pandas] joinpandas
정의 는 index 기반 결합 의 편의 API. 내부적으로는 와 동등하지만 더 짧다. pandas 에서 두 DataFrame 을 결합하는 세 가지 주요 함수: - : 컬럼 / 인…
[Pandas] merge_asofpandas
정의 는 가장 가까운 키 기준 join. 일반 가 정확히 일치하는 키만 매칭하는 반면, 는 이전 (또는 가장 가까운) 키를 찾는다. 시계열 데이터 (가격, 이벤트) 와 다른 시계…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기