Kaggle チャレンジ 2日目 タイタニックの問題でデータ分析に挑戦

シェアする

  • このエントリーをはてなブックマークに追加

スポンサーリンク

はじめに

 今回は、タイタニックの問題のデータを使ってデータ分析を行なっていきたいと思います。この記事は前回の続きとなっているので、まだ見ていない方は最初にそちらを見ることをお勧めします。

前回の記事:

Kaggle チャレンジ 1日目 タイタニックの問題からデータを読み解いてみる


やること

 前回に引き続き、タイタニックの問題をGoogleColaboratoryKernelsを使って実際に分析していきたいと思います。

・今回参考にするKernelsTitanic Data Science Solutions

・GoogleColaboratoryの使い方はこちら

対象者

機械学習をKaggleを使って学びたい方、Kaggleに興味があるけどどうすればいいかわからない方。

 

Titanic : Machine Learning from Disaster

データ分析

  • Pclass = 1とSurvivedの間に重要な相関(> 0.5)があることが観察からわかる。 なので、この特徴をモデルに含めることにした。
  • 性別のデータからも、性別が女性の生存率が74%と非常に高いことを確認できました。
  • SibSpParchは、特定の値に対してゼロ相関を持ちます。 

In[1]:

train_df[['Pclass', 'Survived']].groupby(['Pclass'], as_index=False).mean().sort_values(by='Survived', ascending=False)

Out[1]:

Pclass Survived
0 1 0.629630
1 2 0.472826
2 3 0.242363

In[2]:

train_df[["Sex", "Survived"]].groupby(['Sex'], as_index=False).mean().sort_values(by='Survived', ascending=False)
Out[2]:
Sex Survived
0 female 0.742038
1 male 0.188908

In[3]:
train_df[["SibSp", "Survived"]].groupby(['SibSp'], as_index=False).mean().sort_values(by='Survived', ascending=False)

Out[3]:

SibSp Survived
1 1 0.535885
2 2 0.464286
0 0 0.345395
3 3 0.250000
4 4 0.166667
5 5 0.000000
6 8 0.000000

In[4]:

train_df[["Parch", "Survived"]].groupby(['Parch'], as_index=False).mean().sort_values(by='Survived', ascending=False)
Out[4]:
Parch Survived
3 3 0.600000
1 1 0.550847
2 2 0.500000
0 0 0.343658
5 5 0.200000
4 4 0.000000
6 6 0.000000

データを視覚化して分析する

 今度は、データを分析するための視覚化を使用して、いくつかの前提を確認していきます。

特徴量の相関

 特徴量と Survived との相関を理解することから始める。

 ヒストグラムチャートは、Ageのような連続的な数値をもつデータから有用なパターンを特定するのに役立ちます。

観察してわかること

  • 幼児(年齢4歳以下)は高い生存率を示した。
  • 最も古い乗客(年齢= 80)は生き残った。
  • 多数の15〜25歳の生存者はいませんでした。
  • ほとんどの乗客は15-35歳の範囲にあります。

やること

  • モデルトレーニングで年齢を考慮する必要がある。
  • 年齢データのnull値を補完します。
  • 年齢層をまとめたほうがいいかもしれない。

In[5]:

g = sns.FacetGrid(train_df, col='Survived')
g.map(plt.hist, 'Age', bins=20)
Out[5]:
<seaborn.axisgrid.FacetGrid at 0x7f2c5bd0a5f8>

数値と序数の特徴の相関

 1つのプロットを使用して相関を識別する複数の特徴を組み合わせる事ができる。 これは、連続する数値を持つ特徴を用いて行うことができる。

観察からわかること

