pandas 에서 cannot reindex on an axis with duplicate labels 또는 cannot reindex from a duplicate axis가 나오면, 데이터를 곧바로 삭제하거나 reset_index()부터 적용하지 않는 편이 안전합니다. 이 오류는 대개 중복된 행 인덱스나 컬럼명이 label 기준 정렬을 모호하게 만들 때 발생하므로, 먼저 df.index.is_unique와 df.columns.is_unique를 각각 확인해야 합니다. 그 결과에 따라 중복을 제거할지, 행 식별자를 새로 만들지, 반복 관측을 업무 키로 보존할지를 결정합니다.
먼저 오류가 발생한 축을 나눠 확인하기
pandas의 Index는 중복 label을 허용합니다. 문제는 reindex()나 대입처럼 label을 기준으로 맞추는 작업에서 하나의 label이 여러 행 또는 여러 컬럼을 가리키게 될 때 생깁니다.
import pandas as pd
df = pd.read_csv("events.csv")
print("index unique:", df.index.is_unique)
print("columns unique:", df.columns.is_unique)
if not df.index.is_unique:
print("duplicate index:", df.index[df.index.duplicated(keep=False)])
if not df.columns.is_unique:
print("duplicate columns:", df.columns[df.columns.duplicated(keep=False)])
index.is_unique가 False이면 행 축을 먼저 살펴보고, columns.is_unique가 False이면 컬럼을 만드는 과정과 컬럼 선택 결과를 살펴봅니다. 둘 다 False일 수도 있으므로 한쪽이 정상이라고 다른 축을 생략하지 않는 것이 좋습니다. Index.duplicated()는 반복된 label을 확인하거나 필터링할 때 사용할 수 있습니다.
중복 행 인덱스라면 삭제보다 의미를 먼저 확인합니다
같은 timestamp가 여러 번 나오는 것이 수집 오류인지, 같은 시각에 발생한 여러 관측을 뜻하는지부터 구분해야 합니다. 단순히 첫 행만 남기는 방식은 중복이 오류라는 업무 규칙이 확인된 경우에만 선택할 수 있습니다.
# 중복 index가 잘못 유입된 것이고 첫 행이 기준일 때
clean = df.loc[~df.index.duplicated(keep="first")].copy()
# 행의 순번이 식별자일 뿐이라면
with_row_id = df.reset_index(drop=True)
반복 관측이 정상이라면 삭제하지 말고 시간 외에 센서 ID, 작업 ID처럼 의미 있는 업무 키를 함께 두는 편이 낫습니다. 여러 키가 하나의 관측을 식별한다면 해당 키를 MultiIndex로 구성하는 방법도 검토할 수 있지만, 글의 핵심은 label을 없애는 것이 아니라 식별 규칙을 분명히 하는 데 있습니다.
reset_index()는 중복 index를 일반 컬럼으로 옮기는 도구이지, 중복 데이터가 잘못되었다는 판정은 아닙니다. 기존 index가 업무상 중요한 식별자라면 버리기 전에 원래 값을 별도 컬럼으로 보존해야 합니다.
중복 컬럼명은 행 인덱스와 다른 문제입니다
컬럼명이 중복되면 df["status"] 같은 선택의 결과가 단일 Series가 아니라 여러 컬럼을 포함한 DataFrame이 될 수 있습니다. 따라서 컬럼을 추가하거나 선택하는 단계에서 같은 이름이 만들어졌는지 확인합니다.
duplicate_columns = df.columns[df.columns.duplicated(keep=False)]
print(duplicate_columns.tolist())
# 컬럼명이 실제로 중복된 것이 오류라면 이름을 명시적으로 정리합니다.
df = df.loc[:, ~df.columns.duplicated(keep="first")].copy()
이 코드도 첫 컬럼이 항상 옳다는 뜻은 아닙니다. CSV나 API 응답에서 같은 이름의 값이 서로 다른 의미라면 status_source_a, status_source_b처럼 출처를 반영한 이름으로 바꾸거나, 결합 전에 컬럼 이름을 명시적으로 지정해야 합니다.
Series 대입과 axis=1 결합에서 다시 확인할 점
다음과 같은 대입은 위치가 아니라 Series의 index label을 기준으로 맞춰집니다.
df["score"] = score_series
그래서 score_series와 df의 행 index가 유일하지 않으면 어느 값을 어느 행에 넣을지 모호해질 수 있습니다. 두 객체의 index가 같은 업무 키를 의미하는지 먼저 확인하고, 단순히 현재 행 순서대로 넣어야 한다면 두 객체의 순서를 별도로 검증한 뒤 명시적으로 처리해야 합니다.
pd.concat([left, right], axis=1)도 다른 축의 label 정렬을 거치므로, 결합 직전에 양쪽의 행 index 유일성과 의미를 확인하는 습관이 필요합니다. merge 후 행 수가 늘어나는 문제는 중복 label 오류와는 다른 질문이므로, 조인 키의 중복과 validate는 별도 기준으로 점검해야 합니다. 관련해서는 pandas merge 후 행이 갑자기 늘어나는 이유와 validate 사용법을 이어서 볼 수 있습니다.
다음 처리에서 중복을 조기에 차단하기
중복 label이 허용되면 안 되는 단계라면 다음처럼 DataFrame의 flag를 설정할 수 있습니다.
strict_df = df.set_flags(allows_duplicate_labels=False)
이 설정은 이미 중복된 label이 있거나 이후 연산으로 중복이 만들어질 때 오류를 일찍 드러내는 데 도움이 됩니다. 다만 pandas 공식 문서에서도 이 기능은 실험적 기능으로 안내되며 모든 메서드가 flag를 완전히 전파하지 않을 수 있으므로, 핵심 검증을 대신하는 만능 방어선으로 보지는 않는 것이 좋습니다.
정리: 삭제·reset_index·업무 키 중 무엇을 선택할까
- 중복이 입력 오류이고 대표 행을 고르는 규칙이 분명하면
Index.duplicated()로 규칙을 코드에 남깁니다. - 행 순번이 식별자일 뿐이면
reset_index()를 사용하되, 기존 index를 보존할 필요가 있는지 먼저 판단합니다. - 같은 timestamp의 반복 관측이 정상이라면 삭제하지 말고 업무 키나
MultiIndex로 관측의 의미를 보존합니다. - 컬럼명이 중복되면 행 index를 고치는 대신 컬럼 생성·결합 단계에서 이름을 명시합니다.
- 이후 단계에서 유일성이 계약이라면
is_unique검사와set_flags(allows_duplicate_labels=False)를 함께 검토합니다.
중요한 것은 오류 문구를 없애는 것이 아니라, 해당 label이 데이터에서 무엇을 식별하는지 결정한 뒤 그 결정이 코드에 드러나게 만드는 일입니다.