13. 연결·병합·결합

🎯 이 장의 목표
  • 여러 데이터프레임을 위아래·좌우로 연결한다(concat)
  • 공통 열(키)을 기준으로 표를 병합한다(merge)
  • 병합의 네 가지 방식(inner·outer·left·right)을 구분한다
💡 팁
현실에선 데이터가 여러 파일·표로 흩어져 있는 경우가 많습니다(주문 표 따로, 고객 표 따로). 이를 하나로 합치는 것이 이 장의 주제입니다.

두 가지 합치기: 쌓기 vs 옆에 붙이기

데이터프레임을 합치는 방식은 크게 둘입니다.

flowchart TB
    subgraph C["concat — 쌓기"]
        direction TB
        A1["표 A"] --> AB["A+B 위아래로"]
        B1["표 B"] --> AB
    end
    subgraph M["merge — 키로 옆에 붙이기"]
        direction LR
        A2["주문 표"] --- key(("공통 키<br/>고객ID")) --- B2["고객 표"]
    end
    classDef box fill:#d0ebff,stroke:#1971c2,color:#000
    classDef keyc fill:#fff3bf,stroke:#f59f00,color:#000
    class A1,B1,AB,A2,B2 box
    class key keyc
  • concat(연결): 같은 형태의 표를 위아래로 쌓거나 좌우로 이어 붙임 (단순 연결)
  • merge(병합): 공통 열(키)을 기준으로 두 표를 짝지어 옆으로 합침 (엑셀 VLOOKUP과 유사)

concat: 표 쌓기

pd.concat([표1, 표2])로 여러 표를 잇습니다. 기본은 위아래로(행 방향) 쌓습니다.

PYTHON
import pandas as pd

jan = pd.DataFrame({"이름": ["철수", "영희"], "매출": [100, 200]})
feb = pd.DataFrame({"이름": ["민수", "지수"], "매출": [150, 250]})

result = pd.concat([jan, feb])
print(result)

실행 결과:

CODE
   이름   매출
0  철수  100
1  영희  200
0  민수  150
1  지수  250
⚠️ 흔한 실수
흔한 함정 — 인덱스가 겹친다
위 결과를 보면 인덱스가 0,1,0,1로 중복됩니다(각 표의 원래 번호를 유지하기 때문). 새로 0부터 매기려면 ignore_index=True를 줍니다.
PYTHON
result = pd.concat([jan, feb], ignore_index=True)
print(result)

실행 결과:

CODE
   이름   매출
0  철수  100
1  영희  200
2  민수  150
3  지수  250
📌 핵심
append는 이제 없습니다
예전 자료에는 df1.append(df2)라는 코드가 보일 수 있는데, 이 메서드는 Pandas 2.0에서 완전히 제거되었습니다. 지금은 반드시 pd.concat([df1, df2])를 쓰세요. (옛 코드를 따라 하다 AttributeError: 'DataFrame' object has no attribute 'append' 에러를 만나면 이것이 원인입니다.)

좌우(열 방향)로 붙이려면 axis=1:

PYTHON
left = pd.DataFrame({"이름": ["철수", "영희"]})
right = pd.DataFrame({"점수": [85, 92]})
print(pd.concat([left, right], axis=1))

실행 결과:

CODE
   이름  점수
0  철수  85
1  영희  92

merge: 키로 병합하기

merge공통 열(키)을 기준으로 두 표를 짝지어 합칩니다. 가장 강력하고 자주 쓰는 합치기입니다.

🔑 새 용어 — 키(key)
두 표를 이어 줄 공통의 기준 열입니다. 예를 들어 "주문 표"와 "고객 표"에 둘 다 고객ID가 있으면, 그걸 키로 삼아 "어느 주문이 어느 고객의 것인지" 연결할 수 있습니다.
PYTHON
import pandas as pd

# 주문 표: 어느 고객이 무엇을 샀나
orders = pd.DataFrame({
    "고객ID": [1, 2, 3],
    "상품": ["노트북", "마우스", "키보드"]
})

# 고객 표: 고객 정보
customers = pd.DataFrame({
    "고객ID": [1, 2, 3],
    "이름": ["철수", "영희", "민수"]
})

merged = pd.merge(orders, customers, on="고객ID")
print(merged)

실행 결과:

CODE
   고객ID   상품  이름
0     1  노트북  철수
1     2  마우스  영희
2     3  키보드  민수

on="고객ID"는 "고객ID를 기준 키로 삼아라"는 뜻입니다. 두 표의 같은 고객ID끼리 짝지어졌습니다.

병합의 네 가지 방식 (how)

키가 양쪽에 모두 있지 않을 때, 어느 쪽을 기준으로 남길지how로 정합니다. 예제 데이터를 살짝 바꿔 봅시다.

