Kaggle(42) - タイタニックをRandom Forestで予測 - グリッドサーチでパラメータの最適化

タイタニック生存予測の正解率をあげるために、Random Forestを生成するときのパラメータを調整します。

(データの前処理は前回記事と全く同じなので省略します。)

グリッドサーチ

グリッドサーチを使って、次の3パラメータに対してチューニングを行います。

  • criterion
    データの分割の方法。
  • n_estimators
    バギングに用いる決定木の個数。デフォルトの値は10。
  • max_depth
    決定木の深さの最大値。過学習を避けるためにはこれを調節するのが最も重要。

グリッドサーチはパラメータ候補をリストで指定して、その中でもっとも成績のいいパラメータの組み合わせを導く手法です。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from sklearn import ensemble, model_selection
from sklearn.model_selection import GridSearchCV

# 試行するパラメータを羅列
params = {
'criterion' : ['gini', 'entropy'],
'n_estimators': [10, 100, 300, 500, 1000, 1500, 2000],
'max_depth' : [3, 5, 7, 9, 11]
}

clf = ensemble.RandomForestClassifier()
grid_search = GridSearchCV(clf, param_grid=params, cv=4)
grid_search.fit(x_titanic, y_titanic)

print(grid_search.best_score_)
print(grid_search.best_params_)

[結果]

最も成績のよいパラメータは上記のようになりました。

Random Forest分割交差検証

グリッドサーチで調べたベストパラメータを指定してRandom Forestのインスタンスを作成します。

その後、cross_val_score関数で分割交差検証を行い、どのくらいの正解率になるかチェックします。

[ソース]

1
2
3
4
5
6
7
from sklearn import ensemble, model_selection
clf = ensemble.RandomForestClassifier(criterion='geni', n_estimators=100, max_depth=9)

for _ in range(5):
score = model_selection.cross_val_score(clf, x_titanic, y_titanic, cv=4) # cv=4は4分割の意
print('各正解率', score)
print('正解率', score.mean())

[結果]

正解率は87.31%~88.55%となりました。

Kaggleに提出

分割交差検証で生成したRandom Forestをそのまま使って、学習・予測を行います。

最後に提出用に出力したCSVファイルをKaggleに提出します。

[ソース]

1
2
3
4
5
6
7
8
9
# 学習
clf.fit(x_titanic, y_titanic)

# 予測
pre = clf.predict(df_test.drop(['Survived'], axis=1))

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre.astype(int)
result.to_csv('result0320l.csv', index=False)

[提出結果]

正解率79.42%となりました。

パラメータを調整していない前回の正解率と全く同じ結果となりました・・・難しいです。

Kaggle(41) - タイタニックをRandom Forestで予測 - 正解率8割り超えまでもう少し編

前回記事では分割交差検証で85%前後となかなかの正解率でしたが、Kaggleへの提出ができませんでした。

理由としてはSurnameカラムをワンホット・エンコーディングすると列数が増えてしまって、訓練データと検証データのカラムの整合性をとりにくくなってしまったためです。

カラム数があっていないと、予測(fit)するときにエラーになりますので。。

訓練データとテストデータを連結

前回の失敗を踏まえて、今回はあらかじめ訓練データとテストデータを連結してから、デッドリストとサバイブリストの作成、データの前処理を行います。

データの前処理が終わったら、訓練データとテストデータと分割し、ランダムフォレストでの学習、推測を行います。

まずは前回記事でのデータ前処理全般を修正していきます。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import pandas as pd
import numpy as np

Dead_list = []
Survived_list = []

def age_trans(df):
# Age を Pclass, Sex, Parch, SibSp からランダムフォレストで推定
from sklearn.ensemble import RandomForestRegressor

# 推定に使用する項目を指定
age_df = df[['Age', 'Pclass','Sex','Parch','SibSp']]

# ラベル特徴量をワンホットエンコーディング
age_df=pd.get_dummies(age_df)

# 学習データとテストデータに分離し、numpyに変換
known_age = age_df[age_df.Age.notnull()].values
unknown_age = age_df[age_df.Age.isnull()].values

# 学習データをX, yに分離
X = known_age[:, 1:]
y = known_age[:, 0]

# ランダムフォレストで推定モデルを構築
rfr = RandomForestRegressor(random_state=0, n_estimators=100, n_jobs=-1)
rfr.fit(X, y)

# 推定モデルを使って、テストデータのAgeを予測し、補完
predictedAges = rfr.predict(unknown_age[:, 1::])
df.loc[(df.Age.isnull()), 'Age'] = predictedAges
return df

def make_list(df):
global Dead_list
global Survived_list
# Nameから敬称(Title)を抽出し、グルーピング
df['Title'] = df['Name'].map(lambda x: x.split(', ')[1].split('. ')[0])
df['Title'].replace(['Capt', 'Col', 'Major', 'Dr', 'Rev'], 'Officer', inplace=True)
df['Title'].replace(['Don', 'Sir', 'the Countess', 'Lady', 'Dona', 'Jonkheer'], 'Royalty', inplace=True)
df['Title'].replace(['Mme', 'Ms'], 'Mrs', inplace=True)
df['Title'].replace(['Mlle'], 'Miss', inplace=True)

# NameからSurname(苗字)を抽出
df['Surname'] = df['Name'].map(lambda name:name.split(',')[0].strip())

