본문 바로가기

기타

정기 예금 가입 여부 예측 데이터 다루기

 

Pattern Recognition Course Project 중 데이터 전처리 과정 정리

과제는 주어진 데이터를 활용하여 고객의 신용카드 유무를 예측하는 것이다.


# 데이터 확인

실제 data set가 아닌 과제용 data set기에 약간 다르다. non-null 데이터만 있는 것 처럼 보이지만 아니다.

categorical data에 null data, 즉 결측치는 unknown이라는 label로 있다.

 

# numerical data(수치형 데이터)

describe는 numerical data만 보여준다.

 

 

 

# categorical data(범주형 데이터)

 

몇몇 unknown 데이터가 존재한다.

딱히 이상치를 보거나 할 게없으니 패스

 


# 데이터 전처리

# 상관관계

데이터간 상관관계를 확인한다.

상관관계가 높으면(독립 변수 간 선형 상관관계가 있으면) 다중공산성이 있다고 해서, 과적합 확률이 높아진다.

숫자가 높게 나오는 euribor3m, emp.var.rate데이터는 제외한다.

(후에 제외하지 않은 버전도 만들 수도)

# 1. 불필요한 컬럼 + 상관계수가 높은 컬럼 삭제
train.drop(['id','euribor3m', 'emp.var.rate'], axis=1 , inplace=True)

# 코드 나누기
num_data = train[train.select_dtypes(include=np.number).columns.tolist()]
cat_data = train[train.select_dtypes(exclude=np.number).columns.tolist()]

 

 

# numerical data(수치형 데이터)_수정전

 

수치형 변수의 경우 먼저 데이터가 정규 분포를 가지지 않으면 정규 분포 형태를 가지도록 로그변환을 해줄 것이다.

log변환은 큰 수를 작게 만들면서, 정규성을 높이고 정확한 값을 얻기 위해 사용한다.

단 학습 후 다시 해당 데이터에 대한 예측이 필요할 때는 값을 복원해야 한다.

(정규화 수정!)

 

1. age(정기 예금 가입 나이)

로그 변환 후 age data

 

boxplot으로 age 데이터의 분포와 이상치를 확인한다.

이상치를 찾는 방법에는 여러개가 있는데, 그중 IQR을 이용한다.

IQR은 간단하게 데이터를 4분위로 나눈 후 정렬하고, Q1~Q3의 범위를 말한다. IQR에서 1.5를 곱한 범위에서 벗어난 값을 이상치(Outlier)로 간주한다.

아래 그림에서 점이 이상치, 주황색 실선은 중앙값이다.

 

여기서 이상치로 분류된 data를 찾았다. 그러나 위 describe()를 보면, 이상치로 분류가 됐을 뿐 가능한 값임을 알 수 있다.

(98세도..예금을 가입할 수 있으니까..)

이 부분에서는 팀원들과 이야기를 했는데, 이상치로 간주하지 않기로 했다. 

모델 훈련시 이상치로 분류해 처리한 데이터 세트를 따로 만들어 볼 수도 있다.

# age 로그 변환
original_age= train['age']
train['age'] = np.log1p(train['age'])

# 불필요한 컬럼 삭제
train.drop(['id'], axis=1 , inplace=True)

데이터 변환과 불필요한 컬럼인 id만 삭제해주었다.

 

2. campaign(client 연락 수)

campaign 데이터는 describe()를 보면 마찬가지로 이상치로 간주하기에는 가능한 값들이라 애매하다.

평균은 작은 값이지만, 충분히 큰 값들도 가능하기 때문!

일단 그대로 뒀다. 로그 변환은 고민중....

 

3. pdays(마지막 campaign연락에서 지난 일수. 연락을 받은 적 없는 경우 999)

정규분포에서 크게 벗어남

오늘 연락을 받았다면 0이 가능하고, 999도 모두 가능하므로 이상치로 간주할 것이 없다.

 

 

4. previous(해당 client 이전 연락 수)

마찬가지로 이상치로 간주할 것이 없다. 로그 변환만 해줌.

 

5. employ.var.rate, cons.price.idx, cons.conf.idx, euribor3m, nr.employed(각종 지표들)

크게 이상 없음. 로그 변환만 실행해줌.

nem=['emp.var.rate', 'cons.price.idx', 'cons.conf.idx', 'euribor3m', 'nr.employed']

for i in range(5):
  train[nem[i]] = np.log1p(train[nem[i]])

 

# numerical data(수치형 데이터)_수정후

데이터에 음수값이 있다. 음수에는 로그를 취할 수 없으므로 MinMaxScaler를 적용하는 것으로 수정했다.

참고로 정규 변환을 취하면 Null값이 나온다...

MinMax와 Z-score 중 고민했다. MinMax는 동일한 척도로 잘 정규화 하지만 이상치에 민감하고, Z-score는 이상치에 강하지만 동일한 척도를 보장하지 못한다.

MinMax로 선택! 큰 이상치는 없다고 봤다.

# 정규화에 문제가 있다. 음수값이 있어 로그변환이 안되므로...MInMax or Z-xcore로 한다.
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
train[train.select_dtypes(include=np.number).columns.tolist()]=scaler.fit_transform(train[train.select_dtypes(include=np.number).columns.tolist()])
train

 

흑..음수를 생각 못했다

 

 

 

# categorical data(카테고리형 데이터)

일단 카테고리형 데이터의 결측치(unknown) 데이터를 어떻게 처리할 것인가?

일단 기본적으로 drop 혹은 최빈값으로 채운다. 일반적으로 결측치가 10%미만이면 이 방식으로 한다고 한다.

가장 결측치가 높은 edcation 데이터에서도 결측치가 10%미만으로 나왔다.

