이미지 로딩 중...

Pandas 데이터 결합 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 11. 21. · 4 Views

Pandas 데이터 결합 완벽 가이드

Pandas의 merge, join, concat 함수를 활용하여 여러 데이터프레임을 효과적으로 결합하는 방법을 배웁니다. 실무에서 자주 사용하는 데이터 결합 패턴과 각 방법의 차이점을 이해하고, 실전 예제를 통해 바로 활용할 수 있습니다.


목차

  1. merge 기본 사용법
  2. merge 조인 방식 이해하기
  3. 여러 열을 키로 사용하기
  4. concat으로 데이터 쌓기
  5. join 메서드 사용하기
  6. 열 이름이 다를 때 merge하기
  7. concat으로 가로 결합하기
  8. merge의 validate 옵션으로 관계 검증하기
  9. indicator로 merge 결과 추적하기
  10. suffixes로 중복 열 이름 관리하기

1. merge 기본 사용법

시작하며

여러분이 고객 정보 테이블과 주문 정보 테이블을 합쳐서 "어떤 고객이 무엇을 주문했는지" 분석하려고 할 때 이런 상황을 겪어본 적 있나요? 두 개의 엑셀 파일이나 CSV 파일이 있는데, 공통된 열(예: 고객 ID)을 기준으로 합쳐야 하는데 어떻게 해야 할지 막막했던 경험 말이죠.

이런 문제는 실제 데이터 분석 현장에서 매일같이 발생합니다. 데이터가 여러 곳에 분산되어 있고, 각각의 데이터를 의미 있게 연결해야만 제대로 된 분석이 가능하기 때문입니다.

잘못 결합하면 데이터가 중복되거나 누락되어 분석 결과가 완전히 달라질 수 있습니다. 바로 이럴 때 필요한 것이 Pandas의 merge 함수입니다.

SQL의 JOIN과 비슷한 방식으로, 두 데이터프레임을 공통 열(키)을 기준으로 똑똑하게 결합해줍니다.

개요

간단히 말해서, merge는 두 개의 데이터프레임을 공통된 열을 기준으로 합치는 함수입니다. 마치 퍼즐 조각을 맞추듯이, 같은 값을 가진 행끼리 자동으로 연결해줍니다.

왜 이 함수가 필요한지 실무 관점에서 생각해볼까요? 예를 들어, 온라인 쇼핑몰을 운영한다면 고객 테이블(이름, 나이, 지역)과 구매 테이블(고객ID, 상품명, 금액)이 따로 있을 겁니다.

이 둘을 고객ID로 merge하면 "30대 서울 거주 고객이 주로 어떤 상품을 구매하는지" 같은 인사이트를 얻을 수 있습니다. 기존에는 엑셀의 VLOOKUP이나 수작업으로 데이터를 찾아서 붙여넣었다면, 이제는 단 한 줄의 코드로 수천, 수만 건의 데이터를 정확하게 결합할 수 있습니다.

merge의 핵심 특징은 세 가지입니다. 첫째, SQL처럼 inner, left, right, outer 조인 방식을 선택할 수 있습니다.

둘째, 여러 개의 열을 동시에 키로 사용할 수 있습니다. 셋째, 같은 이름의 열이 양쪽에 있으면 자동으로 접미사(_x, _y)를 붙여 구분해줍니다.

이러한 특징들이 복잡한 데이터 결합 작업을 안전하고 정확하게 만들어줍니다.

코드 예제

import pandas as pd

# 고객 정보 데이터프레임
customers = pd.DataFrame({
    'customer_id': [1, 2, 3, 4],
    'name': ['김철수', '이영희', '박민수', '정수진'],
    'city': ['서울', '부산', '대구', '인천']
})

# 주문 정보 데이터프레임
orders = pd.DataFrame({
    'customer_id': [1, 2, 2, 5],
    'product': ['노트북', '마우스', '키보드', '모니터'],
    'amount': [1500000, 30000, 80000, 300000]
})

# customer_id를 기준으로 inner join (기본값)
result = pd.merge(customers, orders, on='customer_id')
print(result)

설명

이것이 하는 일: merge 함수는 두 개의 데이터프레임에서 공통된 값을 가진 열을 찾아서, 그 값이 일치하는 행끼리 옆으로 붙여주는 작업을 수행합니다. 마치 두 명부를 펼쳐놓고 같은 이름을 찾아서 정보를 합치는 것과 같습니다.

첫 번째로, customers와 orders 두 데이터프레임을 준비합니다. 둘 다 'customer_id'라는 공통 열을 가지고 있죠.

이 열이 바로 두 데이터를 연결하는 '다리' 역할을 합니다. customers에는 고객의 기본 정보가, orders에는 주문 내역이 담겨 있습니다.

그 다음으로, pd.merge() 함수를 호출하면서 on='customer_id'를 지정합니다. 이렇게 하면 Pandas가 양쪽 데이터프레임에서 customer_id 값이 같은 행을 찾아냅니다.

예를 들어, customer_id가 1인 김철수의 정보와 customer_id가 1인 주문 정보가 자동으로 매칭됩니다. 마지막으로, 매칭된 정보들이 하나의 행으로 합쳐집니다.

기본값은 inner join이라서, 양쪽 데이터프레임 모두에 존재하는 customer_id만 결과에 포함됩니다. 위 예제에서 customer_id 5번(모니터 주문)은 고객 정보가 없어서 제외되고, customer_id 3, 4번 고객은 주문 내역이 없어서 제외됩니다.

여러분이 이 코드를 사용하면 흩어져 있던 정보를 하나로 모아서 통합 분석이 가능해집니다. 고객별 구매 패턴 분석, 지역별 매출 분석, 고객 세그먼트별 선호 상품 분석 등 다양한 인사이트를 얻을 수 있습니다.

실무에서는 이런 merge 작업이 데이터 분석의 80% 이상을 차지합니다. 데이터베이스에서 추출한 여러 테이블을 결합하거나, 외부 데이터와 내부 데이터를 매칭할 때 필수적으로 사용됩니다.

실전 팁

💡 how 매개변수로 조인 방식을 변경할 수 있습니다. how='left'를 사용하면 왼쪽 데이터프레임의 모든 행을 유지하면서 오른쪽 데이터를 붙일 수 있어요. 고객 정보는 모두 남기고 주문 내역만 추가하고 싶을 때 유용합니다.

💡 merge 전에 반드시 키 열의 데이터 타입을 확인하세요. 한쪽은 정수형, 다른 쪽은 문자열이면 매칭이 안 됩니다. df['customer_id'].dtype으로 확인하고 필요하면 astype()으로 변환하세요.

💡 중복 키가 있으면 결과 행 수가 폭발적으로 증가할 수 있습니다. 1:다 관계가 아니라 다:다 관계면 모든 조합이 생성되므로 주의하세요. validate='1:m' 옵션으로 관계를 검증할 수 있습니다.

💡 indicator=True 옵션을 추가하면 '_merge' 열이 생성되어 각 행이 어디서 왔는지 추적할 수 있습니다. 데이터 품질 체크나 디버깅할 때 매우 유용합니다.

💡 대용량 데이터를 merge할 때는 메모리 사용량에 주의하세요. 필요한 열만 먼저 선택한 후 merge하면 속도와 메모리 효율이 크게 개선됩니다.


2. merge 조인 방식 이해하기

시작하며

여러분이 merge를 처음 사용할 때 이런 고민을 해본 적 있나요? "왜 결과 행 수가 예상과 다르지?", "어떤 데이터는 사라지고 어떤 건 NaN으로 채워지는데 이게 맞는 건가?" 같은 의문 말이죠.

이런 혼란은 조인 방식(inner, left, right, outer)의 차이를 명확히 이해하지 못해서 발생합니다. 각 방식은 매칭되지 않는 데이터를 다르게 처리하기 때문에, 잘못 선택하면 중요한 정보를 잃거나 원하지 않는 결과를 얻게 됩니다.

바로 이럴 때 필요한 것이 조인 방식에 대한 정확한 이해입니다. SQL을 알고 있다면 쉽게 이해할 수 있고, 처음이라도 벤다이어그램으로 생각하면 직관적으로 파악할 수 있습니다.

개요

