이번에는 로지스틱 회귀분석을 시행해보겠습니다.
이 포스트에는 코드 위주로 업로드되며, 관련 이론은 다음을 참고해주세요.
2021.11.17 - [공부/통계학] - 로짓모형, 프로빗모형의 추정 (+최우법)
데이터는 타이타닉 데이터를 사용했습니다.
원본 데이터
전처리 코드는 간단하게 다음과 같습니다.
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)
전처리 후 데이터
그렇다면 바로 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
'공부 > 통계학' 카테고리의 다른 글
Monte Carlo simulation (몬테카를로 시뮬레이션) python (0) | 2022.03.28 |
---|---|
선형회귀 기초 (0) | 2022.03.24 |
일원분산분석 (One-way ANOVA) python (0) | 2022.02.15 |
기초 통계 (중심도 이해) python (0) | 2022.02.04 |
PACF (Partial Auto Correlation Function, 편자기상관함수) python (0) | 2022.01.20 |
댓글