데이터 전처리시 이상치 제거하기

2021-07-29

.

Data_Preprocessing_TIL(20210729)

[학습자료]

패스트캠퍼스 온라인 강의 “파이썬을 활용한 데이터 전처리 Level UP 올인원 패키지 Online.” 를 공부하고 정리한 내용입니다.

URL : https://fastcampus.co.kr/data_online_preprocess

[학습내용]

  • 이상치 데이터란

변수범위에서 많이 벗어난 아주 작은 값이나 아주 큰 값으로, 일반화된 모델을 생성하는데 악영향을끼치는 값으로 이상치를 포함하는 레코드를 제거하는 방법으로 이상치를 제거해야 한다.

절대 추정의 대상이 아님에 주의하자.

이상치는 일반적으로 1프로 미만으로 분포되어 있다.

1

  • 이상치 데이터여부 판단 방법 1. IQR 규칙 활용

변수별로 IQR 규칙을 만족하지 않는 샘플들을 판단하여 삭제하는 방법으로 가장 많이 쓰이는 방법중에 하나이다.

직관적이고 사용이 간편하다는 장점이 있지만, 단일변수로 이상치를 판단하기 어려운 경우가 있다는 문제가 있음

다시말해서 이 IQR 기준으로만 갖고는 이상치라고 단언할 수는 없다는 것이다.

2

  • 자주쓰이는 함수 : numpy.quantile

array의 q번째 quantile을 구하는 함수

주요입력

a : input array (list, ndarray, array 등)

q : quantile (0과 1사이)

  • 이상치 데이터여부 판단 방법 2. 밀도기반 군집화 수행

말그대로 데이터의 밀도를 기반으로 군집을 만드는 방법이다. 이 방법의 대표 알고리즘이 DBSCAN이다.

디비스캔에서는 앱실론이라고 부르는 반경내에 특정갯수의 샘플이 들어오면 이 샘플들을 중심점이라고 부르고 그리고 중심점은 아니지만 그 중심점의 경계안에 들어온 샘플을 경계점이라고 부른다. 그리고 중심점도 아니고 경계점도 아닌 점들은 이상치라고 판단한다.

3

다만 DBSCAN 등의 밀도 기반 군집화 모델의 파라미터 튜닝이 쉽지 않다는 단점이 있음

  • 자주쓰이는 함수 : sklearn.cluster.DBSCAN

DBSCAN 군집화를 수행하는 인스턴스를 생성하는 함수

주요입력

eps : 이웃이라고 판단하는 반경

min_samples : 중심점이라 판단하기 위해, eps 내에 들어와야 하는 최소 샘플 수

metric : 사용하는 거리 척도

주요 attribute

.labels_ : 각 샘플이 속한 군집 정보 (-1 : 이상치)

  • 실습

실습 1. 이상치 탐색 및 제거 : IQR Rule 활용

import os
import pandas as pd

os.chdir(r"C:/Users/user/Desktop/aa/5. 머신러닝 모델의 성능 향상을 위한 전처리\데이터")

df = pd.read_csv("glass.csv")
df
RI Na Mg Al Si K Ca Fe Glass_type
0 1.51766 13.21 3.69 1.29 72.61 0.57 8.22 0.00 1
1 1.51742 13.27 3.62 1.24 73.08 0.55 8.07 0.00 1
2 1.51756 13.15 3.61 1.05 73.24 0.57 8.24 0.00 1
3 1.51918 14.04 3.58 1.37 72.08 0.56 8.30 0.00 1
4 1.51755 13.00 3.60 1.36 72.99 0.57 8.40 0.11 1
... ... ... ... ... ... ... ... ... ...
209 1.51653 11.95 0.00 1.19 75.18 2.70 8.93 0.00 7
210 1.51514 14.85 0.00 2.42 73.72 0.00 8.39 0.00 7
211 1.51658 14.80 0.00 1.99 73.11 0.00 8.28 0.00 7
212 1.51732 14.95 0.00 1.80 72.99 0.00 8.61 0.00 7
213 1.51831 14.39 0.00 1.82 72.86 1.41 6.47 0.00 7

214 rows × 9 columns

