본문 바로가기

파이썬/머신러닝

타이타닉 데이터 전처리와 결정트리 모델

오늘은 titanic 데이터를 갖고 decision tree 모델을 공부해봤다.

머신러닝 공부할 때 kaggle은 꼭 보는 것이 좋다. 워낙 훌륭하신 분들이 많아.. 필사를 해보는 것 만으로도 아주 많은 도움이 된다.

 

결정트리(Decision Tree) 기본 개념

 

import numpy as np
import pandas as pd
import re
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold, cross_val_score, train_test_split
from sklearn.tree import DecisionTreeClassifier

 

titanic_test.csv
0.03MB
titanic_train.csv
0.06MB

갖고 있는 타이타닉 데이터가 캐글에 올라와있는거랑 살짝 달라서 캐글에 있는걸로 다시 다운받았다. 통으로 된 csv 파일을 train, test 구분하는건 train_test_split으로 구분하면 될 것 같은데 아직 방법을 잘 모르겠어서 다른 것들 보면서 공부할 예정!

 

train = pd.read_csv('titanic_train.csv')
test = pd.read_csv('titanic_test.csv')

PassengerId = test['PassengerId']

train.head()

PassengerId, Survived, Pclass 등의 컬럼들은 숫자로 되어있지만 Name, Sex와 같은 컬럼들은 object형태이기 때문에 전처리를 통해서 int타입으로 바꿔줘야 한다.

 

print(train.info())

print([(cab,type(cab))  for cab in train.Cabin.unique()])

 

 

특히 Cabin 컬럼에서 null값이 많은데 이런 null값들도 전처리로 보정이 필요하다. unique 함수로 cabin컬럼에 있는 데이터들의 타입을 뽑아보면 값이 있는 컬럼들은 string이고 null 값은 데이터타입이 float이다.

 

 

#원본 데이터 유지를 위해 복사본 생성
original_train = train.copy()

# train, test 합친 full data생성
full_data = [train, test]

# Cabin컬럼 null값 보정
train['Has_Cabin'] = train["Cabin"].apply(lambda x: 0 if type(x) == float else 1)
test['Has_Cabin'] = test["Cabin"].apply(lambda x: 0 if type(x) == float else 1)

# SibSp and Parch을 결합해 FamilySize feature 새로 생성
for dataset in full_data:
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1
    
# FamilySize로 IsAlone feature 새로 생성
for dataset in full_data:
    dataset['IsAlone'] = 0
    dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1
    
# Embarked column null값 제거(fillna함수)
for dataset in full_data:
    dataset['Embarked'] = dataset['Embarked'].fillna('S') --null값은 'S'로 대체
    
# are column null값 제거(fillna함수)
for dataset in full_data:
    dataset['Fare'] = dataset['Fare'].fillna(train['Fare'].median()) --null값은 중앙값으로 대체

# Age column null값 제거
for dataset in full_data:
    age_avg = dataset['Age'].mean()
    age_std = dataset['Age'].std()
    age_null_count = dataset['Age'].isnull().sum()
    age_null_random_list = np.random.randint(age_avg - age_std, age_avg + age_std, size=age_null_count)
    # 오류 방지
    dataset.loc[np.isnan(dataset['Age']), 'Age'] = age_null_random_list
    dataset['Age'] = dataset['Age'].astype(int)

# passenger names으로 title 컬럼 생성
def get_title(name):
    title_search = re.search(' ([A-Za-z]+)\.', name)
    # If the title exists, extract and return it.
    if title_search:
        return title_search.group(1)
    return ""

for dataset in full_data:
    dataset['Title'] = dataset['Name'].apply(get_title)
    
# Group all non-common titles into one single grouping "Rare"
for dataset in full_data:
    dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col','Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')

    dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')

