[데이터분석] 📊 이상치(Outlier) 처리, 완전 실무형 정리

 

데이터를 분석할 때 이상치(Outlier) 는 반드시 마주치는 문제입니다.

특히 병원 환자 데이터처럼 복잡하고 개인 차이가 큰 데이터를 다룰 때는,

수치만 보고 단순히 제거하는 것은 매우 위험합니다. 🚨

 

이번 글에서는 단변량(univariable) 이상치 처리부터

전문가 협의 후 유지해야 하는 경우,

그리고 다변량(multivariable) 이상치 탐지까지

실제 현장에서 쓸 수 있는 깊이로 정리합니다. 🚀

 


 

1. 이상치란 무엇인가요? 🤔

 

이상치(Outlier)대부분의 데이터 패턴과 크게 동떨어진 값을 의미합니다.

발생 원인은 다양합니다.

 

원인 설명
입력 오류 오타, 센서 오류 등
극단적 사건 금융 위기, 감염병 폭발 등
희귀 이벤트 드문 특수 사례 (ex. 100세 이상 고령자)
개인 특성 중증 질환자, 특수 환자군

 

👉 중요한 점 : 이상치는 무조건 제거할 대상이 아닙니다.

때로는 핵심 인사이트가 숨겨진 곳이기도 합니다.

 


 

2. 단변량(Univariable) 이상치 탐지 방법 📈

 

(1) 수치적 탐지

  • Z-Score 방법
    • 평균에서 몇 표준편차 떨어져 있는지 계산합니다.
    • 일반적으로 |Z| > 3이면 이상치로 간주합니다.
    • 정규분포 가정이 약하면 주의해야 합니다.
import numpy as np
import pandas as pd
from scipy.stats import zscore

# 예시 데이터 (혈압 데이터)
bp = np.array([120, 125, 130, 128, 122, 118, 250])  # 250은 이상치

# 데이터프레임 변환
df = pd.DataFrame({'bp': bp})

# Z-Score 계산
df['z_score'] = zscore(df['bp'])

# 이상치 여부
df['outlier'] = df['z_score'].abs() > 3

print(df)

 

  • IQR 방법
    • IQR 방법은 정규성을 가정하지 않고도 이상치를 탐지할 수 있습니다.
    • 사분위수(Q1, Q3)를 이용하여 IQR = Q3 - Q1 계산
    • 이상치 하한 = Q1 - 1.5 × IQR
    • 이상치 상한 = Q3 + 1.5 × IQR
