범주형 반응변수의 예측 | LogisticRegression()

linear_model
Author

강신성

Published

2023-10-19

예측해야 할 유형이 범주형일 때 사용할 수 있는 분석 기법 중 하나인 로지스틱 회귀분석을 해보자!

해당 포스트는 전북대학교 통계학과 최규빈 교수님의 강의내용을 토대로 재구성되었음을 알립니다.

1. 라이브러리 imports

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import sklearn.linear_model

2. 로지스틱 회귀분석

- 연속형 설명변수와 범주형 반응변수와의 관계

A. 성적과 취업 여부 데이터


  • 학점과 토익 성적, 그리고 취업 여부를 나타낸 데이터가 있다.(교수님이 만드신 페이크 데이터이다.)
df = pd.read_csv('https://raw.githubusercontent.com/guebin/MP2023/main/posts/employment.csv')
df  ## 페이크 데이터입니다.
toeic gpa employment
0 135 0.051535 0
1 935 0.355496 0
2 485 2.228435 0
3 65 1.179701 0
4 445 3.962356 1
... ... ... ...
495 280 4.288465 1
496 310 2.601212 1
497 225 0.042323 0
498 320 1.041416 0
499 375 3.626883 1

500 rows × 3 columns

plt.plot(df.toeic[df.employment == 0], df.gpa[df.employment == 0], 'o')
plt.plot(df.toeic[df.employment == 1], df.gpa[df.employment == 1], 'o')
plt.show()

- 뭔가 관련성을 찾을 수 있을 것 같지 않은가?~(당연하지 그렇게 만드셨으니까)~

그래서 토익 성적ㆍ학점과 취업여부의 관계를 구하고 싶다.

### B. 분석

# step 1
X = pd.get_dummies(df[['toeic', 'gpa']])
y = df.employment

# step 2
predictr = sklearn.linear_model.LogisticRegression()

# step 3
predictr.fit(X, y)

# step 4
df = df.assign(employment_hat = predictr.predict(X))

sklearn.linear_model.LogisticRegression()

df
toeic gpa employment employment_hat
0 135 0.051535 0 0
1 935 0.355496 0 0
2 485 2.228435 0 0
3 65 1.179701 0 0
4 445 3.962356 1 1
... ... ... ... ...
495 280 4.288465 1 1
496 310 2.601212 1 0
497 225 0.042323 0 0
498 320 1.041416 0 0
499 375 3.626883 1 1

500 rows × 4 columns

로지스틱 회귀분석으로 적합 및 예측이 완료되었다.

C. 평가


predictr.score(X, y)
0.882

이건 y와 y_hat이 동일한 정도를 나타낸다, 나름 잘 맞춘 것 같지 않은가?

- 시각화를 해야 정확히 알 수 있겠지? 현재 예측치와 기존 예측치를 비교해보자.

df
toeic gpa employment employment_hat
0 135 0.051535 0 0
1 935 0.355496 0 0
2 485 2.228435 0 0
3 65 1.179701 0 0
4 445 3.962356 1 1
... ... ... ... ...
495 280 4.288465 1 1
496 310 2.601212 1 0
497 225 0.042323 0 0
498 320 1.041416 0 0
499 375 3.626883 1 1

500 rows × 4 columns

df_filtered = df[predictr.predict(X) == 1]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize = (12,5))

fig.suptitle('Constrat Pradiction and Real')

ax1.plot(df.toeic, df.gpa, 'o', color = 'C0', label = 'employed')
ax1.plot(df.loc[df.employment == 1].toeic, df.loc[df.employment == 1].gpa, 'o', color = 'C1', label = 'not yet employed')
ax1.set_title('Real Data')

ax2.plot(df.toeic, df.gpa, 'o', color = 'C0', label = 'employed')
ax2.plot(df_filtered.toeic, df_filtered.gpa, 'o', color = 'C1', label = 'not yet employed')
ax2.set_title('Estimated Data')

plt.legend()
plt.show()

어때요, 나름 합리적이지 않나요?

3. 로지스틱 회귀분석의 실적용

그럼 타이타닉 데이터에서 로지스틱 회귀분석을 통해 결과를 잘 예측할 수 있지 않을까요?

df_train = pd.read_csv('https://raw.githubusercontent.com/HollyRiver/Machine_learning_in_practice/main/kaggle/titanic/data/train.csv')
df_test = pd.read_csv('https://raw.githubusercontent.com/HollyRiver/Machine_learning_in_practice/main/kaggle/titanic/data/test.csv')
df_train.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

kaggle 입문하기에서 보았던 타이타닉 데이터이다.