간단히 말해서, 조인 방식은 "매칭되지 않는 데이터를 어떻게 처리할 것인가"를 결정하는 옵션입니다. 네 가지 방식은 각각 다른 상황에 적합합니다.

왜 이 개념이 필요한지 실무 관점에서 설명하겠습니다. 예를 들어, 전체 직원 목록과 이번 달 급여 지급 내역을 합친다고 해볼까요?

inner join을 쓰면 급여를 받은 직원만 나오고, left join을 쓰면 급여를 못 받은 직원도 확인할 수 있습니다. 어떤 걸 선택하느냐에 따라 분석 결과가 완전히 달라집니다.

기존에는 "일단 합쳐보고 이상하면 다시 해보자"는 식으로 시행착오를 겪었다면, 이제는 목적에 맞는 조인 방식을 정확히 선택할 수 있습니다. 조인 방식의 핵심 특징은 이렇습니다.

inner는 양쪽 모두에 있는 데이터만(교집합), left는 왼쪽 전체 + 매칭되는 오른쪽, right는 오른쪽 전체 + 매칭되는 왼쪽, outer는 양쪽 모두(합집합)를 가져옵니다. 이러한 차이를 이해하면 데이터 손실이나 중복 없이 정확한 결합이 가능합니다.

코드 예제

import pandas as pd

customers = pd.DataFrame({
    'id': [1, 2, 3],
    'name': ['김철수', '이영희', '박민수']
})

orders = pd.DataFrame({
    'id': [2, 3, 4],
    'product': ['마우스', '키보드', '모니터']
})

# inner join: 양쪽 모두에 있는 id만 (2, 3)
inner_result = pd.merge(customers, orders, on='id', how='inner')

# left join: 왼쪽(customers) 전체 유지
left_result = pd.merge(customers, orders, on='id', how='left')

# right join: 오른쪽(orders) 전체 유지
right_result = pd.merge(customers, orders, on='id', how='right')

# outer join: 양쪽 모두 포함
outer_result = pd.merge(customers, orders, on='id', how='outer')

설명

이것이 하는 일: how 매개변수는 두 데이터프레임을 결합할 때 매칭되지 않는 행을 포함할지 제외할지를 결정합니다. 이는 최종 결과의 행 수와 내용에 직접적인 영향을 줍니다.

첫 번째로, inner join(기본값)은 가장 엄격한 방식입니다. 양쪽 데이터프레임에 모두 존재하는 키 값만 결과에 포함됩니다.

위 예제에서는 id가 2, 3인 행만 결과에 나타나고, 김철수(id=1)와 모니터 주문(id=4)은 제외됩니다. 확실하게 매칭된 데이터만 분석하고 싶을 때 사용합니다.

그 다음으로, left join은 왼쪽 데이터프레임의 모든 행을 유지합니다. 오른쪽에 매칭되는 데이터가 없으면 해당 열들이 NaN으로 채워집니다.

김철수(id=1)는 주문 내역이 없어서 product 열이 NaN이 되죠. "전체 고객 중 주문하지 않은 사람은 누구인가?"를 파악할 때 유용합니다.

right join은 left의 반대입니다. 오른쪽 데이터프레임을 기준으로 전체를 유지하고, 왼쪽에 매칭되는 게 없으면 NaN으로 채웁니다.

모니터 주문(id=4)은 고객 정보가 없어서 name 열이 NaN이 됩니다. 실무에서는 left join을 더 많이 쓰고, right는 순서를 바꿔서 left로 표현하는 경우가 많습니다.

마지막으로, outer join(full outer join)은 가장 포괄적입니다. 양쪽 데이터프레임의 모든 행을 포함하고, 매칭되지 않는 부분은 NaN으로 채웁니다.

결과는 id가 1, 2, 3, 4 모두 포함되며, 각각 매칭되지 않은 열은 NaN입니다. 전체 데이터를 보존하면서 결합하고 싶을 때 사용합니다.

여러분이 각 조인 방식을 제대로 이해하면 데이터 분석의 정확도가 크게 향상됩니다. "왜 이 고객이 결과에 없지?"나 "이 NaN 값은 어디서 온 거지?" 같은 혼란을 겪지 않게 되고, 의도한 대로 정확한 분석이 가능해집니다.

실무에서는 상황에 따라 다른 방식을 선택합니다. 정확한 매출 분석에는 inner, 미주문 고객 파악에는 left, 데이터 품질 검사에는 outer를 주로 사용합니다.

실전 팁

💡 left join 결과에서 오른쪽 열이 NaN인 행을 필터링하면 "왼쪽에만 있는 데이터"를 쉽게 찾을 수 있습니다. result[result['product'].isna()]로 주문하지 않은 고객을 추출하세요.

💡 outer join 후 양쪽의 NaN을 체크하면 데이터 불일치를 한 번에 파악할 수 있습니다. 마스터 데이터와 실제 데이터를 비교할 때 유용합니다.

💡 대부분의 경우 left join이 가장 안전합니다. 기준이 되는 데이터(고객, 상품 등)를 왼쪽에 두고 부가 정보를 오른쪽에서 가져오는 방식으로 설계하세요.

💡 조인 방식을 선택하기 전에 "어떤 데이터를 절대 잃어서는 안 되는가?"를 먼저 생각하세요. 그 데이터를 왼쪽에 두고 left join을 사용하면 됩니다.

💡 실전에서는 right join을 거의 사용하지 않습니다. 필요하면 데이터프레임 순서를 바꿔서 left join으로 표현하는 게 코드 가독성이 더 좋습니다.


3. 여러 열을 키로 사용하기

시작하며

여러분이 실제 업무 데이터를 다루다 보면 이런 상황을 만나게 됩니다. 하나의 열만으로는 행을 고유하게 식별할 수 없는 경우 말이죠.

예를 들어, 같은 날짜에 같은 고객이 여러 번 주문할 수 있고, 같은 상품을 여러 매장에서 판매할 수 있습니다. 이런 문제는 복합 키(composite key)가 필요한 상황입니다.

단일 열로는 유일성을 보장할 수 없어서, 여러 열의 조합으로 행을 식별해야 합니다. 잘못 결합하면 같은 데이터가 중복으로 매칭되거나 전혀 엉뚱한 데이터끼리 합쳐질 수 있습니다.

바로 이럴 때 필요한 것이 다중 키 merge입니다. on 매개변수에 리스트를 전달하여 여러 열을 동시에 키로 사용할 수 있습니다.

개요

간단히 말해서, 여러 열을 키로 사용하는 merge는 모든 지정된 열의 값이 동시에 일치하는 행끼리만 결합합니다. 마치 주민등록번호처럼 여러 정보의 조합으로 사람을 특정하는 것과 같습니다.

왜 이게 필요한지 구체적인 예를 들어볼까요? 매장별, 날짜별 판매 데이터와 재고 데이터를 결합한다고 해봅시다.

'날짜'만으로는 여러 매장의 데이터가 섞이고, '매장'만으로는 여러 날짜가 섞입니다. '날짜 + 매장' 조합을 키로 사용해야 정확한 매칭이 가능합니다.

기존에는 두 열을 하나의 문자열로 합쳐서(예: '2024-01-01_강남점') 임시 키를 만들고 merge 후 다시 분리하는 번거로운 작업을 했다면, 이제는 on=['date', 'store']처럼 간단하게 처리할 수 있습니다. 다중 키 merge의 핵심 특징은 AND 조건이라는 점입니다.

모든 키 열이 동시에 일치해야만 매칭됩니다. 날짜는 같지만 매장이 다르면 매칭되지 않고, 매장은 같지만 날짜가 다르면 역시 매칭되지 않습니다.

이렇게 정확한 매칭 조건을 설정할 수 있어서 데이터 무결성을 보장합니다.

코드 예제

import pandas as pd

# 매장별 판매 데이터
sales = pd.DataFrame({
    'date': ['2024-01-01', '2024-01-01', '2024-01-02'],
    'store': ['강남점', '홍대점', '강남점'],
    'revenue': [1000000, 800000, 1200000]
})

# 매장별 직원 수
staff = pd.DataFrame({
    'date': ['2024-01-01', '2024-01-01', '2024-01-02'],
    'store': ['강남점', '홍대점', '강남점'],
    'staff_count': [5, 3, 6]
})

