ПРОГНОЗ ВЫЖИВАНИЯ ТИТАНИКА

В этом уроке мы узнаем об одном из самых популярных наборов данных в науке о данных. Это даст вам представление о том, как анализировать и соотносить с реальными условиями.

Соревнование

Гибель «Титаника» — одно из самых печально известных кораблекрушений в истории.

15 апреля 1912 года во время своего первого рейса «Титаник», считавшийся «непотопляемым», затонул после столкновения с айсбергом. К сожалению, спасательных шлюпок на всех на борту не хватило, в результате чего из 2224 пассажиров и членов экипажа погибли 1502 человека.

Хотя в выживании был некоторый элемент удачи, похоже, что у некоторых групп людей было больше шансов выжить по сравнению с другими.

В этой задаче нам нужно будет построить прогностическую модель, отвечающую на вопрос: «У каких людей больше шансов выжить?» используя данные о пассажирах (например, имя, возраст, пол, социально-экономический класс и т. д.).

Эта публикация поможет вам начать изучение данных и познакомиться с машинным обучением. Соревнование простое: используйте машинное обучение, чтобы создать модель, которая предсказывает, какие пассажиры выжили после кораблекрушения Титаника.

A. Переменная информация здесь

PassengerId — уникальный идентификатор строки (не влияет на цель).

Выжил — это цель, которую мы пытаемся предсказать 1 : Выжил 0 : Не выжил

Pclass (пассажирский класс) — это социально-экономический статус пассажира и категориальная порядковая характеристика, имеющая три уникальных значения (1, 2 или 3).

Имя, Пол и Возраст говорят сами за себя.

SibSp — общее количество братьев, сестер и супругов пассажиров.

Parch — общее количество родителей и детей пассажиров.

Билет — номер билета пассажира.

Тариф – это пассажирский тариф.

Cabin — номер каюты пассажира.

Посадка – это порт посадки и категориальный признак, который имеет 3 уникальных значения (C, Q или S):

C = Cherbourg
Q = Queenstown
S = Southampton

Примечания к переменным

Pclass: 1 = верхний 2 = средний 3 = нижний

SibSp: набор данных определяет родство как Sibling = брат, сестра, сводный брат, сводная сестра.

Супруг = муж, жена (любовницы и женихи не учитывались)

Parch: набор данных определяет отношение как Родитель = мать, отец

Ребенок = дочь, сын, падчерица, пасынок

Некоторые дети путешествовали только с няней, поэтому для них parch=0.

Ниже приведены несколько вопросов, на которые мы хотели бы ответить после нашего анализа.

  • Есть ли связь между предоставленной информацией о пассажирах и их выживанием?
  • Какова выживаемость для разных возрастных групп?
  • Отдавалось ли предпочтение женщинам и детям для спасения?
  • Помогал ли более высокий социальный статус людям повысить шансы на выживание?
  • Каковы последствия пребывания в одиночестве или с семьей?
  • Повлиял ли на выживаемость пассажирский класс
  • Можем ли мы предсказать, выжил ли пассажир после катастрофы, используя методы машинного обучения?

Давайте начнем

