programming/Python

로지스틱회귀 모델을 이용한 분류 분석_sklearn 머신러닝

Jofresh 2023. 7. 23. 20:00
728x90
반응형

안녕하세요.

오늘은 예제를 통해 sklearn로지스틱 회귀모델링을 통한 분류 분석에 대해서 포스팅해보겠습니다.

 

타이타닉호의 생존자/비생존자 그룹을 분류하여 어떤 피처가 생존/비생존에 영향이 있는지 알아보도록 하겠습니다.

 

예제는 예제일 뿐, 여러분들이 로지스틱회귀 모델로 본업에서 활용하기 위해서라면 각 변수나 피처들을 예제와 매칭시켜서 각자만의 문제를 해결하도록 이번 포스팅을 응용하는 능력을 기르시길 바랍니다.

 

저 또한 이러한 예제를 통해 방법을 익히고, 데이터만 교체해서 예제를 응용하여 활용하고 있습니다.

 

타이타닉 테이터 살펴보기

타이타닉 데이터셋의 구성

- pclass : Passenger Class, 승객 등급
- survived : 생존 여부
- name : 승객 이름
- sex : 승객 성별
- age : 승객 나이
- sibsp : 탑승 한 형제/배우자 수
- parch : 탑승 한 부모/자녀 수
- ticket : 티켓 번호
- fare : 승객 지불 요금
- cabin : 선실 이름
- embarked : 승선항 (C = 쉘 부르그, Q = 퀸즈타운, S = 사우스 햄튼)
- body : 사망자 확인 번호
- home.dest : 고향/목적지

분석에 사용할 데이터는 총 2개의 파일로 구성되어 있습니다.

 

1. 학습 데이터셋

2. 테스트 데이터셋

 

타이타닉 데이터셋의 기본 정보 구하기

# -*- coding: utf-8 -*-

%matplotlib inline

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

import warnings
warnings.filterwarnings("ignore")

df_train = pd.read_csv("../data/titanic_train.csv")
df_test = pd.read_csv("../data/titanic_test.csv")
df_train.head(5)

# 출력 결과

데이터 기본 정보 출력 결과

기본정보2

훈련 데이터셋은 916개, 테스트 데이터셋은 392개의 데이터가 존재합니다.

print(df_train.info())
print("-----------------")
print(df_test.info())

# 출력 결과

기본정보2 출력 결과

 

불필요한 피처 제거

이제는 분석에 필요 없을 것 같은 피처들을 제거해주겠습니다.

 

# 데이터셋에서 name, ticket, body, cabin, home.dest 피처를 제거합니다.
df_train = df_train.drop(['name', 'ticket', 'body', 'cabin', 'home.dest'], axis=1)
df_test = df_test.drop(['name', 'ticket', 'body', 'cabin', 'home.dest'], axis=1)

 

 

데이터 그룹 분류

다음으로 각 피처가 분류 분석에 미칠 영향에 대해 알아보겠습니다. 생존/비생존 그룹으로 나누어서 피처의 그룹간 차이를 탐색해보겠습니다.

 

생존여부는 
생존=1 / 비생존 =0으로

survived를 그룹으로 하여 pclass피처의 그룹별 분포를 출력해보겠습니다. 
(plcass = 승객 등급)


# survived 피처를 기준으로 그룹을 나누어, 그룹별 pclass 피처의 분포를 살펴봅니다.
print(df_train['pclass'].value_counts())
ax = sns.countplot(x='pclass', hue = 'survived',  data = df_train)

# 출력 결과

pclass 그룹화 코드 출력결과

그룹별 pclass의 분포는 상이하게 나타났습니다. 이를 통해 pclass 피처는 생존자 분류에 유의미한 영향을 미친다는 가설을 세워볼 수 있습니다. 

(높은 등급만 살아남는 더러운세상 ㅠㅠ)

 

다음으로 age, sibsp와 같은 수치형 피처들에 대한 탐색을 진행합니다. 다음 코드는 이러한 피처들을 탐색할 수 있는 자동화 함수valid_features()를 작성한 것입니다.

 

함수가 실행하는 내용은 다음과 같습니다.

 

- 두 그룹 간 분포를 비교하는 그래프 출력

- 두 그룹 각각의 표준편차 출력

- 두 그룹간의 T검정 실시

- 두 그룹 각각에 shapiro-wilk 검정 실시

 

**sapiro-wilk검정 =  주어진 데이터가 얼마나 정규성을 따르는지, 즉 정규분포에 얼마나 가까운지를 측정하는 검정 

 

 

변수 탐색작업 자동화하기

 