# date와 store 두 개를 키로 사용
# 두 열이 모두 일치하는 행끼리만 매칭됨
result = pd.merge(sales, staff, on=['date', 'store'])
print(result)
# 각 매장의 날짜별 매출과 직원 수가 정확히 결합됨

설명

이것이 하는 일: 다중 키 merge는 여러 열의 값을 조합하여 유일한 행을 식별하고, 그 조합이 양쪽 데이터프레임에서 모두 일치하는 경우에만 결합합니다. 마치 주소를 찾을 때 '시 + 구 + 동'을 모두 확인하는 것과 같습니다.

첫 번째로, sales와 staff 데이터프레임을 준비합니다. 둘 다 'date'와 'store' 열을 가지고 있고, 이 두 열의 조합이 각 행을 고유하게 식별합니다.

예를 들어, '2024-01-01의 강남점'과 '2024-01-02의 강남점'은 완전히 다른 행입니다. 그 다음으로, pd.merge()에서 on=['date', 'store']로 두 개의 키를 지정합니다.

Pandas는 이제 date 값과 store 값이 모두 일치하는 행을 찾습니다. '2024-01-01'이고 '강남점'인 행끼리, '2024-01-01'이고 '홍대점'인 행끼리 매칭됩니다.

이 과정에서 Pandas는 내부적으로 두 열의 값을 조합하여 비교합니다. 만약 sales에 '2024-01-01, 강남점'이 있고 staff에도 '2024-01-01, 강남점'이 있다면, 이 두 행이 결합됩니다.

하지만 sales에 '2024-01-01, 을지로점'이 있는데 staff에는 없다면, inner join이므로 이 행은 결과에서 제외됩니다. 마지막으로, 결합된 결과는 date, store, revenue, staff_count 네 개의 열을 가지게 됩니다.

각 행은 특정 날짜의 특정 매장에 대한 판매액과 직원 수를 정확하게 표현합니다. 이제 '직원 1인당 매출'같은 지표를 계산할 수 있습니다.

여러분이 이 기법을 사용하면 복잡한 실무 데이터를 정확하게 결합할 수 있습니다. 시계열 데이터(날짜 + 제품), 다차원 데이터(지역 + 카테고리 + 월), 로그 데이터(사용자 + 세션 + 이벤트) 등 다양한 상황에서 필수적입니다.

실무에서는 2-3개의 키를 사용하는 경우가 매우 흔합니다. 온라인 광고 분석에서는 '날짜 + 광고ID + 플랫폼'을, 재고 관리에서는 '창고 + 제품코드 + 날짜'를 키로 사용합니다.

실전 팁

💡 다중 키를 사용하기 전에 각 키 열의 데이터 타입과 공백, 대소문자를 확인하세요. '강남점'과 '강남점 '(뒤에 공백)은 다른 값으로 인식됩니다. df['store'].str.strip()으로 공백을 제거하세요.

💡 키 조합의 유일성을 먼저 검증하세요. df.duplicated(subset=['date', 'store']).sum()으로 중복을 확인하고, 중복이 있다면 집계나 중복 제거를 먼저 수행해야 합니다.

💡 좌우 데이터프레임의 키 열 이름이 다르면 left_on, right_on을 따로 지정할 수 있습니다. pd.merge(df1, df2, left_on=['d', 's'], right_on=['date', 'store'])처럼 사용하세요.

💡 다중 키 merge 후에는 키 열의 개수만큼 정렬하면 결과를 이해하기 쉽습니다. result.sort_values(['date', 'store'])로 정렬하세요.

💡 키가 3개 이상이면 성능이 저하될 수 있습니다. 가능하면 날짜+시간을 하나의 datetime으로 합치거나, 카테고리 인코딩을 사용해서 키 개수를 줄이는 게 좋습니다.


4. concat으로 데이터 쌓기

시작하며

여러분이 월별로 저장된 판매 데이터 12개 파일을 하나로 합치려고 할 때 이런 고민을 해본 적 있나요? "merge를 12번 써야 하나?", "반복문으로 하나씩 붙여야 하나?" 같은 의문 말이죠.

이런 상황은 데이터 분석에서 매일 발생합니다. 같은 구조의 데이터가 여러 파일로 분산되어 있거나, 시간에 따라 계속 추가되는 데이터를 통합해야 하는 경우가 많습니다.

merge는 이런 용도에 적합하지 않고, 반복문은 비효율적입니다. 바로 이럴 때 필요한 것이 concat 함수입니다.

같은 구조의 데이터프레임들을 위아래로(세로) 또는 옆으로(가로) 단순하게 붙이는 작업에 최적화되어 있습니다.

개요

간단히 말해서, concat은 여러 개의 데이터프레임을 한 방향으로 이어붙이는 함수입니다. 마치 레고 블록을 위아래로 쌓거나 옆으로 늘어놓는 것처럼 단순하고 직관적입니다.

왜 이게 필요한지 실무 예시를 들어볼까요? 1월부터 12월까지 매달 생성된 거래 내역 CSV 파일이 있다고 해봅시다.

각 파일은 '날짜, 고객ID, 금액' 같은 동일한 열 구조를 가지고 있습니다. 이 12개 파일을 concat으로 세로로 쌓으면, 1년 전체 데이터가 하나의 데이터프레임이 됩니다.

merge처럼 공통 키를 찾거나 매칭하는 복잡한 과정 없이요. 기존에는 빈 데이터프레임을 만들고 for 반복문으로 하나씩 append했다면, 이제는 리스트에 담아서 한 번에 concat으로 처리할 수 있습니다.

속도도 훨씬 빠르고 코드도 깔끔합니다. concat의 핵심 특징은 세 가지입니다.

첫째, axis=0(기본값)으로 세로 결합, axis=1로 가로 결합을 선택할 수 있습니다. 둘째, ignore_index=True로 인덱스를 새로 부여할 수 있습니다.

셋째, 열 이름이 다르면 자동으로 모든 열을 포함하고 없는 값은 NaN으로 채웁니다. 이러한 특징들이 데이터 통합 작업을 매우 유연하게 만들어줍니다.

코드 예제

import pandas as pd

# 1월 판매 데이터
jan_sales = pd.DataFrame({
    'date': ['2024-01-15', '2024-01-20'],
    'product': ['노트북', '마우스'],
    'amount': [1500000, 30000]
})

# 2월 판매 데이터
feb_sales = pd.DataFrame({
    'date': ['2024-02-03', '2024-02-18'],
    'product': ['키보드', '모니터'],
    'amount': [80000, 300000]
})

# 세로로 이어붙이기 (axis=0이 기본값)
# 같은 열 구조를 가진 데이터를 위아래로 쌓음
result = pd.concat([jan_sales, feb_sales], ignore_index=True)
print(result)
# 4개 행이 하나의 데이터프레임으로 통합됨

설명

이것이 하는 일: concat 함수는 여러 데이터프레임을 받아서 지정된 축(axis) 방향으로 단순하게 연결합니다. 공통 키를 찾거나 값을 비교하지 않고, 그냥 순서대로 이어붙이는 작업을 수행합니다.

첫 번째로, jan_sales와 feb_sales 두 개의 데이터프레임을 준비합니다. 둘 다 'date', 'product', 'amount' 세 개의 열을 가지고 있어서 구조가 완전히 동일합니다.

각각 2개 행씩 총 4개의 행이 있죠. 그 다음으로, pd.concat()에 리스트 형태로 두 데이터프레임을 전달합니다.

리스트이므로 2개든 10개든 100개든 원하는 만큼 추가할 수 있습니다. axis를 지정하지 않으면 기본값인 0(세로 방향)이 적용되어, 데이터프레임들이 위아래로 쌓입니다.

ignore_index=True 옵션은 매우 중요합니다. 이 옵션이 없으면 원본 데이터프레임의 인덱스(0, 1)가 그대로 유지되어 결과에 0, 1, 0, 1이 됩니다.

이렇게 되면 중복 인덱스가 생겨서 나중에 문제가 될 수 있어요. ignore_index=True를 사용하면 0, 1, 2, 3으로 새로운 인덱스가 부여됩니다.

마지막으로, 결합된 결과는 4개 행, 3개 열을 가진 하나의 통합된 데이터프레임이 됩니다. 1월 데이터 2개 행 다음에 2월 데이터 2개 행이 순서대로 쌓여 있습니다.

