Day to_day

[데이터 전처리] Feature Engineering의 종류와 구현하기! (이상치제거, 결측치 처리, log 변환) 본문

Machine Learning/머신러닝 기초

[데이터 전처리] Feature Engineering의 종류와 구현하기! (이상치제거, 결측치 처리, log 변환)

m_inglet 2023. 3. 22. 22:32
728x90
반응형

본 포스팅은 권철민 선생님의 '파이썬 머신러닝 완벽가이드' 강의와 '파이썬 라이브러리를 활용한 머신러닝' 서적을 기반으로 개인적인 정리 목적 하에 재구성하여 작성된 글입니다.

 

 

포스팅 개요

Feature Engineering이란 무엇인지, 그리고 어떤 기법들이 있는지에 대해서 살펴보고 이번 포스팅에서는 Feature Engineering 중에서 이상치 제거, 결측치 처리, log 변환에 대해서 알아보고, 실습코드를 구현해 보겠다.

 

 

Feature Engineering이란?

Feature Engineering은 모델 정확도를 높이기 위해서 주어진 데이터를 예측 모델의 문제를 잘 표현할 수 있는 features로 변형시키는 과정이다.

 

Feature Engineering의 종류

여러 가지가 존재하지만 그중에 내가 살펴볼 것들로 정렬해 봤다.

  • 이상치 제거
  • 누락된 값 처리 (결측치 처리)
  • 중요 feature의 데이터 분포도 변경 - log 변환
  • 스케일링
  • 범주형 데이터 변환 - label 인코딩
  • 오버 샘플링, 언더 샘플링

 

 

먼저 Feature Engineering을 해야 하는 이유?

1. 전처리 과정을 통해 덜 복잡한 모델을 써도 좋은 결과를 얻을 수 있다.

2. 가장 최적화된 하이퍼 파라미터를 찾으려고 노력하지 않아도 된다.

3. 더 나은 feature가 더 나은 결과를 가져온다.

 

 

이상치 제거

이상치(Outliers)는 일반적인 추세를 따르지 않는 데이터 포인트들이다.

많은 알고리즘은 이상치에 민감한 경향이 있어 처리를 해주어야 한다. 이상치를 확인하기 위해서는 Scatterplot, 히스토그램, boxplot 등을 이용해서 이상치를 시각화하고, 찾을 수 있다. 

이상치의 수가 많지 않으면 제거해 버려도 상관없다.

 

  • Q1 제1 사분위수 : 전체 데이터를 작은 값에서 큰 값으로 나열했을 때 전체 데이터의 25% 지점은 1 사분위수
  • Q2 제2 사분위수 : 2 사분위수를 기준으로 데이터의 50%가 상위, 나머지 50%가 하위에 분포되어 있음을 의미
  • Q3 제3 사분위수 : 전체 데이터의 75% 지점
  • IQR : 상자의 길이 (Q3에서 Q1을 뺀 값)
  • 최댓값 : 3 사분위수에서 IQR의 1.5배만큼 떨어진 지점
  • 최솟값 : 1 사분위수에서 IQR의 1.5배만큼 떨어진 지점
  • 이상치 : 최댓값과 최솟값을 넘어서는 지점 (우리가 제거해야 할 타깃!)

 

[예제 코드]

import numpy as np

def get_outlier(df=None, column=None, weight=1.5):
    # fraud에 해당하는 column 데이터만 추출, 1/4 분위와 3/4 분위 지점을 np.percentile로 구하기
    fraud = df[df['Class']==1][column]
    quantile_25 = np.percentile(fraud.values, 25) # 백분위 25에 해당하는 부분의 값이 들어감
    quantile_75 = np.percentile(fraud.values, 75) # 백분위 75에 해당하는 부분의 값이 들어감
    # IQR을 구하고, IQR에 1.5를 곱하여 최대값과 최소값 지점 구함
    iqr = quantile_75 - quantile_25
    iqr_weight = iqr*weight
    lowest_val = quantile_25 - iqr_weight
    hightest_val = quantile_75 + iqr_weight
    
    # 최대값 보다 크거나, 최소값보다 작은 값을 아웃라이어로 설정하고 dataframe index 반환
    outlier_index = fraud[(fraud < lowest_val) | (fraud > hightest_val)].index
    return outlier_index

 

좀 더 자세히 살펴보자면, 분류해야 할 라벨 데이터 (타깃 데이터)와 가장 상관관계가 높은 칼럼을 찾아서 이상치를 제거할 것이다. corr() 함수를 이용하여서 '신용카드 사기 데이터'에서 'V14'의 칼럼을 가지고 이상치를 살펴보고, 제거할 것이다.

 

fraud = credit_pre_df[credit_pre_df['Class']==1]['V14']
# column에 있는 모든 value값을 가지고 퍼센테이지로 나누기
np.percentile(fraud.values, 25) # precentile(values, q=퍼센트)
# -9.692722964972386

'Class' = 1인 데이터 중 'V14' 칼럼에서 numpy의 percentile 함수를 이용해서 25% 지점에 해당하는 데이터 값을 가져온다.

 

# 이상치 데이터 인덱스 뽑기
outlier_index = get_outlier(credit_pre_df, 'V14', weight=1.5)
print(outlier_index)

