9 minute read

캐글 타이타닉 생존자 예측

Titanic - Machine Learning from Disaster에서 직접 머신러닝을 통해 생존자 예측 모델을 만들어보고 제출하여 성적을 받아볼 것이다.

들어가기에 앞서, 사이트에서 훈련셋(test.csv)과 테스트셋(test.csv)을 이미 잘 정리해 놓은 상태이기 때문에 이에 대해서는 따로 다루지 않고 바로 사용할 것이다.

페이지의 Data 카테고리에서 확인할 수 있다.

image

타이타닉에 대한 정보를 찾다보니 위와 같은 사진을 찾았다. 생존자 예측을 할 때 도움이 될 수도 있을 것 같다.

기본 설정

# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno

# Input data files are available in the read-only "../input/" directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using "Save & Run All" 
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session
/kaggle/input/titanic/train.csv
/kaggle/input/titanic/test.csv
/kaggle/input/titanic/gender_submission.csv

데이터 다운로드와 적재

train_data = pd.read_csv('/kaggle/input/titanic/train.csv')
test_data = pd.read_csv('/kaggle/input/titanic/test.csv')

train_data, test_data와 같이 캐글에서 제공하는 훈련셋과 테스트셋을 생성할 수 있다.

head()를 사용해 각 데이터의 내용을 확인해보자.

train_data.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
test_data.head()
PassengerId Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 892 3 Kelly, Mr. James male 34.5 0 0 330911 7.8292 NaN Q
1 893 3 Wilkes, Mrs. James (Ellen Needs) female 47.0 1 0 363272 7.0000 NaN S
2 894 2 Myles, Mr. Thomas Francis male 62.0 0 0 240276 9.6875 NaN Q
3 895 3 Wirz, Mr. Albert male 27.0 0 0 315154 8.6625 NaN S
4 896 3 Hirvonen, Mrs. Alexander (Helga E Lindqvist) female 22.0 1 1 3101298 12.2875 NaN S

데이터셋 기본 정보 확인

info()를 사용해 데이터 안에 들어있는 정보들을 확인할 수 있다.

train_data.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

범주형 특성 탐색

Dtypeobject를 자료형으로 갖는 특성은 범주형 특성이다.

train_data["Name"].value_counts()
Braund, Mr. Owen Harris                     1
Boulos, Mr. Hanna                           1
Frolicher-Stehli, Mr. Maxmillian            1
Gilinski, Mr. Eliezer                       1
Murdlin, Mr. Joseph                         1
                                           ..
Kelly, Miss. Anna Katherine "Annie Kate"    1
McCoy, Mr. Bernard                          1
Johnson, Mr. William Cahoone Jr             1
Keane, Miss. Nora A                         1
Dooley, Mr. Patrick                         1
Name: Name, Length: 891, dtype: int64
train_data["Sex"].value_counts()
male      577
female    314
Name: Sex, dtype: int64
train_data["Ticket"].value_counts()
347082      7
CA. 2343    7
1601        7
3101295     6
CA 2144     6
           ..
9234        1
19988       1
2693        1
PC 17612    1
370376      1
Name: Ticket, Length: 681, dtype: int64
train_data["Cabin"].value_counts()
B96 B98        4
G6             4
C23 C25 C27    4
C22 C26        3
F33            3
              ..
E34            1
C7             1
C54            1
E36            1
C148           1
Name: Cabin, Length: 147, dtype: int64
train_data["Embarked"].value_counts()
S    644
C    168
Q     77
Name: Embarked, dtype: int64

나머지는 전부 수치형 특성들이다. 이는 describe()를 사용해 확인할 수 있다.

train_data.describe()
PassengerId Survived Pclass Age SibSp Parch Fare
count 891.000000 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000
mean 446.000000 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208
std 257.353842 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429
min 1.000000 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000
25% 223.500000 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400
50% 446.000000 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200
75% 668.500000 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000
max 891.000000 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200

결측치 확인

msno.bar(df=train_data.iloc[:, :], figsize=(12, 8), color=(0.3, 0.3, 0.3))

image

Age 특성에서는 결측치를 채워줄 것이고 Cabin 특성은 결측치가 너무 많은 관계로 고려하지 않을 것이다.


  • 캐글에서 제공되는 Data Dictionay를 통해 변수명의 의미를 알아보자.
Variable Definition Key
survival Survival 0 = No, 1 = Yes
pclass Ticket class 1 = 1st, 2 = 2nd, 3 = 3rd
sex Sex  
Age Age in years  
sibsp # of siblings / spouses aboard the Titanic  
parch # of parents / children aboard the Titanic  
ticket Ticket number  
fare Passenger fare  
cabin Cabin number  
embarked Port of Embarkatoin C = Cherbourg, Q = Queenstown, S = Southampton

Variable Notes Pclass: A proxy for socio-economic status (SES) 1st = Upper 2nd = Middle 3rd = Lower