이제 전체 기간의 데이터를 하나의 분석 단위로 다룰 수 있습니다. 여러분이 이 함수를 사용하면 분산된 데이터를 빠르게 통합할 수 있습니다.

여러 소스에서 수집한 데이터, 시간별로 쌓이는 로그 데이터, 여러 지점의 데이터를 하나로 합치는 작업이 간단해집니다. 실무에서는 수십, 수백 개의 파일을 한 번에 concat하는 경우가 많습니다.

일별 로그 파일 365개를 연간 데이터로 통합하거나, 여러 지역의 판매 데이터를 전국 데이터로 통합할 때 필수적입니다.

실전 팁

💡 여러 파일을 concat할 때는 리스트 컴프리헨션을 활용하세요. dfs = [pd.read_csv(f) for f in file_list]; result = pd.concat(dfs, ignore_index=True) 이렇게 두 줄이면 수백 개 파일도 통합됩니다.

💡 대용량 데이터를 concat할 때는 ignore_index=True를 반드시 사용하세요. 인덱스 재배치 작업이 한 번에 처리되어 메모리 효율이 훨씬 좋습니다.

💡 열 이름이 조금씩 다른 데이터프레임들을 concat하면 모든 열이 포함되고 없는 값은 NaN이 됩니다. 통합 전에 df.columns로 열 이름을 확인하고 통일하세요.

💡 concat 후에는 중복 행이 생길 수 있습니다. result.drop_duplicates()로 중복을 제거하거나, 의도적인 중복인지 확인하세요.

💡 세로 concat(axis=0) 후에는 데이터 타입이 변할 수 있습니다. 한쪽은 int, 다른 쪽은 float면 결과가 float가 됩니다. 통합 전에 dtypes를 맞추는 게 안전합니다.


5. join 메서드 사용하기

시작하며

여러분이 merge와 비슷한데 살짝 다른 join이라는 메서드를 본 적 있나요? "이것도 데이터를 합치는 건데, merge와 뭐가 다르지?", "언제 merge 대신 join을 써야 하지?" 같은 궁금증을 가져본 적 있을 겁니다.

이런 혼란은 Pandas가 SQL의 용어를 차용하면서 생긴 부분입니다. join은 merge의 간소화 버전으로, 인덱스를 기준으로 결합하는 데 특화되어 있습니다.

같은 작업을 merge로도 할 수 있지만, 특정 상황에서는 join이 더 간결하고 직관적입니다. 바로 이럴 때 필요한 것이 join의 특성을 이해하는 것입니다.

언제 merge를 쓰고 언제 join을 쓸지 명확히 구분할 수 있으면, 상황에 맞는 최적의 도구를 선택할 수 있습니다.

개요

간단히 말해서, join은 데이터프레임의 인덱스를 기준으로 결합하는 메서드입니다. merge가 특정 열(column)을 키로 사용한다면, join은 인덱스(row label)를 키로 사용합니다.

왜 이게 유용한지 설명하겠습니다. 고객ID를 인덱스로 설정한 고객 정보 데이터프레임과, 역시 고객ID를 인덱스로 한 구매 내역 데이터프레임이 있다고 해볼까요?

merge를 쓰면 on='customer_id'라고 명시해야 하지만, join은 그냥 df1.join(df2)만 하면 자동으로 인덱스끼리 매칭됩니다. 기존에는 merge(left_index=True, right_index=True)처럼 긴 옵션을 써야 했다면, 이제는 간단하게 join()으로 같은 결과를 얻을 수 있습니다.

join의 핵심 특징은 인덱스 중심이라는 점입니다. 기본적으로 왼쪽 데이터프레임의 인덱스와 오른쪽 데이터프레임의 인덱스를 비교하여 결합합니다.

그리고 기본 조인 방식이 left join이라는 점도 merge(기본값 inner)와 다릅니다. 이러한 차이를 이해하면 코드가 훨씬 간결해집니다.

코드 예제

import pandas as pd

# 고객 정보 (customer_id를 인덱스로 설정)
customers = pd.DataFrame({
    'name': ['김철수', '이영희', '박민수'],
    'city': ['서울', '부산', '대구']
}, index=[101, 102, 103])  # 인덱스를 고객ID로 설정

# 구매 금액 (customer_id를 인덱스로 설정)
purchases = pd.DataFrame({
    'total_amount': [1500000, 800000, 200000]
}, index=[101, 102, 104])  # 인덱스를 고객ID로 설정

# 인덱스를 기준으로 join (기본값은 left join)
# customers의 모든 고객을 유지하고 purchases를 붙임
result = customers.join(purchases, how='left')
print(result)
# 박민수(103)는 구매 내역이 없어서 total_amount가 NaN
# 104번 고객은 고객 정보가 없어서 결과에 없음 (left join)

설명

이것이 하는 일: join 메서드는 호출한 데이터프레임(왼쪽)의 인덱스와 매개변수로 전달된 데이터프레임(오른쪽)의 인덱스를 비교하여, 일치하는 인덱스의 행들을 가로로 결합합니다. 첫 번째로, customers와 purchases 데이터프레임을 준비하는데, 중요한 점은 둘 다 의미 있는 인덱스를 가지고 있다는 것입니다.

일반적인 0, 1, 2 인덱스가 아니라 고객ID인 101, 102, 103을 인덱스로 설정했습니다. 이렇게 인덱스에 의미를 부여하면 join이 매우 유용해집니다.

그 다음으로, customers.join(purchases)를 호출합니다. 이때 Pandas는 양쪽의 인덱스를 비교합니다.

101, 102는 양쪽 모두에 있으므로 매칭되고, 103은 customers에만 있고, 104는 purchases에만 있습니다. 기본값이 left join이므로, customers의 모든 인덱스(101, 102, 103)가 결과에 포함됩니다.

101과 102는 purchases의 데이터가 매칭되어 total_amount 값이 채워지고, 103은 매칭되는 게 없어서 total_amount가 NaN이 됩니다. 104는 customers에 없으므로 결과에서 제외됩니다.

마지막으로, 결과 데이터프레임은 customers의 열(name, city)과 purchases의 열(total_amount)을 모두 가지게 됩니다. 인덱스는 101, 102, 103이 유지되고, 각 행은 해당 고객ID의 정보와 구매 금액을 함께 보여줍니다.

여러분이 join을 사용하면 인덱스 기반 데이터 구조를 효과적으로 활용할 수 있습니다. 시계열 데이터(날짜를 인덱스로), 계층적 데이터(멀티인덱스), 사전 형태의 데이터 등에서 특히 유용합니다.

실무에서는 set_index()로 원하는 열을 인덱스로 만든 후 join하는 패턴을 자주 사용합니다. merge보다 코드가 간결하고, 인덱스의 의미가 명확해서 가독성도 좋아집니다.

실전 팁

💡 join을 사용하기 전에 의미 있는 인덱스를 설정하세요. df.set_index('customer_id')로 원하는 열을 인덱스로 만들면 join이 훨씬 직관적이 됩니다.

💡 join의 기본값은 left join입니다. merge는 기본값이 inner라서 다릅니다. 이 차이를 모르면 예상치 못한 결과를 얻을 수 있으니 주의하세요.

💡 여러 데이터프레임을 한 번에 join할 수 있습니다. df1.join([df2, df3, df4])처럼 리스트로 전달하면 df1의 인덱스를 기준으로 모두 결합됩니다.

💡 열 이름이 충돌하면 merge처럼 자동으로 _x, _y 접미사가 붙습니다. lsuffix, rsuffix 매개변수로 원하는 접미사를 지정할 수 있습니다.

💡 join 후에는 reset_index()로 인덱스를 일반 열로 되돌릴 수 있습니다. 분석이 끝나고 결과를 저장할 때 유용합니다.


6. 열 이름이 다를 때 merge하기

시작하며

여러분이 실제 업무에서 데이터를 받다 보면 이런 짜증나는 상황을 자주 만나게 됩니다. 같은 고객ID인데 한쪽은 'customer_id', 다른 쪽은 'cust_id'로 되어 있거나, 한쪽은 '고객번호', 다른 쪽은 'CustomerID'처럼 완전히 다른 이름을 사용하는 경우 말이죠.