따라서 먼저 결측치를 최빈값으로 대체해줄 것이다.

 

ca_list = ['job', 'marital', 'education', 'default','housing','loan','contact','month','day_of_week','poutcome']

train = train.replace('unknown', train.mode().iloc[0])

예시로 job 데이터가 잘 대체된 것을 확인할 수 있다.

 

범주형 데이터는 크기를 줄이고 알고리즘 성능 향상을 위해 숫자형 데이터로 인코딩한다.

가장 흔하게 사용되는 원-핫 인코딩을 적용했다.

그러나 범주형 데이터가 많아 피처의 양이 많아지고, 트리계열에서 특히 인코딩 후 성능이 나빠지는 것을 고려하여 다른 버전 데이터로 만들 예정.

 

train = pd.get_dummies(train, dtype=int)
train

범주형 데이터들의 각 카테고리가 피처 이름이 되고, 1/0으로 변환 된 것을 확인 가능하다.

 

# 요약

너무 횡설수설 적은 것 같아서 요약함

 

1. 숫자형 데이터 상관관계 분석 후 제거(euribor3m, emp.var.rate)

2. 숫자형 데이터 이상치 분석, 큰 이상치가 보이지 않으므로 제거 X -> 숫자형 데이터 로그 변환(정규화)

3. 카테고리형 데이터 결측치 최빈값으로 대체

4. 카테고리형 데이터 원-핫 인코딩

 

# 데이터 추출

dataset = pd.concat([X_features,train.iloc[:, -1]], axis=1)

전처리한 X_features와 label 합치기

# dataset 저장
import pandas as pd
import os


dataset.to_csv("dataset1.csv")

이렇게 하면 연결한 Google Drive에 저장됨


# 임시 모델 돌리기

내 역할은 여기까지이지만 혹시나 데이터가 잘못됐을 가능성을 생각해서 모델을 돌려봤다.

XGBoost로 돌림.

 

# 데이터셋 분리

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X_features, y_labels,
                                                    test_size=0.2, random_state=0)

 

# 모델 학습

from xgboost import XGBClassifier
from sklearn.metrics import f1_score, roc_auc_score, confusion_matrix, recall_score, precision_score, accuracy_score

# X_train, y_train을 다시 학습과 검증 데이터 세트로 분리. (조기중단)
X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train,
                                                    test_size=0.3, random_state=0)

# n_estimators는 500으로, learning_rate 0.05, random state는 예제 수행 시마다 동일 예측 결과를 위해 설정. 
xgb_clf = XGBClassifier(n_estimators=500, learning_rate=0.05, random_state=156)

# 성능 평가 지표를 auc로, 조기 중단 파라미터는 100으로 설정하고 학습 수행. 
xgb_clf.fit(X_tr, y_tr, early_stopping_rounds=100, eval_metric='auc', eval_set=[(X_tr, y_tr), (X_val, y_val)])

xgb_roc_score = roc_auc_score(y_test, xgb_clf.predict_proba(X_test)[:, 1])
print('ROC AUC: {0:.4f}'.format(xgb_roc_score))

 

 

# 결과

# roc
xgb_roc_score = roc_auc_score(y_test, xgb_clf.predict_proba(X_test)[:, 1])
print('ROC AUC: {0:.4f}'.format(xgb_roc_score))

# f1-score
pred_probs = xgb_clf.predict(X_test)
pred = [ 1 if x > 0.5 else 0 for x in pred_probs ] # f1-score는 확률값으로 안됨
xgb_roc_score = f1_score(y_test, pred, average= 'micro')
print('F1: {0:.4f}'.format(xgb_roc_score))

맞겠지...?

끝!

 


# 추가(datawig)사용하기 도전..

datawig를 이용해서 결측값을 채워보기

datawig는 간단하게 결측값을 다른 features들의 비율을 분석하여 채워주는 것이다.

대규모 데이터에서는 느리고 1개의 변수에만 가능하다는 단점이 있지만, 결측치의 비율이 클수록 정확하다.

이 데이터는 결측치 비율이 그다지 큰 데이터는 아니지만..사용해보자.

먼저 pip install datawig를 통해 라이브러리를 설치해주자.

이때 pip 설치 오류가 날 수 있다. 그러면 git으로 직접 설치해야 함..

!git clone https://github.com/awslabs/datawig

cd datawig

pip install .

 

라이브러리를 사용하기 위해 이 unknown을 null값으로 바꿔주어야 한다.

# 3-1. datawig 이용
import numpy as np
cat_list = ['job', 'marital', 'education', 'default','housing','loan','contact','month','day_of_week','poutcome']
for i in cat_list: # 데이터 null값으로 변환
  X_features[i] = X_features[i].replace('unknown', np.NaN)
X_features['job'].isnull().sum() # 257

 

# datawig 사용하기
import datawig

imputer = datawig.SimpleImputer(input_columns=X_features.columns,
                                output_column='job')
imputer.fit(train_df=X_features, num_epochs=50)
null_train = X_features[X_features['job'].isnull()]
null_imputed = imputer.predict(null_train)
imputed_train = pd.DataFrame(null_imputed)

imputed_train.value_counts()

 

아 numpy 버전이랑 충돌하는 것 같은 문제가...

numpy를 다시 하려해도 colab이 말썽..;; anaconda나..다른 걸로 시도해 봐야할듯;


# 회고

 

원래 최빈값 말고 datawig라이브러리를 사용하기..다양한 시각화 어쩌고 도전할 예정이였는데 할 일이 넘 많다..^^

급한 일 끝나면 다시..!

나중에 시간이 있으면 다양한 모델과 파라미터, 데이터셋을 이용해서 추가로 돌려볼것이다!!꼭..!