DataFrame 병합 — merge와 concat
이 토픽을 마치면
merge와 concat의 차이를 명확히 구분하고, inner/left/right/outer join을 이해하며, 실무에서 올바른 병합 방식을 선택할 수 있습니다.
언제 병합이 필요한가
실무 데이터는 대부분 여러 테이블에 분산되어 있습니다.
users 테이블: orders 테이블:
┌────┬──────┐ ┌─────────┬────────┬────────┐
│ id │ name │ │ order_id│ user_id│ amount │
├────┼──────┤ ├─────────┼────────┼────────┤
│ 1 │ Alice│ │ 101 │ 1 │ 5000 │
│ 2 │ Bob │ │ 102 │ 2 │ 3000 │
│ 3 │ Carol│ │ 103 │ 1 │ 7000 │
└────┴──────┘ └─────────┴────────┴────────┘"각 사용자별 주문 합계"를 구하려면 두 테이블을 합쳐야 합니다. 이것이 **병합(merge)**입니다.
merge — 키를 기준으로 합치기
merge()는 SQL의 JOIN과 동일합니다. **공통 컬럼(키)**을 기준으로 두 DataFrame을 합칩니다.
import pandas as pd
users = pd.DataFrame({ "user_id": [1, 2, 3], "name": ["Alice", "Bob", "Carol"]})
orders = pd.DataFrame({ "order_id": [101, 102, 103], "user_id": [1, 2, 1], "amount": [5000, 3000, 7000]})
result = pd.merge(users, orders, on="user_id")print(result)# user_id name order_id amount# 0 1 Alice 101 5000# 1 1 Alice 103 7000# 2 2 Bob 102 3000Alice는 주문이 2개이므로 2행으로 나타납니다. Carol은 주문이 없으므로 결과에 없습니다 — 이것이 inner join의 기본 동작입니다.
Join 종류
users = pd.DataFrame({ "user_id": [1, 2, 3], "name": ["Alice", "Bob", "Carol"]})
orders = pd.DataFrame({ "order_id": [101, 102, 104], "user_id": [1, 2, 4], "amount": [5000, 3000, 8000]})# user_id=3(Carol)은 주문 없음, user_id=4는 사용자 테이블에 없음Inner Join (기본값)
양쪽 모두에 있는 키만.
pd.merge(users, orders, on="user_id", how="inner")# user_id name order_id amount# 0 1 Alice 101 5000# 1 2 Bob 102 3000Left Join
왼쪽 DataFrame의 모든 행 유지. 오른쪽에 없으면 NaN.
pd.merge(users, orders, on="user_id", how="left")# user_id name order_id amount# 0 1 Alice 101.0 5000.0# 1 2 Bob 102.0 3000.0# 2 3 Carol NaN NaNCarol은 주문이 없지만 결과에 포함됩니다.
Right Join
오른쪽 DataFrame의 모든 행 유지.
pd.merge(users, orders, on="user_id", how="right")# user_id name order_id amount# 0 1 Alice 101 5000# 1 2 Bob 102 3000# 2 4 NaN 104 8000user_id=4는 사용자 테이블에 없지만 결과에 포함됩니다.
Outer Join
양쪽 모든 행 유지.
pd.merge(users, orders, on="user_id", how="outer")# user_id name order_id amount# 0 1 Alice 101.0 5000.0# 1 2 Bob 102.0 3000.0# 2 3 Carol NaN NaN# 3 4 NaN 104.0 8000.0키 컬럼 이름이 다를 때
users = pd.DataFrame({"id": [1, 2], "name": ["Alice", "Bob"]})orders = pd.DataFrame({"customer_id": [1, 2], "amount": [5000, 3000]})
# left_on / right_on 사용pd.merge(users, orders, left_on="id", right_on="customer_id")# id name customer_id amount# 0 1 Alice 1 5000# 1 2 Bob 2 3000concat — 단순히 쌓기
concat()는 키 매칭 없이 DataFrame을 위아래 또는 좌우로 붙입니다.
위아래 (axis=0, 기본값)
jan = pd.DataFrame({"product": ["A", "B"], "sales": [100, 200]})feb = pd.DataFrame({"product": ["A", "B"], "sales": [150, 250]})
quarterly = pd.concat([jan, feb], ignore_index=True)# product sales# 0 A 100# 1 B 200# 2 A 150# 3 B 250ignore_index=True를 쓰면 인덱스가 0부터 재설정됩니다. 안 쓰면 원래 인덱스(0,1,0,1)가 중복됩니다.
좌우 (axis=1)
info = pd.DataFrame({"name": ["Alice", "Bob"], "age": [30, 25]})scores = pd.DataFrame({"math": [90, 85], "english": [88, 92]})
combined = pd.concat([info, scores], axis=1)# name age math english# 0 Alice 30 90 88# 1 Bob 25 85 92merge vs concat — 언제 뭘 쓰는가
| merge | concat | |
|---|---|---|
| 목적 | 공통 키로 매칭해서 합치기 | 단순히 쌓기/붙이기 |
| SQL 비유 | JOIN | UNION |
| 키 필요 | Yes (on, left_on/right_on) | No |
| 방향 | 좌우 (열 추가) | 위아래 또는 좌우 |
| 사용 사례 | 사용자 + 주문, 상품 + 카테고리 | 월별 데이터 합치기, 분할 파일 합치기 |
"사용자와 주문을 연결" → merge (키 매칭)
"1월 데이터와 2월 데이터를 이어붙이기" → concat (단순 쌓기)merge 주의사항 — 중복과 키 불일치
다대다 병합의 행 폭발
# 한 user_id에 주문이 여러 개, 배송도 여러 개orders = pd.DataFrame({ "user_id": [1, 1, 1], "order_id": [101, 102, 103]})shipments = pd.DataFrame({ "user_id": [1, 1], "shipment_id": ["S1", "S2"]})
result = pd.merge(orders, shipments, on="user_id")print(len(result)) # 6! (3 × 2 = 카르테시안 곱)양쪽에 같은 키가 여러 개 있으면 모든 조합이 생성됩니다. 3행 × 2행 = 6행. 대규모 데이터에서 이런 병합은 메모리를 폭발시킵니다. validate 파라미터로 방지할 수 있습니다:
pd.merge(orders, shipments, on="user_id", validate="many_to_one")# MergeError: Merge keys are not unique in right dataset실전 패턴 — 여러 파일 합치기
import osimport pandas as pd
data_dir = "monthly_reports"all_dfs = []
for filename in sorted(os.listdir(data_dir)): if filename.endswith(".csv"): filepath = os.path.join(data_dir, filename) df = pd.read_csv(filepath) df["source_file"] = filename all_dfs.append(df)
combined = pd.concat(all_dfs, ignore_index=True)print(f"Total rows: {len(combined)}")월별 CSV 파일을 하나로 합치는 패턴입니다. source_file 컬럼을 추가해서 어느 파일에서 왔는지 추적할 수 있습니다.
핵심 정리
| 함수 | 용도 | 핵심 파라미터 |
|---|---|---|
merge() | 키 기반 병합 (JOIN) | on, how, left_on/right_on |
concat() | 단순 쌓기 (UNION) | axis, ignore_index |
| Join | 포함 범위 |
|---|---|
| inner | 양쪽 모두에 있는 키만 |
| left | 왼쪽 전부 + 오른쪽 매칭 |
| right | 오른쪽 전부 + 왼쪽 매칭 |
| outer | 양쪽 모두 (없으면 NaN) |
데이터 분석의 80%는 올바른 테이블을 올바른 방식으로 합치는 것입니다. merge와 concat의 차이, 4가지 join의 동작을 명확히 이해하면, 데이터 전처리에서 가장 흔한 실수(중복 행, NaN 폭발, 키 불일치)를 예방할 수 있습니다.