이런 문제는 데이터가 여러 팀, 여러 시스템에서 오는 실무 환경에서 매우 흔합니다. 열 이름을 통일하려고 rename을 쓰는 것도 방법이지만, 매번 이름을 바꾸는 건 번거롭고 원본 데이터를 수정하는 것도 꺼려집니다.

바로 이럴 때 필요한 것이 left_on과 right_on 매개변수입니다. 각 데이터프레임에서 서로 다른 이름의 열을 키로 지정하여 merge할 수 있습니다.

개요

간단히 말해서, left_on과 right_on은 양쪽 데이터프레임에서 이름이 다른 열을 각각 지정하여 merge할 수 있게 해주는 옵션입니다. on은 양쪽 이름이 같을 때 쓰고, left_on과 right_on은 이름이 다를 때 사용합니다.

왜 이게 필요한지 구체적인 예를 들어볼까요? 회사 내부 시스템에서는 '사원번호'라고 하는데, HR 외부 시스템에서는 'employee_id'라고 합니다.

두 시스템의 데이터를 결합할 때마다 rename으로 이름을 바꾸는 건 비효율적이고, 실수로 원본을 수정할 위험도 있습니다. 기존에는 df.rename(columns={'사원번호': 'employee_id'})를 먼저 실행하고 merge했다면, 이제는 left_on='사원번호', right_on='employee_id'로 원본을 건드리지 않고 바로 merge할 수 있습니다.

이 방식의 핵심 특징은 유연성과 안전성입니다. 원본 데이터프레임을 수정하지 않고도 결합이 가능하고, 나중에 두 열의 값이 정말 일치하는지 검증할 수도 있습니다.

단, 결과 데이터프레임에는 두 열이 모두 포함되므로, 필요 없는 열은 drop으로 제거해야 합니다.

코드 예제

import pandas as pd

# 내부 시스템: '고객번호' 사용
internal_data = pd.DataFrame({
    '고객번호': [1001, 1002, 1003],
    '등급': ['VIP', '일반', 'VIP']
})

# 외부 시스템: 'customer_id' 사용
external_data = pd.DataFrame({
    'customer_id': [1001, 1002, 1004],
    'email': ['kim@example.com', 'lee@example.com', 'park@example.com']
})

# 서로 다른 이름의 열을 각각 지정하여 merge
result = pd.merge(
    internal_data,
    external_data,
    left_on='고객번호',    # 왼쪽 데이터프레임의 키 열
    right_on='customer_id', # 오른쪽 데이터프레임의 키 열
    how='inner'
)
print(result)
# 결과에는 '고객번호'와 'customer_id' 두 열이 모두 포함됨
# 필요하면 result.drop(columns=['customer_id'])로 중복 제거

설명

이것이 하는 일: left_on과 right_on은 각각 왼쪽과 오른쪽 데이터프레임에서 비교할 열의 이름을 독립적으로 지정할 수 있게 해줍니다. Pandas는 이 두 열의 값을 비교하여 일치하는 행을 찾아 결합합니다.

첫 번째로, internal_data는 '고객번호'라는 한글 열 이름을, external_data는 'customer_id'라는 영문 열 이름을 사용합니다. 하지만 둘 다 같은 고객을 식별하는 ID입니다.

1001, 1002, 1003 같은 값은 양쪽에서 동일한 의미를 가집니다. 그 다음으로, pd.merge()에서 left_on='고객번호'와 right_on='customer_id'를 지정합니다.

Pandas는 이제 internal_data의 '고객번호' 열과 external_data의 'customer_id' 열을 비교합니다. 1001은 양쪽에 있고, 1002도 양쪽에 있지만, 1003은 internal에만 있고 1004는 external에만 있습니다.

inner join이므로 양쪽 모두에 있는 1001과 1002만 결과에 포함됩니다. 하지만 여기서 주의할 점은, 결과 데이터프레임에 '고객번호'와 'customer_id' 두 열이 모두 나타난다는 것입니다.

값은 같지만 이름이 다르기 때문에 Pandas가 두 개의 별도 열로 취급합니다. 마지막으로, 이 중복된 열을 어떻게 처리할지 결정해야 합니다.

보통은 result.drop(columns=['customer_id'])로 한쪽을 제거하거나, 두 열의 값이 정말 일치하는지 검증 후 제거합니다. 검증은 (result['고객번호'] == result['customer_id']).all()로 할 수 있습니다.

여러분이 이 기법을 사용하면 다양한 소스의 데이터를 유연하게 결합할 수 있습니다. 레거시 시스템과 신규 시스템, 한국어와 영어 시스템, 외부 파트너 데이터 등 명명 규칙이 다른 데이터들을 원본 수정 없이 통합할 수 있습니다.

실무에서는 데이터 검증 차원에서 의도적으로 두 열을 모두 유지하기도 합니다. merge 후 두 열의 값이 100% 일치하는지 확인하고, 불일치하는 행이 있으면 데이터 품질 문제로 보고하는 방식입니다.

실전 팁

💡 merge 후 두 키 열의 값이 정말 일치하는지 검증하세요. assert (result['고객번호'] == result['customer_id']).all()로 확인하면 데이터 품질 문제를 조기에 발견할 수 있습니다.

💡 여러 열을 키로 사용할 때도 left_on과 right_on에 리스트를 전달할 수 있습니다. left_on=['date', 'store'], right_on=['날짜', '매장']처럼 사용하세요.

💡 불필요한 키 열을 제거할 때는 merge 직후 바로 하는 게 좋습니다. result = pd.merge(...).drop(columns=['customer_id'])처럼 메서드 체이닝을 사용하세요.

💡 left_on과 right_on을 사용하면 결과의 열 순서가 예상과 다를 수 있습니다. 원하는 순서로 정렬하려면 result = result[['col1', 'col2', ...]]로 재정렬하세요.

💡 한글과 영문 열 이름이 섞여 있다면, 통일된 명명 규칙을 정하고 merge 후 rename하는 것을 고려하세요. 장기적으로 코드 유지보수가 훨씬 쉬워집니다.


7. concat으로 가로 결합하기

시작하며

여러분이 concat은 세로로 데이터를 쌓는 거라고 배웠는데, 가로로도 결합할 수 있다는 걸 아시나요? "그럼 merge와 뭐가 다른데?"라는 의문이 생길 수 있습니다.

이런 혼란은 concat의 axis 매개변수를 제대로 이해하지 못해서 생깁니다. axis=0은 세로(행 추가), axis=1은 가로(열 추가)인데, 가로 결합은 merge와 비슷하지만 중요한 차이가 있습니다.

concat은 인덱스를 기준으로 매칭하고, merge는 특정 열을 기준으로 매칭합니다. 바로 이럴 때 필요한 것이 concat의 axis=1 옵션입니다.

같은 인덱스를 가진 여러 데이터프레임의 열들을 옆으로 붙일 때 유용합니다.

개요

간단히 말해서, concat의 axis=1은 여러 데이터프레임을 인덱스를 기준으로 가로로 이어붙입니다. merge나 join과 비슷하지만, 여러 개(3개 이상)를 한 번에 결합할 때 더 편리합니다.

왜 이게 유용한지 예를 들어볼까요? 학생별 국어 점수, 수학 점수, 영어 점수가 각각 별도의 데이터프레임으로 되어 있다고 해봅시다.

세 개 모두 학생 이름을 인덱스로 가지고 있습니다. merge를 두 번 쓸 수도 있지만, concat([국어, 수학, 영어], axis=1)로 한 번에 결합할 수 있습니다.

기존에는 df1.merge(df2).merge(df3)처럼 여러 번 merge를 체인으로 연결했다면, 이제는 concat으로 간결하게 처리할 수 있습니다. 가로 concat의 핵심 특징은 인덱스 기반 매칭입니다.

인덱스 값이 같은 행끼리 옆으로 붙고, 한쪽에만 있는 인덱스는 outer join처럼 NaN으로 채워집니다(기본값). join='inner' 옵션으로 inner join처럼 동작하게 할 수도 있습니다.

코드 예제

import pandas as pd

# 학생별 국어 점수 (학생 이름이 인덱스)
korean = pd.DataFrame({
    'korean': [85, 90, 78]
}, index=['김철수', '이영희', '박민수'])

# 학생별 수학 점수
math = pd.DataFrame({
    'math': [92, 88, 95]
}, index=['김철수', '이영희', '박민수'])

# 학생별 영어 점수
english = pd.DataFrame({
    'english': [88, 94, 82]
}, index=['김철수', '이영희', '정수진'])  # 정수진은 다른 학생

# axis=1로 가로 결합 (인덱스를 기준으로 매칭)
result = pd.concat([korean, math, english], axis=1)
print(result)
# 김철수, 이영희, 박민수, 정수진 모두 포함
# 정수진은 국어, 수학 점수가 NaN
# 박민수는 영어 점수가 NaN

설명

이것이 하는 일: concat의 axis=1 옵션은 여러 데이터프레임의 인덱스를 비교하여 같은 인덱스 값을 가진 행끼리 가로로 붙이고, 한쪽에만 있는 인덱스는 NaN으로 채워서 모두 포함합니다(outer join 방식). 첫 번째로, korean, math, english 세 개의 데이터프레임을 준비합니다.

각각 하나의 열(점수)만 가지고 있고, 학생 이름이 인덱스로 설정되어 있습니다. 중요한 점은 완전히 일치하지 않는다는 것입니다.

박민수는 english에 없고, 정수진은 korean과 math에 없습니다. 그 다음으로, pd.concat()에 세 개의 데이터프레임을 리스트로 전달하고 axis=1을 지정합니다.

axis=0이 기본값(세로)이므로, 가로로 붙이려면 반드시 axis=1을 명시해야 합니다. Pandas는 이제 각 데이터프레임의 인덱스를 모아서 합집합을 만듭니다.

인덱스 합집합은 '김철수', '이영희', '박민수', '정수진'입니다. 이 네 명이 결과의 행이 되고, 각 데이터프레임의 열들이 옆으로 붙습니다.

korean 열, math 열, english 열이 순서대로 추가됩니다. 마지막으로, 매칭되지 않는 부분은 NaN으로 채워집니다.

정수진은 korean과 math 데이터프레임에 없으므로 해당 열 값이 NaN이 되고, 박민수는 english 데이터프레임에 없으므로 english 열이 NaN이 됩니다. 이렇게 모든 학생과 모든 과목 점수를 하나의 표로 볼 수 있습니다.

여러분이 이 방식을 사용하면 여러 소스의 데이터를 빠르게 통합할 수 있습니다. 특히 3개 이상의 데이터프레임을 결합할 때 merge를 반복하는 것보다 훨씬 간결하고 효율적입니다.

실무에서는 시계열 데이터를 결합할 때 자주 사용합니다. 날짜를 인덱스로 한 여러 지표(매출, 방문자 수, 전환율 등)를 하나의 데이터프레임으로 통합하여 분석하는 경우가 많습니다.

실전 팁

💡 join='inner' 옵션을 추가하면 모든 데이터프레임에 공통으로 있는 인덱스만 결과에 포함됩니다. NaN 없이 완전한 데이터만 원할 때 유용합니다.

💡 열 이름이 중복되면 자동으로 접미사가 붙습니다. keys 매개변수로 의미 있는 접두사를 지정할 수 있습니다. concat([df1, df2], axis=1, keys=['A', 'B'])처럼 사용하세요.

💡 대량의 데이터프레임을 가로로 concat할 때는 메모리 사용량에 주의하세요. 인덱스 합집합이 매우 커지면 NaN이 많아져서 메모리가 낭비됩니다.

💡 concat 후 NaN이 너무 많으면 dropna()로 불완전한 행을 제거하거나, fillna()로 기본값을 채우세요. 분석 목적에 따라 적절한 처리가 필요합니다.

💡 시계열 데이터를 가로로 concat할 때는 인덱스가 datetime 타입인지 확인하세요. pd.to_datetime()으로 변환하고 정렬하면 시간순으로 깔끔하게 정리됩니다.


8. merge의 validate 옵션으로 관계 검증하기

시작하며

여러분이 merge를 실행했는데 결과 행 수가 원본보다 훨씬 많아져서 당황한 적 있나요? "분명 100개 행이었는데 merge 후 1000개가 되었네?"라는 상황 말이죠.

이건 버그가 아니라 데이터 관계를 제대로 파악하지 못해서 발생하는 문제입니다. 이런 문제는 다대다(many-to-many) 관계에서 발생합니다.

한쪽에 중복 키가 있고 다른 쪽에도 중복 키가 있으면, 모든 조합이 생성되어 행이 폭발적으로 증가합니다. 예를 들어, 키 값 'A'가 왼쪽에 3개, 오른쪽에 4개 있으면 3×4=12개의 행이 생깁니다.

바로 이럴 때 필요한 것이 validate 매개변수입니다. merge 실행 전에 데이터 관계(일대일, 일대다 등)를 검증하여, 예상치 못한 중복이나 관계 오류를 미리 발견할 수 있습니다.

개요

간단히 말해서, validate는 merge 실행 전에 양쪽 데이터프레임의 키 관계를 검증하는 옵션입니다. '1:1', '1:m', 'm:1', 'm:m' 중 하나를 지정하면, 실제 데이터가 그 관계를 만족하는지 확인합니다.

왜 이게 중요한지 실무 관점에서 설명하겠습니다. 고객 테이블(customer_id가 유일)과 주문 테이블(customer_id가 중복 가능)을 merge한다고 해봅시다.

이건 명백히 1:m(일대다) 관계입니다. validate='1:m'을 지정하면, 만약 고객 테이블에 같은 customer_id가 두 번 나타나면 에러가 발생하여 데이터 품질 문제를 즉시 알 수 있습니다.

기존에는 merge 후 행 수를 확인하고 "이상한데?"라고 생각하며 디버깅했다면, 이제는 validate로 사전에 검증하여 문제를 예방할 수 있습니다. validate의 핵심 특징은 네 가지 관계 유형을 지원한다는 것입니다.

'1:1'(양쪽 모두 유일), '1:m'(왼쪽 유일, 오른쪽 중복 가능), 'm:1'(왼쪽 중복 가능, 오른쪽 유일), 'm:m'(양쪽 모두 중복 가능)입니다. 잘못된 관계가 감지되면 MergeError가 발생하여 merge가 중단됩니다.

코드 예제

import pandas as pd

# 고객 정보 (customer_id는 유일해야 함)
customers = pd.DataFrame({
    'customer_id': [1, 2, 3],
    'name': ['김철수', '이영희', '박민수']
})

# 주문 정보 (한 고객이 여러 주문 가능)
orders = pd.DataFrame({
    'customer_id': [1, 1, 2, 3, 3, 3],
    'product': ['노트북', '마우스', '키보드', '모니터', '스피커', '헤드셋'],
    'amount': [1500000, 30000, 80000, 300000, 50000, 70000]
})

# validate='1:m'으로 관계 검증
# customers의 customer_id는 유일, orders는 중복 가능
result = pd.merge(
    customers,
    orders,
    on='customer_id',
    validate='1:m'  # 일대다 관계 검증
)
print(result)
# 검증 통과하고 정상적으로 merge됨
# 만약 customers에 중복 customer_id가 있었다면 에러 발생

설명

이것이 하는 일: validate 매개변수는 merge를 실행하기 전에 각 데이터프레임의 키 열에서 중복 여부를 검사하여, 지정된 관계 유형(1:1, 1:m, m:1, m:m)과 일치하는지 확인합니다. 불일치하면 MergeError를 발생시켜 merge를 중단합니다.

첫 번째로, customers 데이터프레임은 customer_id가 1, 2, 3으로 모두 유일합니다. 각 고객은 한 번씩만 나타납니다.

반면 orders 데이터프레임은 customer_id가 1이 2번, 3이 3번 나타나서 중복이 있습니다. 이는 전형적인 일대다 관계입니다.

그 다음으로, validate='1:m'을 지정하여 merge를 실행합니다. Pandas는 merge를 수행하기 전에 먼저 검증을 실행합니다.

왼쪽(customers)의 customer_id를 확인하여 중복이 없는지(일) 체크하고, 오른쪽(orders)은 중복이 있어도 괜찮습니다(다). 검증이 통과되면 정상적으로 merge가 진행됩니다.