Age: Age is fractional if less than 1. If the age is estimated, is it in the form of xx.5

SibSp: The dataset defines family relations in this way… Sibling = brother, sister, stepbrother, stepsister Spouse = husband, wife (mistresses and fiancés were ignored)

Parch: The dataset defines family relations in this way… Parent = mother, father Child = daughter, son, stepdaughter, stepson Some children travelled only with a nanny, therefore parch=0 for them.

특성들의 의미를 알아보자.

  • survival: 생존 여부(0은 생존하지 못함, 1은 생존)

  • pclass: 사회 경제적 지위(1st = 높음, 2nd = 중간, 3rd = 낮음)

  • sex: 성별

  • Age: 나이(나이가 1보다 작으면 분수이다. xx.5의 형태)

  • sibsp: 배우자, 형제 자매 수

  • parch: 부모, 자식 수

  • ticket: 티켓 숫자

  • fare: 요금

  • cabin: 케비넷 숫자

  • embarked: 탑승 장소 (C = Cherbourg, Q = Queenstown, S = Southampton)

train_data.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

생존을 제외한 10개의 특성 중, 생존과 연관성이 낮을 것으로 예상되는 특성은

  • Name

  • Ticket

  • Cabin

이 세 특성을 제외할 것이다.

나머지 특성들에 대해 생존과 상관관계를 구해보자.

수치형 특성 간의 상관관계

수치형 특성 간에서도 생존과 강한 상관관계를 가질 것으로 예상되는 특성은

  • Pclass

이다.

  • PclassSurvived와의 관계를 알아보자.
ratio = train_data['Pclass'].value_counts(sort=False)
labels = ratio.size
survived = train_data[train_data['Survived']==1]['Pclass'].value_counts()
dead = train_data[train_data['Survived']==0]['Pclass'].value_counts()

plt.plot(aspect='auto')
plt.pie(ratio, labels=ratio.index, autopct='%1.1f%%')
plt.title('Pclass' + '\'s ratio')
plt.show()

for i, index in enumerate(ratio.index):
    plt.subplot(1, ratio.size + 1, i + 1, aspect='equal')
    plt.pie([survived[index], dead[index]], labels=['Survivied', 'Dead'], autopct='%1.1f%%')
    plt.title(str(index) + '\'s ratio')

plt.show()

image

image

Pclass는 사회 경제적 지위로, 숫자가 작을수록 높은 사회 경제적 지위를 가지고 있다는 뜻이다.

사회 경제적 지위가 높으면 생존률도 높았다는 것을 볼 수 있다.

범주형 특성 간의 상관관계

위에서 범주형 특성을 제외시켰기 때문에 남은 범주형 특성은

  • Sex

  • Embarked

이다.

  • 성별에 따른 생존률을 알아보자.
ratio = train_data['Sex'].value_counts(sort=False)
labels = ratio.size
survived = train_data[train_data['Survived']==1]['Sex'].value_counts()
dead = train_data[train_data['Survived']==0]['Sex'].value_counts()

plt.plot(aspect='auto')
plt.pie(ratio, labels=ratio.index, autopct='%1.1f%%')
plt.title('Sex' + '\'s ratio')
plt.show()

for i, index in enumerate(ratio.index):
    plt.subplot(1, ratio.size + 1, i + 1, aspect='equal')
    plt.pie([survived[index], dead[index]], labels=['Survivied', 'Dead'], autopct='%1.1f%%')
    plt.title(str(index) + '\'s ratio')

plt.show()

image

image

남자의 비율이 여자보다 많았고, 여자의 생존률이 남자의 생존률보다 높았던 것을 알 수 있다.

  • 탑승 장소에 따른 생존률을 알아보자.
ratio = train_data['Embarked'].value_counts(sort=False)
labels = ratio.size
survived = train_data[train_data['Survived']==1]['Embarked'].value_counts()
dead = train_data[train_data['Survived']==0]['Embarked'].value_counts()

plt.plot(aspect='auto')
plt.pie(ratio, labels=ratio.index, autopct='%1.1f%%')
plt.title('Embarked' + '\'s ratio')
plt.show()

for i, index in enumerate(ratio.index):
    plt.subplot(1, ratio.size + 1, i + 1, aspect='equal')
    plt.pie([survived[index], dead[index]], labels=['Survivied', 'Dead'], autopct='%1.1f%%')
    plt.title(str(index) + '\'s ratio')

plt.show()

image

image

Southampthon에서 탑승한 사람이 많았고, Cherbourg에서 탑승한 사람의 생존률이 가장 높은 것을 알 수 있다.

  • Pclass, Sex, Embarked 세 특성간 상관관계를 알아보자.
f, ax = plt.subplots(ncols=3, figsize=(20, 8))

