데이터 분석을 하다 보면 중복된 행을 무조건 삭제하기보다는 먼저 살펴보며 데이터를 깊이 이해해야 하는 경우가 종종 있습니다. 다행히 Pandas에는 중복 데이터를 손쉽게 다룰 수 있는 다양한 메서드가 마련되어 있습니다.
이 글에서는 duplicated(), drop_duplicates(), unique(), nunique() 네 가지 핵심 메서드를 예제와 함께 자세히 알아보겠습니다.
.duplicated() 메서드
duplicated() 메서드는 DataFrame에서 중복된 행을 추출할 때 사용합니다. 중복 값이 포함된 HR 데이터셋을 불러와 실습해 보겠습니다.
import pandas as pd
import numpy as np
# 특정 컬럼만 선택하여 HR 데이터셋 불러오기
df = pd.read_csv("https://raw.githubusercontent.com/sasankac/TestDataSet/master/HRDataset.csv",
usecols=("Employee_Name", "PerformanceScore", "Position", "CitizenDesc"))
# 직원 이름 기준으로 정렬 후 원본에 반영
df.sort_values("Employee_Name", inplace=True)
df.head(3)| Employee_Name | Position | CitizenDesc | PerformanceScore | |
|---|---|---|---|---|
| 0 | Adinolfi | Production Technician I | US Citizen | Exceeds |
| 1 | Adinolfi | Sr. DBA | US Citizen | Fully Meets |
| 2 | Adinolfi | Production Technician II | US Citizen | Fully Meets |
keep 파라미터의 기본 동작
duplicated()는 기본적으로 keep 파라미터에 따라 동작하며, 각 값의 첫 번째 등장은 '중복 아님'으로 표시합니다.
즉, 어떤 행이 여러 번 나타나더라도 그 행 자체가 중복으로 표시되는 것이 아니라, 첫 번째 등장 이후의 행들만 중복으로 표시됩니다. 비유하자면 바구니에 사과가 3개 들어 있다면, 첫 번째 사과는 '중복 아님'으로, 나머지 두 개는 '중복'으로 표시하는 방식입니다.
예제
df["Employee_Name"].head(3)
출력 결과:
0 Adinolfi 1 Adinolfi 2 Adinolfi Name: Employee_Name, dtype: object
df["Employee_Name"].duplicated().head(3)
출력 결과:
0 False 1 True 2 True Name: Employee_Name, dtype: bool
중복 행을 실제로 추출하려면(첫 번째 등장은 제외하고 이후 등장만 출력) 이 불리언 시리즈를 DataFrame에 전달하면 됩니다.
df.shape
(310, 4)
df[df["Employee_Name"].duplicated()]
| Employee_Name | Position | CitizenDesc | PerformanceScore | |
|---|---|---|---|---|
| 1 | Adinolfi | Sr. DBA | US Citizen | Fully Meets |
| 2 | Adinolfi | Production Technician II | US Citizen | Fully Meets |
| 3 | Adinolfi | Production Technician I | US Citizen | Fully Meets |
| 4 | Adinolfi | Production Manager | US Citizen | Fully Meets |
| 6 | Anderson | Production Technician I | US Citizen | Exceeds |
| ... | ... | ... | ... | ... |
| 303 | Wang | Production Technician II | US Citizen | Fully Meets |
| 304 | Wang | Production Technician II | US Citizen | Fully Meets |
| 305 | Wang | Production Technician I | US Citizen | PIP |
| 306 | Wang | CIO | US Citizen | Exceeds |
| 307 | Wang | Data Analyst | US Citizen | Fully Meets |
전체 310행 중 79개의 중복 행이 .duplicated() 메서드로 추출되었습니다.
keep='last' 옵션
기본 설정에서는 첫 번째 등장이 '중복 아님'으로 표시되지만, keep='last' 인자를 전달하면 이 동작을 반대로 바꿀 수 있습니다.
사과 비유로 말하면, 앞의 두 사과는 '중복'으로 표시하고 마지막 사과만 '중복 아님'으로 남기는 방식입니다.
df[df["Employee_Name"].duplicated(keep="last")]
| Employee_Name | Position | CitizenDesc | PerformanceScore | |
|---|---|---|---|---|
| 0 | Adinolfi | Production Technician I | US Citizen | Exceeds |
| 1 | Adinolfi | Sr. DBA | US Citizen | Fully Meets |
| 2 | Adinolfi | Production Technician II | US Citizen | Fully Meets |
| 3 | Adinolfi | Production Technician I | US Citizen | Fully Meets |
| 5 | Anderson | Production Technician I | US Citizen | Fully Meets |
| ... | ... | ... | ... | ... |
| 302 | Wang | Production Technician II | US Citizen | Exceeds |
| 303 | Wang | Production Technician II | US Citizen | Fully Meets |
| 304 | Wang | Production Technician II | US Citizen | Fully Meets |
| 305 | Wang | Production Technician I | US Citizen | PIP |
| 306 | Wang | CIO | US Citizen | Exceeds |
keep=False 옵션
keep 파라미터는 False도 받을 수 있으며, 이 경우 두 번 이상 등장하는 모든 값을 중복으로 표시합니다. 즉, 위 예제들처럼 첫 번째나 마지막 하나만 남기는 것이 아니라, 3개의 사과가 모두 '중복'으로 표시됩니다.
주의 – False를 지정할 때는 따옴표 없이 작성해야 합니다.
df[df["Employee_Name"].duplicated(keep=False)]
| Employee_Name | Position | CitizenDesc | PerformanceScore | |
|---|---|---|---|---|
| 0 | Adinolfi | Production Technician I | US Citizen | Exceeds |
| 1 | Adinolfi | Sr. DBA | US Citizen | Fully Meets |
| 2 | Adinolfi | Production Technician II | US Citizen | Fully Meets |
| 3 | Adinolfi | Production Technician I | US Citizen | Fully Meets |
| 4 | Adinolfi | Production Manager | US Citizen | Fully Meets |
| ... | ... | ... | ... | ... |
| 303 | Wang | Production Technician II | US Citizen | Fully Meets |
| 304 | Wang | Production Technician II | US Citizen | Fully Meets |
| 305 | Wang | Production Technician I | US Citizen | PIP |
| 306 | Wang | CIO | US Citizen | Exceeds |
| 307 | Wang | Data Analyst | US Citizen | Fully Meets |
물결표(~)로 고유값만 추출하기
마지막으로, 데이터셋에서 완전히 고유한 값(단 한 번만 등장한 값)만 추출하려면 물결표(~) 기호로 조건을 부정(negate)하면 됩니다.
df_unique = ~df["Employee_Name"].duplicated(keep=False) df[df_unique]
| Employee_Name | Position | CitizenDesc | PerformanceScore | |
|---|---|---|---|---|
| 7 | Andreola | Software Engineer | US Citizen | Fully Meets |
| 25 | Bozzi | Production Manager | US Citizen | Fully Meets |
| 26 | Bramante | Director of Operations | US Citizen | Exceeds |
| 27 | Brill | Production Technician I | US Citizen | Fully Meets |
| 34 | Burkett | Production Technician II | US Citizen | Fully Meets |
| ... | ... | ... | ... | ... |
| 276 | Sweetwater | Software Engineer | US Citizen | Exceeds |
| 277 | Szabo | Production Technician I | Non-Citizen | Fully Meets |
| 278 | Tavares | Production Technician II | US Citizen | Fully Meets |
| 308 | Zhou | Production Technician I | US Citizen | Fully Meets |
| 309 | Zima | NaN | NaN | NaN |
drop_duplicates() 메서드
drop_duplicates()는 앞서 본 메서드와 매우 유사하지만, Series가 아닌 DataFrame 전체에 적용할 수 있다는 점이 다릅니다.
참고: 이 메서드는 DataFrame의 모든 컬럼을 기준으로 중복 행을 판별하여 제거합니다.
len(df)
출력 결과:
310
len(df.drop_duplicates())
출력 결과:
290
subset 파라미터
subset 파라미터에 문자열 형태의 컬럼명 리스트를 전달하면, 지정한 컬럼만을 기준으로 중복 여부를 검사할 수 있습니다.
df1 = df.drop_duplicates(subset=["Employee_Name"], keep="first") df1
| Employee_Name | Position | CitizenDesc | PerformanceScore | |
|---|---|---|---|---|
| 0 | Adinolfi | Production Technician I | US Citizen | Exceeds |
| 5 | Anderson | Production Technician I | US Citizen | Fully Meets |
| 7 | Andreola | Software Engineer | US Citizen | Fully Meets |
| 14 | Athwal | Production Technician I | US Citizen | Fully Meets |
| 20 | Beak | Production Technician I | US Citizen | Fully Meets |
| ... | ... | ... | ... | ... |
| 293 | Von Massenbach | Production Technician II | US Citizen | Fully Meets |
| 295 | Wallace | Production Technician I | US Citizen | Needs Improvement |
| 300 | Wang | Production Technician I | Eligible NonCitizen | Fully Meets |
| 308 | Zhou | Production Technician I | US Citizen | Fully Meets |
| 309 | Zima | NaN | NaN | NaN |
여러 개의 컬럼을 지정할 수도 있으며, 앞서 설명한 모든 keep 파라미터(first, last, False)를 그대로 활용할 수 있습니다.
df1 = df.drop_duplicates(subset=["Employee_Name", "CitizenDesc"], keep=False) df1
| Employee_Name | Position | CitizenDesc | PerformanceScore | |
|---|---|---|---|---|
| 7 | Andreola | Software Engineer | US Citizen | Fully Meets |
| 16 | Beak | Production Technician I | Eligible NonCitizen | Fully Meets |
| 25 | Bozzi | Production Manager | US Citizen | Fully Meets |
| 26 | Bramante | Director of Operations | US Citizen | Exceeds |
| 27 | Brill | Production Technician I | US Citizen | Fully Meets |
| ... | ... | ... | ... | ... |
| 287 | Tejeda | Network Engineer | Eligible NonCitizen | Fully Meets |
| 286 | Tejeda | Software Engineer | Non-Citizen | Fully Meets |
| 300 | Wang | Production Technician I | Eligible NonCitizen | Fully Meets |
| 308 | Zhou | Production Technician I | US Citizen | Fully Meets |
| 309 | Zima | NaN | NaN | NaN |
unique() 메서드
unique() 메서드는 Series에서 고유값을 찾아 배열(Array) 형태로 반환합니다. 이 메서드는 결측값(NaN)을 제외하지 않는다는 점에 유의하세요.
len(df["Employee_Name"])
출력 결과:
310
df["Employee_Name"].unique()
출력 결과:
array(['Adinolfi', 'Anderson', 'Andreola', 'Athwal', 'Beak', 'Bondwell',
'Bozzi', 'Bramante', 'Brill', 'Brown', 'Burkett', 'Butler',
'Carabbio', 'Carey', 'Carr', 'Carter', 'Chace', 'Champaigne',
... 생략 ...
'Von Massenbach', 'Wallace', 'Wang', 'Zhou', 'Zima'], dtype=object)len(df["Employee_Name"].unique())
출력 결과:
231
nunique() 메서드
nunique() 메서드는 Series에 포함된 고유값의 개수를 반환합니다. 기본적으로 dropna=True 파라미터가 적용되어 결측값은 개수에서 제외됩니다.
결측값까지 포함하여 세고 싶다면 dropna 파라미터에 False를 전달하면 됩니다.
df["Employee_Name"].nunique()
출력 결과:
231
df["Employee_Name"].nunique(dropna=False)
출력 결과:
231
마무리
정리하면, duplicated()는 중복 여부를 확인하고 추출할 때, drop_duplicates()는 중복 행을 실제로 제거할 때, unique()와 nunique()는 고유값과 그 개수를 확인할 때 각각 유용하게 사용할 수 있습니다. 상황에 맞는 메서드를 조합하면 데이터 정제 작업을 훨씬 효율적으로 수행할 수 있습니다.