관리 메뉴

꿈 많은 사람의 이야기

[5주차] 새벽 5시 캐글(kaggle) 필사하기 - home credit 데이터 편 - 1 본문

kaggle(캐글)

[5주차] 새벽 5시 캐글(kaggle) 필사하기 - home credit 데이터 편 - 1

이수진의 블로그 이수진의 블로그 2019.02.03 16:26

안녕하세요! 이 글을 쓰는 현재 설 연휴가 시작되었네요

모두들 새해 복 많이 받으세요!

올 한해 건강하시고 하는 일 다 잘 되시길 바랍니다!


어느덧 새벽 5시 캐글 필사 5주차입니다.

벌써 5주차네요

이제 3번째 주제로 넘어갔습니다.

처음은 타이타닉, 두 번째는 porto 데이터였습니다.

이제는 home credit 대회를 기준으로 진행해보려고 합니다.

근데 여기까지 하면서 느낀점이 있다면 캐글 필사를 하면서 진짜 하나를 제대로 해야겠다는 느낌이 드네요

계속 주제는 넘어가지만 porto 데이터와 타이타닉 데이터는 계속 중간중간 복습을 하고 있습니다.

안그러면 계속 까먹네요 ㅠ


아무튼 시작하겠습니다.




이 데이터 셋의 배경은 위 설명과 같습니다.

신용기록이 없는 사람들에게도 대출이 가능하도록 이 사람이 상환 능력이 되는가? 되지 않는가?의 예측 모델을 만드는 것이죠

꽤나 재밌는 주제이죠?

근데 이 데이터는 좀 복잡합니다.

컬럼도 복잡하고, 양도 많고 꽤나 쉽지 않은 주제입니다.

오늘은 그래서 잘 나오는 예측 모델을 만들기보다 EDA를 중심으로 진행하려고 합니다.

필사한 캐글 커널은 위에 나와있습니다.


그리고 여기서는 ROC, AUC를 이용합니다.

이것을 이용하려면 혼동 행렬(confusion matrix)를 이해해야 합니다.

흔히 말하는 TP, TN, FP, FN 부터 precision(정밀도), recall(재현율)을 알아야하죠.


TP : '정답' 라벨에서 '정답'이라고 예측한 애들

TN : '정답 아님' 라벨에서 '정답 아님' 이라고 예측

FP : '정답 아님' 라밸인데 '정답' 이라고 예측한 것

FN : '정답' 라벨인데 '정답 아님' 이라고 예측한 것


precision(정밀도) : TP / TP + FP 즉, 정답이라고 예측한 것 중에서 실제 정답인 애들의 비율입니다.

recall : TP / FN + TP 즉, 실제 정답 라벨인 애들 중에서 정답이라고 예측한 비율입니다.


흔히 accuracy(정확도)로 쓰는 것은 여기서 TP + TN / TP + TN + FP +FN을 뜻하죠

f1-score도 여기서 구할 수 있는데요. f1-score는 2 * precision * recall / precision + recall 이 되겠습니다.


그리고 참 양성 비율(TPR)도 있는데요. 이것은 TP / TP + FN입니다.

거짓 양성 비율(FPR)은 FP / FP + TN을 뜻합니다.


이제 여기서 정밀도-재현율 곡선이 있는데요

x축을 정밀도, y축을 재현율을 두고 어느때에 정확도가 좋은지를 보는 것입니다.

그리고 정밀도와 재현율을 서로 상충됩니다. 하나가 높아지면 하나는 낮아지는 구조이죠.



이것이 정밀도-재현율 곡선인데요. 곡선이 오른쪽 위로 갈 수록 좋은 분류기입니다. 오른쪽 위 지점(동그라미 말고)은 정밀도와 재현율이 둘 다 높은 곳입니다. 검은색 원은 predict 메서드를 호출할 때의 임계값 지점입니다.



이거는 로지스틱 회귀(logistic regression)과 랜덤 포레스트(random forest)를 비교해서 본 정밀도-재현율 곡선인데요

여기서 logistic regression이 더 위에 있으니까 random forest보다 더 낫다고 해석할 수 있습니다.

만약 단순히 정확도(accuracy)와 f1-score로만 봤으면 이런 세세한 것들을 놓쳤겠죠.



ROC곡선은 x축을 거짓 양성 비율(FPR)과 y축을 참 양성 비율(TPR, 재현율)로 둬서 보는 것입니다.

이거는 왼쪽 위로 갈 수록 좋은 것입니다. FPR이 낮게 유지 되면서 TPR이 높으면 그게 좋은 것이죠!

그리고 곡선 아래 면적 값을 AUC라고 해서 roc_auc_score를 이용할 수 있습니다.



이제 본격적으로 시작해봅니다.

import를 통해서 numpy, matplotlib.pyplot, pandas, seaborn과 warnings를 가져옵니다.

그리고 나중에 쓸 LabelEncoder를 위해서 sklearn.preprocessing에서 LabelEncoder를 가져옵니다. Imputer도 미리 가져오면 좋습니다.



데이터를 가져오고 shape를 보니까 컬럼이 122개입니다.

