머신러닝과 딥러닝/머신러닝

Titanic 데이터 파이썬으로 분석하기 - 1 (EDA)

Stat_in_KNU 2020. 6. 9. 20:03

[ADP대비, 데이터 분석 PT면접 대비]

 

학부시절부터 수도 없이 만났던 타이타닉 데이터, 주먹구구식으로 분석하지말고 Kaggle Kernel을 따라 차근차근 따라가보자.

 

참고 커널

https://www.kaggle.com/ash316/eda-to-prediction-dietanic

 

EDA To Prediction(DieTanic)

Explore and run machine learning code with Kaggle Notebooks | Using data from Titanic: Machine Learning from Disaster

www.kaggle.com

https://www.kaggle.com/startupsci/titanic-data-science-solutions

 

Titanic Data Science Solutions

Explore and run machine learning code with Kaggle Notebooks | Using data from Titanic: Machine Learning from Disaster

www.kaggle.com

데이터 소개

타이타닉호는 역사상 가장 악명 높은 재난이었다. 1912년 4월 15일 타이타닉호의 첫 항해 도중 빙산과 충돌하여 총 2224명 중 1502명이 사망했다. 

타이타닉호를 만드는데는 약 750만 달러가 들었다, (과거의 화폐가치를 따졌을때나, 현재의 가치 그대로나 엄청난 돈)  지금은 타이타닉데이터는 기계학습 분류 데이터셋으로 데이터 분석가/사이언티스트들이 많이 쓰고있다.

 

 

변수설명

survival Survival (0 = No; 1 = Yes)
pclass Passenger Class (1 = 1st; 2 = 2nd; 3 = 3rd)
name Name
sex Sex
age Age
sibsp Number of Siblings/Spouses Aboard
parch Number of Parents/Children Aboard
ticket Ticket Number
fare Passenger Fare
cabin Cabin
embarked Port of Embarkation (C = Cherbourg; Q = Queenstown; S = Southampton)

조금 해석이 어려운것들

Sibsp : 동반한 배우자/형제자매 수라고 보면됨

Parch : 부모/자식 수라고 보면됨

Cabin : 객실번호

Embarked : 탑승지

 

분석과정

1. EDA

2. 데이터 전처리(Feature Engineering, Data Cleaning)

3. 데이터 모델링 (ML modeling)

4. 모델 평가


 

1. EDA

 

- Package Import

 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')

 

- Data head 확인

 

data.head()

대충 비정형/정형 데이터 범주형, 연속형, 이산형, 연속형 변수들을 확인 할 수 있다.

우리가 궁금한것은 Survived여부이므로 이친구가 Y가 될 것이다.

 

 

데이터 결측값 확인

data.isnull().sum()

Embarked에 일부 결측치, Cabin과 Age에도 결측치가 있음을 알 수 있다.

 

 

Survived(Y)에 대해 알아보자.

f, ax = plt.subplots(1,2,figsize=(18,8))
data['Survived'].value_counts().plot.pie(explode = [0,0.1], autopct = '%1.1f%%', ax=ax[0], shadow = True)
ax[0].set_title('Survived')
ax[0].set_ylabel('')
sns.countplot('Survived', data = data, ax = ax[1])
ax[1].set_title('Survived')
plt.show()

Survived 변수에 대한 Plot

약 62%가 Survived = 0 (사망), 38% 정도가 Survived = 1 (생존) 했음을 알 수 있다.

 

Sex와 Survived의 관계

f, ax = plt.subplots(1,2,figsize=(18,8))
data[['Sex','Survived']].groupby(['Sex']).mean().plot.bar(ax=ax[0])
ax[0].set_title('Survived vs Sex')
sns.countplot('Sex', hue = 'Survived', data= data, ax = ax[1])
ax[1].set_title('Sex:Survived vs Dead')
plt.show()

 

좌측은 남/여 생존율, Lady/Baby First Policy 여자의 생존률이 남자에 비해 압도적으로 높은것을 알 수 있다.

우측은 남녀 각 생존자수와 사망자수를 count하여 barplot으로 나타낸것이다.

 

Pclass변수는 순서형 변수

 

pd.crosstab(data.Pclass, data.Survived, margins = True).style.background_gradient(cmap = 'summer_r')

crosstable로 확인해보았을때, Pclass = 3 일때 상대적으로 생존율이 낮았고, Pclass = 2일때 상대적으로 생존율이 높았음을 알 수 있다.

