Задача про спасенных с “Титаника”, имеет большую популярность среди людей, только начинающих заниматься анализом данных и машинным обучением.

Итак суть задачи состоит в том, чтобы с помощью методов машинного обучения построить модель, которая прогназировала бы спасется человек или нет. К задачи прилагаются 2 файла:

Для анализ понадобятся модули Pandas и sklearn. С помощью Pandas мы проведем начальный анализ данных, а sklearn поможет в вычислении прогнозной модели. Итак, для начала загрузим нужные модули:

Кроме того даются пояснения по некоторым полям:

from pandas import read_csv, DataFrame, Series

Теперь можно загрузить тестовую выборку и посмотреть на нее:

data = read_csv('Kaggle_Titanic/Data/train.csv')
data
<class 'pandas.core.frame.DataFrame'>
Int64Index: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891  non-null values
Survived       891  non-null values
Pclass         891  non-null values
Name           891  non-null values
Sex            891  non-null values
Age            714  non-null values
SibSp          891  non-null values
Parch          891  non-null values
Ticket         891  non-null values
Fare           891  non-null values
Cabin          204  non-null values
Embarked       889  non-null values
dtypes: float64(2), int64(5), object(5)

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

data.pivot_table('PassengerId', 'Pclass', 'Survived', 'count').plot(kind='bar', stacked=True)
<matplotlib.axes.AxesSubplot at 0x3c57070>

png

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

fig, axes = plt.subplots(ncols=2)
data.pivot_table('PassengerId', ['SibSp'], 'Survived', 'count').plot(ax=axes[0], title='SibSp')
data.pivot_table('PassengerId', ['Parch'], 'Survived', 'count').plot(ax=axes[1], title='Parch')
<matplotlib.axes.AxesSubplot at 0x3cea970>

png

Как видно из графиков наше предположение снова потдвердилось, и из людей имеющих больше 1 родственников спаслись не многие.

Теперь давайте взглянем столбец с номерами кают и посмотрим насколько нам пригодятся эти данные:

data.PassengerId[data.Cabin.notnull()].count()
204

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

Итак продолжем разбираться с полями и на очереди поле Age в котором записан возраст. Посмортирим на сколько оно заполено:

data.PassengerId[data.Age.notnull()].count()
714

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

data.Age = data.Age.median()

У нас осталось разобратся с полями Ticket, Embarked, Fare, Name. Давайте посмотрим на поле Embarked, в котором находится порт посадки и проверим есть ли такие пассажиры у которых порт не указан:

data[data.Embarked.isnull()]
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
61 62 1 1 Icard, Miss. Amelie female 28 0 0 113572 80 B28 NaN
829 830 1 1 Stone, Mrs. George Nelson (Martha Evelyn) female 28 0 0 113572 80 B28 NaN

Итак у нас нашлось 2 таких пассажира. Давайте присвоим эти пассажирам порт в котором село больше всего людей:

MaxPassEmbarked = data.groupby('Embarked').count()['PassengerId']
data.Embarked[data.Embarked.isnull()] = MaxPassEmbarked[MaxPassEmbarked == MaxPassEmbarked.max()].index[0]

Ну что же разобрались еще с одним полем и теперь у нас остались поля с имя пассажира, номером билета и ценой билета.

По сути нам из этих трех полей нам нужна только цена(Fare), т.к. она в какой-то мере определяем ранжирование внутри классов поля Pclass. Т. е. например люди внутри среднего класса могут быть разделены на тех, кто ближе к первому(высшему) классу, а кто к третьему(низший). Проверим это поле на пустые значения и если таковые имеются заменим цену медианой по цене из все выборки:

data.PassengerId[data.Fare.isnull()]
Series([], dtype: int64)

Номер же билета и имя пассажира нам никак не помогут, т. к. это просто справочная информация. Единственное для чего они могут пригодиться - это определение кто из пассажиров потенциально являются родственниками, но так как люди у которых есть родственники практически не спаслись (это было показано выше) можно принебречь этими данными.

Теперь наш набор выглядит так:

