Kaggle(45) - タイタニック生存予測 - LightGBM編

前回記事でタイタニックの生存予測を正解率8割以上にすることができました。

今回はそのソースの予測アルゴリズムをRandom ForestからLightGBMに変えて検証してみます。

LightGBMで予測

ソースの全体は下記のようになります。アルゴリズム変更箇所は140行目~144行目です。

LightGBM生成時のパラメータは以前の記事で調整したパラメータを使っています。

[ソース]

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# データセットの読み込み
train_data = pd.read_csv('/kaggle/input/titanic/train.csv')
test_data = pd.read_csv('/kaggle/input/titanic/test.csv')

# train_dataとtest_dataの連結
test_data['Survived'] = np.nan
df = pd.concat([train_data, test_data], ignore_index=True, sort=False)

# ------------ Age ------------
# 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

# ------------ Name --------------
# 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'], 'Royalty', inplace=True)
df['Title'].replace(['Mme', 'Ms'], 'Mrs', inplace=True)
df['Title'].replace(['Mlle'], 'Miss', inplace=True)
df['Title'].replace(['Jonkheer'], 'Master', inplace=True)

# ------------ Surname ------------
# 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 に反映させる
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']

# ----------- Fare -------------
# 欠損値を Embarked='S', Pclass=3 の平均値で補完
fare=df.loc[(df['Embarked'] == 'S') & (df['Pclass'] == 3), 'Fare'].median()
df['Fare']=df['Fare'].fillna(fare)

# ----------- Family -------------
# Family = SibSp + Parch + 1 を特徴量とし、グルーピング
df['Family']=df['SibSp']+df['Parch']+1
df.loc[(df['Family']>=2) & (df['Family']<=4), 'Family_label'] = 2
df.loc[(df['Family']>=5) & (df['Family']<=7) | (df['Family']==1), 'Family_label'] = 1 # == に注意
df.loc[(df['Family']>=8), 'Family_label'] = 0

# ----------- Ticket ----------------
# 同一Ticketナンバーの人が何人いるかを特徴量として抽出
Ticket_Count = dict(df['Ticket'].value_counts())
df['TicketGroup'] = df['Ticket'].map(Ticket_Count)

# 生存率で3つにグルーピング
df.loc[(df['TicketGroup']>=2) & (df['TicketGroup']<=4), 'Ticket_label'] = 2
df.loc[(df['TicketGroup']>=5) & (df['TicketGroup']<=8) | (df['TicketGroup']==1), 'Ticket_label'] = 1
df.loc[(df['TicketGroup']>=11), 'Ticket_label'] = 0

# ------------- Cabin ----------------
# Cabinの先頭文字を特徴量とする(欠損値は U )
df['Cabin'] = df['Cabin'].fillna('Unknown')
df['Cabin_label']=df['Cabin'].str.get(0)

# ---------- Embarked ---------------
# 欠損値をSで補完
df['Embarked'] = df['Embarked'].fillna('S')

# ------------- 前処理 ---------------
# 推定に使用する項目を指定
df = df[['Survived','Pclass','Sex','Age','Fare','Embarked','Title','Family_label','Cabin_label','Ticket_label']]

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

# データセットを trainとtestに分割
train = df[df['Survived'].notnull()]
test = df[df['Survived'].isnull()].drop('Survived',axis=1)

# データフレームをnumpyに変換
X = train.values[:,1:]
y = train.values[:,0]
test_x = test.values

# ----------- 推定モデル構築(Random Forest) ---------------
# from sklearn.feature_selection import SelectKBest
# from sklearn.ensemble import RandomForestClassifier
# from sklearn.pipeline import make_pipeline
# from sklearn.model_selection import cross_validate

# # 採用する特徴量を25個から20個に絞り込む
# select = SelectKBest(k = 20)

# clf = RandomForestClassifier(random_state = 10,
# warm_start = True,
# n_estimators = 26,
# max_depth = 6,
# max_features = 'sqrt')
# # clf = RandomForestClassifier()
# pipeline = make_pipeline(select, clf)
# pipeline.fit(X, y)

# ----------- 推定モデル構築(LightGBM) ---------------
import lightgbm as lgb
from sklearn.metrics import accuracy_score
# LightGBMの分類器をインスタンス化
gbm = lgb.LGBMClassifier(objective='binary', num_leaves=5, reg_alpha=0, reg_lambda=22)
gbm.fit(X, y)

# ----- Submit dataの作成 -------
PassengerId=test_data['PassengerId']
#predictions = pipeline.predict(test_x) # Random Forestで予測
predictions = gbm.predict(test_x) # LightGBMで予測
submission = pd.DataFrame({"PassengerId": PassengerId, "Survived": predictions.astype(np.int32)})
submission.to_csv("result0323.csv", index=False)

[提出結果]

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

思ったよりもいい正解率となりました。もう少しパラメータを調整したら正解率8割超えるかもしれません。

Kaggle(44) - タイタニック生存予測 - 正解率8割を超える参考ソース

タイタニックの生存予測ですが、なかなか正解率8割の壁を超えることができなかったので、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
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# データセットの読み込み
train_data = pd.read_csv('/kaggle/input/titanic/train.csv')
test_data = pd.read_csv('/kaggle/input/titanic/test.csv')

# train_dataとtest_dataの連結
test_data['Survived'] = np.nan
df = pd.concat([train_data, test_data], ignore_index=True, sort=False)

# ------------ Age ------------
# 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