PYTHON
import pandas as pd

orders = pd.DataFrame({
    "고객ID": [1, 2, 4],        # 4번 고객은 고객 표에 없음
    "상품": ["노트북", "마우스", "모니터"]
})
customers = pd.DataFrame({
    "고객ID": [1, 2, 3],        # 3번 고객은 주문이 없음
    "이름": ["철수", "영희", "민수"]
})
how의미남는 것
inner (기본)양쪽에 다 있는 키만교집합
outer한쪽에라도 있는 키 전부합집합
left왼쪽 표 기준 전부왼쪽 다 + 맞는 오른쪽
right오른쪽 표 기준 전부오른쪽 다 + 맞는 왼쪽
PYTHON
print(pd.merge(orders, customers, on="고객ID", how="inner"))

실행 결과 (양쪽 공통인 1,2번만):

CODE
   고객ID   상품  이름
0     1  노트북  철수
1     2  마우스  영희
PYTHON
print(pd.merge(orders, customers, on="고객ID", how="outer"))

실행 결과 (1,2,3,4번 전부, 없는 칸은 NaN):

CODE
   고객ID   상품   이름
0     1  노트북   철수
1     2  마우스   영희
2     3  NaN   민수
3     4  모니터  NaN
PYTHON
print(pd.merge(orders, customers, on="고객ID", how="left"))

실행 결과 (주문 표 기준, 4번은 이름 없음):

CODE
   고객ID   상품   이름
0     1  노트북   철수
1     2  마우스   영희
2     4  모니터  NaN
💡 팁
어떤 how를 쓸까?
가장 많이 쓰는 건 inner(공통만)와 left(왼쪽 기준 유지)입니다. "주문 표는 다 살리되 고객 정보를 붙이고 싶다"면 left가 적합합니다. 합쳤더니 행 수가 예상과 다르거나 NaN이 잔뜩 생겼다면, how 설정이 의도와 맞는지 가장 먼저 확인하세요.
⚠️ 흔한 실수
흔한 함정 — 키가 중복되면 행이 불어난다
한쪽 표에 같은 키가 여러 번 나오면, 병합 결과 행 수가 곱으로 불어날 수 있습니다. 병합 전 df["키"].duplicated().sum()으로 중복을 확인하는 습관이 좋습니다.

🛠 미니 챌린지

PYTHON
import pandas as pd
students = pd.DataFrame({
    "학번": [101, 102, 103],
    "이름": ["가", "나", "다"]
})
scores = pd.DataFrame({
    "학번": [101, 102, 104],
    "점수": [90, 85, 70]
})
  1. 두 표를 학번 기준으로 inner 병합하세요. 몇 행이 나오나요?
  2. 같은 두 표를 outer 병합하세요. NaN이 어디에 생기나요?
  3. students를 기준(left)으로 점수를 붙이세요.

✅ 미니 챌린지 해설

PYTHON
# 1. inner — 공통 학번(101,102)만
print(pd.merge(students, scores, on="학번", how="inner"))

실행 결과 (2행):

CODE
   학번 이름  점수
0  101  가  90
1  102  나  85
PYTHON
# 2. outer — 101,102,103,104 전부
print(pd.merge(students, scores, on="학번", how="outer"))

실행 결과:

CODE
   학번   이름    점수
0  101    가  90.0
1  102    나  85.0
2  103    다   NaN   ← 103은 점수 표에 없음
3  104  NaN  70.0   ← 104는 학생 표에 없음
PYTHON
# 3. left — 학생 표 기준
print(pd.merge(students, scores, on="학번", how="left"))

실행 결과:

CODE
   학번 이름    점수
0  101  가  90.0
1  102  나  85.0
2  103  다   NaN

이 장에서 배운 것

  • pd.concat([표들])로 표를 위아래(기본)나 좌우(axis=1)로 잇는다. ignore_index=True로 번호를 새로 매긴다.
  • append는 Pandas 2.0에서 제거됐으니 concat을 쓴다.
  • pd.merge(A, B, on="키")로 공통 열 기준 병합한다.
  • how로 inner(공통)·outer(전부)·left·right를 정하며, inner와 left가 가장 흔하다.

✍️ 확인 문제

  1. concatmerge의 차이를 한 문장으로 설명하세요.
  2. concat 후 인덱스가 0,1,0,1로 중복됐습니다. 어떻게 고치나요?
  3. "왼쪽 표의 모든 행을 유지하면서 오른쪽 정보를 붙이고 싶다"면 how에 무엇을 줘야 하나요?
다음 장에서는 데이터를 그룹별로 묶어 요약하는 group-by와, 인덱스를 여러 층으로 쌓는 멀티인덱싱을 배웁니다. 데이터 요약의 핵심입니다.
👉 14. 멀티인덱싱과 그룹화