# 특징과 라벨 분리
X = df.drop(['Glass_type'], axis = 1)
Y = df['Glass_type']
# 학습 데이터와 평가 데이터 분리
from sklearn.model_selection import train_test_split
Train_X, Test_X, Train_Y, Test_Y = train_test_split(X, Y)
Train_X.shape
(160, 8)
import numpy as np
def IQR_rule(val_list): # 한 특징에 포함된 값 (열 벡터)
    # IQR 계산    
    Q1 = np.quantile(val_list, 0.25)
    Q3 = np.quantile(val_list, 0.75)
    IQR = Q3 - Q1
    
    # IQR rule을 위배하지 않는 bool list 계산 (True: 이상치 X, False: 이상치 O)
    not_outlier_condition = (Q3 + 1.5 * IQR > val_list) & (Q1 - 1.5 * IQR < val_list)
    return not_outlier_condition
# apply를 이용하여 모든 컬럼에 IQR rule 함수 적용
conditions = Train_X.apply(IQR_rule) 
conditions
RI Na Mg Al Si K Ca Fe
0 True True True True True True True True
98 True False True True True True True True
148 True True True True True True True True
17 True True True False True True True True
102 True True True False True True True True
... ... ... ... ... ... ... ... ...
212 True True True True True True True True
133 True True True True True True True True
193 True True True True True True True False
197 True True True True True True True True
123 True True True True True True True True

160 rows × 8 columns

# 하나라도 IQR 규칙을 위반하는 요소를 갖는 레코드를 제거하기 위한 규칙
total_condition = conditions.sum(axis = 1) == len(Train_X.columns)
total_condition
0       True
98     False
148     True
17     False
102    False
       ...  
212     True
133     True
193    False
197     True
123     True
Length: 160, dtype: bool
# 이상치 제거
Train_X = Train_X.loc[total_condition]
Train_X
RI Na Mg Al Si K Ca Fe
0 1.51766 13.21 3.69 1.29 72.61 0.57 8.22 0.00
148 1.51409 14.25 3.09 2.08 72.28 1.10 7.08 0.00
155 1.51655 12.75 2.85 1.44 73.27 0.57 8.79 0.22
176 1.51662 12.85 3.51 1.44 73.01 0.68 8.23 0.25
124 1.51775 12.85 3.48 1.23 72.97 0.61 8.56 0.22
... ... ... ... ... ... ... ... ...
100 1.51645 14.94 0.00 1.87 73.11 0.00 8.67 0.00
212 1.51732 14.95 0.00 1.80 72.99 0.00 8.61 0.00
133 1.51926 13.20 3.33 1.28 72.36 0.60 9.14 0.11
197 1.51829 14.46 2.24 1.62 72.38 0.00 9.26 0.00
123 1.51747 12.84 3.50 1.14 73.27 0.56 8.55 0.00

115 rows × 8 columns

Train_X.shape 
(115, 8)
# 이상치의 비율이 거의 30퍼 가까이 되는데 이상치가 30프로라는거는 이는 사실 말이 안되고,
# 일반적으로는 이상치는 1프로 미만이다.
# 위에서 (Q3 + 1.5 * IQR > val_list) & (Q1 - 1.5 * IQR < val_list)로 IRQ를 계산했는데
# 1.5라는 숫자가 공식적으로 지정된 숫자는 아니어서 이를 조절해도 무방한데
# 1.5를 높이는 걸로 조정하면 이상치의 비율이 떨어질것이다.
# 이런식으로 해서 이상치를 1프로 미만으로 낮춰서 조정해주면 된다.
45/160
0.28125

이상치 탐색 및 제거 실습 2. DBSCAN 활용

import pandas as pd
import os
import numpy as np

df = pd.read_csv("glass.csv")
df
RI Na Mg Al Si K Ca Fe Glass_type
0 1.51766 13.21 3.69 1.29 72.61 0.57 8.22 0.00 1
1 1.51742 13.27 3.62 1.24 73.08 0.55 8.07 0.00 1
2 1.51756 13.15 3.61 1.05 73.24 0.57 8.24 0.00 1
3 1.51918 14.04 3.58 1.37 72.08 0.56 8.30 0.00 1
4 1.51755 13.00 3.60 1.36 72.99 0.57 8.40 0.11 1
... ... ... ... ... ... ... ... ... ...
209 1.51653 11.95 0.00 1.19 75.18 2.70 8.93 0.00 7
210 1.51514 14.85 0.00 2.42 73.72 0.00 8.39 0.00 7
211 1.51658 14.80 0.00 1.99 73.11 0.00 8.28 0.00 7
212 1.51732 14.95 0.00 1.80 72.99 0.00 8.61 0.00 7
213 1.51831 14.39 0.00 1.82 72.86 1.41 6.47 0.00 7

