kaggleのTitanic問題をといてみる
kaggleでチュートリアルがわりに使われているTitanicの問題を解いてみて実際に行われている分析の流れを把握できるようにしたいと思います。 kaggleでは個人の解答が公開、議論されているので普段分析をしない人でも学習にはちょうど良さそうな気がします。
まずはデータの読み込み
import pandas as pd from pandas import Series,DataFrame # numpy, matplotlib, seaborn import numpy as np import matplotlib.pyplot as plt import seaborn as sns sns.set_style('whitegrid') %matplotlib inline # machine learning from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC, LinearSVC from sklearn.ensemble import RandomForestClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.naive_bayes import GaussianNB # get titanic & test csv files as a DataFrame titanic_df = pd.read_csv("train.csv") test_df = pd.read_csv("test.csv") # preview the data
それから読み込んだ情報を確認してみます。
titanic_df.head()
titanic_df.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 891 entries, 0 to 890 Data columns (total 12 columns): PassengerId 891 non-null int64 Survived 891 non-null int64 Pclass 891 non-null int64 Name 891 non-null object Sex 891 non-null object Age 714 non-null float64 SibSp 891 non-null int64 Parch 891 non-null int64 Ticket 891 non-null object Fare 891 non-null float64 Cabin 204 non-null object Embarked 889 non-null object dtypes: float64(2), int64(5), object(5) memory usage: 83.6+ KB
Titanicの問題では学習データのcsvを読み込んで生き残った人(survived=1)を学習し、テストデータに対して分類を行うものとなっています。csvを読み込んだままの情報だと欠損があったり、不要な項目があったり、そのままでは分析に利用できない項目がありますので、実際に生存に影響のある項目だけが残すようにしていき機械学習して分類となります。
モデルの学習
とりあえず生存に明らかに必要のな誘うな項目は削除します。
titanic_df = titanic_df.drop(['PassengerId','Name','Ticket'], axis=1) test_df = test_df.drop(['Name','Ticket'], axis=1)
欠損が多い項目は削除しておきます。
titanic_df.drop("Cabin",axis=1,inplace=True) test_df.drop("Cabin",axis=1,inplace=True)
Embarkedの項目を処理する
Embarkedの項目を分析で扱いようにします。まず各項目がどんな値を取っているのか確認してみます。
titanic_df[["Embarked", "Survived"]].groupby(['Embarked'],as_index=False).mean()
C,Q,Sの3つの値取ることが確認できました。次に、titanic_df.info()
実行時にEmbarkedは2項目欠損があったのでとりあえずS
で埋めときます。
titanic_df["Embarked"] = titanic_df["Embarked"].fillna("S")
それからEmbarkedの項目が生存に影響しているかグラフ表示してみます。
sns.factorplot('Embarked','Survived', data=titanic_df,size=4,aspect=3)
Sに比べてCとQの方が生存確率は高いかもしれないといった感じなのでしょうか。S,C,Qのぞれぞれの累計値、生存確率も出してみます。
fig, (axis1,axis2,axis3) = plt.subplots(1,3,figsize=(15,5)) sns.countplot(x='Embarked', data=titanic_df, ax=axis1) sns.countplot(x='Survived', hue="Embarked", data=titanic_df, order=[1,0], ax=axis2) embark_perc = titanic_df[["Embarked", "Survived"]].groupby(['Embarked'],as_index=False).mean() sns.barplot(x='Embarked', y='Survived', data=embark_perc,order=['S','C','Q'],ax=axis3)
C,Qかどうかを判断するための項目を分析用にデータフレームに追加します。
embark_dummies_titanic = pd.get_dummies(titanic_df['Embarked']) embark_dummies_titanic.drop(['S'], axis=1, inplace=True) embark_dummies_test = pd.get_dummies(test_df['Embarked']) embark_dummies_test.drop(['S'], axis=1, inplace=True) titanic_df = titanic_df.join(embark_dummies_titanic) test_df = test_df.join(embark_dummies_test) titanic_df.drop(['Embarked'], axis=1,inplace=True) test_df.drop(['Embarked'], axis=1,inplace=True) titanic_df.head()
Fare(運賃)の項目を処理する
欠損は中央値で埋めときます。
test_df["Fare"].fillna(test_df["Fare"].median(), inplace=True)
それからデータがFloat64になっていたのでintに変換しておきます。でグラフ表示します。
titanic_df['Fare'] = titanic_df['Fare'].astype(int) test_df['Fare'] = test_df['Fare'].astype(int) titanic_df['Fare'].plot(kind='hist', figsize=(15,3),bins=100, xlim=(0,50))
生存者の運賃の平均と中央値を確認してみます。
fare_not_survived = titanic_df["Fare"][titanic_df["Survived"] == 0] fare_survived = titanic_df["Fare"][titanic_df["Survived"] == 1] avgerage_fare = DataFrame([fare_not_survived.mean(), fare_survived.mean()]) std_fare = DataFrame([fare_not_survived.std(), fare_survived.std()]) avgerage_fare.index.names = std_fare.index.names = ["Survived"] avgerage_fare.plot(yerr=std_fare,kind='bar',legend=False) std_fare.plot(yerr=std_fare,kind='bar',legend=False)
運賃も生存に影響があったということでデータフレームに残しておきます。
年齢、性別を処理する
まずは年齢のデータを確認する
欠損値はSurvive毎で平均 ± 標準偏差
の範囲の乱数を設定します。
average_age_titanic = titanic_df["Age"].mean() std_age_titanic = titanic_df["Age"].std() count_nan_age_titanic = titanic_df["Age"].isnull().sum() average_age_test = test_df["Age"].mean() std_age_test = test_df["Age"].std() count_nan_age_test = test_df["Age"].isnull().sum() rand_1 = np.random.randint(average_age_titanic - std_age_titanic, average_age_titanic + std_age_titanic, size = count_nan_age_titanic) rand_2 = np.random.randint(average_age_test - std_age_test, average_age_test + std_age_test, size = count_nan_age_test) titanic_df["Age"][np.isnan(titanic_df["Age"])] = rand_1 test_df["Age"][np.isnan(test_df["Age"])] = rand_2
Ageがfloat型だったのでint型に変換します。
titanic_df['Age'] = titanic_df['Age'].astype(int) test_df['Age'] = test_df['Age'].astype(int)
それからグラフ表示します。
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,4)) axis1.set_title('Original Age values - Titanic') axis2.set_title('New Age values - Titanic') titanic_df['Age'].dropna().astype(int).hist(bins=70, ax=axis1) titanic_df['Age'].hist(bins=70, ax=axis2)
年齢別の生存率を表示します。
facet = sns.FacetGrid(titanic_df, hue="Survived",aspect=4) facet.map(sns.kdeplot,'Age',shade= True) facet.set(xlim=(0, titanic_df['Age'].max())) facet.add_legend() fig, axis1 = plt.subplots(1,1,figsize=(18,4)) average_age = titanic_df[["Age", "Survived"]].groupby(['Age'],as_index=False).mean() sns.barplot(x='Age', y='Survived', data=average_age)
子供の方が生存率が高いことがわかります。
次に性別も合わせて子供か、成人男性か、成人女性かで分けてSurviveに相関関係があるかみてみます。
def get_person(passenger): age,sex = passenger return 'child' if age < 16 else sex titanic_df['Person'] = titanic_df[['Age','Sex']].apply(get_person,axis=1) test_df['Person'] = test_df[['Age','Sex']].apply(get_person,axis=1) person_dummies_titanic = pd.get_dummies(titanic_df['Person']) person_dummies_titanic.columns = ['Child','Female','Male'] person_dummies_test = pd.get_dummies(test_df['Person']) person_dummies_test.columns = ['Child','Female','Male']
グラフで表示すると子供と女性が助かっていることがわかるのでデータフレームに残します。
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(10,5)) sns.countplot(x='Person', data=titanic_df, ax=axis1) person_perc = titanic_df[["Person", "Survived"]].groupby(['Person'],as_index=False).mean() sns.barplot(x='Person', y='Survived', data=person_perc, ax=axis2, order=['male','female','child'])
person_dummies_titanic.drop(['Male'], axis=1, inplace=True) person_dummies_test.drop(['Male'], axis=1, inplace=True) titanic_df = titanic_df.join(person_dummies_titanic) test_df = test_df.join(person_dummies_test) titanic_df.drop(['Sex'],axis=1,inplace=True) test_df.drop(['Sex'],axis=1,inplace=True) titanic_df.drop(['Person'],axis=1,inplace=True) test_df.drop(['Person'],axis=1,inplace=True)
SibSp,Parchを処理する
SibSp, Parchは同伴者を表しているようで以下のように家族ずれかどうかを判断するように変換します。
titanic_df['Family'] = titanic_df["Parch"] + titanic_df["SibSp"] titanic_df['Family'].loc[titanic_df['Family'] > 0] = 1 titanic_df['Family'].loc[titanic_df['Family'] == 0] = 0 test_df['Family'] = test_df["Parch"] + test_df["SibSp"] test_df['Family'].loc[test_df['Family'] > 0] = 1 test_df['Family'].loc[test_df['Family'] == 0] = 0
Familyの項目に変換したので使わなくなったSibSp, Parchは削除します。
titanic_df = titanic_df.drop(['SibSp','Parch'], axis=1) test_df = test_df.drop(['SibSp','Parch'], axis=1)
それからグラフ表示したら家族ずれの方が生存確率がたかそうなのがわかるので残しておきます。
fig, (axis1,axis2) = plt.subplots(1,2,sharex=True,figsize=(10,5)) # sns.factorplot('Family',data=titanic_df,kind='count',ax=axis1) sns.countplot(x='Family', data=titanic_df, order=[1,0], ax=axis1) # average of survived for those who had/didn't have any family member family_perc = titanic_df[["Family", "Survived"]].groupby(['Family'],as_index=False).mean() sns.barplot(x='Family', y='Survived', data=family_perc, order=[1,0], ax=axis2) axis1.set_xticklabels(["With Family","Alone"], rotation=0)
Pclassを処理する
表示してみる
sns.factorplot('Pclass','Survived',order=[1,2,3], data=titanic_df,size=5) pclass_dummies_titanic = pd.get_dummies(titanic_df['Pclass']) pclass_dummies_titanic.columns = ['Class_1','Class_2','Class_3'] pclass_dummies_test = pd.get_dummies(test_df['Pclass']) pclass_dummies_test.columns = ['Class_1','Class_2','Class_3'] titanic_df.drop(['Pclass'],axis=1,inplace=True) test_df.drop(['Pclass'],axis=1,inplace=True)
pclassは1,2の場合に生存率高いことがわかったのでpclass1,2かどうか判定した結果をデータフレームに付与します。
pclass_dummies_test.drop(['Class_3'], axis=1, inplace=True) pclass_dummies_titanic.drop(['Class_3'], axis=1, inplace=True) titanic_df = titanic_df.join(pclass_dummies_titanic) test_df = test_df.join(pclass_dummies_test)
最終的にモデルはこのようになりました。
titanic_df.head()
機械学習
このように分析に影響のある項目だけが残るようにデータフレームを操作するのですが、最終的には機械学習で分類を行います。scikit-learn
なら簡単に使うことができるので良いと思います。ロジスティック回帰、ランダムフォレストであれば以下のようになります。
# ロジスティック回帰 X_train = titanic_df.drop("Survived",axis=1) Y_train = titanic_df["Survived"] logreg = LogisticRegression() logreg.fit(X_train, Y_train) Y_pred = logreg.predict(X_test) logreg.score(X_train, Y_train) 0.8058361391694725 # ランダムフォレスト random_forest = RandomForestClassifier(n_estimators=100) random_forest.fit(X_train, Y_train) Y_pred = random_forest.predict(X_test) random_forest.score(X_train, Y_train) 0.9640852974186308
今回はランダムフォレストの方がうまく分類できているようです。事前に意味のある情報に分類をしていたのでその場合はランダムフォレストの精度がよくなるのでしょうか。 kaggleのkernelをみたら実際の分析手順を追えるけど欠損の扱いや目的変数に対して影響があるのかを判断して説明できるようになるにはちゃんと勉強した方がよさそうな気がします。