from scipy import stats

# 두 집단의 피처를 비교해주며 탐색작업을 자동화하는 함수를 정의합니다.
def valid_features(df, col_name, distribution_check=True):
    
    # 두 집단 (survived=1, survived=0)의 분포 그래프를 출력합니다.
    g = sns.FacetGrid(df, col='survived')
    g.map(plt.hist, col_name, bins=30)

    # 두 집단 (survived=1, survived=0)의 표준편차를 각각 출력합니다.
    titanic_survived = df[df['survived']==1]
    titanic_survived_static = np.array(titanic_survived[col_name])
    print("data std is", '%.2f' % np.std(titanic_survived_static))
    titanic_n_survived = df[df['survived']==0]
    titanic_n_survived_static = np.array(titanic_n_survived[col_name])
    print("data std is", '%.2f' % np.std(titanic_n_survived_static))
    
     # T-test로 두 집단의 평균 차이를 검정합니다.
    tTestResult = stats.ttest_ind(titanic_survived[col_name], titanic_n_survived[col_name])
    tTestResultDiffVar = stats.ttest_ind(titanic_survived[col_name], titanic_n_survived[col_name], equal_var=False)
    print("The t-statistic and p-value assuming equal variances is %.3f and %.3f." % tTestResult)
    print("The t-statistic and p-value not assuming equal variances is %.3f and %.3f" % tTestResultDiffVar)
    
    if distribution_check:
        # Shapiro-Wilk 검정 : 분포의 정규성 정도를 검증합니다.
        print("The w-statistic and p-value in Survived %.3f and %.3f" % stats.shapiro(titanic_survived[col_name]))
        print("The w-statistic and p-value in Non-Survived %.3f and %.3f" % stats.shapiro(titanic_n_survived[col_name]))
        
        # 앞서 정의한 valid_features 함수를 실행합니다. age 피처를 탐색합니다.
valid_features(df_train[df_train['age'] > 0], 'age', distribution_check=True)

 

#출력 결과

 

아래의 실행 결과는 valid_features()를 실행한 것입니다. 이를 통해 살펴본 피처는 age, sibsp 두 피처입니다.

age 피처 출력 결과

 

아래는 'sibsp' 피처 출력 결과

# 앞서 정의한 valid_features 함수를 실행합니다. sibsp 피처를 탐색합니다.
valid_features(df_train, 'sibsp', distribution_check=False)

 

#출력 결과

sibsp 피처 출력 결과

 

 

분석 결과 age 피처는 두 그룹간의 평균 차이가 없기 때문에 생존자 분류에 미치는 영향력이 낮은 것이라 가정해볼 수 있습니다. 반면 sibsp 피처에서는 두 그룹간의 평균 차이가 어느 정도 존재한다는 것을 알 수 있습니다.

글이 길어져서 추가로 넣진 않겠지만, valid_features()에 sex,fare 등 다른 피처들도 넣고 돌려보시는 것을 추천드립니다.

 

 

 

중간 점검

피처 두 그룹 간 분포 혹은 평균의 차이가 있는지?
pclass O
age X
sibsp,parch
fare O
sex O
embarked

 

생존자 분류 모델 만들기

이제 분류 모델을 만들어 보겠습니다. 예측 모델과 마찬가지로 분류 모델 역시 다양한 방법이 존재합니다. 

 

오늘은 로지스틱 회귀 모델을 이용한 분류 모델을 차용해보겠습니다.

 

로지스틱 회귀모델은 기존 회귀 분석의 예측값 Y를 0~1 사이의 값으로 제한하여 0.5보다 크면 1, 0.5보다 작으면 0이라고 분류하는 방법입니다. 
로지스틱 회귀 모델은 일반적인 회귀 모델과 마찬가지로 계수 분석을 통한 피처의 영향력 해석이 용이하다는 장점이 있습니다.

로지스틱 모델을 사용하기 위해 회귀 분석을 수행할 때와 동일한 방법으로 데이터를 가공합니다. 

 

우선 결측값을 처리합니다. 결측값이 존재하는 피처를 전처리하는 방법은 크게 두가지 입니다.

 

1. 결측이 존재하는 데이터들을 삭제하는 방법
2. 평균값, 혹은 중앙값이나 최빈값 등의 임의의 수치로 채워넣는 방법

 

1.은 처리가 쉽고 분석에서의 주관이 개입될 여지가 없다는 장점이 있지만, 중요한 정보를 삭제하게 될 수 있습니다.
2.는 데이터를 모두 분석에 활용할 수 있다는 장점이 있지만, 수치가 왜곡될 가능성이 있습니다.

 

 