data = data.drop(['PassengerId','Name','Ticket','Cabin'],axis=1)
data.head()
Survived Pclass Sex Age SibSp Parch Fare Embarked
0 0 3 male 28 1 0 7.2500 S
1 1 1 female 28 1 0 71.2833 C
2 1 3 female 28 0 0 7.9250 S
3 1 1 female 28 1 0 53.1000 S
4 0 3 male 28 0 0 8.0500 S

Итак для построения нашей модели, нужно закодировать все наши текстовые значения. Можно это сделать в ручную, а можно с помощью модуля sklearn.preprocessing. Давайте воспользуемся вторым вариантом.

Закодировать список с фиксированными значениями можно с помощью объекта LabelEncoder().

from sklearn.preprocessing import LabelEncoder
label = LabelEncoder()
dicts = {}

label.fit(data.Sex.drop_duplicates())
dicts['Sex'] = list(label.classes_)
data.Sex = label.transform(data.Sex)

label.fit(data.Embarked.drop_duplicates())
dicts['Embarked'] = list(label.classes_)
data.Embarked = label.transform(data.Embarked)

В итоге наши исходные данные будут выглядеть так:

data.head()
Survived Pclass Sex Age SibSp Parch Fare Embarked
0 0 3 1 28 1 0 7.2500 2
1 1 1 0 28 1 0 71.2833 0
2 1 3 0 28 0 0 7.9250 2
3 1 1 0 28 1 0 53.1000 2
4 0 3 1 28 0 0 8.0500 2

Теперь нам надо написать код для приведения проверочного файла в нужный нам вид. Для этого можно просто скопировать куски кода которые были выше(или просто написать функцию для обработки входного файла):

test = read_csv('Kaggle_Titanic/Data/test.csv')
test.Age[test.Age.isnull()] = test.Age.mean()
test.Fare[test.Fare.isnull()] = test.Fare.median()
MaxPassEmbarked = test.groupby('Embarked').count()['PassengerId']
test.Embarked[test.Embarked.isnull()] = MaxPassEmbarked[MaxPassEmbarked == MaxPassEmbarked.max()].index[0]
result = DataFrame(test.PassengerId)
test = test.drop(['Name','Ticket','Cabin','PassengerId'],axis=1)

label.fit(dicts['Sex'])
test.Sex = label.transform(test.Sex)

label.fit(dicts['Embarked'])
test.Embarked = label.transform(test.Embarked)
test.head()
Pclass Sex Age SibSp Parch Fare Embarked
0 3 1 34.5 0 0 7.8292 1
1 3 0 47.0 1 0 7.0000 2
2 2 1 62.0 0 0 9.6875 1
3 3 1 27.0 0 0 8.6625 2
4 3 0 22.0 1 1 12.2875 2

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

Итак рассмотрим несколько алгоритмов машинного обучения:

Загрузим нужные нам библиотки:

from sklearn import cross_validation, svm
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, auc
import pylab as pl

Для начала, надо разделить нашу обучаюшую выборку на показатель, который мы исследуем, и признаки его определяющие:

target = data.Survived
train = data.drop(['Survived'], axis=1) #из исходных данных убираем Id пассажира и флаг спасся он или нет

kfold = 5 #количество подвыборок для валидации
itog_val = {} #список для записи результатов кросс валидации разных алгоритмов

Теперь наша обучающая выборка выглядит так:

train.head()
Pclass Sex Age SibSp Parch Fare Embarked
0 3 1 28 1 0 7.2500 2
1 1 0 28 1 0 71.2833 0
2 3 0 28 0 0 7.9250 2
3 1 0 28 1 0 53.1000 2
4 3 1 28 0 0 8.0500 2