고객 1번의 정보가 주문 2개와 결합되어 2개 행이 되고, 고객 3번은 주문 3개와 결합되어 3개 행이 됩니다. 총 6개 행이 결과로 나오는데, 이는 주문 수와 정확히 일치합니다.

만약 customers에 customer_id 1이 두 번 나타났다면 어떻게 될까요? validate='1:m'은 왼쪽이 유일해야 한다는 조건인데, 실제로는 중복이 있으니 MergeError가 발생합니다.

"왼쪽 데이터프레임에 중복 키가 있습니다"라는 명확한 에러 메시지와 함께 merge가 중단되어, 데이터 품질 문제를 즉시 알 수 있습니다. 여러분이 validate를 사용하면 "왜 행이 이상하게 늘어났지?"같은 문제를 사전에 방지할 수 있습니다.

특히 프로덕션 환경에서 주기적으로 실행되는 데이터 파이프라인에서는 필수적인 안전장치입니다. 실무에서는 항상 validate를 사용하는 것을 권장합니다.

처음에는 귀찮게 느껴질 수 있지만, 한 번이라도 이상한 중복 데이터로 인한 버그를 경험하면 그 가치를 알게 됩니다.

실전 팁

💡 프로덕션 코드에서는 항상 validate를 사용하세요. 개발 중에는 귀찮아도, 실제 운영에서는 데이터 품질 문제를 조기 발견하는 데 매우 중요합니다.

💡 관계 유형을 모르겠으면 먼저 df['key'].duplicated().any()로 각각 중복 여부를 확인하세요. 양쪽 모두 중복 없으면 1:1, 한쪽만 중복이면 1:m 또는 m:1입니다.

💡 validate 에러가 발생하면 에러 메시지에 중복된 키 값이 표시됩니다. 그 값으로 필터링하여 df[df['key']=='중복값']으로 문제를 정확히 파악하세요.

💡 다대다(m:m) 관계는 대부분 의도하지 않은 상황입니다. m:m이 필요하다면 정말 그게 맞는지 다시 생각해보고, 맞다면 명시적으로 validate='m:m'을 지정하세요.

💡 validate는 성능에 약간의 오버헤드가 있지만 무시할 수준입니다. 안전성이 훨씬 중요하므로 대부분의 경우 사용하는 것이 좋습니다.


9. indicator로 merge 결과 추적하기

시작하며

여러분이 merge 후에 이런 궁금증을 가져본 적 있나요? "이 행은 원래 어느 데이터프레임에서 왔지?", "양쪽 모두에 있던 건지, 한쪽에만 있던 건지 어떻게 알지?" 특히 outer join을 사용했을 때 이런 의문이 생깁니다.

이런 문제는 데이터 품질 검증이나 디버깅할 때 매우 중요합니다. 예를 들어, 고객 마스터와 실제 거래 데이터를 merge했는데, "마스터에는 있지만 거래가 없는 고객"이나 "거래는 있는데 마스터에 없는 고객"을 찾고 싶을 때가 있습니다.

바로 이럴 때 필요한 것이 indicator 매개변수입니다. merge 결과에 '_merge'라는 특별한 열을 추가하여 각 행이 어디서 왔는지 명확하게 표시해줍니다.

개요

간단히 말해서, indicator=True는 merge 결과에 '_merge'라는 열을 추가합니다. 이 열은 'left_only'(왼쪽에만 있음), 'right_only'(오른쪽에만 있음), 'both'(양쪽 모두 있음) 세 가지 값 중 하나를 가집니다.

왜 이게 유용한지 구체적으로 설명하겠습니다. 고객 데이터베이스와 이번 달 활동 로그를 outer join으로 합쳤다고 해봅시다.

indicator를 사용하면 "등록은 했지만 활동이 없는 고객"(left_only), "활동 로그는 있는데 고객 정보가 없는 이상한 케이스"(right_only), "정상적인 활동 고객"(both)을 한눈에 구분할 수 있습니다. 기존에는 merge 전후의 인덱스를 비교하거나 NaN을 체크하는 복잡한 방법을 썼다면, 이제는 '_merge' 열 하나로 모든 정보를 얻을 수 있습니다.

indicator의 핵심 특징은 명확성과 추적성입니다. 각 행의 출처를 명확히 알 수 있고, 이를 기반으로 필터링, 집계, 보고가 쉬워집니다.

그리고 indicator='custom_name'으로 열 이름을 원하는 대로 지정할 수도 있습니다.

코드 예제

import pandas as pd

# 고객 마스터 데이터
customers = pd.DataFrame({
    'customer_id': [1, 2, 3, 4],
    'name': ['김철수', '이영희', '박민수', '정수진']
})

# 이번 달 구매 고객
purchases = pd.DataFrame({
    'customer_id': [2, 3, 5],
    'amount': [50000, 80000, 30000]
})

# indicator=True로 출처 추적
result = pd.merge(
    customers,
    purchases,
    on='customer_id',
    how='outer',
    indicator=True  # '_merge' 열 추가
)
print(result)
print("\n양쪽 모두 있는 고객:")
print(result[result['_merge'] == 'both'])
print("\n등록만 하고 구매 안 한 고객:")
print(result[result['_merge'] == 'left_only'])
print("\n고객 정보 없는 구매 (데이터 이상):")
print(result[result['_merge'] == 'right_only'])

설명

이것이 하는 일: indicator 옵션은 merge 실행 시 각 행이 왼쪽 데이터프레임에서만 왔는지, 오른쪽에서만 왔는지, 양쪽에서 매칭되어 왔는지를 나타내는 카테고리형 열을 결과에 추가합니다. 첫 번째로, customers에는 1, 2, 3, 4번 고객이 있고, purchases에는 2, 3, 5번 고객이 있습니다.

outer join을 사용하므로 1, 2, 3, 4, 5번이 모두 결과에 포함됩니다. 그 다음으로, indicator=True를 지정하여 merge를 실행하면, Pandas는 각 행의 출처를 분석합니다.

2번과 3번 고객은 양쪽 데이터프레임에 모두 있으므로 '_merge' 열에 'both'가 기록됩니다. 1번과 4번은 customers에만 있으므로 'left_only', 5번은 purchases에만 있으므로 'right_only'가 됩니다.

결과 데이터프레임을 보면 '_merge' 열이 추가되어 있고, 각 행마다 'both', 'left_only', 'right_only' 중 하나의 값을 가집니다. 이 정보를 활용하여 다양한 분석이 가능합니다.

마지막으로, '_merge' 열로 필터링하면 원하는 케이스만 추출할 수 있습니다. result[result['_merge'] == 'left_only']는 고객 정보는 있지만 이번 달 구매가 없는 고객을 반환합니다.

이들에게 프로모션 이메일을 보낼 수 있겠죠. result[result['_merge'] == 'right_only']는 데이터 이상 케이스(고객 정보 없는 구매)를 찾아 조사할 수 있습니다.

여러분이 indicator를 사용하면 데이터 품질 관리와 비즈니스 분석이 훨씬 쉬워집니다. 어떤 데이터가 매칭되고 매칭되지 않았는지 명확히 파악할 수 있어서, 데이터 파이프라인의 신뢰성을 높일 수 있습니다.

실무에서는 outer join과 indicator를 함께 사용하여 데이터 불일치를 찾는 경우가 매우 많습니다. 마스터 데이터와 트랜잭션 데이터의 정합성 검증, 외부 데이터와 내부 데이터의 매칭률 분석 등에 필수적입니다.

실전 팁

💡 indicator='source'처럼 원하는 열 이름을 지정할 수 있습니다. '_merge'보다 의미 있는 이름을 사용하면 코드 가독성이 좋아집니다.

💡 분석이 끝나면 result.drop(columns=['_merge'])로 indicator 열을 제거하세요. 최종 결과물에는 필요 없는 경우가 많습니다.

💡 '_merge' 열의 값 분포를 확인하려면 result['_merge'].value_counts()를 사용하세요. 몇 개가 매칭되고 안 되었는지 한눈에 파악할 수 있습니다.

💡 데이터 품질 보고서를 만들 때 indicator는 필수입니다. 매칭률, 누락률, 이상 케이스 수를 계산하여 정기적으로 모니터링하세요.

