## 시각화 라이브러리 로드
importseabornassnsimportmatplotlib.pyplotasplt%matplotlibinline# 나눔고딕 설치
!apt-qq-yinstallfonts-nanum>/dev/nullimportmatplotlib.font_managerasfmfontpath='/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'font=fm.FontProperties(fname=fontpath,size=9)fm._rebuild()# 그래프에 retina display 적용
%configInlineBackend.figure_format='retina'# Colab 의 한글 폰트 설정
plt.rc('font',family='NanumBarunGothic')
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
defsummarize_categoricals(df,show_levels=False):data=[[df[c].unique(),len(df[c].unique()),df[c].isnull().sum()]forcindf.columns]df_temp=pd.DataFrame(data,index=df.columns,columns=['Levels','No. of Levels','No. of Missing Values'])returndf_temp.iloc[:,0ifshow_levelselse1:]deffind_categorical(df,cutoff=10):cat_cols=[]forcolindf.columns:iflen(df[col].unique())<=cutoff:cat_cols.append(col)returncat_colsdefto_categorical(columns,df):forcolincolumns:df[col]=df[col].astype('category')returndf
위에서 알 수 있듯, ‘totalcharge(총요금)’은 고객이 머무르던 동안의 총 월요금을 합한 것이므로 ‘monthlycharges(월요금)’과 ‘tenure(고객이 머무른 기간)’과 높은 상관관계가 있다.
correlation 분석 - 2) 범주형 변수간 correlation 분석
변수들의 연속성 여부와 관계없이 correlation을 분석하고 싶고, 측정치 분포 표준화가 되어있지 않으므로, Cramer V 계수를 사용한다.
V 계수는 서로 최대의 관련성을 가질때 1, 아무 상관이 없을때 0으로 표현된다.
# Cramer V 계수 계산
fromscipy.statsimportchi2_contingencydefcramers_corrected_stat(contingency_table):chi2=chi2_contingency(contingency_table)[0]n=contingency_table.sum().sum()phi2=chi2/nr,k=contingency_table.shaper_corrected=r-(((r-1)**2)/(n-1))k_corrected=k-(((k-1)**2)/(n-1))phi2_corrected=max(0,phi2-((k-1)*(r-1))/(n-1))return(phi2_corrected/min((k_corrected-1),(r_corrected-1)))**0.5
# 범주형 변수간 Cramer V 계수 계산
defcategorical_corr_matrix(df):df=df.select_dtypes(include='category')cols=df.columnsn=len(cols)corr_matrix=pd.DataFrame(np.zeros(shape=(n,n)),index=cols,columns=cols)forcol1incols:forcol2incols:ifcol1==col2:corr_matrix.loc[col1,col2]=1breakdf_crosstab=pd.crosstab(df[col1],df[col2],dropna=False)corr_matrix.loc[col1,col2]=cramers_corrected_stat(df_crosstab)corr_matrix+=np.tril(corr_matrix,k=-1).Treturncorr_matrix
전화 서비스가 없는 사람들은 여러 회선을 가질 수 없기 때문에 ‘phone service(전화 서비스)’와 ‘multiple lines(다중 회선)’간에는 약간의 상관 관계가 있다. 따라서 특정 고객이 전화 서비스에 가입되어 있지 않다는 것을 알면 고객이 여러 회선을 가지고 있지 않다고 알 수 있다. 마찬가지로 ‘internet service’와 ‘online security’, ‘online backup’, ‘device protection’, ‘streaming tv’, ‘streaming movies’간에도 상관 관계가 있다.
만약 계산량을 줄이기 위해 feature를 줄인다면 고려해볼만한 항목이다.
또한 상관관계가 높은 것들을 위주로 원-핫 인코딩을 해볼 수도 있겠다.
Train-Test split
테스트 데이터 세트를 Stratified 방식으로 추출해 훈련 데이터 세트와 테스트 데이터 세트의 레이블 분포도를 서로 동일하게 만든다.
fromsklearn.model_selectionimporttrain_test_splitdata_splits=train_test_split(x,y,test_size=0.25,random_state=0,shuffle=True,stratify=y)x_train,x_test,y_train,y_test=data_splitsprint('Train 데이터 레이블 값 비율')print(y_train.value_counts()/y_train.shape[0]*100)print('Test 데이터 레이블 값 비율')print(y_test.value_counts()/y_test.shape[0]*100)
Train 데이터 레이블 값 비율
N 73.416761
Y 26.583239
Name: churn, dtype: float64
Test 데이터 레이블 값 비율
N 73.435722
Y 26.564278
Name: churn, dtype: float64
**불균형 데이터 처리 : SMOTENC를 이용한 OverSampling
라벨 Y가 N보다 훨씬 적은 불균형데이터이므로 훈련 데이터 셋을 SMOTE로 Over Sampling한다.
피쳐를 표준화하지 않으면, 큰 분산을 갖고 있는 피쳐가 모델 학습에 있어 영향을 더 많이 행사하게 되고, 모델이 다른 피쳐를 학습할 수 없기 때문이다.
따라서 연속형 변수를 스케일링한다.
## 기존의 데이터 분포
plt.figure(figsize=(8,10))plt.subplot(3,1,1);sns.distplot(df['tenure'])plt.subplot(3,1,2);sns.distplot(df['monthlycharges'])plt.subplot(3,1,3);sns.distplot(df['totalcharges'])# Show the plot
plt.show()
데이터 인코딩 : One-hot encoding
범주형 변수는 원-핫 인코딩을 통해 숫자형으로 변환한다.
fromsklearn.composeimportColumnTransformerfromsklearn.preprocessingimportOneHotEncoder,StandardScaler,LabelEncodercategorical_columns=list(x.select_dtypes(include='category').columns)numeric_columns=list(x.select_dtypes(exclude='category').columns)## Column Transformer
transformers=[('one_hot_encoder',OneHotEncoder(drop='first',dtype='int'),categorical_columns),('standard_scaler',StandardScaler(),numeric_columns)]x_trans=ColumnTransformer(transformers,remainder='passthrough')## Applying Column Transformer
x_train=x_trans.fit_transform(pd.DataFrame(x_train,columns=categorical_columns+numeric_columns))x_test=x_trans.transform(pd.DataFrame(x_test))## Label encoding
y_trans=LabelEncoder()y_train=y_trans.fit_transform(pd.DataFrame(y_train))y_test=y_trans.transform(pd.DataFrame(y_test))## Save feature names after one-hot encoding for feature importances plots
feature_names=list(x_trans.named_transformers_['one_hot_encoder'] \
.get_feature_names(input_features=categorical_columns))feature_names=feature_names+numeric_columns
# SMOTE 전후 비교를 위한 data set
x_train_o,x_test_o,y_train_o,y_test_o=data_splitsx_train_o=x_trans.fit_transform(pd.DataFrame(x_train_o,columns=categorical_columns+numeric_columns))x_test_o=x_trans.transform(pd.DataFrame(x_test_o))y_train_o=y_trans.fit_transform(pd.DataFrame(y_train_o))y_test_o=y_trans.transform(pd.DataFrame(y_test_o))
# 비교를 위한 데이터프레임화
after_prep=pd.DataFrame(x_train,columns=feature_names)after_prep.head(3)
gender_M
seniorcitizen_1
partner_Y
dependents_Y
phoneservice_Y
multiplelines_No phone
multiplelines_Y
internetservice_Fiber
internetservice_N
onlinesecurity_No internet
onlinesecurity_Y
onlinebackup_No internet
onlinebackup_Y
deviceprotection_No internet
deviceprotection_Y
techsupport_No internet
techsupport_Y
streamingtv_No internet
streamingtv_Y
streamingmovies_No internet
streamingmovies_Y
contract_2 yr
contract_Monthly
paperlessbilling_Y
paymentmethod_Credit card
paymentmethod_Electronic check
paymentmethod_Mailed check
tenure
monthlycharges
totalcharges
0
0.0
1.0
0.0
0.0
1.0
0.0
0.0
1.0
0.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0
1.0
1.0
0.0
0.0
0.0
-1.071261
0.554873
-0.861789
1
0.0
0.0
1.0
0.0
1.0
0.0
0.0
0.0
1.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.900700
-1.668186
-0.521394
2
1.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0
1.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
0.0
1.0
1.0
0.0
1.0
0.0
-0.609738
-1.669941
-0.821179
## 표준화 이후 데이터 분포
importwarningswarnings.filterwarnings(action='ignore')plt.figure(figsize=(8,10))plt.subplot(3,1,1);sns.distplot(after_prep['tenure'])plt.subplot(3,1,2);sns.distplot(after_prep['monthlycharges'])plt.subplot(3,1,3);sns.distplot(after_prep['totalcharges'])# Show the plot
plt.show()
## 원핫 인코딩 이후 범주형 변수 상태
after_prep.iloc[:3,:-3]
gender_M
seniorcitizen_1
partner_Y
dependents_Y
phoneservice_Y
multiplelines_No phone
multiplelines_Y
internetservice_Fiber
internetservice_N
onlinesecurity_No internet
onlinesecurity_Y
onlinebackup_No internet
onlinebackup_Y
deviceprotection_No internet
deviceprotection_Y
techsupport_No internet
techsupport_Y
streamingtv_No internet
streamingtv_Y
streamingmovies_No internet
streamingmovies_Y
contract_2 yr
contract_Monthly
paperlessbilling_Y
paymentmethod_Credit card
paymentmethod_Electronic check
paymentmethod_Mailed check
0
0.0
1.0
0.0
0.0
1.0
0.0
0.0
1.0
0.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0
1.0
1.0
0.0
0.0
0.0
1
0.0
0.0
1.0
0.0
1.0
0.0
0.0
0.0
1.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
2
1.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0
1.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
1.0
0.0
0.0
1.0
1.0
0.0
1.0
0.0
Part3. 데이터 모델링
모델 학습/예측 : Logistic Regression(+Cross Validation), Random Forest, AdaBoost, XGBoost, KNN
학습 데이터 셋이 작기 때문에 Cross Validation을 사용한다.
성능평가 : Confusion Matrix, Accuracy, precision, recall, F1-score, AUC -> 이상탐지이므로 주로 F1 Score와 AUC를 중심으로 본다.
특히, SMOTE를 적용하면 재현율(recall)은 높아지나, 정밀도(precision)는 낮아지는 것이 일반적이다. 따라서 SMOTE 적용의 효과를 보기 위해서는 SMOTE 전후의 재현율 증가율과, 정밀도 감소율을 살펴보려고 한다.
# 모델 평가 함수 정의
fromsklearn.metricsimportaccuracy_score,precision_score,recall_score,confusion_matrix,roc_auc_score,f1_scorefromsklearn.metricsimportplot_confusion_matrixdefget_clf_eval(y_test,pred):confusion=confusion_matrix(y_test,pred)accuracy=accuracy_score(y_test,pred)precision=precision_score(y_test,pred)recall=recall_score(y_test,pred)f1=f1_score(y_test,pred)# ROC-AUC 추가
roc_auc=roc_auc_score(y_test,pred)print('오차 행렬')print(confusion)# ROC-AUC print 추가
print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1: {3:.4f}, AUC:{4:.4f}'.format(accuracy,precision,recall,f1,roc_auc))
# 모델 학습/예측 함수 정의
defget_model_train_eval(model,ftr_train=None,ftr_test=None,tgt_train=None,tgt_test=None):model.fit(ftr_train,tgt_train)pred=model.predict(ftr_test)pred_proba=model.predict_proba(ftr_test)[:,1]get_clf_eval(tgt_test,pred)
Logistic Regression
## 불균형 데이터 처리(SMOTE) 전 모델 성능
fromsklearn.linear_modelimportLogisticRegressionCVlogit_cv=LogisticRegressionCV(Cs=10,class_weight='balanced',cv=5,dual=False,fit_intercept=True,intercept_scaling=1.0,l1_ratios=None,max_iter=500,multi_class='auto',n_jobs=None,penalty='l1',random_state=0,refit=True,scoring='f1',solver='liblinear',tol=0.0001,verbose=0)get_model_train_eval(logit_cv,ftr_train=x_train_o,ftr_test=x_test_o,tgt_train=y_train_o,tgt_test=y_test_o)
Logistic Regression의 경우, 정밀도는 높아졌지만, SMOTE이후 재현율이 낮아졌다. 왜 그런지 보기 위해 분류 결정 임계값에 따른 정밀도-재현율 곡선을 그려본다.
fromsklearn.metricsimportprecision_recall_curvedefprecision_recall_curve_plot(y_test,pred_proba_c1):# threshold ndarray와 이 threshold에 따른 정밀도, 재현율 ndarray 추출.
precisions,recalls,thresholds=precision_recall_curve(y_test,pred_proba_c1)# X축을 threshold값으로, Y축은 정밀도, 재현율 값으로 각각 Plot 수행. 정밀도는 점선으로 표시
plt.figure(figsize=(8,6))threshold_boundary=thresholds.shape[0]plt.plot(thresholds,precisions[0:threshold_boundary],linestyle='--',label='precision')plt.plot(thresholds,recalls[0:threshold_boundary],label='recall')# threshold 값 X 축의 Scale을 0.1 단위로 변경
start,end=plt.xlim()plt.xticks(np.round(np.arange(start,end,0.1),2))# x축, y축 label과 legend, 그리고 grid 설정
plt.xlabel('Threshold value');plt.ylabel('Precision and Recall value')plt.legend();plt.grid()plt.show()precision_recall_curve_plot(y_test,logit_cv.predict_proba(x_test)[:,1])