1. 라이브러리 불러오기
# 데이터 처리 라이브러리
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno
%matplotlib inline # notebook을 실행한 브라우저에서 바로 그림을 볼 수 있게 해줌.
plt.style.use('seaborn') # 'seaborn' 폰트로 변경
sns.set(font_scale=2.5) # 폰트 크기 설정
# 경고 창 생략
import warnings
warnings.filterwarnings('ignore')
seaborn은 matplotlib을 기반으로 다양한 색상 테마와 통계용 차트 등의 기능을 추가한 시각화 패키지입니다.
2. 데이터 불러오기
df_train = pd.read_csv('/kaggle/input/titanic/train.csv')
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
데이터를 불러올 때 pandas를 사용합니다. pandas는 데이터를 처리하는 강력한 도구입니다. csv 파일의 가장 위에 있는 인덱스를 columns로 지정합니다.
head
df_train.head(5)
head 함수는 pandas로 불러온 데이터를 위에서부터 숫자만큼 순서대로 불러옵니다.
describe
df_train.describe()
describe 함수는 데이터 통계 분석 도구입니다. 개수, 평균, 표준편차, 최소값 등을 표로 멋지게 표현해줍니다.
3. 데이터 대략적 분석하기
①결측값 파악하기
df_train
for col in df_train.columns:
print('{:>10}\t Percentage of NaN value: {:.2f}'.format(col, 100*(df_train[col].isnull().sum() / len(df_train[col].shape[0]))))
df_train에는 12개의 columns가 들어있습니다. 이 중 Age, Cabin, Embarked 3가지엔 결측값이 존재하므로 이를 다른 값으로 채워주거나, feature를 사용하지 않는 방법 중 하나를 택해야 합니다. 이 때, Cabin은 결측값의 비율이 77프로에 육박하므로 사용하지 않는 편이 낫겠습니다.
df_test
for col in df_test.columns:
print('{:>10}\t Percentage of NaN value: {.2f}'.format(col, df_test[col].isnull().sum() / len(df_test[col].shape[0])))
df_test는 Age, Fare, Cabin 3가지에 관하여 결측값이 존재하고 df_train과 달리 Fare에 결측값이 존재합니다. 마찬가지로 Cabin의 결측값 비율은 높으므로 사용하지 않습니다.
데이터 시각화
msno.matrix(df=df_train.iloc[:,:],figsize=(8,8), color=(0.8,0.5,0.2))
msno.bar(df=df_train, figsize=(8,8), color=(0.8,0.5,0.2)
②생존률 분석
f, ax = plt.subplots(1,2,figsize=(18,8))
df_train['Survived'].value_counts().plot.pie(explode=[0,0.1], autopct='%1.1f%%', ax=ax[0], shadow=True)
ax[0].set_title('Pie plot - Survived')
ax[0].set_ylabel('')
sns.countplot('Survived', data=df_train, ax=ax[1])
ax[1].set_title('Count plot - Survived')
plt.show()
4. 데이터 세부 분석하기
① Pclass
df_train[['Pclass','Survived']].groupby(['Pclass'], as_index=True).count()
Pclass는 티켓의 등급이며 숫자가 낮을수록 높은 등급입니다. pandas dataframe의 groupby 함수를 사용하여 티켓 등급별 인원수를 알아낼 수 있습니다. as_index를 True로 하게 되면 Pclass를 index로 쓰게 됩니다.
df_train[['Pclass','Survived']].groupby(['Pclass']).sum()
생존자는 0(사망), 1(생존)으로 표현됩니다. 따라서 sum() 함수를 사용하여 티켓 등급별 생존자의 수를 구할 수 있습니다. 등급별 인원수와 등급별 생존자 수를 구했으므로, 등급별로 생존율을 구할 수 있습니다.
pd.crosstab(df_train['Pclass'], df_train['Survived'], margins=True).style.background_gradient(cmap='cool')
crosstab은 Pclass와 Survived 칼럼을 각각이 갖고 있는 값으로 분류하여 표로 멋지게 나타내 줍니다. 예를 들어, Pclass가 1등급이고, 생존한 사람의 수는 136명인 식입니다.
df_train[['Pclass','Survived']].groupby(['Pclass']).mean().sort_values(by='Survived',ascending=False).plot.bar()
생존률을 구한 다음 생존률이 높은 순(ascending=False)대로 히스토그램을 그렸습니다. 티켓 등급이 높을수록 생존률이 높습니다. 돈이 많은 사람이 생존할 확률이 높다는 것을 알 수 있습니다. 역시.. 재력이..
y_position = 1.02
f, ax = plt.subplots(1,2,figsize=(18,8))
df_train['Pclass'].value_counts().plot.bar(color=['#CD7F32','#FFDF00','#D3D3D3'],ax=ax[0]])
ax[0].set_title('Number of passengers By Pclass', y = y_position)
ax[0].set_title('Count')
sns.countplot('Pclass', hue='Survived',data=df_train, ax=ax[1])
ax[1].set_title('Pclass: Survived vs Dead', y = y_position)
plt.show()
matplotlib.pyplot의 subplots 함수는 그래프를 그릴 개수와 그래프의 크기를 설정합니다. 첫번째 ax[0] 위치에는 티켓 등급별로 인원수를 히스토그램으로 나타내고, 두번째 ax[1] 위치에는 seaborn의 countplot 함수를 사용하여 티켓 등급별로 사망자와 생존자를 나타내도록 했습니다. 그래프의 set_title 함수를 사용하여 각 그래프의 제목을 나타내줍니다.
② Sex
f, ax = plt.subplots(1,2,figsize=(18,8))
df_train[['Sex','Survived']].groupby(['Sex'],as_index=True).mean().plot.bar(ax=ax[0])
ax[0].set_title('Survived vs Sex')
sns.countplot('Sex', hue='Survived', data=df_train,ax=ax[1])
ax[1].set_title('Sex: Survived vs Dead')
plt.show()
성별로 생존률을 파악하고, 성별로 총 생존자, 사망주 추이를 나타냈습니다.
pd.crosstab(df_train['Sex'], df_train['Survived'], margins = False).style.background_gradient(cmap='summer_r')
crosstab에서 margins를 False로 두게 되면 총 합이 나오지 않습니다.
sns.factorplot('Pclass','Survived',hue='Sex',data=df_train.size=6, aspect=1.5)
factorplot은 x, y 축에 두가지의 값을 받고, hue에 있는 데이터를 분류하여 그래프로 표현합니다.
③ Age
연령 분석
print('제일 나이 많은 탑승객: {:.1f}'.format(df_train['Age'].max())
print('제일 나이 어린 탑승객: {:.1f}'.format(df_train['Age'].min())
print('탑승객 평균 나이 : {.1f}'.format(df_train['Age'].mean())
fig, ax = plt.subplots(1,1,figsize=(9,5))
sns.kdeplot(df_train[df_train['Survived']==1]['Age'],ax=ax)
sns.kdeplot(df_train[df_train['Survived']==0]['Age'],ax=ax)
plt.legend(['Survived==1', 'Survived==0'])
plt.show()
seaborn의 kdeplot은 커널밀도함수로 데이터에 맞게 그래프를 fitting 해주는 함수입니다. Age가 0 이하로는 실제 데이터가 없지만 그래프를 fitting 한 결과로 저렇게 그려집니다. 히스토그램으로 그릴 경우 비교할 대상이 겹치면 보이지 않는 현상이 나타나지만, kdeplot은 여러개를 비교할 수 있습니다.
plt.subplots(1,1,figsize=(8,6))
df_train[df_train['Pclass']==1]['Age'].plot(kind='kde')
df_train[df_train['Pclass']==2]['Age'].plot(kind='kde')
df_train[df_train['Pclass']==3]['Age'].plot(kind='kde')
plt.legend(['1st Class','2nd Class','3rd Class'])
plt.title('Age Distribution within classes')
plt.xlabel('Age')
plt.show()
티켓등급별 나이 분포를 kdeplot으로 나타낸 그래프입니다.
plt.figure(figsize=(8,6)
df_train[(df_train['Survived'] == 1 ) & (df_train['Pclass'] == 1)]['Age'].plot(kind='kde')
df_train[(df_train['Survived'] == 0 ) & (df_train['Pclass'] == 1)]['Age'].plot(kind='kde')
plt.title('1st class')
plt.legend(['Survived==1', 'Survived==0'])
plt.show()
티켓등급이 1등급일 때, 생존자 비율을 나타낸 그래프입니다.
plt.figure(figsize=(8,6))
aging_survived_ratio=[]
for i in range(80):
aging_survived_ratio.append(df_train[(df_train['Age'] <= i)]['Survived'].sum() / len(df_train[df_train['Age']<=i]['Survived'])
plt.plot(aging_survived_ratio)
plt.title('Survival ratio change depending on range of Age')
plt.xlabel('Age 0~x')
plt.show()
해당 나이까지의 생존률을 나타낸 그래프를 보면 나이가 영유아는 생존률이 높은 것을 알 수 있습니다. 나이가 들수록 생존률이 줄어듭니다.
Pclass, Sex, Age
f, ax = plt.subplots(1,2,figsize=(18,8))
sns.violinplot('Pclass','Age',hue='Survived', data=df_train, scale='count', 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=df_train,scale='count',split=True, ax=ax[1])
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0,110,10))
plt.show()
violinplot은 그래프의 형태가 바이올린을 닮았다고 해서 붙여진 이름입니다. violinplot을 통해 티켓등급이 높고 나이가 어리며, 여성이라면 살아남을 확률이 더 높은 것을 한 눈에 확인할 수 있습니다.
④ Embarked
f,ax = plt.subplots(1,1,figsize=(7,7))
df_train[['Embarked','Survived']].groupby(['Embarked'],as_index=True).mean().sort_values(by='Survived',ascending=False).plot.bar(ax=ax)
sort_values(by='Survived',ascending=False) 함수를 통해 생존율(평균치)이 높은 순대로 출력하였습니다. 도착하는 항구가 C일 경우 생존률이 가장 높은 것을 볼 수 있습니다.
f,ax = plt.subplots(2,2,figsize=(20,15))
sns.countplot('Embarked',data = df_train, ax=ax[0,0])
ax[0,0].set_title('(1) No. Of Passengers Board')
sns.countplot('Embarked',hue='Sex', data = df_train, ax=ax[0,1])
ax[0,1].set_title('(2) Male-Female split for embarked')
sns.countplot('Embarked', hue='Survived', data =df_train, ax=ax[1,0])
ax[1,0].set_title('(3) Embarked vs Survived')
sns.countplot('Embarked', hue = 'Pclass', data = df_train, ax=ax[1,1])
ax[1,1].set_title('(4) Embarked vs Pclass')
plt.show()
C 항구에 도착하는 사람 중 여성의 비율이 가장 높고 따라서 생존율도 높은 것을 볼 수 있습니다. 또한 1등급에 티켓을 갖고 있는 비율도 비교적 높은 편임을 알 수 있습니다.
⑤ Family = SibSp + Parch
df_train['FamilySize'] = df_train['SibSp'] + df_train['Parch'] + 1
df_test['FamilySize'] = df_train['SibSp'] + df_train['Parch'] + 1
f,ax = plt.subplots(1,3,figsize=(40,10))
sns.countplot('Familysize',data=df_train,ax=ax[0])
ax[0].set_title('(1) No. of Passenger Board',y=1.02)
sns.countplot('FamilySize',hue='Survived',data=df_train, ax=ax[1])
ax[1].set_title('(2) Survived countplot depending on FamilySize',y=1.02)
df_train[['FamilySize','Survived']].groupby(['FamilySize'],as_index=True).mean().sort_values('Survived',ascending=False).plot.bar(ax=ax[2])
ax[2].set_title('(3) Survived rate depending on FamilySize',y=1.02)
가족 인원수가 생존률에 미치는 영향을 나타낸 그래프입니다. 이 때, set_title 함수의 y는 title의 위치를 지정해줍니다. default는 1입니다.
⑥ Fare
fig, ax = plt.subplots(1,1,figsize=(8,8))
g = sns.distplot(df_train['Fare'], color='b', label='skewness: {:.2f}'.format(df_train['Fare'].skew()),ax=ax)
g = g.legend(loc='best')
fare 은 데이터가 한쪽으로 치우쳐진 모습을 볼 수 있습니다. 이를 객관적으로 나타내는 지표가 skewness, 왜도입니다. 왜도는 실수 값 확률 변수의 확률 분포 비대칭성을 나타내는 지표입니다. 왜도 값이 클수록 데이터가 치우쳐 있는 것을 확인할 수 있습니다. 이를 고르게 퍼뜨리기 위해서 log 함수를 취합니다.
df_train['Fare'] = df_train['Fare'].map(lambda i: np.log(i) if i>0 else 0)
df_test['Fare'] = df_test['Fare'].map(lambda i: np.log(i) if i>0 else 0)
fig, ax = plt.subplots(1,1,figsize=(8,8))
g = sns.distplot(df_train['Fare'], color ='b', label='Skewness: {:.2f}'.format(df_train['Fare'].skew()),ax=ax)
g = g.legend(loc='best')
아까와 다르게 skewness 가 0에 가까워졌고, 데이터가 조금 더 고르게 퍼져 있는 모습을 볼 수 있습니다.
⑦ Ticket
티켓 이름은 데이터로 처리하기 까다롭고, 티켓 이름 간에 그렇게 큰 연관성이 보이지 않기 때문에 모델이 학습할 데이터에서 제외하도록 하겠습니다.
⑧ Name
df_train['Initial'] = df_train['Name'].str.extract('([A-Za-z]+)\.')
df_test['Initial'] = df_test['Name'].str.extract('([A-Za-z]+)\.')
정규 표현식을 사용하여 이름에서 영어 호칭만을 추출해냅니다. [A-Za-z]는 모든 알파벳을 의미하고, '+'는 앞에 있는 것이 1번 이상 반복되는 것을 의미합니다. 따라서, '.'으로 끝나는 알파벳 단어를 추출해내면 우리가 원하는 영어 호칭만을 추출할 수 있습니다. 이를 새로운 column인 Initial을 만들어 저장합니다.
pd.crosstab(df_train['Sex'],df_train['Initial']).T.style.background_gradient(cmap='summer_r')
성별과 호칭을 crosstab으로 추출해본 결과 많은 호칭들이 나오는데 이것을 other로 묶어주겠습니다.
df_train['Initial'].replace(['Mlle', 'Mme','Ms','Dr','Major','Lady','Countess','Jonkheer','Col','Rev','Capt','Sir','Don','Dona'],
['Miss','Miss','Miss','Mr','Mr','Mrs', 'Other','Other','Other','Other','Mr','Mr','Mr','Mr'], inplace=True)
df_test['Initial'].replace(['Mile', 'Mme','Ms','Dr','Major','Lady','Countess','Jonkheer','Col','Rev','Capt','Sir','Don','Dona'],
['Miss','Miss','Miss','Mr','Mr','Mrs', 'Other','Other','Other','Other','Mr','Mr','Mr','Mr'], inplace=True)
replace 함수의 옵션으로 inplace를 True로 주게 되면 처리한 결과가 df_train['Initial']에 바로 저장이 됩니다.
df_train.groupby(['Initial']).mean()
생존률이 Miss, Mrs가 상대적으로 높다는 것을 알 수 있습니다. Master의 연령은 낮고 Mr보다 생존률이 높다는 것을 볼 수 있습니다.
df_train.groupby('Initial')['Survived'].mean().plot.bar()
5. 결측값 넣어주기
① Age
df_all = pd.concat([df_train, df_test])
df_all.groupby('Initial')['Survived'].mean()
Age 는 결측값이 약 20%정도 존재했습니다. Master 호칭을 갖는 사람은 평균 나이가 5세, Miss는 22세, Mr는 32세, Mrs는 37세, Other은 44세입니다. 결측값으로 각 호칭의 평균나이를 넣어줍니다.
df_train.loc[(df_train['Age'].isnull()) & df_train['Initial'] == 'Mr'),'Age'] = 33
df_train.loc[(df_train['Age'].isnull()) & df_train['Initial'] == 'Miss'),'Age'] = 22
df_train.loc[(df_train['Age'].isnull()) & df_train['Initial'] == 'Master'),'Age'] = 5
df_train.loc[(df_train['Age'].isnull()) & df_train['Initial'] == 'Mrs'),'Age'] = 37
df_train.loc[(df_train['Age'].isnull()) & df_train['Initial'] == 'Other'),'Age'] = 44
df_test.loc[(df_test['Age'].isnull()) & df_test['Initial'] == 'Mr'),'Age'] = 33
df_test.loc[(df_test['Age'].isnull()) & df_test['Initial'] == 'Miss'),'Age'] = 22
df_test.loc[(df_test['Age'].isnull()) & df_test['Initial'] == 'Master'),'Age'] = 5
df_test.loc[(df_test['Age'].isnull()) & df_test['Initial'] == 'Mrs'),'Age'] = 37
df_test.loc[(df_test['Age'].isnull()) & df_test['Initial'] == 'Other'),'Age'] = 44
② Embarked
df_train['Embarked'].isnull().sum()
2
Embarked의 결측값은 2개이므로, 가장 많은 Embarked가 있는 것으로 값을 채워줍니다.
df_train['Embarked'].fillna('S',inplace=True)
6. 데이터 변환하기
① 'Age'
def category_age(x):
if x<10:
return 0
elif x<20:
return 1
elif x<30:
return 2
elif x<40:
return 3
elif x<50:
return 4
elif x<60:
return 5
elif x<70:
return 6
else:
return 7
df_train['Age_cat'] = df_train['Age'].apply(category_age)
df_test['Age_cat'] = df_test['Age'].apply(category_age)
category_age 함수를 사용하여 연령대별로 분류를 합니다. apply 함수를 사용하여 모든 데이터에 함수를 적용합니다.
df_train.drop(['Age'], axis=1, inplace=True)
df_test.drop(['Age'], axis=1, inplace=True)
학습에 사용하지 않을 'Age' column을 삭제합니다.
② 'Initial'
df_train['Initial'] = df_train['Initial'].map({'Master':0,'Miss':1, 'Mr':2, 'Mrs':3, 'Other':4})
df_test['Initial'] = df_train['Initial'].map({'Master':0,'Miss':1, 'Mr':2, 'Mrs':3, 'Other':4})
문자열을 숫자로 매핑합니다.
③ 'Embarked'
df_train['Embarked'] = df_train['Embarked'].map({'C':0,'Q':1,'S':2})
df_test['Embarked'] = df_test['Embarked'].map({'C':0,'Q':1,'S':2})
④ 'Sex'
df_train['Sex'] = df_train['Sex'].map({'female'=0,'male'=1})
df_test['Sex'] = df_test['Sex'].map({'female'=0,'male'=1})
heatmap_data=df_train[['Survived','Pclass','Sex','Fare','Embarked','FamilySize','Initial','Age_cat']]
colormap=plt.cm.BuGn
plt.figure(figsize=(14,12))
plt.title('Person Correlation of Features', y=1.05, size=15)
sns.heatmap(heatmap_data.astype(float).corr(), linewidths=0.1, vmax=1.0, square=True, cmap=colormap, linecolor='white',annot=True, annot_kws={'size':16},fmt='.2f')
df_train = pd.get_dummies(df_train, columns=['Initial'], prefix='Initial')
df_test = pd.get_dummies(df_test, columns=['Initial'], prefix='Initial')
df_train = pd.get_dummies(df_train, columns = ['Embarked'], prefix = 'Embarked')
df_test = pd.get_dummies(df_train, columns = ['Embarked'], prefix = 'Embarked')
df_train.drop(['PassengerId','Ticket','Cabin','Name','SibSp','Parch'], axis=1, inplace=True)
df_test.drop(['PassengerId','Ticket','Cabin','Name','SibSp','Parch'], axis=1, inplace=True)
영어 호칭과 도착할 항구는 숫자 값으로 맵핑할 때 연속된 수의 개념보다는 분류의 개념이 더 크기 때문에 원핫 인코딩 기법을 사용하여 데이터를 표현해줍니다. 그리고 필요없는 데이터 column은 삭제하여줍니다. 이로써 데이터 전처리가 완료되었습니다. 간단히 모델을 설계하여 생존자를 예측해보도록 합니다.
7. 모델 설계하기
from sklearn.ensemble import RandomForestClassifier
from sklearn import metrics
from sklearn.model_selection import train_test_split
X_train = df_train.drop(['Survived'],axis=1)
target_label = df_train['Survived'].values
X_test = df_test.values
x_tr, x_vld, y_tr, y_vld = train_test_split(X_train, target_label, test_size=0.3, random_state=2018)
model = RandomForestClassifier()
model.fit(X_tr, y_tr)
sklearn 의 랜덤 포레스트로 학습을 시킨 후 모델 예측을 해보겠습니다.
prediction = model.predict(X_vld)
print('총 {}명 중 {:.2f}% 정확도로 생존 맞춤'.format(y_vld.shape[0], 100*metrics.accuracy_score(prediction, y_vld)))
총 268명 중 82.84% 정확도로 생존 맞춤