f, ax = plt.subplots(1,2, figsize= (20,8))
sns.set(font_scale = 1)
data['Pclass'].value_counts().plot.bar(color = ['#CD7F32','#FFDF00','#D3D3D3'], ax=ax[0], fontsize = 20)
ax[0].set_title('Number Of Passengers By Pclass')
ax[0].set_ylabel('Count')
sns.countplot('Pclass', hue = 'Survived', data = data, ax = ax[1])
ax[1].set_title('Pclass:Survived vs Dead')
plt.show()

좌측은 각 Pclass에 대해서 승객수를 count한 plot이다. 3,1,2순으로 승객이 많았다.

우측은 각 Pclass에 대해서 사망한 승객수와, 생존한 승객수를 Count한Plot이다. 상대적으로 Pclass = 3에서 생존율이 극적으로 낮았고, Pcalss = 2에서는 생존/사망이 비슷, Pclass = 3에서는 오히려 생존한 경우가 더 많았다.

재밌는게 Kernel에서 이를 보고 "Money Can`t Buy Everthing"을 부정하고있다. Pclass = 1, 2, 3으로 가면서 (1등객실, 2등객실, 3등객실) 생존율이 현저하게 떨어지고 있기 때문이다. 돈으로 목숨을 산 격이되나...

 

Sex,Survived와 Pclass의 관계

pd.crosstab([data.Sex, data.Survived], data.Pclass, margins = True).style.background_gradient(cmap = 'summer_r')

 

sns.factorplot('Pclass', 'Survived', hue = 'Sex', data= data)
plt.show()

먼저, Pclass와 Survived 모두 Categorical 데이터 이므로 factorplot을 이용했음을 알아두자.

위 Plot을 보면, 남 녀 모두 Pclass가 증가함에 따라(하등 객실로 감에따라) 생존할 가능성이 떨어지는 것을 알 수 있다.

특히 여자의경우 더욱 극적이다..

물론 일단 여자의 경우 Lady/Baby First Policy에 의해 남자보다는 생존율이 압도적으로 높다.

즉, 여자이면서 Pclass = 1에 머물렀을경우 거의 모두 살아남았다고 할 수 있다. (여자 1등객실 94명중 3명만 사망했다고 한다)

 

 

Age는 연속형 변수

 

f, ax = plt.subplots(1,2,figsize = (18,8))
sns.violinplot("Pclass", "Age", hue = 'Survived', data= data, split = True, ax = ax[0])
ax[0].set_title('Pclass and Age vs Survived')
ax[0].set_yticks(range(0,110,10))
sns.violinplot('Sex', "Age", hue = "Survived", data = data, split = True, ax = ax[1])
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0, 110, 10))
plt.show()

Pclass, Survived, Sex는 범주형, Age는 연속형 변수이라서 Violin Plot형태로 표현하면 좋다.

 

좌측에서 Pclass = 1일때는 생존자 연령평균이 사망자 연령 평균보다 낮다는 것을 알 수 있다. 

Pclass =1 인 경우를 제외하고는 모두 평균은 비슷하게 분포하는데, 대신 영유아의 경우 생존을 좀 더 잘했다고 할 수 있겠다. (분포가 정규분포 모양이 아닌 영유아 층에서 살짝 봉우리가 있음)

 

우측도 마찬가지로 영유아가 생존하는데 유리했다고 할 수 있겠다. 평균은 비슷하게 분포하나 여성남성 모두의 경우  생존자의 연령 평균이 약간 더 높다고 할 수 있다.

 

 

승객들 이름을 통해 feature 만들기

 

data["Initial"] = 0
for i in data:
    data["Initial"] = data.Name.str.extract('([A-Za-z]+)\.')
data["Initial"].unique()

 

'([A-Za-z]+)\.' 의 정규표현식을 통해서 사람들 이름데이터(Name)에서 Initial을 추출할 수 있다.

정규표현식이 의미하는것 -> 알파벳 아무거나 반복되는것 그리고 .이 오면 ()안에 있는것만 빼와주세요

[A-Za-z] : 알파벳 아무거나

+: 일정 수만큼 반복