아래의 코드에서는 2.를 이용하여 age와 embark 피처의 결측값을 보정하였습니다. 

 

그리고 원-핫인코딩 방법으로 범주형 변수를 변환합니다. 

 

현재 데이터셋은 train 데이터와 test 데이터로 분리되어 있기 때문에 원-핫 인코딩을 적용하려면 하나의 데이터로 합쳐줄 필요가 있습니다.

 

그래서 두 데이터를 합친 whole_df에 원-핫 인코딩을 적용한 뒤, 다시 train과 test로 데이터를 분리합니다.

 

# age의 결측값을 평균값으로 대체합니다.
#na값을 age의 평균값으로 채워 넣습니다.
replace_mean = df_train[df_train['age'] > 0]['age'].mean()
df_train['age'] = df_train['age'].fillna(replace_mean)
df_test['age'] = df_test['age'].fillna(replace_mean)

# embark : 2개의 결측값을 최빈값으로 대체합니다.
embarked_mode = df_train['embarked'].value_counts().index[0]
df_train['embarked'] = df_train['embarked'].fillna(embarked_mode)
df_test['embarked'] = df_test['embarked'].fillna(embarked_mode)

# one-hot encoding을 위한 통합 데이터 프레임(whole_df)을 생성합니다.
whole_df = df_train.append(df_test)
train_idx_num = len(df_train)

# pandas 패키지를 이용한 one-hot 인코딩을 수행합니다.
whole_df_encoded = pd.get_dummies(whole_df)
df_train = whole_df_encoded[:train_idx_num]
df_test = whole_df_encoded[train_idx_num:]

df_train.head()

위 코드에서 결과적으로 embarked_mode 변수에는 가장 많이 등장하는 승선 항구 값이 저장됩니다. 이후 해당 값이 필요한 분석이나 처리 작업에서 사용될 수 있습니다. 예를 들어, 승선 항구 정보가 누락된 데이터에 이 값을 대입하여 결측치를 채우는 등의 활용이 가능합니다.

 

# 출력 결과

 

분류 모델 전처리 코드 실행 결과

컬럼 sex와 embark의 경우는 int가 아닌 str 형태였는데, 변환된 것을 확인 할 수 있습니다.

 

이제 sklearn 모듈의 LogisticRegression 클래스로 모델을 학습합니다. 학습코드는 아래와 같습니다.

 

분류 모델링: 로지스틱 회귀 모델

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

# 데이터를 학습 데이터셋, 테스트 데이터셋으로 분리합니다.
x_train, y_train = df_train.loc[:, df_train.columns != 'survived'].values, df_train['survived'].values
x_test, y_test = df_test.loc[:, df_test.columns != 'survived'].values, df_test['survived'].values

# 로지스틱 회귀 모델을 학습합니다.
lr = LogisticRegression(random_state=0)
lr.fit(x_train, y_train)

# 학습한 모델의 테스트 데이터셋에 대한 예측 결과를 반환합니다.
y_pred = lr.predict(x_test)
y_pred_probability = lr.predict_proba(x_test)[:,1]

 

분류 모델 평가

 

분류 모델 평가 기준은 일반적으로 Confusion Matrix라는 것을 활용합니다. 

predicted class = 모델이 예측하여 분류한 값
true class = 실제 데이터 값

1. True Positive (TP): 모델이 실제 Positive(양성)인 샘플을 Positive로 정확하게 예측한 경우의 수를 나타냅니다.

2. False Positive (FP): 모델이 실제 Negative(음성)인 샘플을 Positive로 잘못 예측한 경우의 수를 나타냅니다. (오류: 양성이라고 예측했지만 실제는 음성인 경우)

3. True Negative (TN): 모델이 실제 Negative인 샘플을 Negative로 정확하게 예측한 경우의 수를 나타냅니다.

4. False Negative (FN): 모델이 실제 Positive인 샘플을 Negative로 잘못 예측한 경우의 수를 나타냅니다. (오류: 음성이라고 예측했지만 실제는 양성인 경우)

 

confusion matrix

Confusion Matrix를 사용하면 다양한 평가 지표를 계산할 수 있습니다. 몇 가지 주요 평가 지표는 다음과 같습니다:

  1. Accuracy (정확도): 모델이 전체 샘플 중에서 올바르게 예측한 샘플의 비율을 나타냅니다. 정확도 = (TP + TN) / (TP + TN + FP + FN)
  2. Precision (정밀도): 모델이 Positive로 예측한 샘플 중에서 실제로 Positive인 비율을 나타냅니다. 정밀도 = TP / (TP + FP)
  3. Recall (재현율) 또는 Sensitivity (민감도): 실제로 Positive인 샘플 중에서 모델이 Positive로 정확하게 예측한 비율을 나타냅니다. 재현율 = TP / (TP + FN)
  4. Specificity (특이성): 실제로 Negative인 샘플 중에서 모델이 Negative로 정확하게 예측한 비율을 나타냅니다. 특이성 = TN / (TN + FP)
  5. F1-score (F1 점수): 정밀도와 재현율의 조화 평균으로, 모델의 정밀도와 재현율을 동시에 고려한 지표입니다. F1 점수 = 2 * (정밀도 * 재현율) / (정밀도 + 재현율)

이 지표들을 응용한 두 가지 평가 지표가 F1-score와 ROC Curve 입니다. 

 

F1-score는 정밀도와 재현도의 조화 평균값으로, 두 값을 동시에 고려할 때 사용하는 지표
ROC Curve는 아래의 그림처럼 재현도와 특이도를 고려하여 종합적인 모델의 성능을 그래프로 나타내는 것인데, 그래프의 넓이를 계산한 AUC를 성능의 지표로 사용합니다. 이 값이 1에 가까울수록 좋은 분류 모델입니다.

ROC Curve 예시

 

다음 코드는 정확도, 정밀도, 특이도, F1-score 네 가지 지표로 모델을 평가한 것입니다.

 

predict()함수로 분류한 예측값들을 sklearn.metrics 모듈의 accuracy_score, precision_score, recall_score, f1_score 함수에 적용하면 다음과 같은 출력 결과를 얻을 수 있습니다.

 

# 테스트 데이터셋에 대한 accuracy, precision, recall, f1 평가 지표를 각각 출력합니다.
print("accuracy: %.2f" % accuracy_score(y_test, y_pred))
print("Precision : %.3f" % precision_score(y_test, y_pred))
print("Recall : %.3f" % recall_score(y_test, y_pred))
print("F1 : %.3f" % f1_score(y_test, y_pred))

 

#출력 결과

분류 모델 평가 출력 결과

 

그리고 다음 코드는 Confusion Matrix를 직접 출력한 것입니다.

 

from sklearn.metrics import confusion_matrix

# Confusion Matrix를 출력합니다.
confmat = confusion_matrix(y_true=y_test, y_pred=y_pred)
print(confmat)

 

#출력결과

 

컨퓨전매트릭스 출력 결과

 

마지막으로 AUC를 출력해보겠습니다.

AUC 출력은 분류 결과인 0 혹은 1의 y값(y_pred)을 사용하는 것이 아니라, 분류 직전의 확률값 (y_pred_probability)인 0~1 사이의 값을 사용해야 합니다.

 

아래 코드는 AUC를 출력함과 동시에 ROC Curve를 그래프로 나타낸 것입니다. 이 모델의 AUC는 약 0.837로, 생존자를 잘 분류해내는 모델이라고 평가할 수 있습니다.

 

from sklearn.metrics import roc_curve, roc_auc_score

# AUC (Area Under the Curve)를 계산하여 출력합니다.
false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_pred_probability)
roc_auc = roc_auc_score(y_test, y_pred_probability)
print("AUC : %.3f" % roc_auc)

# ROC curve를 그래프로 출력합니다.
plt.rcParams['figure.figsize'] = [5, 4]
plt.plot(false_positive_rate, true_positive_rate, label='ROC curve (area = %0.3f)' % roc_auc, 
         color='red', linewidth=4.0)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC curve of Logistic regression')
plt.legend(loc="lower right")

 

#출력 결과

 

AUC / ROC curv 출력 결과


아래의 코드와 실행 결과는 로지스틱 회귀 모델과 더불어 분류 분석의 가장 대표적인 방법인 사결정 나무모델(decision tress)을 적용한 결과입니다. 하지만 로지스틱 회귀 모델에 비해 모든 평가 지표가 낮은 것을 확인할 수 있습니다.

 

from sklearn.tree import DecisionTreeClassifier

# 의사결정나무를 학습하고, 학습한 모델로 테스트 데이터셋에 대한 예측값을 반환합니다.
dtc = DecisionTreeClassifier()
dtc.fit(x_train, y_train)
y_pred = dtc.predict(x_test)
y_pred_probability = dtc.predict_proba(x_test)[:,1]

# 학습한 모델의 성능을 계산하여 출력합니다.
print("accuracy: %.2f" % accuracy_score(y_test, y_pred))
print("Precision : %.3f" % precision_score(y_test, y_pred))
print("Recall : %.3f" % recall_score(y_test, y_pred))
print("F1 : %.3f" % f1_score(y_test, y_pred))

 