# 同じSurname(苗字)の出現頻度をカウント(出現回数が2以上なら家族)
df['FamilyGroup'] = df['Surname'].map(df['Surname'].value_counts())

# 家族で16才以下または女性の生存率
Female_Child_Group=df.loc[(df['FamilyGroup']>=2) & ((df['Age']<=16) | (df['Sex']=='female'))]
Female_Child_Group=Female_Child_Group.groupby('Surname')['Survived'].mean()

# 家族で16才超えかつ男性の生存率
Male_Adult_Group=df.loc[(df['FamilyGroup']>=2) & (df['Age']>16) & (df['Sex']=='male')]
Male_Adult_List=Male_Adult_Group.groupby('Surname')['Survived'].mean()

# デッドリストとサバイブリストの作成
Dead_list=set(Female_Child_Group[Female_Child_Group.apply(lambda x:x==0)].index)
Survived_list=set(Male_Adult_List[Male_Adult_List.apply(lambda x:x==1)].index)


def trans_by_list(df):
# Nameから敬称(Title)を抽出し、グルーピング
df['Title'] = df['Name'].map(lambda x: x.split(', ')[1].split('. ')[0])
df['Title'].replace(['Capt', 'Col', 'Major', 'Dr', 'Rev'], 'Officer', inplace=True)
df['Title'].replace(['Don', 'Sir', 'the Countess', 'Lady', 'Dona', 'Jonkheer'], 'Royalty', inplace=True)
df['Title'].replace(['Mme', 'Ms'], 'Mrs', inplace=True)
df['Title'].replace(['Mlle'], 'Miss', inplace=True)

# NameからSurname(苗字)を抽出
df['Surname'] = df['Name'].map(lambda name:name.split(',')[0].strip())

# 同じSurname(苗字)の出現頻度をカウント(出現回数が2以上なら家族)
df['FamilyGroup'] = df['Surname'].map(df['Surname'].value_counts())

# デッドリストとサバイブリストをSex, Age, Title に反映させる
df.loc[(df['Surname'].apply(lambda x:x in Dead_list)), ['Sex','Age','Title']] = ['male',28.0,'Mr']
df.loc[(df['Surname'].apply(lambda x:x in Survived_list)), ['Sex','Age','Title']] = ['female',5.0,'Mrs']
return df

# データ前処理
def preprocessing(df):
df = age_trans(df)
df = trans_by_list(df)

df['Deck'] = df['Cabin'].apply(lambda s:s[0] if pd.notnull(s) else 'M')
df['TicketFrequency'] = df.groupby('Ticket')['Ticket'].transform('count')

# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 欠損値処理
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Embarked'] = df['Embarked'].fillna('S')

df['Fare'] = pd.qcut(df['Fare'], 10, labels=False)
df['Age'] = pd.cut(df['Age'], 10, labels=False)

# カテゴリ変数の変換
df = pd.get_dummies(df, columns=['Sex', 'Embarked', 'Deck', 'Title', 'Surname'])

return df

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')

# 訓練データとテストデータを連結
df_test['Survived'] = np.nan
df_all = pd.concat([df_train, df_test], ignore_index=True, sort=False)

make_list(df_all) # デッドリストとサバイブリストを作成

df_all = preprocessing(df_all)
print(df_all.shape)

# ひとまとめにしたデータを訓練データとテストデータに分割
df_train = df_all.loc[df_all['Survived'].notnull()]
df_test = df_all.loc[df_all['Survived'].isnull()]

x_titanic = df_train.drop(['Survived'], axis=1)
y_titanic = df_train['Survived']

ポイントは109~110行目でのデータ連結処理と118~119行目のデータ分割処理になります。

こうしておくと一括でデータの前処理ができるだけではなく、デッドリスト・サバイブリストを作る時のデータも増やすことができて一石二鳥でした。

これからのデータ分析にも役立ちそうな手法だなーと小さい発見をした気持ちになりました。

Random Forest分割交差検証

データの前処理は全て終わっていますので、Random Forestのインスタンスを作成し、cross_val_score関数で分割交差検証を行い、どのくらいの正解率になるかチェックします。

[ソース]

1
2
3
4
5
6
7
8
from sklearn import ensemble, model_selection

clf = ensemble.RandomForestClassifier()

for _ in range(5):
score = model_selection.cross_val_score(clf, x_titanic, y_titanic, cv=4) # cv=4は4分割の意
print('各正解率', score)
print('正解率', score.mean())

[結果]

正解率は86.98%~87.88%となりました。前回の記事を超える正解率です。

少し手ごたえを感じました。

Kaggleに提出

訓練データ全体で学習を行います。

データの前処理は終わっていますので、すんなりと学習・推測処理を行うことができます。

1つ注意する点としては、検証データ(df_test)にはデータ前処理のためにSurvived項目が仮設定されているのでこれを省いておきます。(5行目)

最後に提出用に出力したCSVファイルをKaggleに提出します。

[ソース]

1
2
3
4
5
6
7
8
9
# 学習
clf.fit(x_titanic, y_titanic)

# 推測
pre = clf.predict(df_test.drop(['Survived'], axis=1))

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre.astype(int)
result.to_csv('result0319c.csv', index=False)

[提出結果]

正解率79.42%となりました。

・・・おしいです。もう少しで8割の正解率を超えられたのですが。。。

次回は、ランダムフォレストのパラメータを調整して念願の正解率8割超えを狙いたいと思います。

Kaggle(40) - タイタニックをRandom Forestで予測

前2回の記事で行ったデータクレンジング処理を踏まえてタイタニックコンペに提出します。

改善内容は以下の通りです。

  • ランダムフォレストで年齢の欠損値を推定。
  • 名前(Name)から特徴量抽出し、デッドリストとサバイブリストを作成し、テストデータに反映。

年齢の欠損値を推定する処理を関数化

ランダムフォレストで年齢の欠損値を推定する処理を関数化します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def age_trans(df):
# Age を Pclass, Sex, Parch, SibSp からランダムフォレストで推定
from sklearn.ensemble import RandomForestRegressor

# 推定に使用する項目を指定
age_df = df[['Age', 'Pclass','Sex','Parch','SibSp']]

# ラベル特徴量をワンホットエンコーディング
age_df=pd.get_dummies(age_df)

# 学習データとテストデータに分離し、numpyに変換
known_age = age_df[age_df.Age.notnull()].values
unknown_age = age_df[age_df.Age.isnull()].values

# 学習データをX, yに分離
X = known_age[:, 1:]
y = known_age[:, 0]

# ランダムフォレストで推定モデルを構築
rfr = RandomForestRegressor(random_state=0, n_estimators=100, n_jobs=-1)
rfr.fit(X, y)

# 推定モデルを使って、テストデータのAgeを予測し、補完
predictedAges = rfr.predict(unknown_age[:, 1::])
df.loc[(df.Age.isnull()), 'Age'] = predictedAges
return df

年齢の欠損値を推定する処理を関数デッドリストとサバイブリストを作成

名前(Name)から特徴量抽出し、デッドリストとサバイブリストを作成する処理を関数化します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def make_list(df):
global Dead_list
global Survived_list
# Nameから敬称(Title)を抽出し、グルーピング
df['Title'] = df['Name'].map(lambda x: x.split(', ')[1].split('. ')[0])
df['Title'].replace(['Capt', 'Col', 'Major', 'Dr', 'Rev'], 'Officer', inplace=True)
df['Title'].replace(['Don', 'Sir', 'the Countess', 'Lady', 'Dona', 'Jonkheer'], 'Royalty', inplace=True)
df['Title'].replace(['Mme', 'Ms'], 'Mrs', inplace=True)
df['Title'].replace(['Mlle'], 'Miss', inplace=True)

# NameからSurname(苗字)を抽出
df['Surname'] = df['Name'].map(lambda name:name.split(',')[0].strip())

# 同じSurname(苗字)の出現頻度をカウント(出現回数が2以上なら家族)
df['FamilyGroup'] = df['Surname'].map(df['Surname'].value_counts())

# 家族で16才以下または女性の生存率
Female_Child_Group=df.loc[(df['FamilyGroup']>=2) & ((df['Age']<=16) | (df['Sex']=='female'))]
Female_Child_Group=Female_Child_Group.groupby('Surname')['Survived'].mean()

# 家族で16才超えかつ男性の生存率
Male_Adult_Group=df.loc[(df['FamilyGroup']>=2) & (df['Age']>16) & (df['Sex']=='male')]
Male_Adult_List=Male_Adult_Group.groupby('Surname')['Survived'].mean()

# デッドリストとサバイブリストの作成
Dead_list=set(Female_Child_Group[Female_Child_Group.apply(lambda x:x==0)].index)
Survived_list=set(Male_Adult_List[Male_Adult_List.apply(lambda x:x==1)].index)

デッドリストに該当した行(乗客)の場合は、必ず死亡と判断されるようにSex, Age, Titleを典型的な死亡データに書き換え、サバイブリストに該当した行がある場合は、必ず生存と判断されるように Sex, Age, Titleを典型的な生存データに書き換える処理を関数化します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def trans_by_list(df):
# Nameから敬称(Title)を抽出し、グルーピング
df['Title'] = df['Name'].map(lambda x: x.split(', ')[1].split('. ')[0])
df['Title'].replace(['Capt', 'Col', 'Major', 'Dr', 'Rev'], 'Officer', inplace=True)
df['Title'].replace(['Don', 'Sir', 'the Countess', 'Lady', 'Dona', 'Jonkheer'], 'Royalty', inplace=True)
df['Title'].replace(['Mme', 'Ms'], 'Mrs', inplace=True)
df['Title'].replace(['Mlle'], 'Miss', inplace=True)

# NameからSurname(苗字)を抽出
df['Surname'] = df['Name'].map(lambda name:name.split(',')[0].strip())

# 同じSurname(苗字)の出現頻度をカウント(出現回数が2以上なら家族)
df['FamilyGroup'] = df['Surname'].map(df['Surname'].value_counts())

# デッドリストとサバイブリストをSex, Age, Title に反映させる
df.loc[(df['Surname'].apply(lambda x:x in Dead_list)), ['Sex','Age','Title']] = ['male',28.0,'Mr']
df.loc[(df['Surname'].apply(lambda x:x in Survived_list)), ['Sex','Age','Title']] = ['female',5.0,'Mrs']
return df

データの読み込みとデータクレンジング改善

Kaggleに準備されているタイタニックの訓練データを読み込み、デッドリストとサバイブリストを作成しておきます。

データの前処理(不要列の削除・欠損処理・カテゴリ変数の変換)と、正解ラベルとそれ以外にデータを分けます。

データの前処理の中では、ランダムフォレストで年齢の欠損値を推定する処理age_trans)デッドリストとサバイブリストを作成する処理(trans_by_list)をコールしています。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import pandas as pd

Dead_list = []
Survived_list = []

# データ前処理
def preprocessing(df):
df = age_trans(df)
df = trans_by_list(df)

df['Deck'] = df['Cabin'].apply(lambda s:s[0] if pd.notnull(s) else 'M')
df['TicketFrequency'] = df.groupby('Ticket')['Ticket'].transform('count')

# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 欠損値処理
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Embarked'] = df['Embarked'].fillna('S')

df['Fare'] = pd.qcut(df['Fare'], 10, labels=False)
df['Age'] = pd.cut(df['Age'], 10, labels=False)

# カテゴリ変数の変換
df = pd.get_dummies(df, columns=['Sex', 'Embarked', 'Deck', 'Title', 'Surname'])

return df

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

make_list(df_train)

df_train = preprocessing(df_train)
x_titanic = df_train.drop(['Survived'], axis=1)
y_titanic = df_train['Survived']

Random Forest分割交差検証

Random Forestのインスタンスを作成し、cross_val_score関数で分割交差検証を行い、どのくらいの正解率になるか調べてみます。

[ソース]

1
2
3
4
5
6
7
8
from sklearn import ensemble, model_selection

clf = ensemble.RandomForestClassifier()

for _ in range(5):
score = model_selection.cross_val_score(clf, x_titanic, y_titanic, cv=4) # cv=4は4分割の意
print('各正解率', score)
print('正解率', score.mean())

[出力]

正解率は全て85%台となりました。

これまでにない高正解率だったので早速Kaggleに提出しようとしたのですが、提出処理に戸惑ってしまったので、明日の記事に持ち越します。すいません。。。

Kaggle(39) - タイタニック生存予測 - 名前(Name)から特徴量抽出

以前名前に含まれる敬称(Mr. Mrs. Miss.など)をパラメータ化しましたが、今回はもう少し名前に関する特徴量に関して深堀りしていきたいと思います。

敬称を抽出しグループ化

名前(Name)から敬称(Title)を抽出し、グループ化します。

Officerは乗船員で、Royaltyは王族、貴族といった意味合いです。

Jonkheerはオランダ語で貴族という意味になります。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd
import seaborn as sns

df = pd.read_csv('/kaggle/input/titanic/train.csv')

# Nameから敬称(Title)を抽出し、グルーピング
df['Title'] = df['Name'].map(lambda x: x.split(', ')[1].split('. ')[0])
df['Title'].replace(['Capt', 'Col', 'Major', 'Dr', 'Rev'], 'Officer', inplace=True)
df['Title'].replace(['Don', 'Sir', 'the Countess', 'Lady', 'Dona', 'Jonkheer'], 'Royalty', inplace=True)
df['Title'].replace(['Mme', 'Ms'], 'Mrs', inplace=True)
df['Title'].replace(['Mlle'], 'Miss', inplace=True)

sns.barplot(x='Title', y='Survived', data=df, palette='Set2')

[出力]

Mrの生存率が一番低く、Mrsの生存率が一番高いことが分かります。

Royalyt(貴族)の生存率も70%を超えていますね。さすがです(?)

同じ苗字のグループ化

名前から苗字を取り出しグループ化します。

家族がいた場合に生存率に影響があったのではないかという視点になります。

[ソース]

1
2
3
4
5
# NameからSurname(苗字)を抽出
df['Surname'] = df['Name'].map(lambda name:name.split(',')[0].strip())

# 同じSurname(苗字)の出現頻度をカウント(出現回数が2以上なら家族)
df['FamilyGroup'] = df['Surname'].map(df['Surname'].value_counts())

家族を16才以下または女性というグループにすると面白い事実が見えてきます。

[ソース]

1
2
3
4
# 家族で16才以下または女性の生存率
Female_Child_Group=df.loc[(df['FamilyGroup']>=2) & ((df['Age']<=16) | (df['Sex']=='female'))]
Female_Child_Group=Female_Child_Group.groupby('Surname')['Survived'].mean()
print(Female_Child_Group.value_counts())

[出力]

1行目の77グループでは生存率100%ですが、2行目の27グループでは生存率0%となっています。

多くのグループは全員生存しているのに、一部のグループだけ全滅しているということになります。


次に16才を超えかつ男性というグループに注目してみます。

[ソース]

1
2
3
4
# 家族で16才超えかつ男性の生存率
Male_Adult_Group=df.loc[(df['FamilyGroup']>=2) & (df['Age']>16) & (df['Sex']=='male')]
Male_Adult_List=Male_Adult_Group.groupby('Surname')['Survived'].mean()
print(Male_Adult_List.value_counts())

[出力]

1行目の70グループは生存率0%で、2行目の14グループでは生存率100%となっています。

多くのグループは全滅しているのに、一部のグループだけ全員生存しているということになります。

上記の結果をまとめると、全体の流れとは逆の運命を辿った少数派がいるということになります。

デッドリストとサバイブリストの作成

今回の名前分析を踏まえて、次の2種類のリストを作成しテストデータに反映します。

  • デッドリスト(Dead_list)
    16才以下または女性のグループで全員死んだ苗字を集めたリスト
  • サバイブリスト(Survived_list)
    16才を超えかつ男性のグループで全員生存した苗字を集めたリスト

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
# デッドリストとサバイブリストの作成
Dead_list=set(Female_Child_Group[Female_Child_Group.apply(lambda x:x==0)].index)
Survived_list=set(Male_Adult_List[Male_Adult_List.apply(lambda x:x==1)].index)

# デッドリストとサバイブリストの表示
print('Dead_list = ', Dead_list)
print('Survived_list = ', Survived_list)

# デッドリストとサバイブリストをSex, Age, Title に反映させる
df.loc[(df['Survived'].isnull()) & (df['Surname'].apply(lambda x:x in Dead_list)),\
['Sex','Age','Title']] = ['male',28.0,'Mr']
df.loc[(df['Survived'].isnull()) & (df['Surname'].apply(lambda x:x in Survived_list)),\
['Sex','Age','Title']] = ['female',5.0,'Mrs']

[出力]

テストデータの中で、デッドリストに該当した行(乗客)の場合は、必ず死亡と判断されるようにSex, Age, Titleを典型的な死亡データに書き換え、サバイブリストに該当した行がある場合は、必ず生存と判断されるように Sex, Age, Titleを典型的な生存データに書き換えています。

次回はこのデッドリストとサバイブリストの判定を踏まえて、タイタニック生存予測を行いたいと思います。

Kaggle(38) - タイタニック生存予測 - ランダムフォレストで年齢の欠損値を推定

タイタニック生存予測の精度を上げるためにあらたな分析方法を探していたところ興味深い記事を見つけました。

それは欠損値がない完全なデータ(Pclass, Sex, SibSp, Parch)を使って、ランダムフォレストで年齢(Age)の欠損値を推定するというものです。

ランダムフォレストで年齢の欠損値を推定

ランダムフォレストで年齢の欠損値を推定する手順は下記の通りです。

  1. 推定に使用する項目を抽出
  2. ラベル特徴量をワンホットエンコーディング
  3. 学習データ(年齢データのあるもの)とテストデータ(年齢データが欠損値)に分離し、numpyに変換
  4. 学習データをX(年齢), y(それ以外)に分離
  5. ランダムフォレストで推定モデルを構築
  6. 推定モデルを使って、テストデータの年齢(Age)を予測し、補完

最後に年齢ごとの生存率と死亡率をグラフ化しています。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import pandas as pd

# Age を Pclass, Sex, Parch, SibSp からランダムフォレストで推定
from sklearn.ensemble import RandomForestRegressor

df = pd.read_csv('/kaggle/input/titanic/train.csv')

# 推定に使用する項目を抽出
age_df = df[['Age', 'Pclass','Sex','Parch','SibSp']]

# ラベル特徴量をワンホットエンコーディング
age_df=pd.get_dummies(age_df)

# 学習データとテストデータに分離し、numpyに変換
known_age = age_df[age_df.Age.notnull()].values
unknown_age = age_df[age_df.Age.isnull()].values

# 学習データをX, yに分離
X = known_age[:, 1:]
y = known_age[:, 0]

# ランダムフォレストで推定モデルを構築
rfr = RandomForestRegressor(random_state=0, n_estimators=100, n_jobs=-1)
rfr.fit(X, y)

# 推定モデルを使って、テストデータのAgeを予測し、補完
predictedAges = rfr.predict(unknown_age[:, 1::])
df.loc[(df.Age.isnull()), 'Age'] = predictedAges

# 年齢別生存曲線と死亡曲線
facet = sns.FacetGrid(df, hue="Survived",aspect=2)
facet.map(sns.kdeplot,'Age',shade= True)
facet.set(xlim=(0, df.loc[:,'Age'].max()))
facet.add_legend()
plt.show()

[出力]

年齢(Age)の欠損値補完をした結果をグラフにしたのが上の図になります。

10歳未満の場合は生存率が上回っていて、20~30歳の場合は死亡率が上回っていることが確認できます。

Kaggle(37) - タイタニックをRandom Forestで予測 - チケットの重複数をパラメータ化

「同一のチケット番号だと一緒に旅行していることの指標となり、チケットの重複数と生存率には関係がある」という記事を見かけました。

今回はチケットの重複数をパラメータ化してタイタニックの生存予測を行います。

チケットの重複数をチェック

チケットの重複数をTicketFrequency項目として追加、重複数ごとに平均生存率を表示します。

[ソース]

1
2
3
4
5
6
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

df_train['TicketFrequency'] = df_train.groupby('Ticket')['Ticket'].transform('count')
df_train[['TicketFrequency', 'Survived']].groupby('TicketFrequency').mean()

[出力]

次にチケットの重複数ごとの平均生存率をグラフ化します。

[ソース]

1
df_train[['TicketFrequency', 'Survived']].groupby('TicketFrequency').mean().plot(kind='bar', figsize=(10,6))

[出力]

チケット重複が2から3の場合は、生存率が高いように見受けられます。

データの読み込みとデータクレンジング改善6

Kaggleに準備されているタイタニックの訓練データを読み込みます。

データの前処理(不要列の削除・欠損処理・カテゴリ変数の変換)と、正解ラベルとそれ以外にデータを分けます。

データクレンジングの改善6として、チケットの重複数をパラメータとして追加しています。(8行目)

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

# データ前処理
def preprocessing(df):
df['Deck'] = df['Cabin'].apply(lambda s:s[0] if pd.notnull(s) else 'M') # 改善2
df['TicketFrequency'] = df.groupby('Ticket')['Ticket'].transform('count') # 改善6

# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 欠損値処理
df['Age'] = df.groupby(['Pclass', 'Sex'])['Age'].apply(lambda x: x.fillna(x.median())) # 改善1
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Embarked'] = df['Embarked'].fillna('S')

df['Fare'] = pd.qcut(df['Fare'], 10, labels=False) # 改善3
df['Age'] = pd.cut(df['Age'], 10, labels=False) # 改善4

# カテゴリ変数の変換
df = pd.get_dummies(df, columns=['Sex', 'Embarked', 'Deck']) # 改善2

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']
# x_titanic.isnull().sum()

x_titanic

[出力]

チケットの重複数としてTicketFrequencyが追加されていることが確認できます。

Random Forest分割交差検証

Random Forestのインスタンスを作成し、cross_val_score関数で分割交差検証を行い、どのくらいの正解率になるか調べてみます。

実行するたびに微妙に正解率が違うことに気づいたので、5回ほど連続実行しています。

[ソース]

1
2
3
4
5
6
7
from sklearn import ensemble, model_selection
clf = ensemble.RandomForestClassifier()

for _ in range(5):
score = model_selection.cross_val_score(clf, x_titanic, y_titanic, cv=4) # cv=4は4分割の意
print('各正解率', score)
print('正解率', score.mean())

[出力]

正解率は78%~80%となりました。

Kaggleに提出

訓練データ全体で学習を行います。

その後、検証データを読み込み、推論・提出用のCSVの出力を行い、Kaggleに提出します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
# 学習
clf.fit(x_titanic, y_titanic)

# 検証データの読み込み
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
df_test = preprocessing(df_test)
df_test['Deck_T'] = 0

pre = clf.predict(df_test)

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre
result.to_csv('result0315.csv', index=False)

[提出結果]

正解率76.55%となりました。正解率あげるのってホントに難しいんですね。

パラメータの追加・削除だけではなくて他の切り口でいく必要があると感じ始めています。

Kaggle(36) - タイタニックをRandom Forestで予測 - 名前に含まれる敬称をパラメータ化

名前に含まれる敬称は社会経済的地位に関する情報となりえる可能性があるような気がします。

そこで今回は名前に含まれる敬称(Mr. Mrs. Miss.など)を抽出し、カテゴリパラメータとして追加してみます。

名前に含まれる敬称抽出

名前から敬称を抽出しTitle項目として追加しています。

同じ敬称の個数が10に満たない場合は’etc’とひとまとめにしました。

[ソース]

1
2
3
4
5
6
# expand=True ⇒ 複数の列に分割してpandas.DataFrameとして取得
df_train['Title'] = df_train['Name'].str.split(', ', expand=True)[1].str.split('.', expand=True)[0]
title_names = df_train['Title'].value_counts() < 10
df_train['Title'] = df_train['Title'].apply(lambda x: 'etc' if title_names.loc[x] == True else x)

df_train

[出力]

名前から敬称がきちんと抽出できていることが分かります。

各敬称の個数を表示すると下記のようになります。

[ソース]

1
df_train.groupby('Title')['Title'].count()

[出力]

メジャーな敬称が問題なく抽出されていると思います。

データの読み込みとデータクレンジング改善5

Kaggleに準備されているタイタニックの訓練データを読み込みます。

データの前処理(不要列の削除・欠損処理・カテゴリ変数の変換)と、正解ラベルとそれ以外にデータを分けます。

データクレンジングの改善5として、名前から敬称を抽出しカテゴリパラメータとしています。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

# データ前処理
def preprocessing(df):
df['Deck'] = df['Cabin'].apply(lambda s:s[0] if pd.notnull(s) else 'M') # 改善2

df['Title'] = df['Name'].str.split(', ', expand=True)[1].str.split('.', expand=True)[0] # 改善5
title_names = df['Title'].value_counts() < 10 # 改善5
df['Title'] = df['Title'].apply(lambda x: 'etc' if title_names.loc[x] == True else x) # 改善5

# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 欠損値処理
df['Age'] = df.groupby(['Pclass', 'Sex'])['Age'].apply(lambda x: x.fillna(x.median())) # 改善1
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Embarked'] = df['Embarked'].fillna('S')

df['Fare'] = pd.qcut(df['Fare'], 10, labels=False) # 改善3
df['Age'] = pd.cut(df['Age'], 10, labels=False) # 改善4

# カテゴリ変数の変換
df = pd.get_dummies(df, columns=['Sex', 'Embarked', 'Deck', 'Title']) # 改善2、5

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']

Random Forest分割交差検証

Random Forestのインスタンスを作成し、cross_val_score関数で分割交差検証を行い、どのくらいの正解率になるか調べてみます。

[ソース]

1
2
3
4
5
6
7
8
from sklearn import ensemble, model_selection

clf = ensemble.RandomForestClassifier()

score = model_selection.cross_val_score(clf, x_titanic, y_titanic, cv=4) # cv=4は4分割の意
print('各正解率', score)
print('正解率', score.mean())

[出力]

正解率は80.25%となりました。

Kaggleに提出

訓練データ全体で学習を行います。

その後、検証データを読み込み、推論・提出用のCSVの出力を行い、Kaggleに提出します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 学習
clf.fit(x_titanic, y_titanic)

# 検証データの読み込み
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
df_test = preprocessing(df_test)
df_test['Deck_T'] = 0

pre = clf.predict(df_test)

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre
result.to_csv('result0314.csv', index=False)

[提出結果]

正解率71.53%となりました。

・・・だいぶ正解率が落ちてしまいました。敬称は生存率には影響しないのかもしれません。

Kaggle(35) - タイタニックをRandom Forestで予測 - 連続変数のビニング2

ビニングまたは離散化は、連続変数または数値変数をカテゴリカル特徴に変換するための処理です。

年齢(Age)に関してビニング処理を行ってみます。

年齢データのチェック

年齢(Age)に着目してみますと、データのばらつきが大きく外れ値が予測に悪い影響を与えている可能性があります。

年齢データのばらつきを確認するために、箱ひげ図で表示します。

[ソース]

1
2
3
4
5
6
7
8
9
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style='darkgrid')
plt.figure(figsize=(12, 8))
sns.boxplot(data=df_train, y='Age')

[出力]

65歳以上のデータが外れ値と判断できるかと思います。

年齢をcut関数を使って、ビニングします。cut関数は最大値と最小値の間を等間隔で分割する関数です。

今回はビンの数が5個になるように分割し(5分割)、各範囲のデータ数を表示します。

[ソース]

1
2
df_train['Age'] = pd.cut(df_train['Age'], 5, labels=False)
df_train['Age'].value_counts()

[出力]

年齢データではなく0~4の数字になっていることが分かります。

次にビニング処理を行った年齢範囲ごとの平均生存率を棒グラフで表示してみます。

[ソース]

1
df_train.groupby('Age')['Survived'].mean().plot(kind='bar', figsize=(10,6))

[出力]

若い人年齢に部類されているほど生存率が高く、年配の人の生存率が10%以下であることが見てとれます。

データの読み込みとデータクレンジング改善4

Kaggleに準備されているタイタニックの訓練データを読み込みます。

データの前処理(不要列の削除・欠損処理・カテゴリ変数の変換)と、正解ラベルとそれ以外にデータを分けます。

データクレンジングの改善4として、年齢データをcut関数を使ってビニング処理を行っています。(18行目)

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

# データ前処理
def preprocessing(df):
df['Deck'] = df['Cabin'].apply(lambda s:s[0] if pd.notnull(s) else 'M') # 改善2

# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 欠損値処理
df['Age'] = df.groupby(['Pclass', 'Sex'])['Age'].apply(lambda x: x.fillna(x.median())) # 改善1
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Embarked'] = df['Embarked'].fillna('S')

df['Fare'] = pd.qcut(df['Fare'], 10, labels=False) # 改善3
df['Age'] = pd.cut(df['Age'], 5, labels=False) # 改善4

# カテゴリ変数の変換
df = pd.get_dummies(df, columns=['Sex', 'Embarked', 'Deck']) # 改善2

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']
# x_titanic.isnull().sum()

x_titanic

[出力]

年齢(Age)が、0~4の数字に変換されていることが確認できます。

Random Forest分割交差検証

Random Forestのインスタンスを作成し、cross_val_score関数で分割交差検証を行い、どのくらいの正解率になるか調べてみます。

[ソース]

1
2
3
4
5
6
7
from sklearn import ensemble, model_selection

clf = ensemble.RandomForestClassifier()

score = model_selection.cross_val_score(clf, x_titanic, y_titanic, cv=4) # cv=4は4分割の意
print('各正解率', score)
print('正解率', score.mean())

[出力]

正解率は80.36%となりました。

Kaggleに提出

訓練データ全体で学習を行います。

その後、検証データを読み込み、推論・提出用のCSVの出力を行い、Kaggleに提出します。

[ソース]

1

[提出結果]

正解率75.11%となりました。

Kaggle(34) - タイタニックをRandom Forestで予測 - 連続変数のビニング

ビニングまたは離散化は、連続変数または数値変数をカテゴリカル特徴に変換するための処理です。

運賃(Fare)に関してビニング処理を行ってみます。

運賃データのチェック

運賃(Fare)に着目してみますと、データのばらつきが大きく外れ値が予測に悪い影響を与えている可能性があります。

運賃データのばらつきを確認するために、箱ひげ図で表示します。

[ソース]

1
2
3
4
5
6
7
8
9
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style='darkgrid')
plt.figure(figsize=(12, 8))
sns.boxplot(data=df_train, y='Fare')

[出力]

データのばらつきは一目瞭然で、とくに500以上のデータは外れ値の最たるものかと思われます。

運賃をqcut関数を使って、ビニングします。qcut関数は各ビン(データ範囲)に含まれる個数(要素数)が等しくなるようにビニング処理(ビン分割)する関数です。

今回はビンの数が10個になるように指定します。(10分割)

[ソース]

1
2
df2 = pd.qcut(df_train['Fare'], 10)
df2.value_counts()

[出力]

データが10分割され、データ数もだいたい同じであることが分かります。

データの読み込みとデータクレンジング改善3

Kaggleに準備されているタイタニックの訓練データを読み込みます。