\.:온점(\이 오지않는다면 그냥 아무거나의 의미이다.

 

pd.crosstab(data.Initial,data.Sex).T.style.background_gradient(cmap='summer_r')

신기하게도 Initial에 따라서 남녀 구별이 확실하게 된다, (Dr. 의 단 한명의 여자분 빼고!)

영문이름의 이니셜의 의미는 잘 모르지만 성별 구분에 굉장히 강하게 작용할 수 있고 성별 + a의 의미를 가지는 Feature을 만들 수 있을 것으로 보인다. (연령의 의미도 포함한다고 함)

 

data['Initial'].replace(['Mlle','Mme','Ms','Dr','Major','Lady','Countess','Jonkheer','Col','Rev','Capt','Sir','Don'],\
                        ['Miss','Miss','Miss','Mr','Mr','Mrs','Mrs','Other','Other','Other','Mr','Mr','Mr'],inplace=True)
data.groupby('Initial')['Age'].mean()

비슷한것 끼리 이름을 대충? 통일해준다. <영문 Initial의 의미는 잘 몰라서 왜이렇게 하는지는 잘 모르겠다>

연령도 대략 이렇게 분포하게된다... 이를 참고해서 Age에 있던 Null값을 아름답게 처리할 수 있다.

data.loc[(data.Age.isnull())&(data.Initial=='Mr'),'Age']=33
data.loc[(data.Age.isnull())&(data.Initial=='Mrs'),'Age']=36
data.loc[(data.Age.isnull())&(data.Initial=='Master'),'Age']=5
data.loc[(data.Age.isnull())&(data.Initial=='Miss'),'Age']=22
data.loc[(data.Age.isnull())&(data.Initial=='Other'),'Age']=46

data.Age.isnull().any() #So no null values left finally 

 

 

Age연속 변수의 null 값을 처리했으니 생존여부별 Age의 분포를 한번 보자.

f,ax=plt.subplots(1,2,figsize=(20,10))
data[data['Survived']==0].Age.plot.hist(ax=ax[0],bins=20,edgecolor='black',color='red')
ax[0].set_title('Survived= 0')
x1=list(range(0,85,5))
ax[0].set_xticks(x1)
data[data['Survived']==1].Age.plot.hist(ax=ax[1],color='green',bins=20,edgecolor='black')
ax[1].set_title('Survived= 1')
x2=list(range(0,85,5))
ax[1].set_xticks(x2)
plt.show()

생존/사망 여부에 Frequency로 나타냈기때문에 20-40대 에서 분포가 비슷하다 할 수 있지만,

영유아의 경우 생존할 가능성이 더 높았던것은 확실하다.

sns.factorplot('Pclass','Survived', col = 'Initial', data = data)
plt.show()

Mr(남성)의 경우 여성이나 영유아(Mrs, Miss, Master)의 경우와 극단적인 차이를 보이고있으나,

Pclass = 3에서는 남녀 할것 없이 사망율이 높았다는 것은 명백해보인다.

 

Embarked는 명목형 변수

pd.crosstab([data.Embarked, data.Pclass], [data.Sex, data.Survived], margins=True).style.background_gradient(cmap = 'summer_r')

Embarked, Pclass vs Sex, Survived Crosstable이다, Embarked = S에서 사망했을 확률이 조금 높아보인다.

 

sns.factorplot('Embarked', 'Survived', data = data)
plt.show()

Crosstable에서 예측했듯이, Embarked = S에서 생존율이 확실히 낮았다, 다음은 Q, C순이다.

 

 

f,ax=plt.subplots(2,2,figsize=(20,15))
sns.countplot('Embarked',data=data,ax=ax[0,0])
ax[0,0].set_title('No. Of Passengers Boarded')
sns.countplot('Embarked',hue='Sex',data=data,ax=ax[0,1])
ax[0,1].set_title('Male-Female Split for Embarked')
sns.countplot('Embarked',hue='Survived',data=data,ax=ax[1,0])
ax[1,0].set_title('Embarked vs Survived')
sns.countplot('Embarked',hue='Pclass',data=data,ax=ax[1,1])
ax[1,1].set_title('Embarked vs Pclass')
plt.subplots_adjust(wspace=0.2,hspace=0.5)
plt.show()

Embarked와 각 설명변수와의 관계를 살펴보자.

 

- 먼저, C<Q<S 순서로 승객이 많았다. 

- 남녀 비율은 세 군데의 탑승지에서 모두 마찬가지로 남성이 더 많은 양상을 띄고있지만 S가 압도적으로 많은 남성이 탑승한 탑승지였다.

- S탑승지의 대부분은 Pclass = 3에 있었다, 즉, S에서 탑승한 사람들의 생존율이 낮은것은, Pclass = 3이었던 것과 관련이 깊어 보인다.

- C탑승지에서 탑승한 승객은 Pclass = 1,2가 Pclass = 3보다 많이 있었다. 이는 C에서 탑승한 승객들의 생존율이 높은것과 깊은 관련이 있어보인다.

- 많은수의 Rich people(Pclass = 1)이 S에서 탑승했지만 생존율이 낮은 이유는 마찬가지로 Poor people(Pclass = 3)도 많이 탑승 했기 때문, Pclass = 3의 생존율은 19%였다.

sns.factorplot('Pclass', 'Survived', hue = 'Sex', col = 'Embarked', data = data)

Embarked 결측값 채우기

최빈값인 S로 결측치를 채우도록 하자.

data['Embarked'].fillna('S', inplace = True)
data.Embarked.isnull().any()
#False

 

SibSip는 이산형 변수!

pd.crosstab([data.SibSp], data.Survived).style.background_gradient(cmap = 'summer_r')

f, ax = plt.subplots(1,2,figsize = (20, 8))
sns.barplot('SibSp', 'Survived', data = data, ax = ax[0])
ax[0].set_title('SibSp vs Survived')
sns.factorplot('SibSp', 'Survived', data= data, ax = ax[1])
ax[1].set_title('SibSp vs Survived')
plt.close(2)
plt.show()

 

놀랍게도 SibSp(형제자매/배우자)이 없는 사람들은 34.5%정도의 생존율을 보였지만

형제 자매가 있으면 생존율이 올라갔다

그런데 5-8명의 동반을 가진 사람들은 왜 모두 살아남지 못했는가? 이들은 모두 Pclass = 3이었다..

 

Parch도 이산형 변수!

 

pd.crosstab(data.Parch,data.Pclass).style.background_gradient(cmap='summer_r')

f,ax=plt.subplots(1,2,figsize=(20,8))
sns.barplot('Parch','Survived',data=data,ax=ax[0])
ax[0].set_title('Parch vs Survived')
sns.factorplot('Parch','Survived',data=data,ax=ax[1])
ax[1].set_title('Parch vs Survived')
plt.close(2)
plt.show()

Parch(부모, 자식 수 ) 또한 생존에 영향을 미쳤다고 할 수 있다.

 

Fare은 연속형 변수!

print('Highest Fare was:',data['Fare'].max())
print('Lowest Fare was:',data['Fare'].min())
print('Average Fare was:',data['Fare'].mean())

f,ax=plt.subplots(1,3,figsize=(20,8))
sns.distplot(data[data['Pclass']==1].Fare,ax=ax[0])
ax[0].set_title('Fares in Pclass 1')
sns.distplot(data[data['Pclass']==2].Fare,ax=ax[1])
ax[1].set_title('Fares in Pclass 2')
sns.distplot(data[data['Pclass']==3].Fare,ax=ax[2])
ax[2].set_title('Fares in Pclass 3')
plt.show()

Pclass = 1의 운임은 대체로 20 이상으로 많이 분포하고 있고 500까지 범위가 굉장히 넓다, 비싸게 주고 탄사람도 많음

Pclass = 2의 운임은 쌍봉형태인데, 10, 30 정도에서 쌍봉을 이루고 있음

Pclass = 3의 운임도 의외로 비싸게 주고 탄 사람도 있었는데 대체로 10정도의 운임을 많이 냈음

 

EDA 결과 정리

- Sex: 남성에 비해 여성의 생존율은 매우 높았다.

- Pclass: 일등석 승객이 되면 생존율이 높아지고, 삼등석은 생존율이 매우 낮았다. 돈이 최고다

- Age: 영유아일 수록 생존 확률이 높았다, 15-35세사이의 승객이 많이 죽었다.

- Embarked: Pclass1의 상당수가 S, Q에서 탑승했음에도 불구하고 C에서의 생존율이 높았다.

- Parch + SibSp : 대가족이 혼자 여행하는 사람들보다 생존율이 올라갔다.

 

Correlation Plot

sns.heatmap(data.corr(),annot=True,cmap='RdYlGn',linewidths=0.2) #data.corr()-->correlation matrix
fig=plt.gcf()
fig.set_size_inches(10,8)
plt.show()

 

 

2부에서 계속..