Python 이상치 탐지 완벽 가이드: IQR, Z-Score, Isolation Forest, DBSCAN 비교와 실무 적용 전략

Updated Feb 6, 2026

이상치 탐지란?

데이터 분석에서 이상치(Outlier)는 대부분의 데이터와 동떨어진 비정상적인 값을 의미합니다. 센서 오류, 입력 실수, 드문 이벤트 등 다양한 원인으로 발생하며, 분석 결과를 왜곡할 수 있어 적절한 탐지와 처리가 필수적입니다.

핵심 포인트: 이상치를 무조건 제거하는 것이 정답은 아닙니다. 비즈니스 맥락에 따라 이상치가 중요한 인사이트를 제공할 수도 있습니다.

이 글에서는 Python으로 구현하는 4가지 주요 이상치 탐지 기법을 비교하고, 실무 데이터에 적용하는 전략을 살펴봅니다.

4가지 이상치 탐지 기법 비교

기법별 특징 요약

기법 유형 변수 수 장점 단점 적합한 경우
IQR 통계 단변량 간단, 빠름, 해석 용이 정규분포 가정 불필요하지만 단변량만 단일 변수 빠른 탐지
Z-Score 통계 단변량 표준화된 기준, 이해 쉬움 정규분포 가정 필수 정규분포 데이터
Isolation Forest ML 다변량 고차원 가능, 분포 가정 불필요 하이퍼파라미터 조정 필요 복잡한 고차원 데이터
DBSCAN 클러스터링 다변량 임의 형태 클러스터, 노이즈 자동 탐지 파라미터 민감, 밀도 차이 큰 데이터 어려움 공간 데이터, 밀도 기반 탐지

1. IQR (Interquartile Range) 방법

사분위수 범위를 이용한 가장 직관적인 방법입니다. 박스플롯의 수염(whisker) 밖 데이터를 이상치로 간주합니다.

수식:

IQR=Q3Q1IQR = Q_3 – Q_1

Lower Bound=Q11.5×IQR\text{Lower Bound} = Q_1 – 1.5 \times IQR

Upper Bound=Q3+1.5×IQR\text{Upper Bound} = Q_3 + 1.5 \times IQR

  • Q1Q_1: 1사분위수 (25번째 백분위수)
  • Q3Q_3: 3사분위수 (75번째 백분위수)
  • IQRIQR: 사분위수 범위
  • 1.51.5: 일반적인 승수 (조정 가능)
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| &gt; 3 을 이상치로 판단합니다.

수식:

z=xμσz = \frac{x – \mu}{\sigma}

  • xx: 개별 데이터 값
  • μ\mu: 평균 (mean)
  • σ\sigma: 표준편차 (standard deviation)
  • zz: 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 + 도메인 룰 생명 관련, 보수적 접근

이상치 처리 방법

탐지 후 처리 옵션:

  1. 제거: 명백한 오류 (입력 실수, 센서 고장)
  2. 대체: 평균, 중앙값, 보간법으로 대체
  3. 변환: 로그 변환, Box-Cox 변환으로 정규화
  4. 상한/하한 설정: Winsorization (99th percentile로 제한)
  5. 별도 분석: 이상치 자체가 인사이트 (사기 탐지, 장비 고장 예측)
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

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

TODAY 400 | TOTAL 2,623