# IQR 계산
Q1 = df['bp'].quantile(0.25)
Q3 = df['bp'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 이상치 여부
df['outlier_IQR'] = (df['bp'] < lower_bound) | (df['bp'] > upper_bound)

print(df)

 

(2) 시각적 탐지

  • Boxplot (상자그림) 📦

Boxplot.png

  • Scatter Plot (산점도) 🎯

Scatter Plot.png

  • Histogram, Density Plot 📊

Histogram + Density Plot.png

 

👉 빠르게 분포를 확인하고, 비정상적인 꼬리(Tail)나 도트(Outlier Point)를 식별할 수 있습니다.

 


 

3. 단변량 이상치 처리 방법 ✍️

 

(1) 제거(Remove)

  • 명백한 입력 오류나 잘못된 데이터는 제거합니다.

 

(2) 대체(Imputation)

  • 중앙값(Median)이나 KNN Imputer 등으로 대체합니다.
  • 그룹별 대체도 가능합니다 (예: 연령대별 중앙값).

 

(3) 변환(Transformation)

  • 로그(Log), 제곱근(Sqrt) 변환으로 이상치 영향을 완화합니다.

 

(4) 강건 모델 사용

  • 트리 기반 모델(Random Forest, XGBoost 등)을 사용하면 이상치 영향이 적습니다.

 


 

4. 전문가 협의 후 “정상”으로 판정된 이상치 처리 방법 🏥

 

실제 병원 환자 데이터를 분석할 때,

수치적으로는 이상치처럼 보여도

전문가 협의 결과 정상으로 판정되는 경우가 많습니다.

 

예시

  • 나이 95세 + 혈압 170 → 수치로는 이상하지만 고령 환자군에서는 정상 범위.

 

처리 방법 설명
데이터 유지 해당 환자 데이터를 삭제하지 않고 그대로 사용합니다.
메타데이터 추가 “전문가 검토 완료” 플래그를 추가합니다.
Feature 확장 고령 여부, 중증 기저질환 여부 등의 변수를 추가합니다.
모델 설계 조정 고령/질병군을 고려한 분석 설계(subgroup 분석)도 고려합니다.

 

✅ 중요한 것은 맥락(Context)을 반영하는 것입니다.

 


 

5. 다변량(Multivariable) 이상치 탐지 방법 🔎

 

Univariable 기준으로 튀어 보이는 값도,

Multivariable 관계 안에서는 정상적일 수 있습니다.

 

상황 설정

  • 환자 데이터 : 나이, 혈압, 혈당
  • 90세 고령 환자 혈압 170, 혈당 150
  • Univariable로 보면 이상치처럼 보임
  • 전문가 협의 결과 90세 고령 환자에서는 자연스러운 수준
     의학적으로 정상

 

“변수 간 관계를 무시하면 진짜 이상치를 놓칠 수 있습니다.”

 

 

주요 다변량 이상치 탐지 방법

 

(1) Mahalanobis Distance

  • 평균 벡터, 공분산 행렬을 고려하여 거리 계산
  • 거리값이 크면 다변량 이상치로 간주
  • 통계적으로 카이제곱 분포를 사용해 임계값 설정
from scipy.spatial.distance import mahalanobis
from numpy.linalg import inv

# 예시 데이터 (혈압, 혈당)
data = np.array([
    [120, 90],
    [125, 92],
    [130, 95],
    [128, 93],
    [122, 91],
    [118, 89],
    [250, 300]  # 이상치
])

df_mv = pd.DataFrame(data, columns=['bp', 'glucose'])

# 평균과 공분산 계산
mean_vec = df_mv.mean().values
cov_mat = np.cov(df_mv.values, rowvar=False)
inv_covmat = inv(cov_mat)

# Mahalanobis 거리 계산
df_mv['mahalanobis'] = df_mv.apply(lambda x: mahalanobis(x, mean_vec, inv_covmat), axis=1)

# 거리 기준 이상치 여부 (임계값은 데이터 상황에 따라 결정)
threshold = 3  # 예시
df_mv['outlier'] = df_mv['mahalanobis'] > threshold

print(df_mv)

 

 

(2) Robust Mahalanobis Distance

  • 이상치에 강건한(Robust) 공분산 추정을 사용
  • 데이터가 오염된 경우에도 안정적인 이상치 탐지 가능
import numpy as np
import pandas as pd
from sklearn.covariance import MinCovDet
from scipy.spatial.distance import mahalanobis

# 예시 데이터 (혈압, 혈당)
data = np.array([
    [120, 90],
    [125, 92],
    [130, 95],
    [128, 93],
    [122, 91],
    [118, 89],
    [250, 300]  # 이상치
])

df = pd.DataFrame(data, columns=['bp', 'glucose'])

# Robust 공분산 추정 (MCD)
mcd = MinCovDet().fit(df)

# Robust Mahalanobis Distance 계산
distances = mcd.mahalanobis(df)

# 결과 저장
df['robust_mahalanobis'] = distances

# 거리 기준 이상치 판단 (카이제곱 분포 기준 임계값 사용 가능)
threshold = np.percentile(distances, 97.5)  # 예: 상위 2.5% 거리
df['outlier'] = df['robust_mahalanobis'] > threshold

print(df)

 

(3) 머신러닝 기반

 - 🌲 Isolation Forest

  • 이상치는 다른 데이터보다 무작위 분할로 더 빨리 고립됩니다.
    * 랜덤으로 변수와 임계값 선택하여 데이터를 둘로 나누고, 또 랜덤으로 변수와 임계값 선택하여 나누고 반복
  • 고차원 데이터에서도 잘 작동합니다. 
    •  설명
      • contamination은 이상치 비율을 미리 알려주는 하이퍼파라미터입니다.
      • -1로 나온 샘플이 이상치입니다.
    • 🚫 주의
      • contamination 설정이 너무 높거나 낮으면 성능이 떨어질 수 있습니다.
  •  
from sklearn.ensemble import IsolationForest

# Isolation Forest 모델 훈련
iso_forest = IsolationForest(contamination=0.1, random_state=42)
df['anomaly_score'] = iso_forest.fit_predict(df[['bp', 'glucose']])

# -1: 이상치, 1: 정상
df['is_outlier'] = df['anomaly_score'] == -1

print(df)

 

 

🔍 One-Class SVM

  • 정상 데이터 주위에 경계를 학습하고, 그 바깥을 이상치로 간주합니다.
  • 비선형 경계도 학습할 수 있습니다.
  • 고차원, 복잡한 분포 데이터에 적합합니다.
    • 설명
      • nu: 이상치 비율에 대한 상한 (0 ~ 1)
      • gamma: 커널 함수의 스케일 (auto로 데이터 크기에 맞게 설정)
    • 🚫 주의
      • 스케일이 다른 변수들은 반드시 StandardScaler RobustScaler로 정규화한 뒤 사용해야 합니다.
from sklearn.svm import OneClassSVM

# One-Class SVM 모델 훈련
oc_svm = OneClassSVM(kernel='rbf', nu=0.1, gamma='auto')
df['ocsvm_score'] = oc_svm.fit_predict(df[['bp', 'glucose']])

# -1: 이상치, 1: 정상
df['is_outlier_ocsvm'] = df['ocsvm_score'] == -1

print(df)

 

💡 Autoencoder (Reconstruction Error)

  • Autoencoder는 입력을 압축했다가 복원하는 신경망입니다.
    * 원본 데이터를 요약(압축)하고 요약한 걸 가지고 다시 복원하는 과정에서 데이터 속에 숨어있는 중요 특징을 자동 학습하는 신경망
  • 정상 데이터는 복원 에러가 작고, 이상치는 복원 에러가 큽니다.
  • 복잡하고 고차원인 데이터에도 적용 가능합니다.
    •  설명
      • 정상 데이터는 재구성 오류(MSE)가 작음.
      • 이상치는 재구성 오류가 큽니다.
      • 오류가 큰 샘플을 이상치로 탐지합니다.
    • 🚫 주의
      • 학습 데이터에 심각한 이상치가 많으면 Autoencoder도 훈련이 왜곡될 수 있습니다.
      • 가능하면 “클린한” 학습 데이터셋을 사용하는 것이 좋습니다.
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense

# 데이터 정규화 (Autoencoder는 반드시 정규화 필요)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df[['bp', 'glucose']])

# Autoencoder 모델 구성
input_dim = X_scaled.shape[1]
encoding_dim = 2

input_layer = Input(shape=(input_dim,))
encoder = Dense(encoding_dim, activation="relu")(input_layer)
decoder = Dense(input_dim, activation="linear")(encoder)

autoencoder = Model(inputs=input_layer, outputs=decoder)

# 컴파일 및 학습
autoencoder.compile(optimizer='adam', loss='mse')
autoencoder.fit(X_scaled, X_scaled, epochs=100, batch_size=2, verbose=0)

# Reconstruction Error 계산
reconstructed = autoencoder.predict(X_scaled)
mse = np.mean(np.power(X_scaled - reconstructed, 2), axis=1)

# 이상치 여부 판단 (예: 상위 5% 이상을 이상치로 간주)
threshold = np.percentile(mse, 95)
df['reconstruction_error'] = mse
df['is_outlier_autoencoder'] = df['reconstruction_error'] > threshold

print(df)

 


 

6. Multivariable 이상치 처리 방법 ✍️

단계 방법
1 Mahalanobis Distance 또는 ML 기법으로 다변량 이상치 탐지
2 이상치 후보를 분리하여 전문가 협의
3 진짜 이상치 vs 정상적 특성 구분
4 제거, 변환, 별도 관리 등 상황에 맞는 전략 선택

 

 


 

7. 실무 적용 체크리스트 🛠️

체크포인트 설명
원인 분석 단순 입력 오류인지, 특수 사례인지 구분합니다.
맥락 고려 고령, 중증질환 등 특수 상황을 반영합니다.
문서화 이상치 탐지 및 처리 방법을 명시하고 재현 가능하게 합니다.
성능 비교 이상치 처리 전후 모델 성능 변화를 검토합니다.
전문가 협의 분석 방향성 결정 전에 반드시 전문가 의견을 듣습니다.

 

 

 


 

✨ 마무리

 

이상치는 제거 대상이 아니라, 이해하고 다루어야 할 존재입니다.

 

✅ 단변량 이상치 탐지는 기본,

✅ 전문가 협의로 맥락 반영,

✅ 다변량 구조까지 고려하는 것,

이것이 진짜 실무형 이상치 처리입니다. 🚀

 

“데이터를 정리하는 것은 세상을 더 잘 이해하려는 노력입니다.” 🌍