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 호출보다 앞서 다음 세 가지를 확인합니다.
- 실제 업무에서 한 행을 식별하는 키가 무엇인지 확인합니다. 고객 번호만으로 유일해야 하는지, 고객 번호와 기준일을 함께 써야 하는지부터 정해야 합니다.
- 양쪽 데이터에서 키별 빈도를 계산합니다. 중복 자체가 오류인지, 이력이나 상세 행 때문에 의도된 것인지 구분해야 합니다.
- 결측 키를 별도로 봅니다. 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가 관계를 확인하고, 행 수 검사가 업무상 기대한 결과 크기를 확인하도록 역할을 나누는 것이 핵심입니다.
실무에서 적용할 판단 순서
다음 순서로 확인하면 원인과 대응이 섞이지 않습니다.
- 조인 키가 실제 업무에서 무엇을 식별하는지 정합니다.
- 병합 전 양쪽 키의 빈도와 결측을 확인합니다.
- 중복이 오류인지 의도된 상세·이력 관계인지 구분합니다.
- 예상 관계에 맞춰
one_to_one,one_to_many,many_to_one중 하나를 지정합니다. - 미매칭을 찾아야 하면
indicator=True를 사용합니다. - 병합 전후 행 수와 핵심 집계의 변화가 업무 기대와 맞는지 확인한 뒤 결과를 저장합니다.
결과 행이 늘었다는 이유만으로 drop_duplicates()를 먼저 호출하면, 중복이 왜 생겼는지 확인할 기회를 잃습니다. 먼저 MergeError가 나도록 관계를 명시하고, 중복이 의도된 경우에만 어떤 행을 남길지 정한 뒤 정제하는 편이 재현 가능한 처리에 가깝습니다.
마무리
pandas merge 후 행 증가를 해결하는 출발점은 결과 행을 지우는 것이 아니라 조인 키의 관계를 정의하는 일입니다. 키 빈도와 결측을 먼저 확인하고, 기준표가 유일해야 한다면 validate="many_to_one"처럼 전제를 코드에 남기며, 미매칭은 indicator=True로 따로 확인하면 잘못된 집계를 저장할 가능성을 줄일 수 있습니다.
이 글의 핵심은 validate와 indicator를 서로 대체하는 도구로 보지 않는 것입니다. validate는 중복 관계를 검사하고, indicator는 어느 쪽 키가 결과에 참여했는지 보여 줍니다. 두 검사를 업무상 행 수 기준과 함께 적용하면 병합 결과를 저장해도 되는지 판단하기가 훨씬 수월해집니다.