CSV나 API 데이터를 pandas.merge로 합친 뒤 결과 행이 갑자기 늘었다면, left 병합이라는 사실만으로 원본 행 수가 보존된다고 판단하면 안 됩니다. pandas merge 후 행이 늘어나는 가장 흔한 이유는 양쪽 데이터의 키가 중복되어 조인 관계가 many-to-many가 되기 때문이며, 병합 전 키와 결측을 확인한 뒤 validate와 indicator로 관계와 미매칭을 검증해야 합니다. 운영에서는 병합이 성공했는지가 아니라, 그 결과를 집계해도 금액이나 건수가 중복되지 않는지를 확인해야 합니다.
왜 left merge인데도 행이 늘어날까요?
left는 왼쪽 데이터의 키를 기준으로 결과를 남긴다는 뜻이지, 왼쪽의 각 행이 결과에서 한 번만 나타난다는 뜻은 아닙니다. 왼쪽과 오른쪽에 같은 키가 여러 번 있으면, 그 키의 왼쪽 행과 오른쪽 행 조합이 모두 결과에 생깁니다.
예를 들어 왼쪽에 고객 ID A가 두 행, 오른쪽에도 A가 세 행 있으면 A에 해당하는 조합은 여섯 행이 됩니다. 이때 결제나 주문 금액 같은 왼쪽 값을 그대로 합산하면 같은 값이 반복 집계될 수 있습니다. pandas 공식 가이드는 이런 many-to-many 병합이 키 조합의 카테시안 곱을 만들고 결과 크기를 크게 늘릴 수 있다고 설명합니다(pandas 병합 가이드).
따라서 먼저 “행이 늘어난 것이 업무적으로 필요한 1:N 관계인가, 아니면 중복 키를 잘못 합친 것인가”를 구분해야 합니다. 오른쪽이 고객별 속성처럼 한 고객당 한 행이어야 한다면 중복을 제거하거나 원인을 확인한 뒤 병합해야 합니다. 반대로 고객 한 명에 여러 주문처럼 1:N이 의도된 구조라면 행 증가는 오류가 아닐 수 있지만, 집계 기준을 별도로 설계해야 합니다.
병합 전에 키의 상태를 확인합니다
실무에서는 병합문부터 고치기보다 양쪽 키의 유일성과 결측을 먼저 확인합니다.
import pandas as pd
left = pd.DataFrame(
{
"customer_id": ["A", "B", "C"],
"amount": [100, 200, 300],
}
)
right = pd.DataFrame(
{
"customer_id": ["A", "A", "B"],
"grade": ["basic", "vip", "basic"],
}
)
key = "customer_id"
print(left[key].isna().sum(), right[key].isna().sum())
print(left[key].duplicated(keep=False))
print(right[key].duplicated(keep=False))
print(len(left), len(right))
여기서 duplicated(keep=False)는 중복된 키를 모두 표시하므로 어느 쪽이 관계를 깨뜨리는지 찾는 데 유용합니다. 결측 키도 별도로 봐야 합니다. pandas는 병합 키의 결측값끼리 매칭할 수 있으며, 이는 일반적인 SQL 동작과 다를 수 있습니다(pandas.merge API). 결측을 하나의 실제 고객이나 상품처럼 취급해도 되는지 업무 규칙을 먼저 정해야 합니다.
validate로 기대한 관계를 강제합니다
키 상태를 확인했다면 validate에 업무적으로 기대하는 관계를 적습니다. 1:1은 양쪽 키가 모두 고유해야 하고, 1:m은 왼쪽 키가 고유해야 하며, m:1은 오른쪽 키가 고유해야 합니다.
result = left.merge(
right,
on="customer_id",
how="left",
validate="m:1",
)
이 예제에서 오른쪽이 고객별 속성 테이블이라면 m:1이 자연스럽습니다. 왼쪽에는 고객별 여러 거래가 있을 수 있지만 오른쪽 고객 속성은 고객마다 한 행이어야 하기 때문입니다. 오른쪽에 같은 고객 ID가 두 행 이상 있으면 병합 결과를 조용히 받아들이지 않고 관계 위반을 확인할 수 있습니다.
반대로 주문 테이블과 고객 테이블을 합칠 때 주문 쪽 키가 유일하고 고객 쪽 키도 유일하다면 1:1을 검토합니다. 고객 한 명에 여러 주문이 있는 구조라면 주문을 왼쪽에 두고 고객 테이블을 오른쪽에 두는 m:1인지부터 명확히 하십시오.
m:m은 many-to-many를 허용하지만 관계 검사를 하지 않습니다. 행 증가를 진단하려는 단계에서 이를 안전장치처럼 사용하면 문제를 숨길 수 있으므로, 정말 의도된 관계인지 설명할 수 있을 때만 선택하는 편이 좋습니다. 관계 이름과 검증 동작은 pandas.merge API의 validate 설명에 정리되어 있습니다.
indicator로 미매칭을 분리합니다
관계가 맞더라도 왼쪽 키가 오른쪽에 없는 경우는 남을 수 있습니다. 이때 indicator=True를 사용하면 _merge 열에 각 행의 출처가 표시됩니다.
checked = left.merge(
right,
on="customer_id",
how="left",
validate="m:1",
indicator=True,
)
print(checked["_merge"].value_counts())
unmatched = checked.loc[checked["_merge"] == "left_only"]
left_only는 왼쪽에만 있는 키, right_only는 오른쪽에만 있는 키, both는 양쪽에 있는 키를 뜻합니다. left 병합에서는 결과에 왼쪽 행이 기준으로 남으므로 left_only를 확인하면 고객 속성 누락이나 코드 체계 불일치 같은 문제를 좁힐 수 있습니다. indicator의 값과 의미는 pandas 공식 문서에서 확인할 수 있습니다(pandas 병합 가이드).
운영에서 적용할 검증 순서
제가 이런 병합을 검토할 때는 다음 순서로 판단합니다.
- 키의 의미를 정합니다. 고객 ID인지, 주문 ID인지, 고객-월 조합인지처럼 한 행을 식별하는 기준을 먼저 고릅니다.
- 양쪽 키를 점검합니다. 결측, 중복, 자료형과 공백 등 같은 키로 볼 수 있는 조건을 확인합니다.
- 예상 관계를 선택합니다.
1:1,1:m,m:1중 실제 업무 관계에 맞는 값을validate에 넣습니다. - 미매칭을 확인합니다.
indicator=True로left_only와right_only를 분리하고, 누락을 허용할지 수정할지 결정합니다. - 집계 기준을 다시 봅니다. 1:N 결과에서 왼쪽 금액을 합산한다면 한 금액이 여러 자식 행에 반복되는 구조인지 확인합니다.
이 순서를 통과했다고 해서 모든 중복이 자동으로 해결되는 것은 아닙니다. 중복 키를 drop_duplicates()로 지우는 것은 어떤 행이 정답인지 결정한 뒤에 해야 하며, 의도된 1:N 데이터를 임의로 한 행으로 줄이면 정보가 사라질 수 있습니다.
핵심 정리
pandas.merge 후 행이 늘어나는 원인은 대개 병합 키의 중복으로 생긴 조인 관계에서 찾을 수 있습니다. 먼저 양쪽 키의 중복과 결측을 확인하고, 예상 관계를 validate로 명시한 뒤, indicator로 미매칭을 분리하십시오. 마지막으로 결과 행 수만 보지 말고, 그 결과를 집계할 때 원래 금액이나 건수가 반복되지 않는지까지 확인해야 안전한 자동화가 됩니다.
출처
- pandas, Merge, join, concatenate and compare —
pandas-merge-guide - pandas, pandas.merge API —
pandas-merge-api