get_outlier 함수를 실행하면 index의 값이 나온다.

Int64Index([8296, 8615, 9035, 9252], dtype='int64')

 

drop으로 해당 데이터를 제거해 준다.

outlier_index = get_outlier(credit_pre_df, 'V14', weight=1.5)
credit_pre_df.drop(outlier_index, axis=0, inplace=True)

 

 

 

결측치 처리

결측치는 머신러닝 모델 성능에 영향을 준다. 그래서 다양한 방법으로 결측치를 처리해 줄 수 있다.

  • 70% 이상의 결측치가 있는 행 또는 열은 지워주는 게 좋다.
  • 숫자형 : 결측치를 0이나 중앙값 또는 평균으로 대체
  • 범주형 : 열의 가장 많이 발생한 값으로 결측치를 대체, 만약 많이 발생한 값이 없으면 ‘other’로 대체한다.
  • 랜덤으로 대체한다.
  • 결측치를 mean+3*std로 대체한다.

 

이번엔 타이타닉 데이터를 가지고 null값 처리를 해보겠다.

# 타이타닉 데이터
# null 확인
titanic.isnull().sum()

[출력 결과]

PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

'Age', 'Cabin', 'Embarked' 칼럼에 null 값이 존재하는 것을 볼 수 있다.

Age는 평균으로 처리해 줄 수 있지만 Cabin과 Embarked는 범주형 데이터이고, 아래와 같이 다양한 분포를 띄고 있기 때문에 'NaN'으로 처리해주었다.

위쪽은 Cabin의 분포, 아래는 Embarked의 분포

# null 처리
# Age는 평균으로 처리, Cabin과 Embarked는 NaN으로 새로운 값으로 분류
titanic['Age'].fillna(titanic['Age'].mean(), inplace=True)
titanic['Cabin'].fillna('NaN', inplace=True)
titanic['Embarked'].fillna('NaN', inplace=True)
titanic.isnull().sum()

[출력 결과]

PassengerId    0
Survived       0
Pclass         0
Name           0
Sex            0
Age            0
SibSp          0
Parch          0
Ticket         0
Fare           0
Cabin          0
Embarked       0
dtype: int64

 

 

 

Log 변환

log 변환은 편향된 분포도를 가진 데이터 세트를 비교적 정규분포에 가깝게 변환해 주는 훌륭한 Feature Engineering 방식이다.

편향된 분포라는 것은 무엇일까?

아래의 그래프에서 첫 번째의 경우 오른쪽으로 치우쳐진 분포를 띄고 있고, 세 번째는 왼쪽으로 치우쳐진 분포를 띄고 있다.

그러면 왜 이렇게 편향된 분포를 갖는 것이 좋지 않은 건가? 편향된 것은 값들의 차이가 많이 난다는 뜻이기도 하고, 그 값의 차이가 커지면 더 분석하기가 어려워진다. 그래서 log를 씌워서 큰 수를 같은 비율의 적은 수로 바꿔주는 것이다. 그러면 복잡한 계산을 쉽게 할 수 있게 된다.

일단 코드로 보자. 

신용카드 사기 거래 데이터를 가지고 분석했다.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

card_df = pd.read_csv('../data/creditcard.csv')
plt.figure(figsize=(8,4))
plt.xticks(range(0,30000,1000), rotation=60)
sns.histplot(card_df['Amount'], bins=100, kde=True)
plt.show()

데이터 분포를 보면 소액 결제는 자주 하지만 거액은 자주 하진 않으니 당연히 작은 금액에 값이 몰려있다. 

위의 데이터가 편향된 분포도를 갖고 있는 것이다.

그러면 log 변환으로 정규 분포로 변환해 보겠다.

 

def get_preprocessed_df(df=None):
    df_copy = df.copy()
    # 넘파이의 log1p()를 이용하여 Amount를 로그 변환
    amount_n = np.log1p(df_copy['Amount'])
    # array.insert(삽입할 index,'column name', item)
    df_copy.insert(0, 'Amount_Scaled', amount_n)
    df_copy.drop(['Time', 'Amount'], axis=1, inplace=True)
    return df_copy

로그 변환은 numpy의 log1p를 이용해 변환한 다음 변환한 값을 'Amount_Scaled'로 삽입하고, 필요 없는 'Time'과 'Amount' 칼럼은 지워주었다.

 

여기서 log1p는 무엇인지 짚고 넘어가야겠다.

 

log1p가 무엇인가?

log(x+1)이라고 생각하면 된다. 이렇게 사용하는 이유는 x가 아주 작은 값일 때 컴퓨터 상에서 그 값을 0으로 판단할 수 있다. 그러면 log(0)은 무한대이므로 +1을 시켜서 그 값을 0으로 만들어 준다.

log1p와 expm1는 한쌍으로 같이 사용할 수 있는데 log1p로 변환하면 expm1으로 복원할 수 있다.

 

변환한 결과는 아래와 같다.

log 변환 후 분포

 

 

포스팅이 길어져 다음 포스팅에서는 스케일링, label 인코딩, 오버샘플링/언더샘플링에 대해서 알아보고자 한다.

 

 

728x90
반응형
Comments