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

[Pandas] join

· 수정 · 📖 약 2분 · 616자/단어 #python #pandas #merge #join #index #concat
Pandas join, DataFrame.join, Pandas merge join concat 차이, 판다스 조인

정의

DataFrame.join(other)index 기반 결합 의 편의 API. 내부적으로는 merge(left_index=True, right_index=True, ...) 와 동등하지만 더 짧다.

pandas 에서 두 DataFrame 을 결합하는 세 가지 주요 함수:

  • merge : 컬럼 / 인덱스 기반 결합. SQL JOIN 에 가장 가깝다.
  • join : 인덱스 기반 결합의 간편 API. merge 의 subset.
  • concat : 수직/수평 이어 붙이기. UNION / 열 추가에 해당.

세 API 비교 시각화

flowchart TD
    subgraph "merge - 컬럼/인덱스 기반"
        M1["pd.merge(left, right, on='key')"]
        M2["pd.merge(left, right, left_on='a', right_on='b')"]
        M3["pd.merge(left, right, left_index=True, right_index=True)"]
    end

    subgraph "join - 인덱스 기반 간편 API"
        J1["left.join(right)"]
        J2["left.join(right, on='col')"]
        J3["left.join([b, c, d])"]
    end

    subgraph "concat - 이어 붙이기"
        C1["pd.concat([a, b])  # 수직"]
        C2["pd.concat([a, b], axis=1)  # 수평"]
    end

    M3 -.->|"동등"| J1

join 기본

left.join(right)                # left join (기본), index 기준
left.join(right, how='outer')
left.join(right, on='key')      # left 의 key 컬럼 vs right 의 index

merge 와의 비교

# 둘은 같음
left.join(right, how='inner')
pd.merge(left, right, left_index=True, right_index=True, how='inner')

joinindex 끼리 매칭 이 기본 가정이라 코드가 짧다. 컬럼 기준 결합이면 merge 가 명확.

join 사용 예

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

print(users.join(orders, how='outer'))
결과
      name  amount
1    Alice   100.0
1    Alice   200.0
2      Bob     NaN
3      NaN    50.0
nameamount
1Alice100.0
1Alice200.0
2BobNaN
3NaN50.0

merge: how 옵션별 동작

flowchart LR
    subgraph "Left"
        LA["A=1"]
        LB["A=2"]
        LC["A=3"]
    end

    subgraph "Right"
        RA["A=2"]
        RB["A=3"]
        RC["A=4"]
    end

    subgraph "inner"
        IA["A=2"]
        IB["A=3"]
    end

    subgraph "left"
        LEA["A=1 (NaN)"]
        LEB["A=2"]
        LEC["A=3"]
    end

    subgraph "outer"
        OA["A=1 (NaN)"]
        OB["A=2"]
        OC["A=3"]
        OD["A=4 (NaN)"]
    end
how결과 행SQL 대응
inner양쪽 모두 있는 keyINNER JOIN
leftleft 전체 + right 매칭LEFT JOIN
rightleft 매칭 + right 전체RIGHT JOIN
outer양쪽 전체 합집합FULL OUTER JOIN
cross카테시안 곱CROSS JOIN
pd.merge(left, right, on='key', how='inner')
pd.merge(left, right, on='key', how='left')
pd.merge(left, right, on='key', how='right')
pd.merge(left, right, on='key', how='outer')
pd.merge(left, right, how='cross')

merge: on / left_on / right_on

# 같은 이름 컬럼 (on)
pd.merge(df1, df2, on='user_id')

# 이름이 다른 컬럼 (left_on / right_on)
pd.merge(orders, products, left_on='product_id', right_on='id')

# 복합 키
pd.merge(df1, df2, on=['year', 'month'])

# 인덱스 기반
pd.merge(df1, df2, left_index=True, right_index=True)
pd.merge(df1, df2, left_on='key', right_index=True)

merge 실전 예시: 주문-상품-사용자

python
import pandas as pd

users = pd.DataFrame({
  'user_id': [1, 2, 3],
  'name': ['Alice', 'Bob', 'Carol'],
})

orders = pd.DataFrame({
  'order_id': [101, 102, 103, 104],
  'user_id':  [1, 2, 1, 4],
  'amount':   [500, 200, 800, 100],
})

# 주문 기준 left join (탈퇴 사용자 포함)
result = pd.merge(orders, users, on='user_id', how='left')
print(result.to_string(index=False))
결과
 order_id  user_id  amount   name
    101        1     500  Alice
    102        2     200    Bob
    103        1     800  Alice
    104        4     100    NaN

concat 사용법

# 수직 (행 이어 붙이기)
pd.concat([df1, df2])
pd.concat([df1, df2], ignore_index=True)   # 인덱스 재지정

# 수평 (열 이어 붙이기)
pd.concat([df1, df2], axis=1)

# keys 로 계층적 인덱스 생성
pd.concat([df1, df2], keys=['train', 'test'])
python
import pandas as pd

q1 = pd.DataFrame({'sales': [100, 200]}, index=['Jan', 'Feb'])
q2 = pd.DataFrame({'sales': [300, 400]}, index=['Mar', 'Apr'])