Теперь разобъем показатели полученные ранее на 2 подвыборки(обучающую и тестовую) для расчет ROC кривых (для скользящего контроля этого делать не надо, т.к. функция проверки это делает сама. В этом нам поможет функция train_test_split модуля cross_validation:

ROCtrainTRN, ROCtestTRN, ROCtrainTRG, ROCtestTRG = cross_validation.train_test_split(train, target, test_size=0.25) 

В качестве праметров ей передается:

.
На выходе функция выдает 4 массива:
  1. Новый обучающий массив параметров
  2. тестовый массив параметров
  3. Новый массив показателей
  4. тестовый массив показателей

Далее представлены перечисленные методы с наилучшими параметрами подобранные опытным путем:

model_rfc = RandomForestClassifier(n_estimators = 80, max_features='auto', criterion='entropy',max_depth=4) #в параметре передаем кол-во деревьев
model_knc = KNeighborsClassifier(n_neighbors = 18) #в параметре передаем кол-во соседей
model_lr = LogisticRegression(penalty='l1', tol=0.01) 
model_svc = svm.SVC() #по умолчанию kernek='rbf'

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

scores = cross_validation.cross_val_score(model_rfc, train, target, cv = kfold)
itog_val['RandomForestClassifier'] = scores.mean()
scores = cross_validation.cross_val_score(model_knc, train, target, cv = kfold)
itog_val['KNeighborsClassifier'] = scores.mean()
scores = cross_validation.cross_val_score(model_lr, train, target, cv = kfold)
itog_val['LogisticRegression'] = scores.mean()
scores = cross_validation.cross_val_score(model_svc, train, target, cv = kfold)
itog_val['SVC'] = scores.mean()

#рисуем результат
DataFrame.from_dict(data = itog_val, orient='index').plot(kind='bar', legend=False)
<matplotlib.axes.AxesSubplot at 0x5df1b10>

png

Когда мы собрали данные по перекрестным проверкам, давайте нарисуем графики ROC-кривых:

pl.clf()
plt.figure(figsize=(8,6))
#SVC
model_svc.probability = True
probas = model_svc.fit(ROCtrainTRN, ROCtrainTRG).predict_proba(ROCtestTRN)
fpr, tpr, thresholds = roc_curve(ROCtestTRG, probas[:, 1])
roc_auc  = auc(fpr, tpr)
pl.plot(fpr, tpr, label='%s ROC (area = %0.2f)' % ('SVC', roc_auc))
#RandomForestClassifier
probas = model_rfc.fit(ROCtrainTRN, ROCtrainTRG).predict_proba(ROCtestTRN)
fpr, tpr, thresholds = roc_curve(ROCtestTRG, probas[:, 1])
roc_auc  = auc(fpr, tpr)
pl.plot(fpr, tpr, label='%s ROC (area = %0.2f)' % ('RandonForest',roc_auc))
#KNeighborsClassifier
probas = model_knc.fit(ROCtrainTRN, ROCtrainTRG).predict_proba(ROCtestTRN)
fpr, tpr, thresholds = roc_curve(ROCtestTRG, probas[:, 1])
roc_auc  = auc(fpr, tpr)
pl.plot(fpr, tpr, label='%s ROC (area = %0.2f)' % ('KNeighborsClassifier',roc_auc))
#LogisticRegression
probas = model_lr.fit(ROCtrainTRN, ROCtrainTRG).predict_proba(ROCtestTRN)
fpr, tpr, thresholds = roc_curve(ROCtestTRG, probas[:, 1])
roc_auc  = auc(fpr, tpr)
pl.plot(fpr, tpr, label='%s ROC (area = %0.2f)' % ('LogisticRegression',roc_auc))
pl.plot([0, 1], [0, 1], 'k--')
pl.xlim([0.0, 1.0])
pl.ylim([0.0, 1.0])
pl.xlabel('False Positive Rate')
pl.ylabel('True Positive Rate')
pl.legend(loc=0, fontsize='small')
pl.show()
<matplotlib.figure.Figure at 0x6280dd0>

png

Как видно по результам обоих проверок алгоритм на основе деревьев решений выдал лучшие результаты, чем остальные. Теперь осталось только применить нашу модель к тестовой выборке:

model_rfc.fit(train, target)
result.insert(1,'Survived', model_rfc.predict(test))
result.to_csv('Kaggle_Titanic/Result/test.csv', index=False)