데이터 정제 — 지저분한 데이터를 쓸 수 있게
이 토픽을 마치면
실전 데이터에서 흔히 발생하는 결측값, 중복, 이상치 문제를 식별하고 처리할 수 있습니다.
왜 데이터 정제가 필요한가
현실 세계의 데이터는 깨끗하지 않습니다. 설문 응답이 빠져 있고, 같은 사람이 두 번 입력되어 있고, 나이가 -5살인 행이 있습니다. 이런 데이터를 그대로 분석하면 결과를 신뢰할 수 없습니다.
데이터 분석 업무의 60~80%는 정제에 들어간다고 합니다. 화려한 분석 기법보다 이 단계가 훨씬 중요합니다.
1단계: 데이터 전체 파악
import pandas as pd
df = pd.read_csv("patients.csv")
# 기본 정보print(df.shape) # (1000, 8) — 1000행, 8열print(df.dtypes) # 각 열의 데이터 타입print(df.info()) # 결측값 현황 포함한 요약
# 첫/끝 확인print(df.head()) # 상위 5행print(df.tail()) # 하위 5행
# 통계 요약print(df.describe()) # 수치형 열의 평균, 표준편차, 최소/최대 등df.info()를 실행하면 Non-Null Count가 나옵니다. 전체 행 수보다 적으면 결측값이 있다는 뜻입니다. df.describe()에서 min/max가 상식적 범위를 벗어나면 이상치를 의심합니다.
2단계: 결측값 처리
# 결측값 확인print(df.isnull().sum())# name 0# age 15# blood_type 3# weight 42
# 전략 1: 삭제 — 결측 행이 소수일 때df_clean = df.dropna(subset=["blood_type"]) # blood_type 없는 3행 삭제
# 전략 2: 채우기 — 수치형 열df["age"] = df["age"].fillna(df["age"].median()) # 중앙값으로df["weight"] = df["weight"].fillna(df["weight"].mean()) # 평균으로
# 전략 3: 특정 값으로 채우기df["blood_type"] = df["blood_type"].fillna("미확인")어떤 전략을 쓸지는 맥락에 따라 다릅니다.
- 결측 비율이 5% 미만이면 삭제가 가장 안전합니다
- 수치형은 중앙값(이상치에 강함) 또는 평균으로 채웁니다
- 범주형(혈액형, 성별)은 최빈값이나 "미확인"으로 채웁니다
3단계: 중복 제거
# 중복 확인print(df.duplicated().sum()) # 23개 중복행
# 중복 내용 보기print(df[df.duplicated(keep=False)]) # 원본 + 중복 모두 표시
# 중복 제거 (첫 번째만 유지)df = df.drop_duplicates()
# 특정 열 기준 중복 제거df = df.drop_duplicates(subset=["patient_id"], keep="last")keep="first"는 첫 번째 행을 유지하고 나머지를 삭제합니다. keep="last"는 마지막을 유지합니다. 데이터가 시간순이라면 last가 최신 기록을 남깁니다.
4단계: 이상치 탐지
# 기본 통계로 확인print(df["age"].describe())# min: -5 ← 비정상# max: 200 ← 비정상
# 범위 필터링df = df[(df["age"] >= 0) & (df["age"] <= 120)]
# IQR 방식 — 통계적 이상치 탐지Q1 = df["weight"].quantile(0.25)Q3 = df["weight"].quantile(0.75)IQR = Q3 - Q1lower = Q1 - 1.5 * IQRupper = Q3 + 1.5 * IQR
outliers = df[(df["weight"] < lower) | (df["weight"] > upper)]print(f"이상치 {len(outliers)}개 발견")
# 이상치 제거df = df[(df["weight"] >= lower) & (df["weight"] <= upper)]이상치를 무조건 삭제하면 안 됩니다. 나이 -5는 명백한 오류이므로 삭제하지만, 체중 150kg은 실제로 존재할 수 있습니다. 도메인 지식이 이상치 판단의 핵심입니다.
5단계: 타입 변환과 형식 통일
# 문자열로 된 숫자 → 숫자형으로df["age"] = pd.to_numeric(df["age"], errors="coerce")
# 날짜 문자열 → datetimedf["visit_date"] = pd.to_datetime(df["visit_date"])
# 문자열 정리 — 앞뒤 공백, 대소문자 통일df["name"] = df["name"].str.strip()df["blood_type"] = df["blood_type"].str.upper()errors="coerce"는 변환 불가능한 값(예: "N/A")을 NaN으로 처리합니다. 에러를 내지 않고 진행할 수 있어서 대량 데이터에 유용합니다.
핵심 정리
| 단계 | 확인 사항 | 주요 도구 |
|---|---|---|
| 전체 파악 | 행/열 수, 타입, 결측 현황 | info(), describe() |
| 결측값 | 비율 확인 → 삭제 or 채우기 | isnull(), fillna(), dropna() |
| 중복 | 동일 행 또는 키 기준 중복 | duplicated(), drop_duplicates() |
| 이상치 | 상식 범위 + IQR | describe(), 범위 필터링 |
| 타입/형식 | 숫자, 날짜, 문자열 통일 | to_numeric(), to_datetime(), str.strip() |
데이터 정제는 반복적이고 지루한 작업이지만, 이 단계를 건너뛰면 이후의 모든 분석 결과가 흔들립니다. "Garbage in, garbage out" — 입력이 쓰레기면 출력도 쓰레기입니다.