sns.barplot(data=train_data, x="Pclass", y="Survived", hue="Sex", ax=ax[0])       # 사회적 지위별 성별의 생존률
sns.barplot(data=train_data, x="Embarked", y="Survived", hue='Sex', ax=ax[1])     # 탑승 장소별 성별의 생존률
sns.barplot(data=train_data, x="Embarked", y="Survived", hue='Pclass', ax=ax[2])  # 탑승 장소별 사회적 지위의 생존률
plt.show()

image

전처리

결측치 찾기

isnull().sum()을 사용하면 결측치를 찾고, 그 개수를 알 수 있다.

train_data.isnull().sum()
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

Age 특성에 있는 결측치를 채워보자.(어차피 사용하지 않을 특성이지만 채워보자.)

결측치를 처리하는 방법에는 세 가지 방법이 있다.

  1. 누락치 특성 포함 샘플 삭제
  2. 누락치를 포함한 특성 삭제
  3. 누락치를 해당 특성의 중앙값/평균값 등으로 대체

여기서는 중앙값으로 대체해보자.

median = train_data['Age'].median()
train_data['Age'].fillna(median, inplace=True)
train_data.isnull().sum()
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age              0
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

Age 특성에 결측치가 사라진 것을 볼 수 있다.

Embarked 특성에도 결측치가 2개 있다. 이때 단순히 가장 많이 탑승한 항구인 Southampton으로 가정해보자.

fillna()를 사용해 결측치를 채워보자.

train_data['Embarked'].fillna('S', inplace=True)
train_data.isnull().sum()
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age              0
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         0
dtype: int64

결측치가 사라진 모습을 볼 수 있다.

특성 추출

사용하지 않을 특성은 train_data에서 빼주자.

drop_features = ['Name', 'Age', 'Ticket', 'Cabin', 'SibSp', 'Parch', 'Fare']

train_data = train_data.drop(drop_features, axis=1)
test_data = test_data.drop(drop_features, axis=1)

train_data = train_data.drop('PassengerId', axis=1)

원하는 특성만 남은 것을 볼 수 있다.

train_data.head()
Survived Pclass Sex Embarked
0 0 3 male S
1 1 1 female C
2 1 3 female S
3 1 1 female S
4 0 3 male S
test_data.head()
PassengerId Pclass Sex Embarked
0 892 3 male Q
1 893 3 female S
2 894 2 male Q
3 895 3 male S
4 896 3 female S

모델 설계를 하기 전에 원-핫 인코딩 및 우리가 원하는 생존을 라벨값으로 지정하고, 훈련셋에서는 생존 특성을 제거해주자.

train_data = pd.get_dummies(train_data)
test_data = pd.get_dummies(test_data)

train_label = train_data['Survived']
train_df = train_data.drop('Survived', axis=1)
test_df = test_data.drop("PassengerId", axis=1).copy()

예측 모델 생성

5가지 예측 모델을 사용할 것이다.

  1. Logistic Regression
  2. Support Vector Machine (SVM)
  3. k-Nearest Neighbor (kNN)
  4. Random Forest
  5. Naive Bayes

위 모델을 사용하기 위해 관련된 사이킷런 라이브러리들을 임포트 해주자.

from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import GaussianNB

from sklearn.utils import shuffle

정렬되어 있을 가능성을 위해 셔플해준다.

train_df, train_label = shuffle(train_df, train_label, random_state = 5)

fit() 메서드와 predict() 메서드를 이용해 쉽게 모델 학습과 평가에 대한 파이프라인을 생성할 수 있다.

def train_and_test(model):
    model.fit(train_df, train_label)
    prediction = model.predict(test_df)
    accuracy = round(model.score(train_df, train_label)*100, 2)
    print(f'Accuracy: {accuracy}%')
    return prediction

5가지 모델을 넣어보자.

# Logistic Regression
log_pred = train_and_test(LogisticRegression())
# SVM
svm_pred = train_and_test(SVC())
#kNN
knn_pred_4 = train_and_test(KNeighborsClassifier(n_neighbors = 4))
# Random Forest
rf_pred = train_and_test(RandomForestClassifier(n_estimators=100))
# Navie Bayes
nb_pred = train_and_test(GaussianNB())
Accuracy: 77.78%
Accuracy: 81.14%
Accuracy: 81.14%
Accuracy: 81.14%
Accuracy: 77.78%

제일 정확도가 높게 나온 Random Forest 모델을 이용해 제출을 해보자.

submission = pd.DataFrame({
    "PassengerId": test_data["PassengerId"],
    "Survived": rf_pred
})

submission.to_csv('submission_rf.csv', index=False)

결과

image

0.77751의 정확도를 얻었다.

마무리

더 좋은 정확도를 얻을 수 있는 방법을 모색해보면 좋을 것 같다.

  • 모든 특성과 생존률과의 상관관계를 확인하고 활용하기
  • 이상치를 확인해 없애기
  • 결측치를 다른 방법으로 채우기
  • 하이퍼파라미터를 조정해 성능 올리기

등과 같은 방법을 활용하면 좋을 것 같다.

Updated: