df.drop_duplicates(keep='first') # 기본df.drop_duplicates(keep='last')df.drop_duplicates(keep=False) # 단 한 번만 나오는 행만
python
import pandas as pddf = pd.DataFrame({'name': ['A', 'B', 'A', 'C', 'B']})print(df.drop_duplicates(keep=False)) # A, B 가 모두 제거됨
결과
name3 C
keep 옵션 비교
데이터 ['A', 'B', 'A', 'C', 'B'] 에서 각 keep 옵션 결과:
flowchart TD
DATA["원본: A B A C B"]
DATA -->|"keep='first'"| F["결과: A B C (첫 등장 유지)"]
DATA -->|"keep='last'"| L["결과: A C B (마지막 등장 유지)"]
DATA -->|"keep=False"| N["결과: C (단 한 번만 등장한 것만)"]
# 어떤 값이 중복인지 확인dup_mask = df.duplicated(subset=['email'], keep=False)df[dup_mask].sort_values('email')
index 다루기
df.drop_duplicates() # 원래 index 유지df.drop_duplicates().reset_index(drop=True) # 0,1,2,... 재배치
drop_duplicates 후 index 가 불연속적으로 남는다. 이후 iloc 로 접근하거나 연속 index 가 필요하면 reset_index(drop=True) 를 붙인다.
그룹별 첫 번째 / 마지막 행
drop_duplicates 는 그룹별 첫/마지막 선택의 빠른 idiom 이다.
# 각 user 의 가장 최근 주문 (마지막 등장)df.sort_values('date', ascending=False).drop_duplicates('user_id')# 각 user 의 첫 주문 (첫 등장)df.sort_values('date').drop_duplicates('user_id')# 각 카테고리에서 가장 비싼 상품df.sort_values('price', ascending=False).drop_duplicates('category')
groupby('user_id').head(1) 보다 약간 빠를 때가 있다.
SQL 비교
-- DISTINCTSELECT DISTINCT name, age FROM table;-- pandas: df[['name','age']].drop_duplicates()-- 중복 제거 후 첫 번째 (window function)SELECT * FROM tableWHERE id IN ( SELECT MIN(id) FROM table GROUP BY user_id);-- pandas: df.sort_values('id').drop_duplicates('user_id')
pandas 2.x: Copy-on-Write 영향
# pandas 2.x CoW 환경에서 inplace=True 는 동작하지만 권장하지 않음df.drop_duplicates(inplace=True) # 옛 코드df = df.drop_duplicates() # 권장 (명시적, CoW 안전)
성능
방법
속도
비고
drop_duplicates()
빠름
해시 기반
groupby().head(1)
비슷
그룹별 첫 행
Python for 루프
매우 느림
절대 피할 것
# 대용량 데이터에서 subset 지정이 더 빠름df.drop_duplicates(subset=['user_id']) # 필요한 컬럼만 비교
함정
1. NaN 의 중복 처리
import numpy as npdf = pd.DataFrame({'x': [1, 1, None, None]})df.duplicated().tolist()# [False, True, False, True]# NaN == NaN 이 일반적으로 False 이지만, duplicated 는 NaN 도 같은 것으로 취급
2. subset 컬럼 순서는 결과에 영향 없음
df.drop_duplicates(['a', 'b']) # (a, b) 조합으로 비교df.drop_duplicates(['b', 'a']) # 결과 동일, 순서 무관
3. inplace=True 의 함정
df.drop_duplicates(inplace=True) # 옛 코드, 권장 안 함df = df.drop_duplicates() # ✓ 명시적
4. keep=False 와 duplicated(keep=False) 의 차이
# drop_duplicates(keep=False): 중복된 행 전부 제거df.drop_duplicates(keep=False)# duplicated(keep=False): 중복된 행 전부 True 표시 (제거 안 함)df[df.duplicated(keep=False)] # 중복된 행 전부 보기
5. 정렬 후 drop_duplicates 순서
# ❌ 정렬 전 drop_duplicates: 원본 순서 기준 첫 행df.drop_duplicates('user_id')# ✓ 최신 이벤트 기준 첫 행df.sort_values('ts', ascending=False).drop_duplicates('user_id')
실전 패턴
중복 제거 후 집계
# 중복 제거 전 개수 vs 후 개수 비교total = len(df)unique = len(df.drop_duplicates())print(f"중복 행: {total - unique} ({(total - unique) / total:.1%})")
특정 컬럼 기준 최신 레코드만
# 이벤트 로그에서 각 사용자의 마지막 상태만latest = ( df .sort_values('updated_at', ascending=False) .drop_duplicates(subset=['user_id']) .reset_index(drop=True))
중복 행 감사 (audit)
# 중복 여부 컬럼 추가 후 원본 보존df['is_duplicate'] = df.duplicated(subset=['email'], keep='first')df[df['is_duplicate']].to_csv('duplicates_audit.csv', index=False)
다중 키 중복 제거
# 날짜 + 상품 + 지역 조합 기준df.drop_duplicates(subset=['date', 'product_id', 'region'])
💬 댓글