pandas merge 중복 행이 늘어나는 이유와 validate 검증법

pandas merge 후 행 수가 갑자기 늘어나는 가장 흔한 이유는 조인 키가 양쪽 데이터에 중복되어 다대다 병합이 만들어지기 때문입니다. 먼저 병합 전 양쪽 키의 빈도와 업무상 예상 관계를 확인한 뒤, 그 관계에 맞는 validate 값을 지정해야 합니다. 미매칭 키까지 확인해야 한다면 indicator=True를 함께 사용하고, 행 수가 늘었다는 이유만으로 drop_duplicates()를 먼저 적용하지 않는 것이 안전합니다.

운영에서 이 문제는 단순히 “pandas가 행을 복사했다”는 현상으로 끝나지 않습니다. 주문과 회원, 상품과 가격표, 로그와 기준표를 붙인 뒤 금액이나 건수가 부풀면 원본 데이터의 중복인지 병합 관계의 오류인지부터 구분해야 합니다. 이 글에서는 키 중복을 진단하고, 1:1·다:1·1:다 중 어떤 관계를 허용할지 정한 다음, 잘못된 병합을 저장하지 않는 순서를 정리합니다.

왜 merge 후 행이 늘어날까?

왼쪽 데이터의 같은 키가 2개이고 오른쪽 데이터의 같은 키도 3개라면, 해당 키에서는 가능한 조합이 서로 만들어집니다. 따라서 결과에는 그 키의 행이 2와 3의 조합만큼 나타날 수 있습니다. 양쪽 모두 중복인 다대다 병합을 의도하지 않았다면, 이 결과는 집계 전에 멈춰서 확인해야 할 신호입니다.

import pandas as pd

orders = pd.DataFrame({
    "customer_id": [101, 101, 102],
    "amount": [12000, 8000, 15000],
})
customers = pd.DataFrame({
    "customer_id": [101, 101, 102],
    "grade": ["basic", "vip", "basic"],
})

merged = orders.merge(customers, on="customer_id", how="left")
print(merged)

이 예시에서 customer_id=101은 양쪽에 반복됩니다. 주문 두 행과 회원 정보 두 행이 서로 결합하므로, 어느 한쪽의 행이 단순히 한 번만 보존되는 결과를 기대했다면 먼저 업무 키 정의를 다시 확인해야 합니다.

병합 전에 조인 키부터 확인하기

저라면 merge 호출보다 앞서 다음 세 가지를 확인합니다.

  1. 실제 업무에서 한 행을 식별하는 키가 무엇인지 확인합니다. 고객 번호만으로 유일해야 하는지, 고객 번호와 기준일을 함께 써야 하는지부터 정해야 합니다.
  2. 양쪽 데이터에서 키별 빈도를 계산합니다. 중복 자체가 오류인지, 이력이나 상세 행 때문에 의도된 것인지 구분해야 합니다.
  3. 결측 키를 별도로 봅니다. pandas 문서에 따르면 양쪽 조인 키가 결측이면 일반적인 SQL과 달리 서로 일치할 수 있으므로, 결측을 같은 그룹으로 취급해도 되는지 업무 규칙을 확인해야 합니다.
key = "customer_id"

left_counts = orders[key].value_counts(dropna=False)
right_counts = customers[key].value_counts(dropna=False)

print("왼쪽 중복 키")
print(left_counts[left_counts > 1])
print("오른쪽 중복 키")
print(right_counts[right_counts > 1])

키 빈도표는 원인을 보여 주지만, 중복을 자동으로 잘못된 데이터라고 판정하지는 않습니다. 예를 들어 왼쪽이 주문 상세라면 고객 번호가 여러 번 나오는 것이 자연스러울 수 있습니다. 반대로 오른쪽이 고객 기준표라면 고객 번호가 유일해야 하는지 먼저 정하고, 그 판단을 validate에 반영해야 합니다.

validate 값으로 예상 관계를 코드에 남기기

validate는 병합이 어떤 키 관계를 전제로 하는지 코드에 적고, 그 전제를 어기면 병합을 중단하게 하는 장치입니다. pandas 공식 문서의 관계별 의미는 다음과 같습니다.

  • validate="one_to_one": 양쪽 조인 키가 모두 유일해야 합니다.
  • validate="one_to_many": 왼쪽 키는 유일하고 오른쪽 키 중복은 허용합니다.
  • validate="many_to_one": 왼쪽 키 중복은 허용하고 오른쪽 키는 유일해야 합니다.
  • validate="many_to_many": 다대다 관계를 허용하며 중복 검사를 하지 않습니다.

주문 상세를 왼쪽에 두고 고객 기준표를 오른쪽에 붙이는 상황이라면, 고객 기준표의 고객 번호가 유일하다는 전제 아래 many_to_one이 보통 더 맞습니다.

validated = orders.merge(
    customers,
    on="customer_id",
    how="left",
    validate="many_to_one",
)

오른쪽에 같은 고객 번호가 두 번 있으므로 이 코드는 MergeError로 중단됩니다. 이 오류는 처리 실패라기보다, 결과를 저장하기 전에 기준표의 유일성 가정을 확인하게 해 주는 안전장치입니다.