データの前処理(不要列の削除・欠損処理・カテゴリ変数の変換)と、正解ラベルとそれ以外にデータを分けます。

データクレンジングの改善3として、運賃データをqcut関数を使ってビニング処理を行っています。(17行目)

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

# データ前処理
def preprocessing(df):
df['Deck'] = df['Cabin'].apply(lambda s:s[0] if pd.notnull(s) else 'M') # 改善2

# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 欠損値処理
df['Age'] = df.groupby(['Pclass', 'Sex'])['Age'].apply(lambda x: x.fillna(x.median())) # 改善1
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Embarked'] = df['Embarked'].fillna('S')

df['Fare'] = pd.qcut(df['Fare'], 10, labels=False) # 改善3

# カテゴリ変数の変換
df = pd.get_dummies(df, columns=['Sex', 'Embarked', 'Deck']) # 改善2

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']

x_titanic

[出力]

運賃(Fare)が金額ではなく、0~9の数字に変換されていることが確認できます。


Random Forest分割交差検証

Random Forestのインスタンスを作成し、cross_val_score関数で分割交差検証を行い、どのくらいの正解率になるか調べてみます。

[ソース]

1
2
3
4
5
6
7
from sklearn import ensemble, model_selection

clf = ensemble.RandomForestClassifier()

score = model_selection.cross_val_score(clf, x_titanic, y_titanic, cv=4) # cv=4は4分割の意
print('各正解率', score)
print('正解率', score.mean())

[出力]

正解率は81.82%となりそこそこの結果ではありますが、これまでの結果と比較して改善したかどうかは微妙なところです。

Kaggleに提出

訓練データ全体で学習を行います。

その後、検証データを読み込み、推論・提出用のCSVの出力を行い、Kaggleに提出します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
# 学習
clf.fit(x_titanic, y_titanic)

# 検証データの読み込み
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
df_test = preprocessing(df_test)
df_test['Deck_T'] = 0

pre = clf.predict(df_test)

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre
result.to_csv('result0312.csv', index=False)

[提出結果]

正解率75.59%となりました。

・・・7割台での足踏みがなかなかにしんどいです。

Kaggle(33) - タイタニックをRandom Forestで予測 - データクレンジング改善編2

欠測値が多いため列ごと削除していたCabinですが、先頭のアルファベットが船の階層を表しているとの情報を見つけました。

Aが船の一番上の階層で、Gが一番下の階層とのことです。

船の階層ごとの生存率チェック

船の上の方にいる方が救命ボートまでの距離が近くて、生存率が高いはずです。

この仮説をデータをチェックして確認したいと思います。

Cabinの頭文字を取得しDeckとして新しい列を追加します。欠損値の場合はとりあえず’M’を仮設定しました。

[ソース]

1
2
3
4
5
6
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

df_train['Deck'] = df_train['Cabin'].apply(lambda s:s[0] if pd.notnull(s) else 'M')
df_train

[出力]

問題なくDeckのデータが設定されていることが確認できます。

次にDeckでグループ化して、平均生存率を棒グラフで表示します。

[ソース]

1
df_train.groupby('Deck')['Survived'].mean().plot(kind='bar', figsize=(10,6))

[出力]

上の階層ほど生存率が高く、下の階層ほど生存率が低いという結果を期待していたのですが、そういう感じの結果にはなりませんでした。

ただ、これまで欠損値として扱っていたパラメータ(Cabin)を分析パラメータとして変換できたので、この修正を踏まえてKaggleに提出しておきます。

データの読み込みとデータクレンジング改善2

Kaggleに準備されているタイタニックの訓練データを読み込みます。

データの前処理(不要列の削除・欠損処理・カテゴリ変数の変換)と、正解ラベルとそれ以外にデータを分けます。

CabinをDeckに変換する処理も追加しておきます。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

# データ前処理
def preprocessing(df):
df['Deck'] = df['Cabin'].apply(lambda s:s[0] if pd.notnull(s) else 'M') # 改善2

# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 欠損値処理
df['Age'] = df.groupby(['Pclass', 'Sex'])['Age'].apply(lambda x: x.fillna(x.median())) # 改善1
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Embarked'] = df['Embarked'].fillna('S')


# カテゴリ変数の変換
df = pd.get_dummies(df, columns=['Sex', 'Embarked', 'Deck']) # 改善2

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']

Random Forestのインスタンスを作成し、cross_val_score関数で分割交差検証を行い、どのくらいの正解率になるか調べてみます。

[ソース]

1
2
3
4
5
6
from sklearn import ensemble, model_selection
clf = ensemble.RandomForestClassifier()

score = model_selection.cross_val_score(clf, x_titanic, y_titanic, cv=4) # cv=4は4分割の意
print('各正解率', score)
print('正解率', score.mean())

[出力]

81.93%という、まずまずの正解率となりました。

Kaggleに提出

訓練データ全体で学習を行います。

その後、検証データを読み込み、推論・提出用のCSVの出力を行い、Kaggleに提出します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
# 学習
clf.fit(x_titanic, y_titanic)

# 検証データの読み込み
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
df_test = preprocessing(df_test)
df_test['Deck_T'] = 0

pre = clf.predict(df_test)

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre
result.to_csv('result0311.csv', index=False)

[提出結果]

正解率77.51%となりました。

悪くはない正解率だとは思いますが、なんとか8割以上の結果を出したいものです。