#출력 결과

의사결정나무모델 출력 결과

 

 

# 학습한 모델의 AUC를 계산하여 출력합니다.
false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_pred_probability)
roc_auc = roc_auc_score(y_test, y_pred_probability)
print("AUC : %.3f" % roc_auc)

# ROC curve를 그래프로 출력합니다.
plt.rcParams['figure.figsize'] = [5, 4]
plt.plot(false_positive_rate, true_positive_rate, label='ROC curve (area = %0.3f)' % roc_auc, 
         color='red', linewidth=4.0)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC curve of Logistic regression')
plt.legend(loc="lower right")

 

#출력 결과

의사결정나무모델 AUC/ROC curve 출력 결과

 


모델 개선 - 피처 엔지니어링

분류 모델의 성능을 더욱 끌어올리기 위해서는 어떻게 해야 할까요?
더 좋은 분류 기법 사용?
 더 많은 데이터 사용?

피처 엔지니어링이란 모델에 사용할 피처를 가공하는 분석 작업을 의미합니다. 이를 수행하기 위해 분석 과정을 처음부터 다시 시작하겠습니다. 먼저 다음의 코드와 같이 age,embark 피처의 결측값을 처리해준 뒤, whole_df라는 통합 데이터 프레임을 생성합니다.

# 데이터를 다시 불러옵니다.
df_train = pd.read_csv("../data/titanic_train.csv")
df_test = pd.read_csv("../data/titanic_test.csv")
df_train = df_train.drop(['ticket', 'body', 'home.dest'], axis=1)
df_test = df_test.drop(['ticket', 'body', 'home.dest'], axis=1)

# age의 결측값을 평균값으로 대체합니다.
replace_mean = df_train[df_train['age'] > 0]['age'].mean()
df_train['age'] = df_train['age'].fillna(replace_mean)
df_test['age'] = df_test['age'].fillna(replace_mean)

# embark : 2개의 결측값을 최빈값으로 대체합니다.
embarked_mode = df_train['embarked'].value_counts().index[0]
df_train['embarked'] = df_train['embarked'].fillna(embarked_mode)
df_test['embarked'] = df_test['embarked'].fillna(embarked_mode)

# one-hot encoding을 위한 통합 데이터 프레임(whole_df)을 생성합니다.
whole_df = df_train.append(df_test)
train_idx_num = len(df_train)

 

이번에는 cabin 피처와 name 피처를 가공하여 분석에 포함합니다. cabin 피처는 선실의 정보를 나타내는 데이터로, 선실을 대표하는 알파벳이 반드시 첫글자에 등장한다는 패턴을 가지고 있습니다.

 

cabin 피처에 관하여

 

이 피처의 결측 데이터는 알파벳이 없다는 의미의 'X'알파벳으로 채워줍니다. 그리고 데이터의 수가 매우 적은 G와 T 선실 역시 X로 대체합니다. 

 

마지막으로 cabin 피처에서 첫 번째 알파벳을 추출하기 위해

whole_df['cabin'].apply(lambda x: x[0])

코드를 실행합니다.

 

# 결측 데이터의 경우는 ‘X’로 대체합니다.
whole_df['cabin'] = whole_df['cabin'].fillna('X')

# cabin 피처의 첫 번째 문자를 추출합니다.
whole_df['cabin'] = whole_df['cabin'].apply(lambda x: x[0])

# 추출한 문자 중, G와 T는 수가 너무 작기 때문에, 마찬가지로 ‘X’로 대체합니다.
whole_df['cabin'] = whole_df['cabin'].replace({"G":"X", "T":"X"})

ax = sns.countplot(x='cabin', hue = 'survived',  data = whole_df)
plt.show()

 

#출력 결과

 

cabin 피처의 생존자/비생존자 그룹 출력 결과

전처리가 완료된 cabin 피처의 생존자/비생존자 그룹 간 분포는 위와 같습니다. 이를 살펴본 결과, 두 그룹간의 유의미한 차이가 있는 것으로 보입니다. 따라서 우리는 이 피처를 분류 모델에 사용해볼 수 있습니다.

 


다음으로 name 피처를 살펴봅시다.

 

얼핏 봐서는 이름이란 데이터를 어떻게 피처로 사용할 수 있을지 난감합니다. 하지만 데이터를 자세히 살펴보면 이 피처 또한 데이터 간의 공통점이 있음을 발견할 수 있습니다. 바로 이름의 구성 중간에 들어가는 호칭 정보입니다.

 

