Pandas로 데이터프레임을 병합하는 가장 효율적인 방법

Pandas로 데이터프레임을 병합하는 가장 효율적인 방법

복잡한 데이터 분석 환경에서는 여러 개의 데이터 소스를 하나로 통합하는 일이 자주 발생합니다. 이때 핵심이 되는 도구가 바로 Pandas의 병합 기능입니다. 다양한 형식과 구조를 가진 데이터를 효율적으로 연결하고 결합하기 위해서는 merge, join, concat을 적절히 활용할 줄 알아야 합니다. 이 글에서는 각 병합 기법의 원리와 차이점을 실무 예시와 함께 차근차근 살펴봅니다.


목차


1. 왜 ‘데이터프레임 병합’은 실무에서 중요한가?

기업에서 다루는 데이터는 단일 소스에서 오지 않습니다. 마케팅팀은 고객 정보를 보유하고 있고, 영업팀은 판매 데이터를 관리하며, 고객 서비스팀은 피드백 기록을 저장하고 있습니다. 이처럼 각기 다른 소스에서 수집된 데이터를 하나의 분석 가능한 구조로 만들기 위해서는 병합(Merge)이라는 과정이 반드시 필요합니다.

엑셀로 데이터를 수작업으로 복사-붙여넣기 하는 방식은 데이터의 양이 많아질수록 불가능에 가까워집니다. 실시간 데이터 분석, 자동화된 리포팅, 머신러닝 모델링 등 현대 데이터 워크플로우는 모두 병합 작업의 정확성과 효율성에 크게 의존합니다. 이때 Python의 Pandas 라이브러리는 데이터프레임 간 병합을 위한 강력한 메서드들을 제공합니다.

특히, Pandas의 merge, join, concat은 SQL에서 테이블을 조인하는 개념과 유사해 데이터베이스 경험이 있는 사람이라면 더 직관적으로 이해할 수 있습니다. 반대로 Pandas를 먼저 접한 사람은 SQL 조인도 더욱 쉽게 이해할 수 있죠.

본격적인 실습에 앞서, 이 글에서는 단순한 문법 나열을 넘어서 실무에서 흔히 발생하는 병합 상황과 그 해결책까지 함께 제시할 것입니다. 잘못된 병합으로 인한 데이터 누락이나 중복, 형식 불일치를 방지하는 방법까지 다룰 예정이니 끝까지 집중해 주세요.

import pandas as pd

# 고객 정보
customers = pd.DataFrame({
    'customer_id': [1, 2, 3],
    'name': ['홍길동', '이영희', '김철수']
})

# 구매 이력
orders = pd.DataFrame({
    'order_id': [100, 101, 102],
    'customer_id': [1, 2, 1],
    'amount': [25000, 30000, 18000]
})

# 고객 ID를 기준으로 병합
merged = pd.merge(customers, orders, on='customer_id', how='inner')
print(merged)

위 예시는 앞으로 다룰 여러 병합 기법의 출발점이 됩니다. 이제 각 메서드가 어떤 상황에서, 어떻게 사용되는지 차례로 탐색해보겠습니다.


2. 데이터프레임 병합의 핵심 개념 정리

본격적인 메서드 학습에 앞서, Pandas에서 제공하는 주요 병합 도구들의 개념을 분명히 정리하고 넘어가는 것이 중요합니다. 이름은 다르지만 이들 메서드는 공통적으로 여러 데이터프레임을 합쳐 하나의 구조로 만드는 데 사용됩니다. 각기 다른 목적과 방식이 존재하므로, 잘 구분하여 사용해야 합니다.

2.1 merge()

merge()는 SQL의 JOIN과 유사한 방식으로, 공통된 열(또는 인덱스)을 기준으로 데이터를 병합합니다. 다양한 병합 유형(inner, left, right, outer)을 선택할 수 있고, 병합 조건을 세밀하게 설정할 수 있습니다.

2.2 join()

join()은 주로 인덱스를 기준으로 병합할 때 사용되며, merge()보다 코드가 간결한 편입니다. 단, 열 기준 병합이 필요한 경우에는 사용에 제한이 따릅니다.

2.3 concat()

concat()은 단순히 여러 데이터프레임을 이어 붙이는 방식입니다. 행 방향 또는 열 방향으로 데이터를 연결하며, 키나 조건 없이 단순한 병합이 필요한 경우 유용합니다.

2.4 관계형 데이터베이스와의 개념 비교

Pandas 메서드 유사 SQL 개념 특징
merge() JOIN (INNER, OUTER 등) 열 또는 인덱스를 기준으로 조건 기반 병합
join() LEFT JOIN (인덱스 기반) 주로 인덱스를 기준으로 병합, 간결한 문법
concat() UNION / UNION ALL 조건 없이 단순하게 이어붙이기

이러한 개념적 차이를 이해하면, 어떤 병합 방식이 현재 상황에 가장 적합한지를 빠르게 판단할 수 있습니다. 단순히 ‘병합이 가능하다’는 것만으로는 부족합니다. 병합 이후의 데이터 구조, 누락 위험, 성능 등을 고려한 설계가 병합 기술의 핵심입니다.

import pandas as pd

df1 = pd.DataFrame({'ID': [1, 2, 3], 'Name': ['A', 'B', 'C']})
df2 = pd.DataFrame({'ID': [2, 3, 4], 'Score': [85, 90, 78]})

# SQL의 INNER JOIN과 같은 방식
result = pd.merge(df1, df2, on='ID', how='inner')
print(result)

이처럼 Pandas의 병합 방식은 SQL에 익숙한 사용자라면 더욱 강력하게 다가올 수 있으며, 반대로 이 개념을 통해 SQL 기초를 간접적으로 익힐 수도 있습니다. 다음 섹션에서는 이러한 개념을 바탕으로 가장 많이 사용되는 merge()에 대해 실전과 이론을 함께 다루겠습니다.


3. merge()를 활용한 데이터 병합의 모든 것

merge()는 Pandas에서 가장 유연하고 자주 사용되는 병합 메서드입니다. SQL의 JOIN 문법과 거의 유사하게 사용할 수 있으며, 병합 기준이 되는 열을 지정하고 다양한 병합 방식(inner, left, right, outer)을 적용할 수 있습니다. 실무에서는 이 메서드를 얼마나 능숙하게 다루느냐가 데이터 처리의 품질을 좌우합니다.

3.1 기본 사용법

가장 간단한 merge() 사용법은 공통 열 이름이 있는 두 데이터프레임을 병합하는 것입니다. 이때 on 인자를 명시적으로 적지 않아도 자동으로 같은 이름의 열이 병합 기준이 됩니다.

df1 = pd.DataFrame({'ID': [1, 2, 3], 'Name': ['Alice', 'Bob', 'Charlie']})
df2 = pd.DataFrame({'ID': [2, 3, 4], 'Score': [88, 92, 75]})

result = pd.merge(df1, df2)
print(result)

공통 열 ID를 기준으로 병합되며, 기본 방식은 inner join입니다. 즉, 양쪽 모두에 존재하는 값만 결과에 포함됩니다.

3.2 다양한 병합 방식

how 파라미터는 병합 방식의 핵심입니다. 지원하는 옵션은 다음과 같습니다.

  • inner: 교집합 (기본값)
  • outer: 합집합 (누락된 값은 NaN으로 채움)
  • left: 왼쪽 데이터프레임 기준
  • right: 오른쪽 데이터프레임 기준
# outer join
pd.merge(df1, df2, on='ID', how='outer')

# left join
pd.merge(df1, df2, on='ID', how='left')

# right join
pd.merge(df1, df2, on='ID', how='right')

3.3 기준 열이 다른 경우

서로 다른 이름의 열을 기준으로 병합해야 할 경우 left_on, right_on을 사용합니다.