annual = pd.concat([q1, q2])
print(annual)
결과
     sales
Jan    100
Feb    200
Mar    300
Apr    400

여러 DataFrame 동시 join

a.join([b, c, d], how='outer')

merge 보다 깔끔. 단, 모두 같은 index 기준.

컬럼 충돌

a.join(b, lsuffix='_a', rsuffix='_b')
# 양쪽 컬럼 이름 충돌 시 suffix

pd.merge(left, right, on='key', suffixes=('_l', '_r'))

on 파라미터 (left 의 컬럼 vs right 의 index)

orders.join(products, on='product_id')
# orders['product_id'] 와 products.index 매칭

SQL 의 LEFT JOIN products ON orders.product_id = products.id 패턴.

merge vs join vs concat: 언제 무엇을

시나리오권장
양쪽 모두 일반 컬럼 기준merge(on='col')
양쪽 모두 index 기준join() 또는 merge(left_index=True, right_index=True)
left 컬럼 + right indexjoin(on='col') 또는 merge(left_on='col', right_index=True)
다중 DataFrame 한 번에join([b, c, d])
다대다 검증 필요merge(validate='one_to_many')
행 이어 붙이기 (UNION)concat([df1, df2])
열 이어 붙이기concat([df1, df2], axis=1)
시간 기반 근사 joinmerge_asof

validate 옵션 (merge 전용)

pd.merge(left, right, on='key', validate='one_to_one')
# 'one_to_one' / 'one_to_many' / 'many_to_one' / 'many_to_many'
# 기대와 다르면 MergeError 발생 → 데이터 품질 검사

indicator 옵션

result = pd.merge(left, right, on='key', how='outer', indicator=True)
result['_merge']
# 'left_only' / 'right_only' / 'both'

# 어느 쪽에만 있는 행 확인
result[result['_merge'] == 'left_only']

성능

큰 DataFrame 병합

# 1. merge 전에 필요한 컬럼만 남기기
left_cols = ['key', 'col_a', 'col_b']
right_cols = ['key', 'col_c']
left_slim = left[left_cols]
right_slim = right[right_cols]
pd.merge(left_slim, right_slim, on='key')

# 2. key 컬럼에 인덱스 설정 후 join
left.set_index('key').join(right.set_index('key'))

# 3. category dtype 로 key 변환 (문자열 key 비교 속도 향상)
left['key'] = left['key'].astype('category')

concat 의 루프 안 사용 금지

# ❌ 매 반복마다 새 DataFrame 생성 - O(n^2) 메모리
result = pd.DataFrame()
for chunk in chunks:
    result = pd.concat([result, chunk])

# ✓ 리스트에 모은 뒤 한 번에
parts = []
for chunk in chunks:
    parts.append(chunk)
result = pd.concat(parts, ignore_index=True)

함정

1. 기본 how 가 다름

mergejoin
기본 howinnerleft

2. index 가 unique 가 아니면 폭증

# 양쪽 index 에 중복이 있으면 곱셈 행 생성
# validate 옵션이 join 에는 없음 → merge 권장
pd.merge(left, right, on='key', validate='one_to_one')

3. 동일 이름 컬럼

lsuffix / rsuffix 둘 다 default 가 '' 이므로 충돌하면 에러.

a.join(b)               # ValueError: columns overlap
a.join(b, lsuffix='_a')    # 한 쪽만 줘도 가능

WARNING

join 의 기본 how='left' 를 인지하지 못해 right-only 행이 사라지는 실수가 흔하다. 항상 how= 를 명시하는 습관을 들이자.

CAUTION

concat([df1, df2], axis=1) 는 내부적으로 index 정렬을 하므로 index 가 다르면 NaN 이 생긴다. 행 수가 같아도 index 가 다르면 의도치 않은 결과가 발생한다. ignore_index=True 또는 reset_index() 를 먼저 적용하라.

관련 위키

이 글의 용어 (6개)
[Pandas] .loc / .ilocpandas
정의 - : 라벨 기반 인덱싱 (index/column 이름) - : 정수 위치 기반 인덱싱 (0-based) 이 두 indexer 가 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] mergepandas
정의 는 두 DataFrame 을 SQL join 처럼 결합. 4 가지 join 타입 (inner / left / right / outer) 지원. 사용 상황 - 두 테이블 키 …
[Pandas] merge_asofpandas
정의 는 가장 가까운 키 기준 join. 일반 가 정확히 일치하는 키만 매칭하는 반면, 는 이전 (또는 가장 가까운) 키를 찾는다. 시계열 데이터 (가격, 이벤트) 와 다른 시계…
[Pandas] MultiIndexpandas
정의 MultiIndex 는 여러 단계(level)의 라벨로 구성된 . SQL의 composite key와 비슷하며, 그룹화된 데이터를 자연스럽게 표현한다. , , 등이 자동으로…

이 개념을 다룬 위키 페이지 (3)

💬 댓글

사이트 검색 / 명령어

검색

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