본문 바로가기
공부/통계학

로지스틱 회귀분석 (Logistic Regression) python

by signature95 2022. 2. 21.
728x90
반응형

이번에는 로지스틱 회귀분석을 시행해보겠습니다.

 

이 포스트에는 코드 위주로 업로드되며, 관련 이론은 다음을 참고해주세요.

 

2021.11.16 - [공부/통계학] - 로짓분석

 

로짓분석

로짓 분석을 설명하기 앞서 선형확률모형에 대한 언급을 하도록한다. (로짓 분석을 왜 이용하는지에 대한 배경이 되기 때문이다.) 선형확률모형 (LPM) feature(설명변수)의 값이 주어졌을 때, label(

signature95.tistory.com

2021.11.17 - [공부/통계학] - 로짓모형, 프로빗모형의 추정 (+최우법)

 

로짓모형, 프로빗모형의 추정 (+최우법)

2021.11.16 - [공부/모델링] - 로짓분석 앞서 다룬 로짓모형 https://signature95.tistory.com/10?category=986931 에 이어서 작성하는 포스트입니다. 로짓분석 로짓 분석을 설명하기 앞서 선형확률모형에 대한..

signature95.tistory.com

 

 

데이터는 타이타닉 데이터를 사용했습니다.

원본 데이터

titanic_test.csv
0.03MB
titanic_train.csv
0.06MB

 

전처리 코드는 간단하게 다음과 같습니다.

 

train0 = train.drop(['Age', 'Cabin'], axis = 1)
test0 = test.drop(['Age', 'Cabin'], axis = 1)

#train0에서 Embarked가 결측인 두 행을 제거합니다.
train0 = train0.dropna()
#제거 여부 확인해봅니다.
train0.isna().sum()

#test0에서 결측된 Fare값을 평균으로 대체합니다.
test0 = test0.fillna(test0.mean())
#처리 여부를 확인해봅니다.
test0.isna().sum()

#Name변수와 Ticket 변수를 제거합니다.
train1 = train0.drop(['Name', 'Ticket'], axis = 1)
test1 = test0.drop(['Name', 'Ticket'], axis = 1)

#Sex데이터를 숫자형으로 변환합니다.
train1['Sex'] = train1['Sex'].map( {'female': 1, 'male': 0} ).astype(int)
test1['Sex'] = test1['Sex'].map( {'female': 1, 'male': 0} ).astype(int)

#Embarked 데이터를 숫자형으로 변환합니다.
train1['Embarked'] = train1['Embarked'].map( {'C': 0, 'Q': 1, 'S':2} ).astype(int)
test1['Embarked'] = test1['Embarkeㅁd'].map( {'C': 0, 'Q': 1, 'S':2} ).astype(int)

 

전처리 후 데이터

preprocessing_titanic_test.csv
0.01MB
preprocessing_titanic_train.csv
0.02MB

 

그렇다면 바로 logistic 분석을 시행해봅니다.

 

#모델의 훈련을 위하여 설명변수와 반응변수를 분리합니다.
X_train = train.drop(["PassengerId","Survived"], axis=1)
Y_train = train["Survived"]

X_test  = test.drop("PassengerId", axis=1).copy()
X_train.shape, Y_train.shape, X_test.shape

>>>

((889, 6), (889,), (418, 6))

설명변수는 feature를 의미하고 반응변수는 target을 의미합니다. 여기서 target은 생존여부가 됩니다.

또한 train data에 대해 logistic regression을 적용한 후 test data에 적용을 하여 생존여부를 도출하게 됩니다.

 

로지스틱 분석을 시행하면 다음과 같습니다. (statsmodel.api의 sm을 사용함)

import statsmodels.api as sm

logreg = sm.Logit(Y_train, sm.add_constant(X_train)).fit()
print(logreg.summary())

>>>

                           Logit Regression Results                           
==============================================================================
Dep. Variable:               Survived   No. Observations:                  889
Model:                          Logit   Df Residuals:                      882
Method:                           MLE   Df Model:                            6
Date:                Mon, 21 Feb 2022   Pseudo R-squ.:                  0.3135
Time:                        17:36:40   Log-Likelihood:                -406.03
converged:                       True   LL-Null:                       -591.41
Covariance Type:            nonrobust   LLR p-value:                 5.378e-77
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const          0.7696      0.370      2.078      0.038       0.044       1.495
Pclass        -0.8365      0.127     -6.583      0.000      -1.086      -0.587
Sex            2.7316      0.196     13.923      0.000       2.347       3.116
SibSp         -0.2343      0.101     -2.321      0.020      -0.432      -0.036
Parch         -0.0741      0.114     -0.652      0.514      -0.297       0.149
Fare           0.0025      0.002      1.036      0.300      -0.002       0.007
Embarked      -0.2352      0.112     -2.103      0.035      -0.454      -0.016
==============================================================================

summary를 해석해봅시다.

 

위에서 parch, fare의 p값은 유의미한 설명력을 가지지 못합니다.  

Log_Likelihood는 최우법을 의미합니다.

pseudo R-squ는 로지스틱 회귀에서의 모형 설명력으로 R square 값과 비슷하다 보시면 됩니다.

각 coef는 변수별 logit값입니다. 이를 좀더 명확하게 해석하면 다음과 같습니다.

 

# odds ratio 구하는 부분
odds = np.exp(logreg.params)

for i in range(len(odds)):
    print(f'변수 {X_train.columns[i]}의 logit : {logreg.params[i] : .3f}')
    print(f'변수 {X_train.columns[i]}가 1단위 증가할 때, 생존할 로짓(odds ratio에 자연로그를 취한 값)이{logreg.params[i] : .3f}배 증가한다.')
    print(f'변수 {X_train.columns[i]}의 odds ratio : {odds[i] : .3f}')
    print(f'변수 {X_train.columns[i]}가 1단위 증가할 때, 생존할 확률(종속변수가 1일 확률)이 그렇지 않을 경우보다{odds[i] : .3f}배 증가한다.\n')
    
    
>>>

변수 Pclass의 logit : -0.625
변수 Pclass가 1단위 증가할 때, 생존할 로짓(odds ratio에 자연로그를 취한 값)이-0.625배 증가한다.
변수 Pclass의 odds ratio :  0.535
변수 Pclass가 1단위 증가할 때, 생존할 확률(종속변수가 1일 확률)이 그렇지 않을 경우보다 0.535배 증가한다.

변수 Sex의 logit :  2.747
변수 Sex가 1단위 증가할 때, 생존할 로짓(odds ratio에 자연로그를 취한 값)이 2.747배 증가한다.
변수 Sex의 odds ratio :  15.599
변수 Sex가 1단위 증가할 때, 생존할 확률(종속변수가 1일 확률)이 그렇지 않을 경우보다 15.599배 증가한다.

변수 SibSp의 logit : -0.257
변수 SibSp가 1단위 증가할 때, 생존할 로짓(odds ratio에 자연로그를 취한 값)이-0.257배 증가한다.
변수 SibSp의 odds ratio :  0.773
변수 SibSp가 1단위 증가할 때, 생존할 확률(종속변수가 1일 확률)이 그렇지 않을 경우보다 0.773배 증가한다.

변수 Parch의 logit : -0.103
변수 Parch가 1단위 증가할 때, 생존할 로짓(odds ratio에 자연로그를 취한 값)이-0.103배 증가한다.
변수 Parch의 odds ratio :  0.902
변수 Parch가 1단위 증가할 때, 생존할 확률(종속변수가 1일 확률)이 그렇지 않을 경우보다 0.902배 증가한다.

변수 Fare의 logit :  0.006
변수 Fare가 1단위 증가할 때, 생존할 로짓(odds ratio에 자연로그를 취한 값)이 0.006배 증가한다.
변수 Fare의 odds ratio :  1.006
변수 Fare가 1단위 증가할 때, 생존할 확률(종속변수가 1일 확률)이 그렇지 않을 경우보다 1.006배 증가한다.

변수 Embarked의 logit : -0.124
변수 Embarked가 1단위 증가할 때, 생존할 로짓(odds ratio에 자연로그를 취한 값)이-0.124배 증가한다.
변수 Embarked의 odds ratio :  0.884
변수 Embarked가 1단위 증가할 때, 생존할 확률(종속변수가 1일 확률)이 그렇지 않을 경우보다 0.884배 증가한다.

이를 도표로 정리해보겠습니다.

 

sig_level = .05
logistic_variable = pd.concat([logreg.params, np.exp(logreg.params), np.round(logreg.pvalues,4)], axis=1)
logistic_variable.columns = ['logit', 'odds ratio', 'p-value']
logistic_variable.loc[logistic_variable['p-value'] > sig_level, f'above {sig_level*100}%'] = 'No'
logistic_variable.loc[logistic_variable['p-value'] < sig_level, f'above {sig_level*100}%'] = 'Yes'
print(logistic_variable)

>>>

             logit  odds ratio  p-value above 5.0%
Pclass   -0.624685    0.535430   0.0000        Yes
Sex       2.747236   15.599452   0.0000        Yes
SibSp    -0.257217    0.773200   0.0098        Yes
Parch    -0.102936    0.902184   0.3614         No
Fare      0.005845    1.005862   0.0044        Yes
Embarked -0.123848    0.883514   0.2097         No

Logit은 logistic regression의 변수 별 계수를 의미합니다.

Odds ratio는 logit변환을 하기전에 도출된 값으로 사건이 일어날 확률이 그렇지 않을 경우의 몇배인지 알려줍니다.

위에는 유의수준을 5%로 설정하여 이를 충족하면 yes로 그렇지 않으면 No를 출력하여 각 변수별 설명력을 판단할 수 있습니다.

 

 

이 모든 과정을 함수로 표현한 코드입니다.

def logistic_variable(X_train,Y_train, sig_level=.05, output = True):
    import statsmodels.api as sm
    import numpy as np
    import pandas as pd
    
    logistic_result = sm.Logit(Y_train, X_train).fit()
    print(logistic_result.summary())
    
    odds = np.exp(logistic_result.params)
    
    if output == True:
        for i in range(len(odds)):
            print(f'변수 {X_train.columns[i]}가 1단위 증가할 때, 생존할 로짓(odds ratio에 자연로그를 취한 값)이{logistic_result.params[i] : .3f}배 증가한다.')
            print(f'변수 {X_train.columns[i]}가 1단위 증가할 때, 생존할 확률(종속변수가 1일 확률)이 그렇지 않을 경우보다{odds[i] : .3f}배 증가한다.\n')
    else:
        pass
    
    logistic_variable = pd.concat([logreg.params, np.exp(logreg.params), np.round(logreg.pvalues,4)], axis=1)
    logistic_variable.columns = ['logit', 'odds ratio', 'p-value']
    logistic_variable.loc[logistic_variable['p-value'] > sig_level, f'above {sig_level*100}%'] = 'No'
    logistic_variable.loc[logistic_variable['p-value'] < sig_level, f'above {sig_level*100}%'] = 'Yes'

    return logistic_variable

 

마지막은 혼동행렬입니다.

 

# 혼동행렬 (confusion matrix)
cm_df = pd.DataFrame(logreg.pred_table())
cm_df.columns = ['Predicted 0', 'Predicted 1']
cm_df = cm_df.rename(index={0: 'Actual 0',1: 'Actual 1'})
print(cm_df)

sns.heatmap(cm_df, cmap = 'RdYlBu_r', annot = True, annot_kws={"size": 10}, linewidths=.5)
plt.title('confusion matrix')
plt.xlabel('actual')
plt.ylabel('pred')
plt.show()

>>>

          Predicted 0  Predicted 1
Actual 0        466.0         83.0
Actual 1        103.0        237.0

 

 

혼동행렬은 실제 값과 분류분석을 통해 도출한 예측값을 비교하여 분류분석 모형의 정확도를 판단할 수 있는 기준이 됩니다.

옳게 분류한 정분류는 Actual 0 & Predicted 0, Actual 1 & Predicted 1로 (0,0), (1,1) 값을 의미합니다.

 

혼동행렬에 대한 포스트는 다음에 이어집니다.

2022.03.17 - [공부/통계학] - confusion matrix (혼동행렬) python

 

confusion matrix (혼동행렬) python

이전 포스트에 이어서 작성하는 내용입니다. 2022.02.21 - [공부/통계학] - 로지스틱 회귀분석 (Logistic Regression) python 로지스틱 회귀분석 (Logistic Regression) python 이번에는 로지스틱 회귀분석을 시행..

signature95.tistory.com

 

728x90

댓글