employees = pd.DataFrame({'emp_id': [1, 2, 3], 'name': ['Kim', 'Lee', 'Park']})
salaries = pd.DataFrame({'id': [1, 2, 4], 'salary': [5000, 6000, 7000]})

result = pd.merge(employees, salaries, left_on='emp_id', right_on='id', how='left')
print(result)

이처럼 열 이름이 다를 때는 명시적으로 어떤 열을 기준으로 할 것인지 지정해 주어야 합니다.

3.4 실전 예시: 고객과 주문 내역 병합

아래는 고객 정보와 주문 내역을 병합하는 실무 예시입니다. 고객이 존재하지만 주문을 하지 않았거나, 주문 기록에만 존재하는 고객이 있을 수 있습니다. 따라서 how 옵션 선택이 중요한 전략이 됩니다.

customers = pd.DataFrame({
    'cust_id': [101, 102, 103, 104],
    'name': ['정우성', '이정재', '김혜수', '전지현']
})

orders = pd.DataFrame({
    'order_id': [9001, 9002, 9003],
    'cust_id': [101, 103, 105],
    'product': ['노트북', '핸드폰', '태블릿']
})

# 고객 기준으로 모든 정보를 유지하는 left join
merged = pd.merge(customers, orders, on='cust_id', how='left')
print(merged)

결과적으로 주문을 하지 않은 고객(이정재, 전지현)은 product 열이 NaN으로 표시되며 유지됩니다. 이는 분석 과정에서 ‘주문하지 않은 고객’을 파악할 때 매우 유용합니다.

3.5 다중 키 병합

두 개 이상의 열을 기준으로 병합하는 것도 가능합니다. 이 경우 on 파라미터에 리스트를 전달하면 됩니다.

df1 = pd.DataFrame({
    'year': [2023, 2023, 2024],
    'quarter': ['Q1', 'Q2', 'Q1'],
    'value': [100, 150, 200]
})

df2 = pd.DataFrame({
    'year': [2023, 2024],
    'quarter': ['Q1', 'Q1'],
    'growth': [1.1, 1.3]
})

merged = pd.merge(df1, df2, on=['year', 'quarter'], how='left')
print(merged)

복수 키 병합은 기간별 통계, 지역별 매출 등의 데이터 분석에서 매우 자주 사용되는 패턴입니다.

이처럼 merge()는 다양한 상황에 맞춰 유연하게 사용할 수 있는 Pandas 병합 도구의 중심입니다. 다음 단락에서는 인덱스를 기준으로 병합하는 join() 메서드의 특성과 실전 예제를 다뤄보겠습니다.


4. join() 메서드의 활용과 특성

join()은 Pandas에서 데이터프레임을 병합할 때 자주 사용되는 또 다른 메서드입니다. merge()에 비해 문법이 간결하고, 주로 인덱스를 기준으로 병합하는 데에 특화되어 있습니다. 만약 병합 기준이 되는 열이 인덱스로 설정되어 있다면 join()은 더 깔끔한 방식이 될 수 있습니다.

4.1 기본 사용법

기본적으로 join()은 호출하는 데이터프레임의 인덱스를 기준으로, 대상 데이터프레임의 인덱스나 열을 병합합니다. 주로 left join 방식으로 동작하며, merge()처럼 how 옵션을 통해 조인 방식을 조절할 수도 있습니다.

df1 = pd.DataFrame({'Name': ['Alice', 'Bob', 'Charlie']}, index=[1, 2, 3])
df2 = pd.DataFrame({'Score': [90, 85, 88]}, index=[2, 3, 4])

# df1 기준 left join
result = df1.join(df2)
print(result)

위 예시에서 df1의 인덱스를 기준으로 병합이 수행되며, Score는 인덱스 값이 일치하는 경우에만 결합됩니다.

4.2 열 기준 병합도 가능

기본적으로 인덱스를 기준으로 병합하지만, set_index()를 통해 특정 열을 인덱스로 설정하거나 on 파라미터를 활용해 열 기준 병합도 가능합니다.

employees = pd.DataFrame({
    'emp_id': [1, 2, 3],
    'name': ['Kim', 'Lee', 'Park']
}).set_index('emp_id')