# importing basic libraries
import warnings
warnings.filterwarnings(‘ignore’)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly as ply
from scipy import stats
import math
# reading the titanic train and test data
train = pd.read_csv(‘https://bit.ly/kaggletrain')
test = pd.read_csv(‘https://bit.ly/kaggletest')
train.shape # (891,12)
test.shape # (418,11)

Мы можем проверить заголовок данных, чтобы увидеть, какие переменные у нас есть. В основном у нас есть два типа переменных: непрерывные и дискретные.

Здесь Survived — наша целевая переменная, поэтому мы сохраняем ее в переменной y и сохраняем PassengerID для отправки результатов на kaggle.

y , testpassenger = train[‘Survived’], test[‘PassengerID’]

Мы также удаляем идентификатор пассажира, так как он уникален для каждой записи и не влияет на прогноз выживания. Также объедините поезд и тест в один для простоты выполнения EDA и операций с отсутствующими значениями и разработки функций.

train = train.drop(‘PassengerId’, axis = 1)
test = test.drop(‘PassengerId’, axis = 1)
total = pd.concat([train,test],axis = 0, sort=False)
total = total.reset_index(drop=True)
# Saving variables into different lists
cat_var = [‘Pclass’,’Sex’,’SibSp’,’Parch’,’Embarked’] # categorical
target = [‘Survived’] # target
con_var = [‘Age’,’Fare’] # continuous

Б. Визуализация

Чтобы построить категориальные переменные:

def catplot(x, df):
   fig, ax = plt.subplots(math.ceil(len(x)/3), 3, figsize = (20,10))
   ax = ax.flatten()
   for axes, cat in zip(ax, x):
   if (cat == ‘Survived’):
      sns.countplot(df[cat], ax = axes)
      axes.set_ylabel(‘Count’, fontsize=12)#, weight=’bold’)
   else:
      sns.countplot(df[cat], hue=’Survived’, data = df, ax = axes)
      axes.legend(title=’Survived ?’, labels = [‘No’,’Yes’],
                      loc= ‘upper right’)
      axes.set_ylabel(‘Count’, fontsize=12)#, weight=’bold’)
# call the plot
catplot(target + cat_var, total)

Чтобы построить непрерывные переменные:

def cont(x, df):
   fig, ax = plt.subplots(1,3, figsize = (18,4))
   sns.distplot( df[df[x].notna()][x] , fit = stats.norm,ax =ax[0],
                     bins = 12)
   # fit parameter in distplot fits a function of distribution
   # passed in argument
   stats.probplot( df[x].fillna(df[x].median()), plot=ax[1])
   sns.boxplot(y = df[x], ax = ax[2])
   plt.show()
# call the plot
cont(‘Age’, total)

cont(‘Fare’, total)

Графики плотности:

Для переменных «Возраст» и «Проезд за проезд» мы строим графики плотности, чтобы увидеть, влияют ли они на выживание.

plot_con = [‘Age’,’Fare’]
for i in plot_con:
   plt.figure(figsize=(12,4))
   sns.distplot(train[train[‘Survived’]==0][i], color=’r’, bins = 5)
   sns.distplot(train[train[‘Survived’]==1][i], color=’g’, bins = 5)
   plt.legend(title=’Survived vs ‘+i, labels = [‘Dead’,’Alive’],
                    loc=’upper right’)
   plt.show()

  • Люди младшего возраста выживали больше
  • Можно сделать вывод, что более высокая стоимость проезда ведет к лучшим шансам на выживание, а объединение тарифов может улучшить границы принятия решений.
  • Однако эта связь может быть результатом и других переменных.

C. Разработка функций

Теперь наши следующие шаги будут направлены на проектирование функций.

1. Разработка функций — название:

Бросив беглый взгляд, мы видим, что само имя ничем нам помочь не может, поэтому мы извлекаем из него новые переменные, т. е. Название и Фамилию.

def map_title(x):
   if x in [‘Mr’]:
      return ‘Mr’
   elif x in [‘Mrs’, ‘Mme’]:
      return ‘Mrs’
   elif x in [‘Miss’, ‘Mlle’, ‘Ms’]:
      return ‘Miss’
   # Master taken separate as all have children age
   elif x in [‘Master’]:
      return ‘Master’
   elif x in [‘Col’,’Capt’,’Major’,’Dr’,’Rev’]:
      return ‘Staff’
   elif x in [‘Don’,’Lady’,’Sir’,’the Countess’,’Jonkheer’,’Dona’]:
      return ‘VIP’
def feature_name(df):
   df[‘LastName’] = df[‘Name’].apply(lambda x:x.split(‘,’)[0])
   df[‘title’] = df[‘Name’].apply(lambda x:x.split(‘, ‘)
                         [1].split(‘.’)[0])
   df[‘title’] = df[‘title’].apply(map_title)
   return df
total = feature_name(total)

Вышеуказанные функции извлекают функции «название» и «фамилия».

2. Разработка функций — размер семьи:

Функции «SibSp» и «Parch» могут быть объединены в одну для получения размера семьи. Также мы создаем еще две функции «IsAlone» и «FamilyAttrib».

def family_size(x):
   if x<2:
      return ‘Alone’
   elif x<5:
      return ‘Small’
   elif x<8:
      return ‘Medium’
   else:
      return ‘Large’
def feature_family(df):
   df[‘FamilySize’] = df[‘SibSp’] + total[‘Parch’] + 1
   df[‘IsAlone’]= df[‘FamilySize’].apply(lambda x: 1 if x==1 else 0)
   df[‘FamilyAttrib’] = df[‘FamilySize’].apply(family_size)
   return df
total = feature_family(total)

Вышеуказанные функции извлекают функции «FamilySize», «IsAlone» и «FamilyAttrib».

Следующая функция, которую мы извлекаем, немного менее интуитивна. Ниже представлена ​​карта палубы и кают титанического круизного лайнера.

3. Разработка характеристик — кабина:

Функции, которые мы извлекаем из Cabin, — это Cabin_Information и Deck.

def feature_cabin(df):
   df[‘Cabin’].fillna(“No”, inplace=True)
   df[‘Cabin_Information’] = df[‘Cabin’].apply(lambda x: “Yes” if x
                                                !=”No” else “No”)
   df[‘Deck’] = df[‘Cabin’].apply(lambda x: x[0] if x !=”No” else x)
   df[‘Deck’] = df[‘Deck’].replace({‘T’:’A’})
   # cabin T has Pclass 1, hence replaced with A
   return df
total = feature_cabin(total)

Приведенная выше функция извлекает функции «Cabin_Information» и «Deck».

Созданные нами переменные действительно помогают???

Нам также необходимо проверить, действительно ли переменные, добавленные на предыдущих этапах, способствуют прогнозированию выживания или нет. Есть два способа сделать этот статистический метод и методы визуализации. Для простоты в этом посте я покажу только метод визуализации.

# The survival rate of passengers by feature
def survivalrate(x, df):
   fig, ax = plt.subplots(math.ceil(len(x)/3),3, figsize = (16,8))
   ax = ax.flatten()
   for cat, axes in zip(x, ax):
   pd.crosstab(df[cat], df[‘Survived’], normalize=’index’).
                           plot.bar(stacked=True, ax = axes)
   axes.legend(title=’Survived ?’, labels = [‘No’,’Yes’],
                                     loc= ‘upper right’)
   axes.set_ylabel(‘Percentage(%)’, fontsize=12 )
   ax[-1].axis(‘off’) # hide the subplot not used
   plt.show()

  • Колоды B, C, D и E имеют максимальную выживаемость.
  • Одиночество снижает шансы на выживание
  • Большой размер семьи значительно снизил шансы на выживание
  • Титулы «Мастер» и «Миссис» имеют лучшие показатели, тогда как «Мистер» имеют худшую выживаемость соответственно.
# The survival count of passengers by feature
def featureplot(x, df):
   fig, ax = plt.subplots(math.ceil(len(x)/3), 3, figsize = (16,8))
   ax = ax.flatten()
   for axes, cat in zip(ax, x):
   if cat == ‘Survived’:
      sns.countplot(df[cat], ax = axes)
   else:
      sns.countplot(df[cat], hue=’Survived’, data = df, ax = axes)
   axes.legend(title=’Survived ?’, labels = [‘No’,’Yes’],
                                    loc= ‘upper right’)
   axes.set_ylabel(‘Count’, fontsize=12)
   ax[-1].axis(‘off’)
   plt.show()

  • Семья размером 1 имеет самый высокий показатель выживаемости, но самый низкий показатель
  • Количество выживших пассажиров-мужчин очень меньше
  • Колоды B, C, D и E больше сохранились, чем мертвы.

Гистограмма Pclass и количества пассажиров на палубе:

pd.crosstab([total[‘Deck’]],total[‘Pclass’],normalize=’index’).plot.bar(stacked=True)

D. Обработка пропущенных значений

Визуализация отсутствующих значений в кадре данных

fig, ax = plt.subplots(1, 2, figsize=(16, 5))
sns.heatmap(train.isnull(), cbar = False, cmap=’inferno’, ax = ax[0], yticklabels=False)
sns.heatmap(test.isnull(), cbar = False,cmap=’inferno’, ax = ax[1], yticklabels=False)
ax[0].set_xticklabels(train.columns, rotation = 90)
ax[1].set_xticklabels(test.columns, rotation = 90)
plt.show()

1. Заполнение отсутствующих значений в столбце «Возраст»

Мы используем средний возраст после группировки по P-классу и полу пассажира.

def fill_age(df):
   df[‘Age’] = df.groupby([‘Sex’,’Pclass’])[‘Age’].apply(lambda
                                     x:x.fillna(x.median()))
   return df
total = fill_age(total)

2. Заполнение отсутствующих значений в столбце «Тариф»

Мы используем средний тариф после группировки по P-классу и полу пассажира.

def fill_fare(df):
   df[‘Fare’]=df.groupby([‘Sex’,’Pclass’])[‘Fare’].apply(lambda
                                     x:x.fillna(x.median()))
   return df
total = fill_fare(total)

3. Заполнение отсутствующих значений для встроенного столбца

Мы используем место, где село максимальное количество пассажиров, т.е. «S».

def fill_embark(df):
   df[‘Embarked’] = df[‘Embarked’].fillna(df[‘Embarked’].mode()[0])
   return df
total = fill_embark(total)

На следующем этапе мы создадим ячейки для столбцов «Возраст» и «Проезд за проезд», чтобы улучшить классификацию выживания.

def age_bin(df):
   bins = [0, 2, 18, 35, 65, np.inf]
   label = [0,1,2,3,4,5]
   df[‘Age_Bin’] = pd.cut(total[‘Age’], bins = bins,labels = label )
   return df
total = age_bin(total)
def fare_bin(df):
   bins = [-1, 7.91, 14.454, 31, 99, 250, np.inf]
   label = [0, 1, 2, 3, 4]
   df[‘Fare_Bin’] = pd.cut(total[‘Fare’],bins = bins,labels = label)
   return df
total = fare_bin(total)

Я интуитивно, с помощью визуализации создал бункеры. Но мы также можем использовать создание квартильных бинов с помощью pd.qcut, что даст близкие результаты, как и выше бины.

Наконец, мы выполнили задачу визуализации, разработки функций и обработки пропущенных значений. Теперь мы переходим к простой части моделирования и прогнозирования результатов в нашем тестовом наборе данных.

E. Предварительная обработка данных

Алгоритмы машинного обучения понимают только числа, поэтому, прежде чем мы перейдем к части построения модели, нам нужно сразу закодировать категориальные столбцы и удалить некоторые избыточные столбцы.

Вы также можете использовать тепловую карту, чтобы проверить корреляцию нового набора функций с целью и друг с другом.

final = total.drop([‘Survived’,’Name’,’Age’,’Fare’,’Cabin’,‘Ticket’,
                            ’LastName’,’Deck’], axis = 1)
final = pd.get_dummies(final,
                       columns=[‘Pclass’,’Sex’,’Embarked’,’IsAlone’,
                          ‘FamilyAttrib’,’title’,’Cabin_Information’
                           ,’Age_Bin’,’Fare_Bin’],
                       drop_first=True)

В процессе построения модели LastName и Deck, похоже, не добавляли никакой информации, поэтому их удалили.

Разделение обучающих и тестовых данных

n_train = 891 # number of rows in the train data
newtrain = final.iloc[:n_train,:]
newtest = final.iloc[n_train:,:]

# importing the libraries
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score, roc_curve
from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier,VotingClassifier
from sklearn.model_selection import GridSearchCV
from catboost import CatBoostClassifier # !pip install catboost
from xgboost import XGBClassifier # !pip install xgboost

Мы также создаем функцию оценки модели для нашего удобства.

def model_eval(model, x_train, y_train, x_test=None, y_test=None):
   print(‘The model evaluation results’)
   model.fit(x_train,y_train)
   train_pred = model.predict(x_train)
   print(classification_report(y_train, train_pred))
   print(‘roc train :’, roc_auc_score(y_train, train_pred))
   print(“ — — — — — — — — — — — — — — — — — — — — — — — — — — — -”)
   if x_test:
      print(‘Predicting for test set’)
      test_pred = model.predict(x_test)
      print(classification_report(y_test, test_pred))
      print(‘roc test :’, roc_auc_score(y_test, test_pred))

F. Построение модели:

Для предсказания мы будем использовать разные модели.

1. Классификатор повышения градиента

2. Классификатор случайного леса

3. Классификатор Catboost

4. Классификатор голосования (использует суммированные результаты уже настроенных моделей)

1. model_cat = CatBoostClassifier()
2. model_gb = GradientBoostingClassifier()
3. model_rf = RandomForestClassifier()
4. Voting classifier
In Voting classifier we provide a list of tuples with named instances of models
clf1 = GradientBoostingClassifier()
clf2 = CatBoostClassifier()
clf3 = RandomForestClassifier()
voting = VotingClassifier([ (‘gb’,clf1),(‘cb’,clf2),(‘rf’,clf3) ] )

Оценка модели:

model_eval(model_gb, newtrain, y)

Мы также можем использовать разделение на поезд-тест, чтобы проверить переобучение/недообучение модели.

Большая разница в поезде и тесте roc_auc указывает на переобучение модели на данных поезда, что делает прогнозы теста менее точными.

Кроме того, для оценки модели можно использовать разделение обучения и тестирования.

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(newtrain, y,
                              train_size = 0.8, random_state= 42)
model_eval(model_gb, x_train, y_train, x_test, y_test)

Важность функции:

plt.figure(figsize = (16, 4))
plt.bar(newtrain.columns, model_gb.feature_importances_)
plt.xticks(rotation=90)
plt.show()

Можем ли мы пойти дальше и улучшить результаты???

GridSearchCV — это один из методов настройки гиперпараметров и улучшения обучения модели, а также уменьшения переобучения. Вышеуказанные базовые модели дали довольно хорошие результаты, но для улучшения результатов очень важна настройка гиперпараметров модели. Для вышеперечисленных моделей произведена настройка по нижеуказанным параметрам

params = { ‘learning_rate’:[0.9,0.10, .11, 0.12, 0.13],
           ‘n_estimators’:[200, 300, 350,400],
           ‘max_depth’:[4,5,6,7,8] }
grid = GridSearchCV(model_cat, params, n_jobs=-1, cv=3)
grid.fit(x_train, y_train)
grid.predict(x_train)

Примечание: cv=3 указывает на 3-кратную перекрестную проверку модели для каждой из комбинаций гиперпараметров.

Прогнозирование выживания пассажиров

survival_pred = pd.Series(model_gb.predict(newtest))
submit = pd.DataFrame({‘PassengerId’:testpassenger ,
                      ‘Survived’:survival_pred})
submit.to_csv(‘model_gb.csv’, index=False)

Выше приведен пример кода для прогнозирования и сохранения результатов.

Спасибо за прочтение поста, и если он вам понравился, ставьте аплодисменты. Также приветствуются любые предложения по улучшению поста.