당시 시대는 사회적 계급이 존재하였기 때문에 호칭 정보는 매우 중요한 데이터로 활용될 수 있습니다. 호칭을 추출한 결과는 다음과 같습니다.

# 이름에서 호칭을 추출합니다.
name_grade = whole_df['name'].apply(lambda x : x.split(", ",1)[1].split(".")[0])
name_grade = name_grade.unique().tolist()
print(name_grade)

# 코드해석 ▼

더보기

위 코드는 whole_df 데이터프레임의 'name' 열에서 각 이름의 호칭(Title)을 추출하여 새로운 시리즈(열)인 'name_grade'를 생성하는 작업을 수행합니다.

코드를 하나씩 해석해보겠습니다:

whole_df['name']: whole_df 데이터프레임의 'name' 열을 선택합니다. 'name' 열은 탑승자의 이름 정보를 담고 있습니다.

.apply(lambda x : x.split(", ",1)[1].split(".")[0]): 'name' 열에 있는 각 이름에 대해 해당 이름의 호칭(Title)을 추출하는 작업을 수행합니다. 이 작업은 apply 메서드를 사용하여 각 행(이름)에 대해 람다 함수를 적용하는 방식으로 이루어집니다.

x.split(", ", 1): split 메서드를 사용하여 각 이름을 쉼표와 공백을 기준으로 최대 1번만 분리합니다. 이렇게 하면 이름이 "성, 이름" 형식으로 되어 있는 경우 "성"과 "이름"이 분리되며, 리스트 형태로 반환됩니다.

[1].split("."): 앞서 분리된 리스트에서 두 번째 요소(인덱스 1)를 선택하고, 다시 split 메서드를 사용하여 점(.)을 기준으로 분리합니다. 이렇게 하면 이름의 "성" 부분과 "이름" 부분 중에서 "이름" 부분만 남게 됩니다.

[0]: 앞서 분리된 결과 리스트에서 첫 번째 요소(인덱스 0)를 선택합니다. 이렇게 하면 "이름" 부분이 추출되어 반환됩니다.

결과적으로 name_grade 시리즈에는 whole_df 데이터프레임의 'name' 열에서 추출한 각 탑승자의 호칭(Title)이 저장되게 됩니다. 이러한 호칭 정보는 해당 데이터프레임의 탑승자들에 대한 추가적인 특성을 나타내거나 그룹화할 때 유용하게 사용될 수 있습니다. 예를 들어 호칭별로 성별이나 생존율을 분석하는 데 활용할 수 있습니다.

 

앞선 단계에서 추출한 호칭을 여섯 가지의 사회적 지위로 정의할 수 있습니다.

 

아래 코드로 name 피처를 A~F 범주형 데이터로 변환하는 작업을 수행합니다.

 

# 호칭에 따라 사회적 지위(1910년대 기준)를 정의합니다.
grade_dict = {'A': ['Rev', 'Col', 'Major', 'Dr', 'Capt', 'Sir'], # 명예직을 나타냅니다.
              'B': ['Ms', 'Mme', 'Mrs', 'Dona'], # 여성을 나타냅니다.
              'C': ['Jonkheer', 'the Countess'], # 귀족이나 작위를 나타냅니다.
              'D': ['Mr', 'Don'], # 남성을 나타냅니다.
              'E': ['Master'], # 젊은남성을 나타냅니다.
              'F': ['Miss', 'Mlle', 'Lady']} # 젊은 여성을 나타냅니다.

# 정의한 호칭의 기준에 따라, A~F의 문자로 name 피처를 다시 정의하는 함수입니다.
def give_grade(x):
    grade = x.split(", ", 1)[1].split(".")[0]
    for key, value in grade_dict.items():
        for title in value:
            if grade == title:
                return key
    return 'G'
    
# 위의 함수를 적용하여 name 피처를 새롭게 정의합니다.
whole_df['name'] = whole_df['name'].apply(lambda x: give_grade(x))
print(whole_df['name'].value_counts())

 

#출력 결과

name 피처 범주형 데이터 변환

 

** 카이제곱검정 결과

더보기

이때, 검정의 결과로 나오는 statistic 값과 pvalue 값을 해석하는 방법은 다음과 같습니다:

statistic 값:

이 값은 카이제곱 통계량(chi-square statistic)으로, 두 범주형 변수 간의 관계를 측정한 값입니다.
두 변수가 독립적이면 이 값은 0에 가까워집니다. 만약 변수 간에 강한 의존성이 있다면 이 값은 커집니다.
위 코드에서 statistic = 125.12585699042339라는 값이 출력되었습니다.
pvalue 값:

이 값은 귀무가설이 참일 때(두 범주형 변수가 독립적일 때) 관측된 카이제곱 통계량 또는 그보다 더 극단적인 값이 발생할 확률을 나타냅니다.


작은 pvalue 값은 귀무가설이 거부되는 것을 의미합니다. 즉, 두 변수가 독립적이지 않다고 할 수 있습니다. 보통 0.05보다 작을 때 유의수준 0.05에서 귀무가설을 기각하고, 두 변수가 연관되어 있다고 판단합니다.
위 코드에서 pvalue = 1.363799923979482e-24라는 매우 작은 값이 출력되었습니다. 이는 매우 작은 확률로 귀무가설을 기각함을 의미합니다.


따라서, 위 결과를 해석하면 "두 변수 cabin과 survived는 강력한 연관성이 있으며, 두 변수 간의 관계가 독립적이 아님을 나타냅니다."라고 할 수 있습니다. 이는 pvalue 값이 매우 작으므로 유의미한 결과라고 볼 수 있습니다.

 

name 피처 생존자/비생존자 그룹 간 차이

ax = sns.countplot(x='name', hue = 'survived',  data = whole_df)
plt.show()

 

#출력 결과

 

name 피처 코드 출력 결과

D그룹 남자들은 거의... 다 죽었...
B,F 그룹 여성분들은 대부분 살렸....
A그룹은 예상외로 많이 죽으셨...

이제 모델을 학습하기 위한 마지막 전처리 단계로 모든 범주형 피처들에 원-핫 인코딩을 적용합니다.

 

# pandas 패키지를 이용한 one-hot 인코딩을 수행합니다.
whole_df_encoded = pd.get_dummies(whole_df)
df_train = whole_df_encoded[:train_idx_num]
df_test = whole_df_encoded[train_idx_num:]
df_train.head()

#출력 결과

원-핫 인코딩 적용 결과


'cabin', 'name'을 대상으로 피처 엔지니어링을 적용한 뒤, 다시 학습한 모델의 평가 결과는 아래와 같습니다.

 

 

# 데이터를 학습 데이터셋, 테스트 데이터셋으로 분리합니다.
x_train, y_train = df_train.loc[:, df_train.columns != 'survived'].values, df_train['survived'].values
x_test, y_test = df_test.loc[:, df_test.columns != 'survived'].values, df_test['survived'].values

# 로지스틱 회귀 모델을 학습합니다.
lr = LogisticRegression(random_state=0)
lr.fit(x_train, y_train)

# 학습한 모델의 테스트 데이터셋에 대한 예측 결과를 반환합니다.
y_pred = lr.predict(x_test)
y_pred_probability = lr.predict_proba(x_test)[:,1]

# 테스트 데이터셋에 대한 accuracy, precision, recall, f1 평가 지표를 각각 출력합니다.
print("accuracy: %.2f" % accuracy_score(y_test, y_pred))
print("Precision : %.3f" % precision_score(y_test, y_pred))
print("Recall : %.3f" % recall_score(y_test, y_pred))
print("F1 : %.3f" % f1_score(y_test, y_pred)) # AUC (Area Under the Curve) & ROC curve

# AUC (Area Under the Curve)를 계산하여 출력합니다.
false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_pred_probability)
roc_auc = roc_auc_score(y_test, y_pred_probability)
print("AUC : %.3f" % roc_auc)

# ROC curve를 그래프로 출력합니다.
plt.rcParams['figure.figsize'] = [5, 4]
plt.plot(false_positive_rate, true_positive_rate, label='ROC curve (area = %0.3f)' % roc_auc, 
         color='red', linewidth=4.0)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC curve of Logistic regression')
plt.legend(loc="lower right")

 

 

#출력 결과

 

accuracy와 precision은 기존 모델에 비해 소폭 감소한 반면, F1 score와 AUC는 대폭 상승했습니다.

이를 통해 분류 모델의 성능이 많이 향상되었다는 것을 알 수 있습니다.

 

피처엔지니어링 이후 검정 결과

 


다음 코드는 분류 모델의 피처 영향력을 그래프로 살펴본 것입니다.

 

우리는 이를 통해 피처엔지니어링으로 생성된, 'name','cabin' 피처의 영향력이 가장 크다는 것을 알 수 있습니다.

 

피처 영향력 살펴보기

# 예측 대상인 survived 피처를 제외한 모든 피처를 리스트로 반환합니다. (그래프의 y축)
cols = df_train.columns.tolist()
cols.remove('survived')
y_pos = np.arange(len(cols))

# 각 피처별 회귀 분석 계수를 그래프의 x축으로 하여, 피처 영향력 그래프를 출력합니다.
plt.rcParams['figure.figsize'] = [5, 4]
fig, ax = plt.subplots()
ax.barh(y_pos, lr.coef_[0], align='center', color='green', ecolor='black')
ax.set_yticks(y_pos)
ax.set_yticklabels(cols)
ax.invert_yaxis()
ax.set_xlabel('Coef')
ax.set_title("Each Feature's Coef")

plt.show()

 

#출력 결과

 

피처 영향력 코드 출력 결과

 


평가 모델 검증하기

마지막 단계는 완성된 분류 모델을 검증하는 단계입니다. 이를 위해 모델의 과적합 여부를 검증해야 합니다. 

우리가 알아볼 과적합 검증 방법은 두 가지입니다.

 

1. K-fold 교차검증
2. 학습곡선

 

K-fold 교차 검증을 수행하기 위한 코드는 아래와 같습니다.

 

Kfold 클래스로 cv라는 객체를 반환하고, 이 객체의 split 함수를 for 반복문과 같이 사용하는데,

반복문에서는 전체 데이터를 k개로 분리하여 학습과 평가를 반복합니다.

 

from sklearn.model_selection import KFold

# K-fold 교차 검증의 k를 5로 설정합니다.
k = 5
cv = KFold(k, shuffle=True, random_state=0)
acc_history = []

# K-fold를 5번의 분할 학습으로 반복합니다.
for i, (train_data_row, test_data_row) in enumerate(cv.split(whole_df_encoded)):

    # 5개로 분할된 fold 중 4개를 학습 데이터셋, 1개를 테스트 데이터셋으로 지정합니다. 매 반복시마다, 테스트 데이터셋은 변경됩니다.
    df_train = whole_df_encoded.iloc[train_data_row]
    df_test = whole_df_encoded.iloc[test_data_row]
    
    # survived 피처를 y, 나머지 피처들을 x 데이터로 지정합니다.
    splited_x_train, splited_y_train = df_train.loc[:, df_train.columns != 'survived'].values, df_train['survived'].values
    splited_x_test, splited_y_test = df_test.loc[:, df_test.columns != 'survived'].values, df_test['survived'].values
    
    # 주어진 데이터로 로지스틱 회귀 모델을 학습합니다.
    lr = LogisticRegression(random_state=0)
    lr.fit(splited_x_train, splited_y_train)
    y_pred = lr.predict(splited_x_test)
    
    # 테스트 데이터셋의 Accuracy를 계산하여 acc_history에 저장합니다.
    splited_acc = accuracy_score(splited_y_test, y_pred)
    acc_history.append(splited_acc)
    
    # acc_history에 저장된 5번의 학습 결과(Accuracy)를 그래프로 출력합니다.
plt.xlabel("Each K-fold")
plt.ylabel("Acc of splited test data")
plt.plot(range(1, k+1), acc_history)

 

#출력 결과

 

아래 출력 결과는 교차 검증의 k번째 실행마다 AUC를 리스트에 저장하고, 이를 그래프로 나타낸 것입니다.

 

그래프를 살펴보면 AUC가 큰 폭으로 변화하고 있습니다. 따라서 이 모델은 다소 불안정한 모델이라고 할 수 있습니다.

 

다만 이러한 결과는 데이터의 개수가 적기 때문에 발생하는 현상입니다. 게다가 모든 실행에서 공통적으로 Test AUC가 0.8 이상의 수치를 기록 했기 때문에 이 분류 모델은 '과적합이 발생했지만 대체로 높은 정확도를 가지는 모델'이라고 할 수 있습니다.

 

 


학습 데이터와 테스트 데이터의 점수가 벌어지는 과적합 상황은 학습 곡선을 관찰함으로써 더 쉽게 관찰할 수 있습니다.

 

다음의 그래프는 학습 데이터 샘플의 개수가 증가함에 따라 학습과 테스트 두 점수가 어떻게 변화하는지를 관찰한 그래프입니다.

 

이를 통해 데이터가 300개 이상인 경우에는 과적합의 위험이 낮아진다는 것을 알 수 있습니다.

 

pip install scikit-plot 
위 코드로 scikitplot 모듈 설치해주세요.

 

import scikitplot as skplt
skplt.estimators.plot_learning_curve(lr, x_train, y_train)
plt.show()

 

#출력 결과

 

 

 

 

 

 

 

instargram Jofresh_

 

728x90
반응형