💡 inner join에서도 indicator를 사용할 수 있지만 모든 값이 'both'가 되므로 의미가 없습니다. outer나 left/right join에서만 유용합니다.


10. suffixes로 중복 열 이름 관리하기

시작하며

여러분이 merge를 실행했는데 결과에 'amount_x'와 'amount_y' 같은 이상한 열 이름이 생긴 적 있나요? "왜 내가 지정하지도 않은 _x, _y가 붙었지?"라는 의문이 들 수 있습니다.

이런 현상은 양쪽 데이터프레임에 같은 이름의 열이 있을 때 발생합니다. 키 열이 아닌데 이름이 같으면 Pandas는 자동으로 '_x'(왼쪽)와 '_y'(오른쪽)를 붙여서 구분합니다.

기본 접미사가 직관적이지 않아서 나중에 코드를 읽을 때 혼란스러울 수 있습니다. 바로 이럴 때 필요한 것이 suffixes 매개변수입니다.

기본 '_x', '_y' 대신 의미 있는 접미사를 직접 지정하여 코드의 가독성을 높일 수 있습니다.

개요

간단히 말해서, suffixes는 merge 시 중복되는 열 이름에 붙일 접미사 쌍을 지정하는 옵션입니다. 기본값은 ('_x', '_y')이지만, ('_left', '_right')나 ('_2023', '_2024') 같은 의미 있는 값으로 변경할 수 있습니다.

왜 이게 필요한지 실무 예를 들어보겠습니다. 2023년 판매 데이터와 2024년 판매 데이터를 제품별로 merge하여 비교한다고 해봅시다.

둘 다 'amount'라는 열을 가지고 있습니다. suffixes=('_2023', '_2024')로 지정하면 'amount_2023'과 'amount_2024'로 명확하게 구분되어, 나중에 코드를 볼 때 바로 이해할 수 있습니다.

기존에는 merge 후 rename으로 열 이름을 다시 바꾸는 번거로운 작업을 했다면, 이제는 suffixes로 한 번에 원하는 이름을 지정할 수 있습니다. suffixes의 핵심 특징은 가독성 향상입니다.

의미 있는 접미사를 사용하면 '_x'가 무엇을 의미하는지 머릿속으로 계산할 필요 없이, 열 이름만 봐도 내용을 알 수 있습니다. 그리고 빈 문자열('')을 사용하여 한쪽에는 접미사를 붙이지 않을 수도 있습니다.

코드 예제

import pandas as pd

# 2023년 제품별 판매액
sales_2023 = pd.DataFrame({
    'product': ['노트북', '마우스', '키보드'],
    'amount': [1500000, 30000, 80000]
})

# 2024년 제품별 판매액
sales_2024 = pd.DataFrame({
    'product': ['노트북', '마우스', '모니터'],
    'amount': [1800000, 35000, 300000]
})

# 의미 있는 접미사 지정
result = pd.merge(
    sales_2023,
    sales_2024,
    on='product',
    how='outer',
    suffixes=('_2023', '_2024')  # 기본 '_x', '_y' 대신 연도 사용
)
print(result)
# amount_2023, amount_2024로 명확하게 구분됨
# 전년 대비 증가율 계산: result['growth'] = (result['amount_2024'] - result['amount_2023']) / result['amount_2023']

설명

이것이 하는 일: suffixes 매개변수는 merge 시 양쪽 데이터프레임에 같은 이름의 열(키 열 제외)이 있을 때, 각각에 붙일 접미사를 튜플로 지정합니다. 첫 번째 요소는 왼쪽, 두 번째는 오른쪽 데이터프레임에 적용됩니다.

첫 번째로, sales_2023과 sales_2024 모두 'product'와 'amount' 열을 가지고 있습니다. 'product'는 merge 키로 사용되므로 결과에 하나만 나타나지만, 'amount'는 키가 아니므로 양쪽 모두 유지되어야 합니다.

그 다음으로, suffixes=('_2023', '_2024')를 지정합니다. Pandas는 merge를 수행하면서 중복된 'amount' 열을 발견하고, 왼쪽(sales_2023)에서 온 것은 'amount_2023', 오른쪽(sales_2024)에서 온 것은 'amount_2024'로 이름을 변경합니다.

결과 데이터프레임을 보면 'product', 'amount_2023', 'amount_2024' 세 개의 열이 있습니다. 노트북의 경우 2023년에 1,500,000원, 2024년에 1,800,000원으로 명확하게 구분됩니다.

키보드는 2023년에만 있어서 amount_2024가 NaN이고, 모니터는 2024년에만 있어서 amount_2023이 NaN입니다. 마지막으로, 이렇게 명확한 열 이름 덕분에 후속 분석이 쉬워집니다.

result['growth'] = (result['amount_2024'] - result['amount_2023']) / result['amount_2023'] * 100처럼 전년 대비 성장률을 계산할 때, 어떤 열이 어느 연도인지 헷갈릴 일이 없습니다. 여러분이 suffixes를 적극 활용하면 코드의 자기 문서화(self-documenting)가 가능해집니다.

6개월 후에 다시 코드를 봐도, 또는 다른 팀원이 봐도 '_2023'이 무엇을 의미하는지 바로 알 수 있습니다. 실무에서는 시간 비교(연도, 분기, 월), 버전 비교(v1, v2), 소스 비교(actual, plan) 등 다양한 상황에서 의미 있는 suffixes를 사용합니다.

특히 정기 보고서 생성 코드에서는 가독성이 매우 중요하므로 필수적입니다.

실전 팁

💡 한쪽에는 접미사를 붙이지 않으려면 빈 문자열을 사용하세요. suffixes=('', '_previous')처럼 하면 현재 데이터는 'amount', 이전 데이터는 'amount_previous'가 됩니다.

💡 접미사는 밑줄로 시작하는 게 관례입니다. '_2023'이 '2023'보다 열 이름으로 더 자연스럽고, 나중에 처리할 때도 편합니다.

💡 여러 번 merge를 체인으로 연결할 때는 각 단계마다 다른 suffixes를 사용하세요. 그렇지 않으면 '_x_x' 같은 복잡한 이름이 생길 수 있습니다.

💡 suffixes를 사용하더라도 가능하면 merge 전에 열 이름을 미리 정리하는 게 좋습니다. df.rename(columns={'amount': 'amount_2023'})처럼 사전에 변경하면 더 명확합니다.

💡 suffixes 설정을 잊어버렸다가 나중에 '_x'와 '_y'를 보고 당황하지 마세요. rename으로 사후에 수정할 수도 있지만, 처음부터 설정하는 게 가장 깔끔합니다.


#Python#Pandas#merge#join#concat#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

데이터 증강과 정규화 완벽 가이드

머신러닝 모델의 성능을 극대화하는 핵심 기법인 데이터 증강과 정규화에 대해 알아봅니다. 실무에서 바로 활용할 수 있는 다양한 기법과 실전 예제를 통해 과적합을 방지하고 모델 성능을 향상시키는 방법을 배웁니다.

ResNet과 Skip Connection 완벽 가이드

딥러닝 모델이 깊어질수록 성능이 떨어지는 문제를 해결한 혁신적인 기법, ResNet과 Skip Connection을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실제 구현 코드와 함께 배워보세요.

CNN 아키텍처 완벽 가이드 LeNet AlexNet VGGNet

컴퓨터 비전의 기초가 되는 세 가지 핵심 CNN 아키텍처를 배웁니다. 손글씨 인식부터 이미지 분류까지, 딥러닝의 발전 과정을 따라가며 각 모델의 구조와 특징을 실습 코드와 함께 이해합니다.

CNN 기초 Convolution과 Pooling 완벽 가이드

CNN의 핵심인 Convolution과 Pooling을 초급자도 쉽게 이해할 수 있도록 설명합니다. 이미지 인식의 원리부터 실제 코드 구현까지, 실무에서 바로 활용 가능한 내용을 담았습니다.

TensorFlow와 Keras 완벽 입문 가이드

머신러닝과 딥러닝의 세계로 들어가는 첫걸음! TensorFlow와 Keras 프레임워크를 처음 접하는 분들을 위한 친절한 가이드입니다. 실무에서 바로 활용할 수 있는 핵심 개념과 예제를 통해 AI 모델 개발의 기초를 탄탄히 다져보세요.