훈련 데이터에서 데이터의 개수는 30만개 정도네요



test도 마찬가지지만 target이 없기에 121개의 컬럼을 가지고 있습니다.



EDA(Exploratory Data Analysis)를 진행해봅니다.

먼저 target data를 좀 봐보면 이것도 unbalance한 데이터입니다.

지난 porto 데이터 셋도 그랬지만 이번에도 역시 unbalance합니다.




그리고 null값의 비율과, 개수를 체크하기 위한 함수를 하나 만듭니다.

df.isnull().sum()을 통해서 null의 개수를 파악하고

pandas concat을 이용해서 개수와, percent를 합쳐줍니다.

그 다음 dataframe.rename(columns = 를 통해서 컬럼 이름을 고쳐줍니다



그리고 나서 데이터를 넣고 보면 이렇게 개수와, 비율을 볼 수 있죠.

많은 것은 거의 70%가 null 값이네요. 이 정도면 못쓸 것 같은데요 ㅎㅎ




자 다음으로는 dtype에 대해서 살펴봅니다. 

dataframe.dtypes.value_counts()를 통해 보면 type에 따른 개수를 볼 수 있습니다.

그리고 dataframe.select_dtype('object').apply(pd.Series.nunique, axis = 0)을 하면 object 타입중에서

unique한 개수를 파악할 수 있습니다.

name_contract_type을 보니까 유니크 값이 2개네요



그리고 이제 인코딩을 해줍니다.

만약 그 유니크한 개수가 2개 이하이면(len(list(train[col].unique())) sklearn의 labelencoder로 encoding을 해줍니다.

그리고 그게 아닌 애들은 pd.get_dummies를 통해서 one-hot을 만들어줍니다.

근데  shape를 보니까 train과 test의 개수가 많이 달라졌습니다.

train에는 값이 더 있을 수 있으니까 get_dummies 과정을 거치면서 컬럼이 더 생겼을 수도 있죠



이렇게 커럼의 개수가 다릅니다



그래서 새롭게 align을 해줍니다. app_train['TARGET']을 통해서 target 데이터를 미리 빼놓고

train, test = train.align(test, join='inner', axis = 1)을 통해서 train과 test를 test를 기준으로 align해줍니다.



다음으로 이상치를 탐지해주는데요.

가장 기본적인 이상치 탐지 방법은 describe를 통해서 데이터를 확인해보는 것입니다.




이렇게 확인하다보면 DAYS_EMPLOYED 값이 뭔가 이상합니다.




train['DAYS_EMPLOYED'].plot.hist()를 통해 보니까 확실히 이상하네요.

365243이라는 값들이 있습니다.



그래서 이 365243인 값들을 좀 빼와서 target과의 연관성을 보니까 이상치 없는 애들보다 laon 평균이 더 낮습니다.

즉 상환율이 더 좋다는 거죠.

뭔가 유의미한 데이터입니다.

그래서 이 이상치들은 따로 없애지는 않아도 될 것 같습니다



그래서 이상치 값들을 DAYS_EMPLOYED_ANOM 컬럼으로 따로 빼줍니다.

그리고 replace를 통해서 365243인 값들을 np.nan으로 넣어주고 hist를 통해서 보면

이제서야 제대로 된 히스토그램을 볼 수 있습니다.



다음으로는 연관성 correlation을 보겠습니다




correlation은 pandas에서 .corr()을 통해 가져올 수 있고 ['TARGET']처럼 붙이면 target과 연관성을 따로 가져옵니다.

그리고 긍정 연관성과, 부정 연관성을 따로 봅니다.

긍정 연관성에서는 DAYS_BIRTH, DAYS_EMPLOYED 등이 있네요

부정 연관성에서는 EXT_SOURCE_1, 2, 3 등이 있습니다




근데 긍정 연관성인 DAYS_BIRTH값이 현재 음수로 되어 있어서 이걸 절댓값을 취하고 다시 보니까

negative corrlation을 띄는 것을 다시 볼 수 있습니다.



그리고 절대값으로 바꾼 뒤 plt.hist()를 통해서 보니까 연령 대 별 빈도수를 볼 수 있습니다.



이제 연령대 별 상환 능력을 보려고 하는데요

연령이 낮을 수록 오히려 상환 능력이 떨어지는 것을 볼 수 있습니다. 

이러한 그래프를 확인하기 위해서 sns.kdeplot(train.loc[train['target'] == 0, 'DAYS_BIRTH']와 같이 sns.kdeplot을 이용해서 볼 수 있습니다.

좀 더 제대로 보기 위해서 구간을 분할해서 확인해보죠



pandas의 cut을 이용해 pd.cut(age_data['YEARS_BIRTH'], bins = np.linspace(20, 70, num = 11)을 통해 10개의 구간으로 나눠봅니다.



그리고 YEARS_BINNED를 groupby를 해서 각 그룹별로 mean을 해줍니다. 

이렇게 하면 각 구간 별 TARGET의 비율을 확인할 수 있습니다.

이제 이걸 plt.plot(age_groups.index.astype(str), 100 * age_groups['TARGET'])을 통해서 비율로써 보면



확실히 젊은 연령대가 더 낮은 상환 능력을 가지고 있다는 것을 볼 수 있습니다!

0은 상환, 1은 상환 실패를 뜻하니까요.



그리고 아까 negative corrleation에서 EXT_SOURCE_1, 2, 3 등이 있었습니다. 얘내를 한 번 살펴보죠



얘내들을 따로 뽑아서 sns.heatmap을 통해 살펴봅니다. source_1과 DAYS_BIRTH가 연관성이 좀 높네요?



그리고 for i, source in enumerate(['EXT_SOURCE_1'..])등을 통해서

plt.subplot(3, 1, i+1)

sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, source], label = 'target == 0') 이런 식으로 이 EXT 데이터를 그래프로 그려줍니다.

target에 따라서 각 column이 어떤 모양이 나오는 지 확인할 수 있습니다.

여기서 보면  EXT_SOURCE_1, EXT_SOURCE_3이 좀 뭔가 target에 따른 영향이 있는 것 같이 보이게 됩니다.



이제 feature engineering인데요. 이 부분을 쓸까 말까 하다가 그냥 씁니다.

제가 복습할 때는 굳이 여기까지는 하지 않고 있습니다.

poly_feature에 EXT_SOURCE_1, 2,3, DAYS_BIRTH, TARGET을 뽑아내서 가져옵니다.

그리고 sklearn.preprocessing에서 Imputer를 가져와서 strategy를 median으로 해줍니다.

이후 target을 뽑아내고 지운 뒤 null 데이터를 imuter로 채워줍니다.

그리고 3차원 다항식을 사용해서 컬럼을 늘려줄 것입니다.



poly_transformer가 아까 그 3차원 다항식인데요. poly_features를 fit 해준 뒤 transform을 통해서 변환 시켜줍니다.

그래서 poly_features를 다시 만들어줍니다



어떤 컬럼이 생겼는지 확인하려면 위 처럼

poly_transformer.get_feature_names(input_features = [])을 통해 어떤 식으로 feature가 더 생겼는지 보실 수 있습니다.

다항식을 3으로 하면 1차원, 2차원, 3차원 다항식에 따른 값들을 추가해주는 것입니다.



그래서 위 처럼 또 다시 corr()값을 뽑아내서 확인할 수 있습니다.



뭐 저 값들이 실제로 영향이 있을지는 모르겠지만 일단 여기 커널에선 merge를 시켜줍니다.

기존의 app_train이라는 원본 데이터에서  SK_ID_CURR을 가져온 뒤  poly_feature에 넣어주고

app_train.merge(poly_feature, on = 'SK_ID_CURR', how='left')를 통해서 조인을 시켜줍니다

테스트도 마찬가지구요.

그리고 다시 한 번 align을 해줍니다.


위 사진 아래 글을 보면 새롭게 또 컬럼을 추가해주는데요.

angular라는 유저가 알려준 도메인 지식이라고 합니다.

그래서 가져다가 썼다고 그런식으로 표현하고 감사를 표현하네요



그리고 테스트 겸 copy를 통해서 복사를 합니다. 위에서 말했던 그 도메인 지식을 사용하려고 새롭게 컬럼을 만들어주고

sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, feature]을 통해서 그래프를 쭉 그려줍니다.



이런식으로 그래프가 나오는데요

이게 과연.. 효과가 있을 지는 모르겠습니다.

일단 이것만 봐선 별 효과를 주진 않을 것 같습니다.



마지막으로 단순한 baseline code를 작성합니다.

원본 데이터를 보존하기 위해서 copy를 통해 가져오구요

inputer와 MinMaxScaler를 가져옵니다.



처음에는 imputer를 통해서 null 값의 데이터를 채워줍니다.

그리고 scaler를 이용해서 minmax로 범위를 조절해줍니다.



자 단순한 RandomForestClassifier를 만들어서 봤는데요

socre가 0.91이 나옵니다. 하지만 여기선 unbalance 데이터라서 이렇게 나오고

실제 f1-score등으로 보면 65% 정도 수준만 나옵니다.

그리고 randomforest에서 중요하게 본 feature_importances_를 확인하기 위해서

feature_importance_values = random_forest.feature_importances_를 가져옵니다.


그리고 a라는 값에다가 feature_imoprtances.sort_values('importance', ascending=False).reset_index().head()를 통해 값을 넘겨줍니다.

plt.figure(figsize=(10, 12))

sns.barplot(a['importance'], a['feature'])

을 통해서 어떤 특성을 중요하게 봤는지 봅니다.

EXT_SOURCE_2, DAYS_BIRTH 등이 있네요!

자 여기까지가 이번 커널의 필사입니다.


좋은 모델을 만들기보다, 이 데이터 셋의 전체적인 구조를 살펴보는데 초점을 두었습니다.


다음 커널에선 정말 이 사람이 상환 능력이 있을지?! 예측을 하고 score가 높은 모델을 필사해보도록 합니다!




2 Comments
댓글쓰기 폼