ほとんどの乗客がPclass = 3だったが、ほとんどが生き残れなかった。 (識別要因の仮定#2)
Pclass = 2およびPclass = 3の幼児乗客はほとんどが生き残った。 (識別要因の仮定#2を満たす。)
Pclass = 1のほとんどの乗客は生き残った。 (識別要因の仮定#3を確認)
Pclassは、乗客の年齢分布に関して変化する。

やること

モデルトレーニングのためにPclassを検討する。

In[6]:

# grid = sns.FacetGrid(train_df, col='Pclass', hue='Survived')
grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', size=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend();
Out[6]:

カテゴリの特徴の相関

観察してわかること

  • 女性の乗客は男性よりはるかに良好な生存率を示した。(識別要因)
  • EmbarkedC の時は例外で男性の生存率が高い。 これは、PclassEmbarkedとの間の相関性であり、必ずしも EmbarkedSurvived の間の直接的な相関関係があるという事ではない。
  • 男性は、CおよびQポートでPclass = 2と比較した場合、Pclass = 3で良好な生存率を示した。(欠損値の補完)
  • 乗船港は、Pclass = 3および男性乗客の生存率が異なる。 (相関)

やること

  • 性別のデータをモデルトレーニングに追加する。
  • モデルトレーニングにEmbarked を追加する。

In[7]:

# grid = sns.FacetGrid(train_df, col='Embarked')
grid = sns.FacetGrid(train_df, row='Embarked', size=2.2, aspect=1.6)
grid.map(sns.pointplot, 'Pclass', 'Survived', 'Sex', palette='deep')
grid.add_legend()
Out[7]:
/usr/local/lib/python3.6/dist-packages/seaborn/categorical.py:1468: FutureWarning: remove_na is deprecated and is a private function. Do not use.
  stat_data = remove_na(group_data[hue_mask])
<seaborn.axisgrid.FacetGrid at 0x7f2c593ef4e0>

カテゴリと数値の相関

 また、(数値以外の値を持つ)カテゴリ特徴と数値特徴を関連付けることもできる。 受動態(カテゴリーが非数値)、性別(カテゴリーが非数値)、運賃(数値連続)、生存(カテゴリ数値)と相関させることを検討できる。

観察からわかること

  • 高い運賃を支払う乗客は生存率が高かった。 このことから、運賃の範囲を作成するという前提について確認できた。
  • 乗船港は生存率と相関する。 相関と補完を確認できた。

やること

  • 運賃のデータの使用を検討する。

In[8]:

# grid = sns.FacetGrid(train_df, col='Embarked', hue='Survived', palette={0: 'k', 1: 'w'})
grid = sns.FacetGrid(train_df, row='Embarked', col='Survived', size=2.2, aspect=1.6)
grid.map(sns.barplot, 'Sex', 'Fare', alpha=.5, ci=None)
grid.add_legend()
Out[8]:
/usr/local/lib/python3.6/dist-packages/seaborn/categorical.py:1428: FutureWarning: remove_na is deprecated and is a private function. Do not use.
  stat_data = remove_na(group_data)
<seaborn.axisgrid.FacetGrid at 0x7f2c5906a908>

データをまとめる

 データセットからいくつかの前提とやることがわかりました。修正、新しい特徴の作成、欠損値の補完をして、前提とやることを実行しましょう。

特徴の削除による修正

 必要のない特徴量を削除することで、データ数が減少し、分析を楽にします。前提とやることに基づいて、CabinTicket の機能を削除したいと思います。この場合、トレーニングデータとテストデータどちらも行いましょう。
In[9]:
print("Before", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape)

train_df = train_df.drop(['Ticket', 'Cabin'], axis=1)
test_df = test_df.drop(['Ticket', 'Cabin'], axis=1)
combine = [train_df, test_df]

"After", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape
Out[9]:
Before (891, 12) (418, 11) (891, 12) (418, 11)
('After', (891, 10), (418, 9), (891, 10), (418, 9))

既存の特徴から新しい特徴を作成する

NamePassengerIdの特徴を削除する前に、Nameのデータからタイトルを抽出し、タイトルと生存の相関関係を分析したい。

 次のコードを使うことで、正規表現を使用してname からタイトルを抽出します。

In[10]:

for dataset in combine:
    dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False)

pd.crosstab(train_df['Title'], train_df['Sex'])
Out[10]:
Sex female male
Title
Capt 0 1
Col 0 2
Countess 1 0
Don 0 1
Dr 1 6
Jonkheer 0 1
Lady 1 0
Major 0 2
Master 0 40
Miss 182 0
Mlle 2 0
Mme 1 0
Mr 0 517
Mrs 125 0
Ms 1 0
Rev 0 6
Sir 0 1

 タイトルが多いので、より一般的な名前に置き換えたり、希少なものとして分類してみましょう。

In[11]:

for dataset in combine:
    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')
    
train_df[['Title', 'Survived']].groupby(['Title'], as_index=False).mean()
Out[11]:
Title Survived
0 Master 0.575000
1 Miss 0.702703
2 Mr 0.156673
3 Mrs 0.793651
4 Rare 0.347826

タイトルを数値に置き換えることもできます。数値にした方が扱いやすいですね。

In[12]:

title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
for dataset in combine:
    dataset['Title'] = dataset['Title'].map(title_mapping)
    dataset['Title'] = dataset['Title'].fillna(0)

train_df.head()

Out[12]:

PassengerId Survived Pclass Name Sex Age SibSp Parch Fare Embarked Title
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 7.2500 S 1
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 71.2833 C 3
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 7.9250 S 2
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 53.1000 S 3
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 8.0500 S 1

これで、トレーニングとテストのデータセットからname を削除してもよさそうです。また、トレーニングデータセットにPassengerIdは必要ありませんのでこちらも削除しておきましょう。

In[13]:

train_df = train_df.drop(['Name', 'PassengerId'], axis=1)
test_df = test_df.drop(['Name'], axis=1)
combine = [train_df, test_df]
train_df.shape, test_df.shape
Out[13]:
((891, 9), (418, 9))

カテゴリフィーチャの変換

これで文字列を含む特徴を数値に変換できるようになりました。 これは、ほとんどのモデルアルゴリズムで必要になります。 性別のデータを、女性= 1、男性= 0の性別という新しい特徴に変換してみましょう。

In[14]:

for dataset in combine:
    dataset['Sex'] = dataset['Sex'].map( {'female': 1, 'male': 0} ).astype(int)

train_df.head()

Out[14]:

Survived Pclass Sex Age SibSp Parch Fare Embarked Title
0 0 3 0 22.0 1 0 7.2500 S 1
1 1 1 1 38.0 1 0 71.2833 C 3
2 1 3 1 26.0 0 0 7.9250 S 2
3 1 1 1 35.0 1 0 53.1000 S 3
4 0 3 0 35.0 0 0 8.0500 S 1

連続する数値を持つ特徴の欠損値を補完する

 今度は、欠損値またはnullの値を持つ特徴を推定して補完する必要があります。 まず、年齢のデータに対して補完を行ないます。連続する数値を持つ特徴を補完するのに、次の3つの方法があげられる。

  1. 簡単な方法は、平均と標準偏差の間の乱数を生成することです。
  2. 欠損値を推測するより正確な方法は、他の相関する特徴を使用することです。 今回のケースでは、年齢、性別、およびPclass 間の相関を記録する。 Pclassと性別のデータの組み合わせの集合にあたるAgeの中央値を使用して、Ageの値を推測します。 
  3. 方法1と2を組み合わせてみる。つまり、中央値に基づいて年齢値を推測する代わりに、PclassGenderの組み合わせのセットに基づいて、平均と標準偏差の間の乱数を使用してみる。

 方法1と3はランダムノイズがモデルに入ることになり、 複数回の実行結果が異なる可能性があるので、方法2を優先します。

In[15]:

# grid = sns.FacetGrid(train_df, col='Pclass', hue='Gender')
grid = sns.FacetGrid(train_df, row='Pclass', col='Sex', size=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend()
Out[15]:
<seaborn.axisgrid.FacetGrid at 0x7f2c58cd6a58>
 Pclass x Genderの組み合わせに基づいて推測されたAge値を含む空の配列を準備することから始めましょう。
In[16]:
guess_ages = np.zeros((2,3))
guess_ages
array([[0., 0., 0.], [0., 0., 0.]])

 今度は、性別(0または1)とPclass(1,2,3)を繰り返し、6つの組み合わせのAgeの推測値を計算します。

In[17]:

for dataset in combine:
    for i in range(0, 2):
        for j in range(0, 3):
            guess_df = dataset[(dataset['Sex'] == i) & \
                                  (dataset['Pclass'] == j+1)]['Age'].dropna()

            # age_mean = guess_df.mean()
            # age_std = guess_df.std()
            # age_guess = rnd.uniform(age_mean - age_std, age_mean + age_std)

            age_guess = guess_df.median()

            # Convert random age float to nearest .5 age
            guess_ages[i,j] = int( age_guess/0.5 + 0.5 ) * 0.5
            
    for i in range(0, 2):
        for j in range(0, 3):
            dataset.loc[ (dataset.Age.isnull()) & (dataset.Sex == i) & (dataset.Pclass == j+1),\
                    'Age'] = guess_ages[i,j]

    dataset['Age'] = dataset['Age'].astype(int)

train_df.head()

Out[17]:

Survived Pclass Sex Age SibSp Parch Fare Embarked Title
0 0 3 0 22 1 0 7.2500 S 1
1 1 1 1 38 1 0 71.2833 C 3
2 1 3 1 26 0 0 7.9250 S 2
3 1 1 1 35 1 0 53.1000 S 3
4 0 3 0 35 0 0 8.0500 S 1
連続する数値を持つ年齢のデータから年齢層の特徴(AgeBand)を作り、 生き残りとの相関を決定してみましょう。

In[18]:

train_df['AgeBand'] = pd.cut(train_df['Age'], 5)
train_df[['AgeBand', 'Survived']].groupby(['AgeBand'], as_index=False).mean().sort_values(by='AgeBand', ascending=True)

Out[18]:

AgeBand Survived
0 (-0.08, 16.0] 0.550000
1 (16.0, 32.0] 0.337374
2 (32.0, 48.0] 0.412037
3 (48.0, 64.0] 0.434783
4 (64.0, 80.0] 0.090909

この年齢層のデータ(AgeBand)に基づいて、年齢のデータを大まかな数値に置き換えましょう。

In[19]:

for dataset in combine:    
    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']
train_df.head()

Out[19]:

Survived Pclass Sex Age SibSp Parch Fare Embarked Title AgeBand
0 0 3 0 1 1 0 7.2500 S 1 (16.0, 32.0]
1 1 1 1 2 1 0 71.2833 C 3 (32.0, 48.0]
2 1 3 1 1 0 0 7.9250 S 2 (16.0, 32.0]
3 1 1 1 2 1 0 53.1000 S 3 (32.0, 48.0]
4 0 3 0 2 0 0 8.0500 S 1 (32.0, 48.0]

 年齢のデータを置き換えたことで必要なくなった年齢層の特徴(AgeBand)は削除しときましょう。

In[20]:

train_df = train_df.drop(['AgeBand'], axis=1)
combine = [train_df, test_df]
train_df.head()

Out[20]:

Survived Pclass Sex Age SibSp Parch Fare Embarked Title
0 0 3 0 1 1 0 7.2500 S 1
1 1 1 1 2 1 0 71.2833 C 3
2 1 3 1 1 0 0 7.9250 S 2
3 1 1 1 2 1 0 53.1000 S 3
4 0 3 0 2 0 0 8.0500 S 1

既存の特徴を組み合わせて新しい特徴を作成する

 ParchSibSpを組み合わせたFamilySizeという新しい特徴を作成できます。 これにより、データセットからParchSibSpを削除できます。

In[21]:

for dataset in combine:
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1

train_df[['FamilySize', 'Survived']].groupby(['FamilySize'], as_index=False).mean().sort_values(by='Survived', ascending=False)

Out[21]:

FamilySize Survived
3 4 0.724138
2 3 0.578431
1 2 0.552795
6 7 0.333333
0 1 0.303538
4 5 0.200000
5 6 0.136364
7 8 0.000000
8 11 0.000000

 また、IsAloneという別の特徴を作成することができます。

In[22]:

for dataset in combine:
    dataset['IsAlone'] = 0
    dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1

train_df[['IsAlone', 'Survived']].groupby(['IsAlone'], as_index=False).mean()

Out[22]:

IsAlone Survived
0 0 0.505650
1 1 0.303538

 Parch、SibSp およびFamilySizeの特徴をIsAloneによって表現できます。そのため、データセットからParch、SibSp およびFamilySize を削除できます。

In[23]:

train_df = train_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)
test_df = test_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)
combine = [train_df, test_df]

train_df.head()

Out[23]:

Survived Pclass Sex Age Fare Embarked Title IsAlone
0 0 3 0 1 7.2500 S 1 0
1 1 1 1 2 71.2833 C 3 0
2 1 3 1 1 7.9250 S 2 1
3 1 1 1 2 53.1000 S 3 0
4 0 3 0 2 8.0500 S 1 1

 PclassAgeを組み合わせた人工的な特徴を作成することもできます。

In[24]:

for dataset in combine:
    dataset['Age*Class'] = dataset.Age * dataset.Pclass

train_df.loc[:, ['Age*Class', 'Age', 'Pclass']].head(10)

Out[24]:

Age*Class Age Pclass
0 3 1 3
1 2 2 1
2 3 1 3
3 2 2 1
4 6 2 3
5 3 1 3
6 3 3 1
7 0 0 3
8 3 1 3
9 0 0 2

カテゴリの特徴を補完する

Embarkedのデータは、乗船港に基づいてS、Q、Cの値を取る。 トレーニングデータセットには2つの欠損値があります。これは、最頻値で補完します。

In[25]:

freq_port = train_df.Embarked.dropna().mode()[0]
freq_port
'S' 

In[26]:

for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].fillna(freq_port)
    
