Titanic
캐글 타이타닉 생존자 예측
Titanic - Machine Learning from Disaster에서 직접 머신러닝을 통해 생존자 예측 모델을 만들어보고 제출하여 성적을 받아볼 것이다.
들어가기에 앞서, 사이트에서 훈련셋(test.csv
)과 테스트셋(test.csv
)을 이미 잘 정리해 놓은 상태이기 때문에 이에 대해서는 따로 다루지 않고 바로 사용할 것이다.
페이지의
Data
카테고리에서 확인할 수 있다.
타이타닉에 대한 정보를 찾다보니 위와 같은 사진을 찾았다. 생존자 예측을 할 때 도움이 될 수도 있을 것 같다.
기본 설정
# 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
범주형 특성 탐색
Dtype
에 object
를 자료형으로 갖는 특성은 범주형 특성이다.
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))
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
이다.
Pclass
와Survived
와의 관계를 알아보자.
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()
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()
남자의 비율이 여자보다 많았고, 여자의 생존률이 남자의 생존률보다 높았던 것을 알 수 있다.
- 탑승 장소에 따른 생존률을 알아보자.
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()
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()
전처리
결측치 찾기
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
특성에 있는 결측치를 채워보자.(어차피 사용하지 않을 특성이지만 채워보자.)
결측치를 처리하는 방법에는 세 가지 방법이 있다.
- 누락치 특성 포함 샘플 삭제
- 누락치를 포함한 특성 삭제
- 누락치를 해당 특성의 중앙값/평균값 등으로 대체
여기서는 중앙값으로 대체해보자.
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가지 예측 모델을 사용할 것이다.
- Logistic Regression
- Support Vector Machine (SVM)
- k-Nearest Neighbor (kNN)
- Random Forest
- 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)
결과
0.77751의 정확도를 얻었다.
마무리
더 좋은 정확도를 얻을 수 있는 방법을 모색해보면 좋을 것 같다.
- 모든 특성과 생존률과의 상관관계를 확인하고 활용하기
- 이상치를 확인해 없애기
- 결측치를 다른 방법으로 채우기
- 하이퍼파라미터를 조정해 성능 올리기
등과 같은 방법을 활용하면 좋을 것 같다.