반대로 왼쪽과 오른쪽 모두 식별 키가 유일해야 하는 업무라면 다음처럼 더 엄격하게 지정할 수 있습니다.

validated = left_df.merge(
    right_df,
    on="record_id",
    how="left",
    validate="one_to_one",
)

관계를 아직 설명할 수 없다면 many_to_many를 습관적으로 지정해 오류를 숨기기보다, 먼저 키의 의미와 중복 원인을 문서화하는 편이 낫습니다. 다대다 병합이 업무상 의도된 경우에만 사용하고, 결과 크기와 집계 방식도 별도로 검토해야 합니다.

indicator=True로 미매칭을 분리하기

키 유일성 검증과 키가 서로 만나는지 확인하는 일은 다른 문제입니다. indicator=True를 지정하면 결과에 _merge 열이 추가되고, 값으로 left_only, right_only, both를 확인할 수 있습니다.

checked = orders.merge(
    customers.drop_duplicates("customer_id"),
    on="customer_id",
    how="outer",
    indicator=True,
)

print(checked["_merge"].value_counts())
print("왼쪽에만 있는 키")
print(checked.loc[checked["_merge"] == "left_only", ["customer_id"]])

left_only는 왼쪽에만 있는 키, right_only는 오른쪽에만 있는 키, both는 양쪽에 존재하는 키를 뜻합니다. 다만 예시의 drop_duplicates()는 “첫 행을 임의로 선택해도 된다”는 업무 규칙이 있을 때만 사용할 수 있습니다. 기준표의 중복을 원인 확인 없이 제거하면 어떤 값이 사라졌는지 알 수 없게 되므로, 검증 코드와 데이터 정제 코드를 구분해야 합니다.

행 수 검증은 관계에 맞춰 적용하기

행 수가 늘었는지만 보는 검사는 원인을 충분히 설명하지 못합니다. 왼쪽이 주문 상세이고 오른쪽이 고객 기준표라면, 오른쪽 키가 유일하다는 전제에서 왼쪽 행이 불필요하게 곱해지지 않았는지 확인하는 방식이 맞습니다. 반대로 집계 전 상세를 의도적으로 확장하는 병합이라면 행 증가 자체가 오류가 아닐 수 있습니다.

운영 코드에서는 병합 전후의 행 수와 키별 빈도를 함께 로그로 남기고, 예상 관계를 위반하면 결과 저장을 중단하는 흐름이 안전합니다. pandas 문서가 업무별 허용 행 수를 정해 주는 것은 아니므로, 허용 범위는 실제 데이터의 키 정의와 처리 목적에 따라 정해야 합니다.

before_rows = len(orders)

result = orders.merge(
    customers,
    on="customer_id",
    how="left",
    validate="many_to_one",
)

if len(result) != before_rows:
    raise ValueError("병합 후 행 수가 예상과 다릅니다.")

이 검사는 모든 정상적인 병합의 행 수가 반드시 같다는 뜻이 아닙니다. validate가 관계를 확인하고, 행 수 검사가 업무상 기대한 결과 크기를 확인하도록 역할을 나누는 것이 핵심입니다.

실무에서 적용할 판단 순서

다음 순서로 확인하면 원인과 대응이 섞이지 않습니다.

  1. 조인 키가 실제 업무에서 무엇을 식별하는지 정합니다.
  2. 병합 전 양쪽 키의 빈도와 결측을 확인합니다.
  3. 중복이 오류인지 의도된 상세·이력 관계인지 구분합니다.
  4. 예상 관계에 맞춰 one_to_one, one_to_many, many_to_one 중 하나를 지정합니다.
  5. 미매칭을 찾아야 하면 indicator=True를 사용합니다.
  6. 병합 전후 행 수와 핵심 집계의 변화가 업무 기대와 맞는지 확인한 뒤 결과를 저장합니다.

결과 행이 늘었다는 이유만으로 drop_duplicates()를 먼저 호출하면, 중복이 왜 생겼는지 확인할 기회를 잃습니다. 먼저 MergeError가 나도록 관계를 명시하고, 중복이 의도된 경우에만 어떤 행을 남길지 정한 뒤 정제하는 편이 재현 가능한 처리에 가깝습니다.

마무리

pandas merge 후 행 증가를 해결하는 출발점은 결과 행을 지우는 것이 아니라 조인 키의 관계를 정의하는 일입니다. 키 빈도와 결측을 먼저 확인하고, 기준표가 유일해야 한다면 validate="many_to_one"처럼 전제를 코드에 남기며, 미매칭은 indicator=True로 따로 확인하면 잘못된 집계를 저장할 가능성을 줄일 수 있습니다.

이 글의 핵심은 validate와 indicator를 서로 대체하는 도구로 보지 않는 것입니다. validate는 중복 관계를 검사하고, indicator는 어느 쪽 키가 결과에 참여했는지 보여 줍니다. 두 검사를 업무상 행 수 기준과 함께 적용하면 병합 결과를 저장해도 되는지 판단하기가 훨씬 수월해집니다.

참고 자료

Leave a Comment