train_df[['Embarked', 'Survived']].groupby(['Embarked'], as_index=False).mean().sort_values(by='Survived', ascending=False)

Out[26]:

Embarked Survived
0 C 0.553571
1 Q 0.389610
2 S 0.339009

カテゴリの特徴を数値に変換する

 Embarkedのデータを数値データに変更しておきましょう。

In[27]:

for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)

train_df.head()
Out[27]:
Survived Pclass Sex Age Fare Embarked Title IsAlone Age*Class
0 0 3 0 1 7.2500 0 1 0 3
1 1 1 1 2 71.2833 1 3 0 2
2 1 3 1 1 7.9250 0 2 1 3
3 1 1 1 2 53.1000 0 3 0 2
4 0 3 0 2 8.0500 0 1 1 6

素早い補完と特徴を数値に変換

 運賃のデータを補完するため、欠損値のところには運賃の中央値を置き換えました。

In[28]:

test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True)
test_df.head()

Out[28]:

PassengerId Pclass Sex Age Fare Embarked Title IsAlone Age*Class
0 892 3 0 2 7.8292 2 1 1 6
1 893 3 1 2 7.0000 0 3 0 6
2 894 2 0 3 9.6875 2 1 1 6
3 895 3 0 1 8.6625 0 1 1 3
4 896 3 1 1 12.2875 0 3 0 3

 運賃帯の特徴を作り、Survived との相関を見てみましょう。