salaries = pd.DataFrame({
    'emp_id': [1, 2],
    'salary': [5000, 6000]
}).set_index('emp_id')

# 인덱스를 기준으로 병합
joined = employees.join(salaries, how='left')
print(joined)

이 예시처럼 인덱스를 명확히 설정한 후 join()을 사용하면, 가독성이 뛰어난 코드를 만들 수 있으며 병합 대상이 명확해지는 장점이 있습니다.

4.3 실전 예시: 시계열 센서 데이터 병합

시계열 데이터 분석에서 시간 인덱스를 기준으로 데이터를 병합하는 상황은 매우 흔합니다. join()은 이런 경우 특히 유용합니다.

import pandas as pd
import numpy as np

# 시간 인덱스를 가진 센서 데이터
time = pd.date_range('2024-01-01', periods=5, freq='D')
sensor1 = pd.DataFrame({'temperature': [20, 21, 19, 22, 23]}, index=time)
sensor2 = pd.DataFrame({'humidity': [30, 35, 33, 31, 34]}, index=time)

# 인덱스를 기준으로 병합
combined = sensor1.join(sensor2)
print(combined)

시간을 기준으로 정렬된 데이터를 병합할 때, join()은 직관적이고 효율적인 방식입니다. 특히 센서 데이터, 주가 데이터, 로그 데이터 등에서는 필수적인 기법이라 할 수 있습니다.

4.4 join vs merge 요약

기능 join() merge()
기준 인덱스 열 또는 인덱스
유연성 제한적 매우 높음
가독성 간결함 정밀함

join()은 단순하고 인덱스 기반의 병합이 필요한 경우 매우 효율적입니다. 반면, 열을 기준으로 정교한 병합이 필요하거나 여러 조건이 붙는 경우 merge()가 더 적합합니다. 각 메서드의 특성을 이해하고 상황에 맞게 선택하는 것이 중요합니다.

다음으로는 서로 다른 데이터프레임을 이어붙이는 방식인 concat()의 구조와 활용법을 살펴보겠습니다.


5. concat()을 활용한 데이터프레임 연결

concat()은 여러 데이터프레임을 단순히 위 또는 옆으로 이어붙이는 데에 사용됩니다. SQL의 UNION 또는 UNION ALL과 유사한 역할을 하며, 열이나 인덱스가 완전히 일치하지 않아도 자동으로 NaN을 채워 넣으며 유연하게 작동합니다. 특히, 같은 구조의 데이터를 월별, 분기별로 수집한 경우 concat()을 통해 손쉽게 전체 데이터를 통합할 수 있습니다.

5.1 기본 사용법

concat()은 기본적으로 행 방향(axis=0)으로 데이터프레임을 연결합니다. 같은 열 구조를 가진 여러 데이터프레임을 하나로 합칠 때 자주 사용됩니다.

import pandas as pd

jan = pd.DataFrame({'Product': ['A', 'B'], 'Sales': [100, 150]})
feb = pd.DataFrame({'Product': ['C', 'D'], 'Sales': [200, 180]})

# 행 방향으로 연결 (기본값 axis=0)
result = pd.concat([jan, feb])
print(result)

이 방식은 인덱스가 중복될 수 있으므로 ignore_index=True 옵션을 사용하면 재정렬된 인덱스를 얻을 수 있습니다.

# 인덱스를 무시하고 새로운 순번 부여
result = pd.concat([jan, feb], ignore_index=True)

5.2 열 방향 연결

axis=1 옵션을 주면 열 방향으로 연결됩니다. 이때는 인덱스를 기준으로 행들이 정렬되며, 동일한 인덱스를 기준으로 열이 나란히 붙습니다.

df1 = pd.DataFrame({'A': [1, 2, 3]})
df2 = pd.DataFrame({'B': [4, 5, 6]})

result = pd.concat([df1, df2], axis=1)
print(result)