# ------------ Name --------------
# 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'], 'Royalty', inplace=True)
df['Title'].replace(['Mme', 'Ms'], 'Mrs', inplace=True)
df['Title'].replace(['Mlle'], 'Miss', inplace=True)
df['Title'].replace(['Jonkheer'], 'Master', inplace=True)

# ------------ Surname ------------
# 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 に反映させる
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']

# ----------- Fare -------------
# 欠損値を Embarked='S', Pclass=3 の平均値で補完
fare=df.loc[(df['Embarked'] == 'S') & (df['Pclass'] == 3), 'Fare'].median()
df['Fare']=df['Fare'].fillna(fare)

# ----------- Family -------------
# Family = SibSp + Parch + 1 を特徴量とし、グルーピング
df['Family']=df['SibSp']+df['Parch']+1
df.loc[(df['Family']>=2) & (df['Family']<=4), 'Family_label'] = 2
df.loc[(df['Family']>=5) & (df['Family']<=7) | (df['Family']==1), 'Family_label'] = 1 # == に注意
df.loc[(df['Family']>=8), 'Family_label'] = 0

# ----------- Ticket ----------------
# 同一Ticketナンバーの人が何人いるかを特徴量として抽出
Ticket_Count = dict(df['Ticket'].value_counts())
df['TicketGroup'] = df['Ticket'].map(Ticket_Count)

# 生存率で3つにグルーピング
df.loc[(df['TicketGroup']>=2) & (df['TicketGroup']<=4), 'Ticket_label'] = 2
df.loc[(df['TicketGroup']>=5) & (df['TicketGroup']<=8) | (df['TicketGroup']==1), 'Ticket_label'] = 1
df.loc[(df['TicketGroup']>=11), 'Ticket_label'] = 0

# ------------- Cabin ----------------
# Cabinの先頭文字を特徴量とする(欠損値は U )
df['Cabin'] = df['Cabin'].fillna('Unknown')
df['Cabin_label']=df['Cabin'].str.get(0)

# ---------- Embarked ---------------
# 欠損値をSで補完
df['Embarked'] = df['Embarked'].fillna('S')

# ------------- 前処理 ---------------
# 推定に使用する項目を指定
df = df[['Survived','Pclass','Sex','Age','Fare','Embarked','Title','Family_label','Cabin_label','Ticket_label']]

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

# データセットを trainとtestに分割
train = df[df['Survived'].notnull()]
test = df[df['Survived'].isnull()].drop('Survived',axis=1)

# データフレームをnumpyに変換
X = train.values[:,1:]
y = train.values[:,0]
test_x = test.values

# ----------- 推定モデル構築 ---------------
from sklearn.feature_selection import SelectKBest
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import cross_validate

# 採用する特徴量を25個から20個に絞り込む
select = SelectKBest(k = 20)

clf = RandomForestClassifier(random_state = 10,
warm_start = True,
n_estimators = 26,
max_depth = 6,
max_features = 'sqrt')
# clf = RandomForestClassifier()
pipeline = make_pipeline(select, clf)
pipeline.fit(X, y)

# ----- Submit dataの作成 -------
PassengerId=test_data['PassengerId']
predictions = pipeline.predict(test_x)
submission = pd.DataFrame({"PassengerId": PassengerId, "Survived": predictions.astype(np.int32)})
submission.to_csv("result0322.csv", index=False)

[提出結果]

正解率80.622%となっています。さすがです。

こちらの処理を勉強させて頂いて、また出直したいと思っております。

Kaggle(43) - タイタニックをRandom Forestで予測 - 特徴量を自動で取捨選択

今回は特徴量(カラム数)の取捨選択を自動で行います。

SelectKbestを使うと特徴量の取捨選択を自動化することができます。

(データの前処理は省略します。こちらの記事をご参照下さい。)

特徴量の自動取捨選択

特徴量を自動で20個に絞り込むためには、select = SelectKBest(k = 20)という形で指定します。(15行目)

特徴量の数を10~903までループしながら、正解率が高くなる特徴量の数を調べます。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import warnings
warnings.simplefilter('ignore')

from sklearn import ensemble, model_selection
clf = ensemble.RandomForestClassifier(n_estimators=100, max_depth=9, criterion='gini')

from sklearn.pipeline import make_pipeline
from sklearn.feature_selection import SelectKBest

x = [] # グラフ表示用x軸
y = [] # グラフ表示用y軸
for k in range(10, 904):
print('k=', k)
x.append(k)
select = SelectKBest(k = k)
pipeline = make_pipeline(select, clf)

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

[結果]
一部略


上記で調べた特徴量の数ごとの正解率をグラフ化します。

[ソース]

1
2
3
4
import matplotlib.pyplot as plt

plt.plot(x, y)
plt.show()

[結果]

明確な最適解は分かりにくいのですが、今回は特徴量の数を420で予測することにしました。

Kaggleに提出

特徴量の数を420に指定して(1行目)、学習・予測を行います。

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

[ソース]

1
2
3
4
5
6
7
8
9
10
11
select = SelectKBest(k = 420)
pipeline = make_pipeline(select, clf)

# # 学習
pipeline.fit(x_titanic, y_titanic)

pre = pipeline.predict(df_test.drop(['Survived'], axis=1))

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

[提出結果]

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

前回結果よりも少々正解率が落ちてしまいました・・・・やっぱり難しいです。

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
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
# 学習
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%となりました。

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


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×