for dataset in full_data:
    # Mapping Sex
    print(dataset.Sex.unique())
    dataset['Sex'] = dataset['Sex'].map( {'female': 0, 'male': 1} ).astype(int)
    
    # Mapping titles
    title_mapping = {"Mr": 1, "Master": 2, "Mrs": 3, "Miss": 4, "Rare": 5}
    dataset['Title'] = dataset['Title'].map(title_mapping)
    dataset['Title'] = dataset['Title'].fillna(0)

    # Mapping Embarked
    dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)
    
    # Mapping Fare
    dataset.loc[ dataset['Fare'] <= 7.91, 'Fare'] = 0
    dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
    dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare']   = 2
    dataset.loc[ dataset['Fare'] > 31, 'Fare'] = 3
    dataset['Fare'] = dataset['Fare'].astype(int)
    
    # Mapping Age
    dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
    dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
    dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
    dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
    dataset.loc[ dataset['Age'] > 64, 'Age'] ;

 

한번에 돌리지 않으면 에러가 난다. 에러가 나면 다시 처음으로 돌아가서 titanic train, test 데이터부터 불러와주면 된다.

 

drop_elements = ['PassengerId', 'Name', 'Ticket', 'Cabin', 'SibSp']
train = train.drop(drop_elements, axis=1) #열에서 삭제
test = test.drop(drop_elements, axis=1)

train.head()

 

PassengerId, Name과 같이 필요 없는 컬럼들은 드롭하고 새로 만든 train data를 가져오면 처음과 달리 모든 컬럼이 숫자 형태로 바뀌었다. 이렇게 각 컬럼별로 코드를 매핑해줘야 모형을 적합시키기 수월해진다.

 

 

#최적의 max depth 찾기

cv = KFold(n_splits=10)            # Desired number of Cross Validation folds
accuracies = list()
max_attributes = len(list(test))
depth_range = range(1, max_attributes + 1)

# Testing max_depths from 1 to max attributes
# Uncomment prints for details about each Cross Validation pass
for depth in depth_range:
    fold_accuracy = []
    tree_clf = DecisionTreeClassifier(max_depth = depth)
    # print("Current max depth: ", depth, "\n")
    for train_fold, valid_fold in cv.split(train):
        f_train = train.loc[train_fold] # Extract train data with cv indices
        f_valid = train.loc[valid_fold] # Extract valid data with cv indices

        model = tree_clf.fit(X = f_train.drop(['Survived'], axis=1), 
                               y = f_train["Survived"]) # We fit the model with the fold train data
        valid_acc = model.score(X = f_valid.drop(['Survived'], axis=1), 
                                y = f_valid["Survived"])# We calculate accuracy with the fold validation data
        fold_accuracy.append(valid_acc)

    avg = sum(fold_accuracy)/len(fold_accuracy)
    accuracies.append(avg)
    # print("Accuracy per fold: ", fold_accuracy, "\n")
    # print("Average accuracy: ", avg)
    # print("\n")
    
# Just to show results conveniently
df = pd.DataFrame({"Max Depth": depth_range, "Average Accuracy": accuracies})
df = df[["Max Depth", "Average Accuracy"]]
print(df.to_string(index=False))

 

타깃이 survived이므로 survived를 제외한 나머지 컬럼은 X변수로, survived는 y변수로 지정한다.

최적의 max_depth는 숫자를 바꿔가면서 일일이 찾아볼 수도 있지만 for문을 사용해서 여러 개의 decision tree를 만들고 각 tree별로 k겹 교차검증을 시행했을 때의 accuracy를 평균내서 가장 값이 높은 것으로 선택하면 된다.

 

돌려보면 max_depth가 3일 때 averge accuracy가 가장 좋다.

 

y_train = train['Survived']
x_train = train.drop(['Survived'], axis=1).values
x_test = test.values

tree_clf = DecisionTreeClassifier(criterion='entropy', max_depth=3, random_state=42)
tree_clf.fit(x_train, y_train)

y_pred = tree_clf.predict(x_test)

acc_decision_tree = round(tree_clf.score(x_train, y_train) * 100, 2)
acc_decision_tree
>>>83.16

 

max_depth=3으로 Decision Tree Classifier 모델을 만들었다.

train set의 정확도는 83.16%이다.