이 경우에도 인덱스가 일치하지 않으면 NaN이 발생할 수 있으며, 정확한 병합을 위해 인덱스를 맞추는 것이 중요합니다.

5.3 실전 예시: 월별 통계 데이터를 하나로 통합하기

회사에서 월별 매출, 사용자 수 등을 CSV로 수집하는 경우가 많습니다. 이 데이터를 모두 읽어들여 하나의 테이블로 통합하는 과정에서 concat()이 매우 유용합니다.

months = ['2024-01', '2024-02', '2024-03']
dataframes = []

for month in months:
    df = pd.read_csv(f'data/stat_{month}.csv')
    df['month'] = month  # 월 정보 추가
    dataframes.append(df)

full_data = pd.concat(dataframes, ignore_index=True)
print(full_data.head())

이러한 방식은 수동 복사 없이도 수백 개의 데이터를 일관되게 병합할 수 있어 업무 자동화에 매우 효과적입니다.

5.4 중복 처리와 정렬

concat() 결과는 기본적으로 인덱스를 유지하지만, 필요한 경우 ignore_index, keys, sort 등의 옵션으로 정제할 수 있습니다.

  • ignore_index=True: 인덱스를 새로 부여
  • keys: 상위 그룹 키를 계층적 인덱스로 추가
  • sort=True: 열 정렬 (열 이름이 다른 경우 NaN 처리 시 유용)

예를 들어, 월별 데이터를 구분하여 계층 인덱스를 만들고 싶다면 다음과 같이 작성할 수 있습니다.

q1_data = pd.concat([jan, feb], keys=['Jan', 'Feb'])
print(q1_data)

출력 결과는 각 월을 구분하는 MultiIndex 형태로 나타나며, 그룹 단위 연산이나 필터링에 유리합니다.

concat()은 단순한 연결일 뿐이지만, 실무에서 반복되는 패턴을 자동화하는 데 핵심적인 역할을 합니다. 다음 장에서는 병합 후 발생할 수 있는 열 중복, 키 충돌, 정리 작업에 대해 집중적으로 다루겠습니다.


6. 고급 팁: 중복 열, 키 충돌, 병합 후 정리 작업

여러 데이터프레임을 병합하다 보면 열 이름의 중복, 키 값의 충돌, 불필요한 열의 잔존 같은 문제들이 자주 발생합니다. 이는 단순히 병합만으로 해결되지 않으며, 후처리 과정에서 데이터 품질을 유지하는 것이 매우 중요합니다.

6.1 열 이름 충돌 방지: suffixes

merge()join()을 사용할 때 공통되지 않은 열이 중복되는 경우, Pandas는 자동으로 접미사를 붙여 구분합니다. 그러나 이 접미사는 명확하지 않으면 오히려 혼란을 줄 수 있습니다. suffixes 옵션을 활용해 명확하게 지정하는 것이 좋습니다.

df1 = pd.DataFrame({'id': [1, 2], 'value': [100, 200]})
df2 = pd.DataFrame({'id': [1, 2], 'value': [300, 400]})

# 접미사 지정
result = pd.merge(df1, df2, on='id', suffixes=('_left', '_right'))
print(result)

이렇게 하면 열 이름이 value_left, value_right처럼 명확히 구분되어 추후 분석 시 혼동을 줄일 수 있습니다.

6.2 병합 후 불필요한 열 제거

병합 시 기준 열이 중복되어 두 번 포함되거나, 의미 없는 컬럼이 남는 경우가 많습니다. drop() 메서드를 통해 명시적으로 제거해야 합니다.

# 병합 후 기준 열이 중복되었을 경우 제거
merged = pd.merge(df1, df2, left_on='id', right_on='id', suffixes=('', '_dup'))
merged = merged.drop(columns=['id_dup'])

병합 대상이 대규모인 경우, 이러한 중복 컬럼 제거는 메모리 절약과 성능 향상에도 영향을 줍니다.

6.3 열 이름 통일 및 정렬