214 rows × 9 columns

# 특징과 라벨 분리
X = df.drop(['Glass_type'], axis = 1)
Y = df['Glass_type']
# 학습 데이터와 평가 데이터 분리
from sklearn.model_selection import train_test_split
Train_X, Test_X, Train_Y, Test_Y = train_test_split(X, Y)
Train_X.shape
(160, 8)
# cdist를 불러온 이유는 DBSCAN의 파라미터를 조절을 하는데 사용되기 때문이다.
# 앱실론이라는 것은 결국 거리일텐데 이 거리라는게 데이터마다 차이가 클수도 있다.
# cdist를 이용하면 쉽게 데이터간에 거리를 구할 수 있다.
from scipy.spatial.distance import cdist
from sklearn.cluster import DBSCAN
# Train_X와 Train_X 거리 행렬계산 => DBSCAN의 파라미터를 설정하기 위함
DM = cdist(Train_X, Train_X)
DM
array([[0.        , 0.70213971, 0.31128829, ..., 3.64593016, 0.79968827,
        0.95530139],
       [0.70213971, 0.        , 0.54781482, ..., 3.31397179, 0.39534864,
        0.69209942],
       [0.31128829, 0.54781482, 0.        , ..., 3.45168131, 0.65802976,
        0.75039993],
       ...,
       [3.64593016, 3.31397179, 3.45168131, ..., 0.        , 3.54681744,
        3.59279877],
       [0.79968827, 0.39534864, 0.65802976, ..., 3.54681744, 0.        ,
        0.72684527],
       [0.95530139, 0.69209942, 0.75039993, ..., 3.59279877, 0.72684527,
        0.        ]])
# 샘플 간 거리의 10% quantile이 0.6692정도임을 확인
# 여기서 10%는 작은값을 기준으로 상위 10%를 말하는 것임
# 작은값을 기준으로 상위 10%의 거리가 0.6692정도 라는 것이다.
# 0.6692라고 나온값은 단순히 참고하는 수치이고 이게 정확한 기준은 아니다.
np.quantile(DM, 0.1) 
0.6692467740466362
cluster_model = DBSCAN(eps = 0.67, min_samples = 3).fit(Train_X)
print(sum(cluster_model.labels_ == -1))
# 34개가 이상치로 판단 => 이정도면 너무 많은 양이 아닌가?? => 파라미터 조정
34
cluster_model = DBSCAN(eps = 2, min_samples = 3).fit(Train_X)
# 앱실론을 크게 늘리면 당연히 반경내에 샘플이 많이 들어갈 가능성이 높다. 
# 그래서 이상치라고 판단되는 데이터의 수도 줄어들 것이다.
# 13개 정도면 괜찮은 양이라고 판단하여 삭제 수행
print(sum(cluster_model.labels_ == -1)) 
13
Train_X = Train_X[cluster_model.labels_ != -1]
Train_X
RI Na Mg Al Si K Ca Fe
114 1.51761 12.81 3.54 1.23 73.24 0.58 8.39 0.00
11 1.51720 13.38 3.50 1.15 72.85 0.50 8.43 0.00
28 1.51824 12.87 3.48 1.29 72.95 0.60 8.43 0.00
105 1.51651 14.38 0.00 1.94 73.61 0.00 8.48 0.00
120 1.51764 12.98 3.54 1.21 73.00 0.65 8.53 0.00
... ... ... ... ... ... ... ... ...
76 1.51665 13.14 3.45 1.76 72.48 0.60 8.38 0.17
106 1.51711 14.23 0.00 2.08 73.36 0.00 8.62 0.00
67 1.52020 13.98 1.35 1.63 71.76 0.39 10.56 0.18
41 1.51646 13.41 3.55 1.25 72.81 0.68 8.10 0.00
164 1.51847 13.10 3.97 1.19 72.44 0.60 8.43 0.00

147 rows × 8 columns

Train_X.shape
(147, 8)