In[29]:

train_df['FareBand'] = pd.qcut(train_df['Fare'], 4)
train_df[['FareBand', 'Survived']].groupby(['FareBand'], as_index=False).mean().sort_values(by='FareBand', ascending=True)

Out[29]:

FareBand Survived
0 (-0.001, 7.91] 0.197309
1 (7.91, 14.454] 0.303571
2 (14.454, 31.0] 0.454955
3 (31.0, 512.329] 0.581081

 この運賃帯に基づき、運賃のデータを置き換えましょう。

In[30]:

for dataset in combine:
    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)

train_df = train_df.drop(['FareBand'], axis=1)
combine = [train_df, test_df]
    
train_df.head(10)
Out[30]:
Survived Pclass Sex Age Fare Embarked Title IsAlone Age*Class
0 0 3 0 1 0 0 1 0 3
1 1 1 1 2 3 1 3 0 2
2 1 3 1 1 1 0 2 1 3
3 1 1 1 2 3 0 3 0 2
4 0 3 0 2 1 0 1 1 6
5 0 3 0 1 1 2 1 1 3
6 0 1 0 3 3 0 1 1 3
7 0 3 0 0 2 0 4 0 0
8 1 3 1 1 1 0 3 0 3
9 1 2 1 0 2 1 3 0 0

In[31]:

test_df.head(10)

Out[31]:

PassengerId Pclass Sex Age Fare Embarked Title IsAlone Age*Class
0 892 3 0 2 0 2 1 1 6
1 893 3 1 2 0 0 3 0 6
2 894 2 0 3 1 2 1 1 6
3 895 3 0 1 1 0 1 1 3
4 896 3 1 1 1 0 3 0 3
5 897 3 0 0 1 0 1 1 0
6 898 3 1 1 0 2 2 1 3
7 899 2 0 1 2 0 1 0 2
8 900 3 1 1 0 1 3 1 3
9 901 3 0 1 2 0 1 0 3


 今回は、データセットのデータを可視化しながら分析をしました。次は、このデータを使って実際に学習させ、モデルを作っていきます。また、ここまで読んだ方なら、kaggle のデータをどのように扱っていけばいいのか理解できるのではないかと思います。ぜひ、周りの方と共有してみてください。

最後まで読んでいただきありがとうございました。よろしければこの記事をシェアしていただけると励みになります。よろしくお願いします。

スポンサーリンク
レクタングル広告(大)
レクタングル広告(大)

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

スポンサーリンク
レクタングル広告(大)