병합 후 컬럼명이 혼재되어 있는 경우, rename()reorder 작업이 필요합니다. 특히 보고서 작성이나 시각화 준비 단계에서는 컬럼 순서와 명확한 네이밍이 중요합니다.

# 열 이름 변경
merged.rename(columns={'value_left': 'Value_2023', 'value_right': 'Value_2024'}, inplace=True)

# 컬럼 순서 재정렬
merged = merged[['id', 'Value_2023', 'Value_2024']]

6.4 병합 키의 데이터 타입 불일치 문제

또 하나 흔히 겪는 문제는 병합 기준이 되는 키 값의 데이터 타입 불일치입니다. 예를 들어, 하나는 정수형이고 하나는 문자열인 경우, 병합 결과가 비어버리는 경우도 있습니다. 이를 방지하려면 병합 전 astype()으로 형식을 통일해주어야 합니다.

df1['id'] = df1['id'].astype(str)
df2['id'] = df2['id'].astype(str)

merged = pd.merge(df1, df2, on='id')

대규모 병합에서 이 한 줄이 결과의 성공 여부를 결정짓는 경우가 많기 때문에, 사전에 키 데이터 타입을 항상 점검하는 습관이 필요합니다.

6.5 중복 데이터 제거

병합 후 같은 행이 여러 번 중복되는 경우가 발생할 수 있으며, 이는 분석 결과를 왜곡시킬 수 있습니다. drop_duplicates() 메서드를 통해 정제해 줍니다.

cleaned = merged.drop_duplicates()
print(cleaned)

이러한 고급 후처리 작업은 병합이 끝난 뒤에도 데이터를 올바르게 유지하기 위한 핵심입니다. 병합 자체보다도 병합 이후의 정리 전략이 분석 품질에 더 큰 영향을 미치는 경우가 많습니다.

다음 섹션에서는 병합 성능을 향상시키는 방법과 대용량 데이터 처리 전략에 대해 살펴보겠습니다.


7. 병합 성능 최적화 전략

데이터가 수천, 수만 건을 넘기 시작하면 Pandas 병합의 성능 문제가 표면화됩니다. 병합 시간이 길어지거나 시스템 메모리가 급격히 상승하며 작업이 멈추는 경우도 발생합니다. 특히 실무에서는 크고 복잡한 데이터셋을 다루기 때문에, 병합 성능을 최적화하는 방법을 숙지해야 합니다.

7.1 병합 기준 열에 인덱스 설정

merge()는 병합 기준이 되는 열을 기준으로 데이터를 정렬하고 비교합니다. 이때 병합 열이 인덱스로 설정되어 있으면 속도가 크게 향상됩니다. 특히 대규모 병합에서는 set_index()를 통해 병합 전 인덱스를 설정하는 것이 유리합니다.

# 병합 전 인덱스 설정
df1.set_index('id', inplace=True)
df2.set_index('id', inplace=True)

# 인덱스를 기준으로 병합
merged = df1.join(df2, how='inner')

7.2 데이터 타입 정규화

병합 전에 데이터 타입이 일치하지 않으면 Pandas는 내부적으로 강제 변환을 수행하며, 이 과정이 성능 저하의 원인이 됩니다. 모든 병합 열의 타입을 명시적으로 동일하게 만들어 주는 것이 중요합니다. 특히 intobject 또는 str 타입 간의 불일치는 자주 발생하는 병합 실패 원인입니다.

7.3 병합 대상 열 최소화

불필요한 열까지 함께 병합하게 되면 메모리 사용량이 증가합니다. 병합 전에 필요한 열만 필터링하여 사용하는 것이 효율적입니다.

# 필요한 열만 필터링
df2_small = df2[['id', 'value']]
merged = pd.merge(df1, df2_small, on='id')

7.4 병합 방식 선택

outer join은 모든 값을 유지해야 하기 때문에 연산량이 많고, inner join은 상대적으로 빠릅니다. 필요한 경우가 아니라면 outer 병합은 피하는 것이 성능상 이점이 있습니다.

7.5 MultiIndex 병합 전략