- 여기서 반응변수를 쉽게 구하려면…

set(df_train.columns) - set(df_test.columns)
{'Survived'}

- 하나만 남는 것을 볼 수 있다.

아! 테스트 셋에 없는 열이니까 저게 y겠구나!

### A. 늘 해왔던 것처럼 분석…~(하면 안된다)~

# step 1
X = pd.get_dummies(df_train.drop(['Survived'], axis = 1))
y = df_train.Survived
XX = pd.get_dummies(df_test)

# step 2
predictr = sklearn.linear_model.LogisticRegression()

# step 3
predictr.fit(X, y)
ValueError: Input X contains NaN.
LogisticRegression does not accept missing values encoded as NaN natively. For supervised learning, you might want to consider sklearn.ensemble.HistGradientBoostingClassifier and Regressor which accept missing values encoded as NaNs natively. Alternatively, it is possible to preprocess the data, for instance by using an imputer transformer in a pipeline or drop samples with missing values. See https://scikit-learn.org/stable/modules/impute.html You can find a list of all estimators that handle NaN values at the following page: https://scikit-learn.org/stable/modules/impute.html#estimators-that-handle-nan-values

오류가 나온다.

Input X contains NaN.

선형 회귀에서 설명변수의 input값에는 결측치가 있으면 안된다!!!

df_train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB

결측치가 있는 열을 제거, 행을 제거, 둘 다. 또는 결측치를 impute해야 하는데…

- 일단 Cabin 열은 결측치가 너무 많으니까 빼자!

- Name이나 Ticket과 같은 변수는 이성적으로 봤을 때 바로 one-hot 인코딩 하기에는 어색하니 빼자!

len(set(df_train.Name)), len(set(df_train.Ticket))  ## 다 다름, 거의 다 다름
(891, 681)

- Age, Embarked에 포함된 약간의 결측치가 마음에 걸리니까 빼자!!

dropna() 메소드나 preprocessing을 써도 되지만… 그건 나중에 해보자.

df_test.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  418 non-null    int64  
 1   Pclass       418 non-null    int64  
 2   Name         418 non-null    object 
 3   Sex          418 non-null    object 
 4   Age          332 non-null    float64
 5   SibSp        418 non-null    int64  
 6   Parch        418 non-null    int64  
 7   Ticket       418 non-null    object 
 8   Fare         417 non-null    float64
 9   Cabin        91 non-null     object 
 10  Embarked     418 non-null    object 
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB

- Fare에 포함된 결측치도 걸린다 -> 빼자! (평균으로 해주는 방법도 있는ㄷ ~나중에 하자고 좀~)

B. 데이터 정리


- 위에서 말한 조건들을 적용해서 데이터를 재가공한 뒤, 로지스틱 회귀를 해보자

df_train.columns
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
      dtype='object')
# step 1
X = pd.get_dummies(df_train.drop(['Survived', 'Cabin', 'Name', 'Ticket', 'Age', 'Embarked', 'Fare'], axis = 1))
y = df_train.Survived
XX = pd.get_dummies(df_test.drop(['Cabin', 'Name', 'Ticket', 'Age', 'Embarked', 'Fare'], axis = 1))

# step 2
predictr = sklearn.linear_model.LogisticRegression()

# step 3
predictr.fit(X, y)

# step 4
XX.assign(Survived = predictr.predict(XX))
PassengerId Pclass SibSp Parch Sex_female Sex_male Survived
0 892 3 0 0 False True 0
1 893 3 1 0 True False 1
2 894 2 0 0 False True 0
3 895 3 0 0 False True 0
4 896 3 1 1 True False 1
... ... ... ... ... ... ... ...
413 1305 3 0 0 False True 0
414 1306 1 0 0 True False 1
415 1307 3 0 0 False True 0
416 1308 3 0 0 False True 0
417 1309 3 1 1 False True 0

418 rows × 7 columns

정상적으로 잘 수행한 것 같다.

### C. 평가

predictr.score(X, y)
0.8002244668911336

생각보단 잘 한 것 같다.

D. 제출


yy = pd.DataFrame({'Survived' : predictr.predict(XX)})
submit_df = pd.concat([df_test.PassengerId, yy], axis = 1)
submit_df

#submit_df.to_csv(directory, index = False)
PassengerId Survived
0 892 0
1 893 1
2 894 0
3 895 0
4 896 1
... ... ...
413 1305 0
414 1306 1
415 1307 0
416 1308 0
417 1309 0

418 rows × 2 columns

kaggle에 제출하고 두근대는 결과는… 0.77511, 그리 높진 않다.