차밍이
[파이썬 머신러닝] Kaggle 타이타닉 데이터 생존자 예측모델 RandomForest와 DecisionTree 본문
Kaggle의 타이타닉 데이터를 바탕으로 의사결정 나무(Decision tree) 모델을 만들어서 예측을 진행해보겠습니다. 그리고 랜덤포레스트 모델을 사용해서 성능을 비교해보겠습니다. 전체적으로 세세하게 데이터를 분석하고 예측하는 것이 아닌, 간단하게 데이터 전처리를 모델을 만들어 보는 것에 초점을 맞추어 실습을 진행하겠습니다.
타이타닉 데이터 출처 : [https://www.kaggle.com/c/titanic]
Library
import pandas as pd
import numpy as np
import re
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn import metrics
의사결정 트리를 위한 sklearn의 라이브러리와 기타 필요한 라이브러리를 불러왔습니다.
EDA : 데이터 탐색
path = r"./titanic"
train = pd.read_csv(path+"/train.csv")
test = pd.read_csv(path+"/test.csv")
train.head()
train.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
와 Cabin
. Embarked
로 3가지가 있습니다.
Embarked
는 2개밖에 되지 않으므로 가장 많은 최빈값으로 채워버리겠습니다.
Cabin
은 있는 것과 없는 것은 없는 것이므로 결측치 그대로 두는 것이 옳은 것 같다고 생각하므로 NaN
이라는 문자를 넣어서 채우겠습니다.
Age
는 다른 feature를 통해서 KNN으로 나이를 유추하여 값을 넣어주도록 하겟습니다.
상관관계나 다른 데이터 탐색은 넘어가도록 하겠습니다.
데이터 전처리
이름과 호칭
먼저 Name
을 통해서 Mr
,Ms
등의 호칭을 뽑아내어 정리해볼 예정입니다. 이름 자체는 의미가 없지만, Mr
,Ms
등은 결혼 여부나 성별 등의 정보를 담고 있으며 Dr
등의 경우는 직업이나 학력을 유추해볼 수 있을 것 같습니다. 그래서 뽑아서 살리는 방향으로 두겠습니다.
# 호칭을 뽑기 위한 정규식
pat = re.compile('[\w]+, ([\w]+)')
# 이름에서 호칭을 뽑기위한 함수 정의
def find_name_title(x):
return pat.findall(x)[0]
#이름 내용을 호칭으로 변경해버리기
train['Name'] = train['Name'].apply(find_name_title)
test['Name'] = test['Name'].apply(find_name_title)
Cabin 유무
Cabin
이 있는 경우와 없는 경우를 0과 1로 구분하여 새로운 column으로 넣었습니다. 넘파이의 np.where 함수를 사용해서 조건에 따른 경우로 나누어주었습니다.
train['Cabin_yes'] = np.where(train['Cabin'].isnull()==False,1,0)
test['Cabin_yes'] = np.where(test['Cabin'].isnull()==False,1,0)
train
Cabin 종류
어떤 종류의 Cabin
인지를 알기 위해서 Cabin
의 앞의 문자를 뽑아옵니다.
pat_cabin = re.compile('([\w])[0-9]*')
def Cabin_word(x):
if x is not np.nan and x is not None:
return pat_cabin.findall(x)[0]
else: return 'X'
train['Cabin_C'] = train['Cabin'].apply(Cabin_word)
test['Cabin_C'] = test['Cabin'].apply(Cabin_word)
One-hot Encoding
지금까지의 결과를 바탕으로 나이 데이터를 채우기 위해서 카테고리형의 문자열 데이터를 encoding 해주도록 하겠습니다. get_dummies
함수를 사용해서 Encoding을 진행하였습니다.
'Name','Sex','Embarked','Cabin_C'
에 해당되는 데이터는 encoding을 진행하고 'PassengerId','Ticket','Cabin'
에 해당되는 데이터는 제거하였습니다. PassengerId
와 Ticket
에서는 얻을 데이터가 없어 보여서 제거하였습니다. 티켓에서 뽑을 데이터가 있을 수 있으나 시간 관계상 무시하였습니다.
train_dummy = pd.get_dummies(data=train,columns=['Name','Sex','Embarked','Cabin_C']).drop(['PassengerId','Ticket','Cabin'], axis=1)
test_dummy = pd.get_dummies(data=test,columns=['Name','Sex','Embarked','Cabin_C']).drop(['PassengerId','Ticket','Cabin'], axis=1)
나이 결측치 채우기
KNN을 사용해서 다른 feature들을 통해 나이를 예측해서 넣도록 하겠습니다.
knn = KNeighborsRegressor()
# 나이가 있는 데이터로 fit해서 모델을 생성
knn.fit(train_dummy[train_dummy['Age'].isnull()==False][train_dummy.columns.drop('Age')],
train_dummy[train_dummy['Age'].isnull()==False]['Age'])
# 나이가 결측인 데이터를 예측
guesses = knn.predict(train_dummy[train_dummy['Age'].isnull()==True][train_dummy.columns.drop('Age')])
guesses
예측한 나잇값에 대한 결과는 아래와 같다.
array([47.8 , 31.8 , 18.4 , 32.8 , 17.6 , 29.8 , 21.584, 17.6 ,
24.6 , 28.7 , 31. , 33.2 , 17.6 , 29.2 , 49.4 , 38. ,
11.934, 29.8 , 31. , 17.6 , 31. , 31. , 29.8 , 30.2 ,
16.4 , 31. , 47.8 , 17.6 , 20.4 , 34.8 , 26.8 , 37. ,
35.8 , 56.4 , 22.4 , 37. , 28.4 , 46.8 , 28.8 , 47.8 ,
17.6 , 35.2 , 47.8 , 29.8 , 12.6 , 26.6 , 17.1 , 19.3 ,
34.8 , 46.4 , 47.8 , 21.5 , 40.9 , 17.6 , 33.4 , 54.6 ,
38. , 48. , 17.6 , 31.4 , 26.1 , 31. , 31.8 , 35.2 ,
20.8 , 37.6 , 29.8 , 36.2 , 50.4 , 32.8 , 17.6 , 17.6 ,
33.2 , 18.4 , 17.6 , 36.4 , 29.8 , 44.4 , 12.6 , 29.8 ,
40.1 , 33.4 , 25.4 , 28.7 , 34.8 , 47.8 , 36.2 , 25.8 ,
27. , 31. , 40.6 , 47.8 , 31. , 33.4 , 44.4 , 34.8 ,
42.2 , 33.4 , 12.6 , 27. , 28.4 , 30.3 , 20.3 , 52.2 ,
31. , 31.4 , 32.8 , 29.9 , 30.8 , 29.9 , 28.2 , 41. ,
35.2 , 47.8 , 30.8 , 47.8 , 31. , 24.2 , 29.9 , 17.6 ,
27.7 , 33.6 , 31. , 21.5 , 31.8 , 32.8 , 29.8 , 29.2 ,
28.6 , 19.3 , 47.8 , 44.4 , 34.4 , 30. , 29.8 , 28.4 ,
29.8 , 17.6 , 29.8 , 34.2 , 40.6 , 33.4 , 20.3 , 29.8 ,
17.6 , 11.934, 50.6 , 28.9 , 17.6 , 33.4 , 29.8 , 29.8 ,
43.4 , 41. , 46.2 , 31.4 , 32.8 , 44.3 , 44.4 , 27.8 ,
47.8 , 37. , 42.7 , 34.4 , 40.1 , 33.6 , 47.2 , 29.9 ,
31. , 43.2 , 35.2 , 34.8 , 29.9 , 37. , 25.8 , 29.8 ,
13.2 ])
예측된 결과를 실제 결측 된 곳에 넣어준다.
train_dummy.loc[train_dummy['Age'].isnull()==True,'Age'] = guesses
train_dummy.isnull().sum()
더 이상 결측 값이 없는 것을 확인할 수 있다.
Survived 0
Pclass 0
Age 0
SibSp 0
Parch 0
Fare 0
Cabin_yes 0
Name_Capt 0
Name_Col 0
Name_Don 0
Name_Dr 0
Name_Jonkheer 0
Name_Lady 0
Name_Major 0
Name_Master 0
Name_Miss 0
Name_Mlle 0
Name_Mme 0
Name_Mr 0
Name_Mrs 0
Name_Ms 0
Name_Rev 0
Name_Sir 0
Name_the 0
Sex_female 0
Sex_male 0
Embarked_C 0
Embarked_Q 0
Embarked_S 0
Cabin_C_A 0
Cabin_C_B 0
Cabin_C_C 0
Cabin_C_D 0
Cabin_C_E 0
Cabin_C_F 0
Cabin_C_G 0
Cabin_C_T 0
Cabin_C_X 0
dtype: int64
나이와 요금 카테고리화
남은 데이터를 확인해보자. 이제 나이와 요금을 카테고리화 하는 것이 좋을 것으로 생각된다.
train_dummy
Survived | Pclass | Age | SibSp | Parch | Fare | Cabin_yes | Name_Capt | Name_Col | Name_Don | ... | Embarked_S | Cabin_C_A | Cabin_C_B | Cabin_C_C | Cabin_C_D | Cabin_C_E | Cabin_C_F | Cabin_C_G | Cabin_C_T | Cabin_C_X | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 22.0 | 1 | 0 | 7.2500 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
1 | 1 | 1 | 38.0 | 1 | 0 | 71.2833 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 1 | 3 | 26.0 | 0 | 0 | 7.9250 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
3 | 1 | 1 | 35.0 | 1 | 0 | 53.1000 | 1 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 3 | 35.0 | 0 | 0 | 8.0500 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
886 | 0 | 2 | 27.0 | 0 | 0 | 13.0000 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
887 | 1 | 1 | 19.0 | 0 | 0 | 30.0000 | 1 | 0 | 0 | 0 | ... | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
888 | 0 | 3 | 13.2 | 1 | 2 | 23.4500 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
889 | 1 | 1 | 26.0 | 0 | 0 | 30.0000 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
890 | 0 | 3 | 32.0 | 0 | 0 | 7.7500 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
891 rows × 38 columns
test_dummy['Age'].plot(kind='hist', bins=10)
train_dummy['Age_cat'] = pd.cut(train_dummy['Age'],10, labels=[*range(10)])
test_dummy['Age_cat'] = pd.cut(test_dummy['Age'],10, labels=[*range(10)])
train_dummy['Fare'].plot(kind='hist', bins=40, xlim=(10,100))
나이와 요금 모두 5개의 구간으로 나누었습니다.
연속형 변수를 카테고리화 하기 위해 나누는 것에는 판 다스의 cut
또는 qcut
함수를 사용하면 쉽게 만들 수 있습니다. 저는 qcut
을 사용해서 간단하게 만들었습니다.
# train_dummy['Fare_cat'] = pd.cut(train_dummy.Fare,precision=5,bins=[0,10,30,80,120,999999] ,labels=['lowlow','low','mid','high','highhigh'])
# test_dummy['Fare_cat'] = pd.cut(test_dummy.Fare,precision=5,bins=[0,10,30,80,120,999999] ,labels=['lowlow','low','mid','high','highhigh'])
train_dummy['Fare_cat'] = pd.qcut(train_dummy.Fare,5, labels=[*range(5)])
test_dummy['Fare_cat'] = pd.qcut(test_dummy.Fare,5, labels=[*range(5)])
train_dummy['Fare_cat'].value_counts()
qcut
을 하면 어느 정도 비슷하게 데이터가 들어가는 것을 알 수 있습니다.
1 184
3 180
0 179
4 176
2 172
Name: Fare_cat, dtype: int64
One-hot Encodnig 2
카테고리 화한 연속형 변수들에 대해서 다시 encoding을 진행합니다. 이전에 빠트린 Pclass
도 같이 one-hot encoding 합니다.
train_final = pd.get_dummies(train_dummy, columns=['Age_cat','Fare_cat','Pclass']).drop(['Age','Fare','Pclass'],axis=1, errors='ignore')
test_final = pd.get_dummies(test_dummy, columns=['Age_cat','Fare_cat','Pclass']).drop(['Age','Fare','Pclass'],axis=1, errors='ignore')
train_final
데이터 전처리가 끝난 결과는 아래의 데이터 프레임과 같습니다. one-hot encoding의 경우 feature를 너무 늘리는 경향이 있습니다. 하지만 여기서 사용하는 데이터가 많지 않으므로 문제 되지 않을 것으로 판단했습니다.
Survived | SibSp | Parch | Cabin_yes | Name_Capt | Name_Col | Name_Don | Name_Dr | Name_Jonkheer | Name_Lady | ... | Age_cat_8 | Age_cat_9 | Fare_cat_0 | Fare_cat_1 | Fare_cat_2 | Fare_cat_3 | Fare_cat_4 | Pclass_1 | Pclass_2 | Pclass_3 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
3 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
886 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
887 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
888 | 0 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
889 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
890 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
891 rows × 53 columns
Decision Tree : 의사결정 나무 모델
X_train, X_test, y_train, y_test = train_test_split(train_final.iloc[:,1:],train_final['Survived'],test_size=0.2,random_state=42)
DT = DecisionTreeClassifier(max_depth=100)
model = DT.fit(X_train, y_train)
pred = model.predict(X_test)
accuracy_score(pred, y_test)
>> 0.8268156424581006
예측 결과는 0.82 정도의 결과를 얻을 수 있었습니다. 생각보다 높은 정확도를 보여주는 것 같습니다.
RandomForest : 랜덤 포레스트 모델
rf_clf = RandomForestClassifier()
model = rf_clf.fit(X_train, y_train)
pred = model.predict(X_test)
accuracy_score(pred, y_test)
>> 0.8435754189944135
DT에 비해서 조금 더 좋은 결과를 보여주는 것을 확인할 수 있습니다.
머신러닝 더 알아보기
'파이썬 > 머신러닝' 카테고리의 다른 글
[DACON] AI프렌즈 시즌1 온도 추정 경진대회 - 02 : 대회 종료 및 소감 (0) | 2020.04.14 |
---|---|
[파이썬] 교차검증 Cross Validation 검증 (0) | 2020.03.26 |
[DACON] AI프렌즈 시즌1 온도 추정 경진대회 - 01 : 대회 소개 및 참여 (0) | 2020.03.24 |
[파이썬] 실습 데이터를 사용한 SVM 모델 생성 및 예측, C와 감마(Gamma) (3) | 2020.03.18 |
[파이썬/데이터분석] LendingClub 원금 상환 여부 예측하기(2) 시각화 소스코드 (0) | 2020.03.12 |
[파이썬/데이터분석] LendingClub 원금 상환 여부 예측하기(1) : EDA와 데이터 시각화 (0) | 2020.03.12 |
[머신러닝] PCA 실습 (2) : 주성분분석이 성능을 높여주는가? (2) | 2020.03.04 |
[머신러닝] 실습으로 보는 PCA(주성분 분석)가 필요한 이유 (3) | 2020.03.02 |