Pandas merge 후 행이 늘어날 때: 기준표 중복 키와 미매칭 행 검사
주문 여러 건에 기준표 한 건을 붙이는 병합에는 validate="many_to_one"을 지정하고, indicator로 찾지 못한 키를 별도 확인하세요.
주문 목록에 상품명을 붙이는 작업에서 기준표의 상품 코드가 중복되면, 주문 한 행이 여러 이름과 연결되어 수량 합계까지 커질 수 있습니다. 이 글은 CSV를 아래로 이어 붙이는 concat과 달리, 상품 코드로 옆 열을 조회해 붙이는 좌측 병합을 다룹니다.
주문 여러 건과 상품 한 건은 어떤 관계인가요?
예제에서는 같은 상품을 주문한 행이 여러 개인 것은 정상입니다. 반면 상품 기준표에서는 상품 코드 한 개에 이름 한 개만 있어야 합니다. 이 계약에 맞춰 many_to_one을 사용합니다. 주문과 기준표 모두 키가 유일해야 하는 업무라면 one_to_one처럼 다른 관계를 검토해야 합니다.
테스트 자료는 주문 4행이며, P01 주문 2행·P02 주문 1행·기준표에 없는 X99 주문 1행입니다. 수량은 각각 1·2·1·4로 합계 8입니다. 기준표에 없는 행도 원본에 남겨 조사하려고 how="left"를 선택했습니다. 이 수치는 아래 합성 자료에서 계산한 예제 결과이지 실제 매출 자료가 아닙니다.
중복 기준표와 미매칭을 검사하는 코드
Windows Python 3.14.4, Pandas 3.0.2에서 실행했습니다. Pandas가 없으면 작업용 가상환경에서 python -m pip install pandas로 설치하세요. 코드의 표는 메모리에서 만들며 기존 CSV나 업무 DB를 읽거나 변경하지 않습니다.
import pandas as pd
def attach_names(orders, master):
for name, frame in (("orders", orders), ("master", master)):
keys = frame["product_id"]
if keys.isna().any() or not keys.map(
lambda x: isinstance(x, str) and bool(x.strip())
).all():
raise ValueError(f"{name}: 빈 값 또는 문자열이 아닌 키")
return orders.merge(
master, how="left", on="product_id",
validate="many_to_one", indicator=True,
)
orders = pd.DataFrame({
"order_id": ["O1", "O2", "O3", "O4"],
"product_id": ["P01", "P01", "P02", "X99"],
"quantity": [1, 2, 1, 4],
})
master = pd.DataFrame({"product_id": ["P01", "P02"], "name": ["파일", "노트"]})
result = attach_names(orders, master)
assert len(result) == len(orders) == 4
assert result["quantity"].sum() == orders["quantity"].sum() == 8
unmatched = result.loc[result["_merge"] == "left_only", "product_id"].tolist()
assert unmatched == ["X99"]
assert (result["_merge"] == "both").sum() == 3
print("rows:", len(result), "unmatched:", unmatched)
duplicate_master = pd.concat([master, master.iloc[[0]]], ignore_index=True)
unchecked = orders.merge(duplicate_master, how="left", on="product_id")
assert len(unchecked) == 6 and unchecked["quantity"].sum() == 11
try:
attach_names(orders, duplicate_master)
except pd.errors.MergeError:
print("duplicate master: rejected")
else:
raise AssertionError("중복 기준표 키를 허용했습니다")
for invalid_key in (None, "", " "):
bad_orders = orders.copy()
bad_orders.loc[0, "product_id"] = invalid_key
try:
attach_names(bad_orders, master)
except ValueError:
pass
else:
raise AssertionError("빈 키를 허용했습니다")
print("missing keys: rejected")
행 수와 합계가 보존됐는지 어떻게 확인하나요?
rows: 4 unmatched: ['X99']
duplicate master: rejected
missing keys: rejected
정상 기준표에서는 결과가 4행이고 수량 합계가 8로 유지됩니다. P01 주문 두 행도 각각 보존됩니다. indicator의 left_only로 남은 X99는 입력 주문에는 있지만 기준표에서 찾지 못했다는 뜻입니다. 이름이 비어 있다고 X99 주문 자체를 버리지 않고 별도 조사 대상으로 모읍니다.
기준표에 P01 한 행을 더 넣은 시험은 MergeError로 중단됩니다. 만약 관계 검증을 빼면 이 예제의 P01 주문들이 각각 복제되어 결과가 6행, 수량 합계가 11이 될 수 있습니다. 잘못된 결과를 만든 뒤 수정하기보다 병합 단계에서 계약 위반을 드러내는 것이 목적입니다.
빈 키와 공백은 왜 먼저 검사하나요?
Pandas는 양쪽의 null 키가 서로 매칭될 수 있어 일반적인 SQL 조인의 기대와 다를 수 있습니다. 예제에서는 None·빈 문자열·공백만 있는 문자열을 병합 전에 거부합니다. 실제로는 누락 행을 격리하거나 별도 상태로 유지하는 등 업무 정책을 정해야 합니다.
이 함수는 문자열 키와 비어 있지 않다는 조건을 검사하지만, 앞뒤 공백을 자동 제거하거나 대소문자를 통일하지 않습니다. 이를 바꾸면 서로 다른 코드가 합쳐질 수 있습니다. 열 이름·자료형·허용 문자·공백 의미를 입력 계약으로 정하고, 규칙을 변경한 뒤에도 기준표의 유일성을 다시 검사하세요.
중복을 drop_duplicates로 지우면 해결되나요?
기준표의 두 이름이 다르다면 어느 행이 맞는지 먼저 결정해야 합니다. 단순히 첫 행을 남기면 날짜별 유효 이력이나 버전 정보를 잃을 수 있습니다. 상품 코드와 적용일 같은 복합 키가 필요한 경우에는 on과 유일성 검사를 함께 바꿔야 합니다.
행 수와 수량 합계가 같아도 상품명이 올바르다는 보장은 아닙니다. 실제 작업에서는 표본의 코드·이름 대조, 미매칭 목록, 빈 결과, 중복 기준표의 원인도 확인하세요. indicator 열은 진단 목적이므로 최종 내보내기 자료에 포함할지 별도로 정합니다.
자주 묻는 질문
주문 쪽 코드가 반복되어도 괜찮나요?
이 예제의 관계에서는 정상입니다. many_to_one은 오른쪽 기준표의 유일성을 검사합니다. 주문 자체의 중복 접수는 별도의 주문 ID 규칙으로 검사해야 합니다.
미매칭이 있으면 merge가 자동으로 실패하나요?
아닙니다. validate는 관계의 유일성을 확인하며 모든 키가 찾혔는지까지 보장하지 않습니다. indicator 결과를 검사하고 미매칭 허용 정책을 정해야 합니다.
inner로 바꾸면 미매칭 문제가 해결되나요?
미매칭 행이 결과에서 사라질 뿐 원인이 해결되는 것은 아닙니다. 원본 행이 빠져도 되는 업무인지 확인하고 누락 목록을 보관하세요.
공식 자료와 관련 글
- pandas.merge 공식 API: validate·indicator·null 키 경고
- Pandas 병합 사용자 가이드: 관계와 결과 검사
데이터 처리 가이드와 함께 CSV 파일의 청크 연결과 입력 검증, 결측치의 삭제·유지·대체 선택을 참고하세요.
작성: 로그 · 2026-10-20 작성 및 공식 자료 확인. AI 보조로 작성했으며 합성 주문·상품표로 정상 병합, 중복 키, 미매칭과 빈 키를 실행 검증했습니다. 실제 업무 자료의 정확성을 판정한 결과는 아닙니다.
댓글
댓글 쓰기