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割以上の結果を出したいものです。

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

いろいろなアルゴリズムを使ってタイタニックの生存予測を行ってきましたが、正解率が上がらなくなってきたので今回からはデータクレンジングを改善していきたいと思います。

予測アルゴリズムとしてはRandom Forestの成績が一番よかったのでこれを使います。

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

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

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

年齢の欠損値にはこれまで一律でデータ全体の中央値を設定してきましたが、これを改善します。

年齢の中央値は乗客クラスによって異なるという論文があり、具体的には次のような点があげられていました。

  • 社会経済的地位の高い人は平均して年配
  • 性別でみると女性の方が若い

上記を踏まえて、乗客クラスと性別にグループ分けして、それぞれの中央値を年齢の欠損値に設定します(12行目)

[ソース]

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

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

# データ前処理
def preprocessing(df):
# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# 欠損値処理
#df['Age'] = df['Age'].fillna(df['Age'].median())
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'])

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
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.59%という、まずまずの正解率となりました。

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)

# 予測
pre = clf.predict(df_test)

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

[提出結果]

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

・・・正解率が下がってしまったような気がしますが、あきらめず引き続きデータクレンジングの改善を行っていきます。

Kaggle(31) - タイタニックをAdaBoostで予測

AdaBoostというアルゴリズムを使って、タイタニックの生存率を予測します。

AdaBoostはアンサンブル学習のブースティングに分類されるアルゴリズムの1つです。

AdaBoostでは、難易度の高いデータを正しく分類できる弱仮説器の分類結果を重視するよう、弱仮説器に対して重みを付けます。

AdaBoostは分類精度が高いですが、学習データのノイズに影響を受けやすい傾向があります。

データの読み込み

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

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

[ソース]

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

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

# データ前処理
def preprocessing(df):
# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

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

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

return df

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

AdaBoostで予測

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

[ソース]

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

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

[出力]

68.23%という、これまで試してきた他のアルゴリズムよりも低めの正解率となりました。

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.isnull().sum()
pre = clf.predict(df_test)

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

[提出結果]

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

AdaBoostアルゴリズはノイズに弱いという話でしたが、もう少し前処理でノイズの除去をする必要があるのかもしれません。