복수 키를 사용할 경우, MultiIndex를 미리 생성해두면 병합 성능이 향상되기도 합니다. 특히 다차원 통계를 다루는 경우에는 계층적 인덱스를 잘 구성하여 병합 단계를 줄일 수 있습니다.

df1.set_index(['year', 'quarter'], inplace=True)
df2.set_index(['year', 'quarter'], inplace=True)

merged = df1.join(df2, how='inner')

7.6 병합 전 샘플링 및 테스트

전체 데이터 병합 전에 소규모 샘플을 병합해 보는 것은 오류나 병합 결과 구조를 사전에 검토하는 데 큰 도움이 됩니다. 데이터 정합성을 먼저 확인하고, 성능 이슈 없이 병합이 가능한 구조인지 확인하는 습관을 갖는 것이 중요합니다.

7.7 대용량 데이터에는 병렬 병합 고려

수백만 건이 넘는 병합에서는 Pandas만으로는 성능에 한계가 있습니다. 이 경우 Dask, PySpark, SQL 병합을 고려하는 것도 좋은 대안입니다. 특히 분산 환경에서 병합할 경우, Pandas 병합의 병목을 완전히 해소할 수 있습니다.

요약하자면, 병합은 단순히 데이터를 붙이는 행위 이상입니다. 사전 준비와 전략적 설계가 없다면 병합 과정에서 성능 병목이 발생하고, 심각한 경우 업무 지연으로 이어질 수 있습니다.

이제 다양한 소스와 파일 형태의 데이터를 실제로 어떻게 통합하고 병합하는지 실전 사례 중심으로 살펴보겠습니다.


8. 실전 시나리오: 이질적 소스 간 통합 사례 분석

실무에서는 하나의 데이터 소스만 다루는 경우는 드뭅니다. 대부분의 데이터는 CSV, Excel, JSON, SQL 등 다양한 형식과 스키마를 가진 채로 존재하며, 이를 통합해 분석 가능한 데이터프레임으로 구성하는 것이 분석 전처리의 핵심입니다. 이번 장에서는 서로 다른 구조와 형식을 가진 데이터를 병합하는 실제 흐름을 단계별로 설명합니다.

8.1 데이터 소스 불일치 상황

아래는 세 가지 유형의 데이터를 예시로 든 것입니다.

  • CSV: 제품 판매 데이터
  • Excel: 제품 정보 데이터
  • JSON: 고객 리뷰 데이터

8.2 다양한 파일 포맷 읽기

import pandas as pd
import json

# CSV - 판매 이력
sales = pd.read_csv('data/sales_2024.csv')

# Excel - 제품 정보
products = pd.read_excel('data/product_list.xlsx', sheet_name='Sheet1')

# JSON - 리뷰 데이터
with open('data/reviews.json') as f:
    reviews_json = json.load(f)
reviews = pd.json_normalize(reviews_json)

8.3 전처리: 열 이름 정리 및 필터링

다양한 소스는 열 이름이나 스키마가 일치하지 않기 때문에, 병합 전 통일 작업이 필수입니다.

# 열 이름 표준화
sales.rename(columns={'productCode': 'product_id'}, inplace=True)
products.rename(columns={'code': 'product_id'}, inplace=True)
reviews.rename(columns={'product.code': 'product_id'}, inplace=True)

# 필요한 열만 필터링
sales = sales[['product_id', 'quantity', 'sale_date']]
products = products[['product_id', 'name', 'category']]
reviews = reviews[['product_id', 'review_score']]

8.4 다단계 병합 수행

여러 소스를 순차적으로 병합할 때는 병합 순서와 방식이 중요합니다. 실무에서는 주로 기본 정보 → 트랜잭션 → 피드백 순으로 병합합니다.

# 1차 병합: 제품 + 판매 이력
merged1 = pd.merge(products, sales, on='product_id', how='left')

# 2차 병합: 리뷰까지 추가
merged2 = pd.merge(merged1, reviews, on='product_id', how='left')

