이상치 탐지란?
데이터 분석에서 이상치(Outlier)는 대부분의 데이터와 동떨어진 비정상적인 값을 의미합니다. 센서 오류, 입력 실수, 드문 이벤트 등 다양한 원인으로 발생하며, 분석 결과를 왜곡할 수 있어 적절한 탐지와 처리가 필수적입니다.
핵심 포인트: 이상치를 무조건 제거하는 것이 정답은 아닙니다. 비즈니스 맥락에 따라 이상치가 중요한 인사이트를 제공할 수도 있습니다.
이 글에서는 Python으로 구현하는 4가지 주요 이상치 탐지 기법을 비교하고, 실무 데이터에 적용하는 전략을 살펴봅니다.
4가지 이상치 탐지 기법 비교
기법별 특징 요약
| 기법 | 유형 | 변수 수 | 장점 | 단점 | 적합한 경우 |
|---|---|---|---|---|---|
| IQR | 통계 | 단변량 | 간단, 빠름, 해석 용이 | 정규분포 가정 불필요하지만 단변량만 | 단일 변수 빠른 탐지 |
| Z-Score | 통계 | 단변량 | 표준화된 기준, 이해 쉬움 | 정규분포 가정 필수 | 정규분포 데이터 |
| Isolation Forest | ML | 다변량 | 고차원 가능, 분포 가정 불필요 | 하이퍼파라미터 조정 필요 | 복잡한 고차원 데이터 |
| DBSCAN | 클러스터링 | 다변량 | 임의 형태 클러스터, 노이즈 자동 탐지 | 파라미터 민감, 밀도 차이 큰 데이터 어려움 | 공간 데이터, 밀도 기반 탐지 |
1. IQR (Interquartile Range) 방법
사분위수 범위를 이용한 가장 직관적인 방법입니다. 박스플롯의 수염(whisker) 밖 데이터를 이상치로 간주합니다.
수식:
- : 1사분위수 (25번째 백분위수)
- : 3사분위수 (75번째 백분위수)
- : 사분위수 범위
- : 일반적인 승수 (조정 가능)
import numpy as np
import pandas as pd
def detect_outliers_iqr(data, column, multiplier=1.5):
"""IQR 방법으로 이상치 탐지"""
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - multiplier * IQR
upper_bound = Q3 + multiplier * IQR
outliers = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
return outliers, lower_bound, upper_bound
# 예시: 매출 데이터
data = pd.DataFrame({'sales': [100, 120, 110, 115, 105, 950, 108, 112, 5, 118]})
outliers, lower, upper = detect_outliers_iqr(data, 'sales')
print(f"이상치 개수: {len(outliers)}")
print(f"범위: [{lower:.2f}, {upper:.2f}]")
print(outliers)
실무 팁:
– multiplier=1.5는 일반적 기준, 더 엄격하게는 1.0, 느슨하게는 2.0 사용
– 재무 데이터, 판매량 등 분포 형태 무관하게 빠르게 적용 가능
2. Z-Score 방법
데이터가 평균에서 몇 표준편차 떨어져 있는지 측정합니다. 일반적으로 |z| > 3 을 이상치로 판단합니다.
수식:
- : 개별 데이터 값
- : 평균 (mean)
- : 표준편차 (standard deviation)
- : Z-Score (표준화된 점수)
from scipy import stats
def detect_outliers_zscore(data, column, threshold=3):
"""Z-Score 방법으로 이상치 탐지"""
z_scores = np.abs(stats.zscore(data[column]))
outliers = data[z_scores > threshold]
return outliers, z_scores
# 예시
data = pd.DataFrame({'temperature': [20, 21, 22, 19, 23, 50, 21, 20, -10, 22]})
outliers, z_scores = detect_outliers_zscore(data, 'temperature')
print(f"이상치 개수: {len(outliers)}")
print(outliers)
주의사항:
– 정규분포 가정 필수 (비정규 데이터는 왜곡 가능)
– 표본 크기가 작으면 부정확
– Modified Z-Score (MAD 기반)가 이상치에 더 강건함
3. Isolation Forest
머신러닝 기반 앙상블 방법으로, 이상치는 트리에서 더 빨리 고립(isolate)된다는 원리를 이용합니다.
from sklearn.ensemble import IsolationForest
def detect_outliers_iforest(data, contamination=0.1):
"""Isolation Forest로 이상치 탐지"""
model = IsolationForest(contamination=contamination, random_state=42)
data['outlier'] = model.fit_predict(data)
# -1: 이상치, 1: 정상
outliers = data[data['outlier'] == -1]
return outliers.drop('outlier', axis=1)
# 예시: 다변량 데이터
data = pd.DataFrame({
'feature1': np.random.normal(50, 10, 100),
'feature2': np.random.normal(30, 5, 100)
})
# 인위적 이상치 추가
data.loc[100] = [200, 100]
data.loc[101] = [-50, -30]
outliers = detect_outliers_iforest(data, contamination=0.05)
print(f"탐지된 이상치: {len(outliers)}개")
하이퍼파라미터:
– contamination: 데이터 내 이상치 비율 (기본 0.1 = 10%)
– n_estimators: 트리 개수 (기본 100)
– max_samples: 각 트리에 사용할 샘플 수
장점:
– 고차원 데이터에 효과적
– 분포 가정 불필요
– 비선형 관계 포착 가능
4. DBSCAN (Density-Based Spatial Clustering)
밀도 기반 클러스터링으로, 어떤 클러스터에도 속하지 않는 점을 이상치로 판단합니다.
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
def detect_outliers_dbscan(data, eps=0.5, min_samples=5):
"""DBSCAN으로 이상치 탐지"""
# 스케일링 필수
scaler = StandardScaler()
scaled_data = scaler.fit_transform(data)
dbscan = DBSCAN(eps=eps, min_samples=min_samples)
labels = dbscan.fit_predict(scaled_data)
# -1: 노이즈(이상치)
data['cluster'] = labels
outliers = data[data['cluster'] == -1]
return outliers.drop('cluster', axis=1)
# 예시
data = pd.DataFrame({
'x': [1, 2, 2, 8, 8, 25, 1, 2, 9, 8],
'y': [2, 2, 3, 7, 8, 80, 2, 1, 7, 8]
})
outliers = detect_outliers_dbscan(data, eps=1.5, min_samples=3)
print(f"이상치: {len(outliers)}개")
print(outliers)
파라미터 설정:
– eps: 이웃으로 간주할 최대 거리 (작을수록 엄격)
– min_samples: 핵심 포인트가 되기 위한 최소 이웃 수
– K-distance 그래프로 최적 eps 찾기 권장
실무 데이터 적용 전략
단계별 접근법
1단계: 데이터 특성 파악
– 변수 개수 (단변량 vs 다변량)
– 분포 형태 (정규성 검정: Shapiro-Wilk)
– 데이터 크기 (샘플 수)
– 비즈니스 맥락 (이상치의 의미)
2단계: 기법 선택
def choose_method(data, column=None):
"""데이터 특성에 따른 기법 추천"""
if column: # 단변량
_, p_value = stats.shapiro(data[column][:5000]) # 샘플 제한
if p_value > 0.05:
return "Z-Score (정규분포)"
else:
return "IQR (비정규분포)"
else: # 다변량
if len(data.columns) > 10:
return "Isolation Forest (고차원)"
else:
return "DBSCAN (저차원 공간)"
3단계: 여러 기법 조합
def ensemble_outlier_detection(data, column):
"""여러 기법의 교집합으로 이상치 탐지"""
# IQR
outliers_iqr, _, _ = detect_outliers_iqr(data, column)
# Z-Score
outliers_z, _ = detect_outliers_zscore(data, column)
# 두 방법 모두에서 탐지된 것만
common_outliers = pd.merge(outliers_iqr, outliers_z, how='inner')
return common_outliers
산업별 적용 사례
| 산업 | 데이터 유형 | 추천 기법 | 이유 |
|---|---|---|---|
| 제조 | 센서 시계열 | IQR + DBSCAN | 빠른 탐지 + 공간적 이상 패턴 |
| 금융 | 거래 금액 | Z-Score | 정규분포 가정, 표준화된 기준 |
| 이커머스 | 고객 행동 (다변량) | Isolation Forest | 고차원, 복잡한 패턴 |
| 물류 | 배송 시간 | IQR | 비정규, 빠른 의사결정 |
| 의료 | 환자 바이탈 | Z-Score + 도메인 룰 | 생명 관련, 보수적 접근 |
이상치 처리 방법
탐지 후 처리 옵션:
- 제거: 명백한 오류 (입력 실수, 센서 고장)
- 대체: 평균, 중앙값, 보간법으로 대체
- 변환: 로그 변환, Box-Cox 변환으로 정규화
- 상한/하한 설정: Winsorization (99th percentile로 제한)
- 별도 분석: 이상치 자체가 인사이트 (사기 탐지, 장비 고장 예측)
def handle_outliers(data, column, method='remove'):
"""이상치 처리"""
outliers, lower, upper = detect_outliers_iqr(data, column)
if method == 'remove':
return data[(data[column] >= lower) & (data[column] <= upper)]
elif method == 'clip':
data[column] = data[column].clip(lower, upper)
return data
elif method == 'replace_median':
median = data[column].median()
data.loc[(data[column] < lower) | (data[column] > upper), column] = median
return data
실전 예제: 복합 접근
import matplotlib.pyplot as plt
import seaborn as sns
# 실제 데이터 시뮬레이션
np.random.seed(42)
data = pd.DataFrame({
'sales': np.random.normal(1000, 200, 200),
'visits': np.random.normal(500, 100, 200)
})
# 이상치 추가
data.loc[200] = [5000, 100] # 높은 매출, 낮은 방문
data.loc[201] = [200, 2000] # 낮은 매출, 높은 방문
# 1. 시각화
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
data.boxplot(column='sales', ax=axes[0])
axes[0].set_title('Sales Boxplot')
axes[1].scatter(data['visits'], data['sales'])
axes[1].set_xlabel('Visits')
axes[1].set_ylabel('Sales')
plt.tight_layout()
plt.savefig('outliers.png')
# 2. 다변량 탐지
outliers_if = detect_outliers_iforest(data[['sales', 'visits']], contamination=0.02)
print("Isolation Forest 탐지:", outliers_if)
# 3. 단변량 검증
for col in ['sales', 'visits']:
outliers, lower, upper = detect_outliers_iqr(data, col)
print(f"{col} IQR 이상치: {len(outliers)}개, 범위: [{lower:.2f}, {upper:.2f}]")
마무리
이상치 탐지는 단일 정답이 없는 영역입니다. 데이터 특성, 비즈니스 맥락, 분석 목적에 따라 적절한 기법을 선택해야 합니다.
핵심 요약
- IQR: 빠르고 간단한 단변량 탐지, 분포 가정 불필요
- Z-Score: 정규분포 데이터에 적합, 해석 직관적
- Isolation Forest: 고차원 복잡 데이터, ML 기반 강력한 탐지
- DBSCAN: 공간/밀도 기반, 클러스터링과 동시 수행
실무 권장 프로세스:
1. EDA로 데이터 분포 파악
2. 단변량은 IQR/Z-Score, 다변량은 Isolation Forest 우선 시도
3. 여러 기법 결과 비교 (교집합 또는 다수결)
4. 도메인 전문가와 결과 검증
5. 이상치 제거보다 이해와 활용 우선
마지막 조언: 이상치는 때로 가장 중요한 정보입니다. 무조건 제거하기 전에 “왜 발생했는가?”를 먼저 질문하세요.
Did you find this helpful?
☕ Buy me a coffee
Leave a Reply