Введение в анализ данных с помощью python на примере задачи про Титаник

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

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

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

Кроме того даются пояснения по некоторым полям:PassengerId - идентификатор пассажираSurvival - поле в котором указано спасся человек(1) или нет (0)Pclass - содержит социально-экономический статус: высокийсреднийнизкийName - имя пассажираSex - пол пассажираAge - возрастSibSp - содержит информацию о количестве родственников 2-го порядка (муж, жена, братья, сетры)Parch - содержит информацию о количестве родственников на борту 1-го порядка (мать, отец, дети)Ticket - номер билетаFare - цена билетаCabin - каютаEmbarked - порт посадкиC - CherbourgQ - QueenstownS - Southampton

from pandas import read_csv, DataFrame, Series

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

data = read_csv('Kaggle_Titanic/Data/train.csv')
data

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

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()]

Итак у нас нашлось 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()

Итак для построения нашей модели, нужно закодировать все наши текстовые значения. Можно это сделать в ручную, а можно с помощью модуля 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()

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

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()

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

Итак рассмотрим несколько алгоритмов машинного обучения: SVM (с линейным ядром и квадратичным полиномом)Метод ближайших соседейRandom forest

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

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()

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

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

В качестве праметров ей передается: Массив параметровМассив значений показателейСоотношение в котром будет разбита обучающая выборка (в нашем случае для тестового набора будет выделена 1/4 часть данных исходной обучающей выборки).На выходе функция выдает 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)
 
comments powered by Disqus