8.5 병합 후 정리

병합이 끝났다고 해서 바로 분석 가능한 상태가 되는 것은 아닙니다. 누락값 처리, 컬럼 정렬, 이상치 제거 등 후처리 작업이 필수입니다.

# 누락값 처리
merged2['review_score'].fillna(0, inplace=True)

# 컬럼 정렬
merged2 = merged2[['product_id', 'name', 'category', 'quantity', 'sale_date', 'review_score']]

# 결과 확인
print(merged2.head())

이처럼 다양한 형식의 데이터를 병합하기 위해서는 다음과 같은 원칙이 중요합니다:

  • 파일별 사전 구조 확인 및 열 이름 통일
  • 병합 기준 열의 명확한 정의
  • 누락값과 중복 데이터에 대한 명확한 처리 방침

실무의 데이터는 항상 ‘깨끗하지 않다’는 사실을 전제로 병합 설계를 해야 하며, 이질적인 데이터의 병합 과정은 반복적으로 발생하는 루틴 업무입니다. 따라서 병합 패턴을 모듈화하거나 함수화하는 것도 좋은 전략이 될 수 있습니다.

마지막 장에서는 지금까지의 내용을 정리하며, 데이터 병합이 왜 단순한 기술을 넘어 데이터 설계의 핵심 전략인지에 대해 짚어보겠습니다.


9. 마무리 – 병합은 ‘기술’이자 ‘설계’다

마무리 – 병합은 ‘기술’이자 ‘설계’다

이번 포스팅에서는 Pandas를 활용한 데이터프레임 병합과 조인에 대해 이론과 실전 사례를 모두 아우르며 살펴보았습니다. 단순한 API 호출로 여겨질 수 있는 merge, join, concat이 사실은 데이터 설계 전반에 큰 영향을 미친다는 점을 강조하고 싶습니다.

데이터 병합은 단순한 기능이 아닙니다. 그 안에는 데이터 간의 관계를 파악하고, 누락 위험을 줄이며, 향후 분석의 정밀도를 좌우하는 설계의 사고가 반드시 동반되어야 합니다. 특히 다음과 같은 핵심 사항은 실무에서 반복적으로 접하게 될 것입니다.

  • 적절한 병합 방식 선택: 상황에 따라 inner, outer, left, right 병합의 선택이 결과를 완전히 바꿉니다.
  • 사전 정제와 통일: 데이터 타입, 열 이름, 누락값 등은 병합 전에 반드시 점검되어야 합니다.
  • 후처리 전략: 병합 이후 불필요한 열 제거, 중복 제거, 재정렬 등은 데이터 정제의 마지막 관문입니다.
  • 성능 고려: 대용량 데이터 병합 시에는 인덱싱, 타입 최적화, 병렬화 도구까지도 활용해야 합니다.

결국, 병합은 기술적인 코드 몇 줄로 끝나는 작업이 아니라, 데이터를 바라보는 시각과 분석 목적을 반영한 논리적 사고의 결과물입니다. 좋은 병합은 좋은 분석의 출발점이 됩니다.

여러분이 이번 포스팅을 통해 데이터 병합의 개념과 구현법을 명확히 이해하고, 실제 업무에서 더 정확하고 빠르게 데이터를 다룰 수 있게 되기를 바랍니다. 데이터는 혼자 존재하지 않으며, 언제나 연결되어 있어야만 진짜 가치를 발휘합니다. 그리고 그 연결의 핵심이 바로 병합입니다.

이제는 단순히 데이터를 ‘붙이는’ 데서 그치지 말고, 데이터를 ‘설계’하는 안목을 갖추시길 바랍니다.

# 병합의 정석: 사전 점검 + 전략적 병합 + 후처리
final = (
    pd.merge(products, sales, on='product_id', how='left')
      .merge(reviews, on='product_id', how='left')
      .drop_duplicates()
      .fillna({'review_score': 0})
      .sort_values(by='sale_date')
)
print(final.head())

댓글 남기기

Table of Contents

Table of Contents