最初に投稿した記事は途中で体力が尽きて最後まで書けなかったので、リベンジです。
ちょっと分かりやすいように編集してます。
目的
タイタニック号のAgeの欠損値が多いので、全部埋めたら精度上がるんじゃないかな
と思いました。
与えられたデータ
今回インポートしたライブラリは下記のとおりです。
import pandas as pd
from sklearn.model_selection import train_test_split as tts
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.ensemble import RandomForestRegressor as RFR
いつも長い名前は自分勝手に省略してしまうので、途中でわけわからんのが出てきたら
そういうことなんだなと思ってください。
元のデータをDataFrameに入れるとこんな感じです。
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
891 | 0 | 3 | Dooley, Mr. Patrick | male | 32.0 | 0 | 0 | 370376 | 7.7500 | NaN | Q |
ちょっと書くのめんどくさいんで最初と最後だけ。
皆が知ってるタイタニック号の学習用データです。
df.info()で情報を見ます。
df.info()
# 結果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
全データは12×891、Age,Cabin,Embarkedで欠損があるのと、
Name,Sex,Ticket,Cabin,EmbarkedはObject(文字列)なのでこのままでは使えません。
【前処理1】SexとEmbarked
たぶん全員やってるであろう処理です。
SexとEmbarkedはObjectですが、要素が2つ、ないしは3つしかないので
簡単な数字に置き換えます。
また、Embarkedは欠損が2こあるんですが、少ないし数値が偏ってるので最頻値で補完します。
# Embarkedの要素数をカウントする
df.Embarked.value_counts()
# 結果
S 644
C 168
Q 77
# Sex を数値化
df['Sex'] = df['Sex'].map({'male':0, 'female':1})
# Embarked を数値化
df['Embarked'] = df['Embarked'].map({'S':0, 'C':1, 'Q':2})
# Embarked はとりあえず一番多いS(0)で補完
df['Embarked'] = df['Embarked'].fillna(0)
【基準】Ageを平均値補完
今回はAgeの欠損をを平均値で補完したものと比べようかと思います。
そのあとランダムフォレストにかけて、それを基準にします。
# 本当は後でごっちゃにならんようにdf.copy()で別のDFつくってやってます。
# Age は欠損値を埋めるため、とりあえずを平均値で補完
df['Age'] = df['Age'].fillna(df['Age'].mean())
# ObjectのままのName,Ticket,Cabinはとりあえずなしでデータを作成
df_data = df[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']]
df_label = df[['Survived']]
(train_data, test_data, train_label, test_label) = tts(df_data, df_label, test_size=0.3, random_state=0)
# train_test_split=ttsとしてます
clf = RFC() # RandomForestClassifier=RFCとしてます
clf.fit(train_data, train_label)
clf.score(test_data, test_label)
今回データの前処理だけのビフォーアフターが見たいので
パラメータとかはdefaultのままです。
結果は・・・
0.8134328358208955
ちょっと基準が高かったです。。
これ以上高みを目指せるのでしょうか。。。
【前処理2】Ageの特徴量観察
年齢で生死が分かれるかというとそうじゃないと信じたいですが
とりあえず何かのデータで大体の年齢がわかったら嬉しいなと思いました。
まず既知のAgeとの相関関係を見てみる
dropnaでAgeの欠損値を抜いたものを、df.corr()で相関係数を見てみる。
| | PassengerId |Survived |Pclass |Sex |Age |SibSp |Parch |Fare |Embarked|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|Age |0.033207 |-0.069809 |-0.331339 |-0.084153 |1.000000 |-0.232625 |-0.179191 |0.091566 |0.007461|
こう見るとPclass,SibSp,Parchが相関高そう。
Pclassはやっぱ年齢を重ねるにつれてグレードアップしてるのか、負の相関です。
SibSpとParchは大家族になると子供が多くなるのでそういう関係かな。
Fareがそんな相関高くないのは、家族ごとに合算されてたりするんでしょうか。
Nameとの関係性は?
この段階でまだ文字列データなのがName,Ticket,Cabinです。
この中でNameにはMrとかMissとかの敬称が入っています。
英語の授業で未婚の女性はMiss、既婚の女性はMrsって習ったんで
もしかしたらこれを分析すればある程度年齢と関係してくるのでは??と思いました。
【前処理3-1】Nameから敬称を取り出す
Nameは、「Braund, Mr. Owen Harris」のように、「, 」「.」で挟まれていますので
この挟まれているところだけ取り出して、「Honorific(敬称)」という列名で保存します。
文字列を取り出す場合、今回apply()でやりましたが、map(カッコ内同じ)でやっても全く同じ結果が出ました。
ここで注意点!!「,」と敬称の間に半角スペースが入ります。
最初入れずにやって文字を認識しないのであれれ??と思ってそこでめっちゃ時間食いました。
# 半角スペース忘れずに!
df['Honorific'] = df['Name'].apply(lambda x: x.split(', ')[1].split('.')[0])
df['Honorific'].value_counts()
# 結果
Mr 517
Miss 182
Mrs 125
Master 40
Dr 7
Rev 6
Mlle 2
Col 2
Major 2
Lady 1
Ms 1
the Countess 1
Don 1
Mme 1
Jonkheer 1
Sir 1
Capt 1
Name: Honorific, dtype: int64
一人だけの敬称もあるようです。
更にdescribe()で統計データを見てみます。
df.groupby('Honorific').describe()['Age']
結果はこんな感じです。
Honorific | count | mean | std | min | 25% | 50% | 75% | max |
---|---|---|---|---|---|---|---|---|
Capt | 1.0 | 70.000000 | NaN | 70.00 | 70.000 | 70.0 | 70.00 | 70.0 |
Col | 2.0 | 58.000000 | 2.828427 | 56.00 | 57.000 | 58.0 | 59.00 | 60.0 |
Don | 1.0 | 40.000000 | NaN | 40.00 | 40.000 | 40.0 | 40.00 | 40.0 |
Dr | 6.0 | 42.000000 | 12.016655 | 23.00 | 35.000 | 46.5 | 49.75 | 54.0 |
Jonkheer | 1.0 | 38.000000 | NaN | 38.00 | 38.000 | 38.0 | 38.00 | 38.0 |
Lady | 1.0 | 48.000000 | NaN | 48.00 | 48.000 | 48.0 | 48.00 | 48.0 |
Major | 2.0 | 48.500000 | 4.949747 | 45.00 | 46.750 | 48.5 | 50.25 | 52.0 |
Master | 36.0 | 4.574167 | 3.619872 | 0.42 | 1.000 | 3.5 | 8.00 | 12.0 |
Miss | 146.0 | 21.773973 | 12.990292 | 0.75 | 14.125 | 21.0 | 30.00 | 63.0 |
Mlle | 2.0 | 24.000000 | 0.000000 | 24.00 | 24.000 | 24.0 | 24.00 | 24.0 |
Mme | 1.0 | 24.000000 | NaN | 24.00 | 24.000 | 24.0 | 24.00 | 24.0 |
Mr | 398.0 | 32.368090 | 12.708793 | 11.00 | 23.000 | 30.0 | 39.00 | 80.0 |
Mrs | 108.0 | 35.898148 | 11.433628 | 14.00 | 27.750 | 35.0 | 44.00 | 63.0 |
Ms | 1.0 | 28.000000 | NaN | 28.00 | 28.000 | 28.0 | 28.00 | 28.0 |
Rev | 6.0 | 43.166667 | 13.136463 | 27.00 | 31.500 | 46.5 | 53.25 | 57.0 |
Sir | 1.0 | 49.000000 | NaN | 49.00 | 49.000 | 49.0 | 49.00 | 49.0 |
the Countess | 1.0 | 33.000000 | NaN | 33.00 | 33.000 | 33.0 | 33.00 | 33.0 |
Masterはどうやら小さい男の子につけるみたいです。
でも12歳でMasterの子もいれば11歳でMrの子もいるんですね。。
あとMrsは既婚の女性らしいですが、最年少は14歳です。・・・本当に??
あとstd(標準偏差)は一人だとNaNになります。そりゃそっか。
ここでデータとして使えるようにすべての敬称を数値化します。
ただ、全部数値化するのは面倒です。
上の表を見ると、1人だけの敬称は皆年齢が入ってるので今回は無視したいと思います。
df_nameに使う敬称を入れて、使わないものもあとで必要になるので別でとっておきます。
要素の前に「~」を入れたらそれ以外ってなるっぽいです。
# 使うデータ
df_name = df[df['Honorific'].isin(['Mr', 'Miss', 'Mrs', 'Master', 'Dr', 'Rev', 'Major', 'Mlle', 'Col'])]
# 使わないデータ
df_unneed_name = df[~df['Honorific'].isin(['Mr', 'Miss', 'Mrs', 'Master', 'Dr', 'Rev', 'Major', 'Mlle', 'Col'])]
# Honorificを数値化
df_name['Honorific'] = df_name['Honorific'].map({'Mr':0, 'Miss':1, 'Mrs':2, 'Master':3, 'Dr':4, 'Rev':5, 'Major':6, 'Mlle':7, 'Col':8})
# 結果
0 517
1 182
2 125
3 40
4 7
5 6
8 2
7 2
6 2
Name: Honorific, dtype: int64
## 【前処理3-2】Ageを他の特徴量で予想
よし、やっと準備が整ったので早速予測させよう。
Ageが入ってるデータを学習用、欠損データをテスト用にします。
df_Agefill = df_name.dropna(subset=['Age'])
df_Agefill_data = df_Agefill[['Pclass', 'Sex', 'SibSp', 'Parch', 'Fare', 'Embarked','Honorific']] # 学習用データ
df_Agefill_label = df_Agefill[['Age']] # 学習用ラベル
df_Agenull = df_name[df_name['Age'].isnull()]
df_Agenull_data = df_Agenull[['Pclass', 'Sex', 'SibSp', 'Parch', 'Fare', 'Embarked','Honorific']] # テスト用データ
df_Agenull_label = df_Agenull[['Age']] # テスト用ラベル
ちなみにdf_Agefill.corr()で学習用データの相関係数を見てみると、
AgeとHonorificの相関係数は
-0.095726
でした。
あんなに頑張ったのに・・・、
いや負けない!
飽くまで線形だから敬称の数値化した数字と合ってないだけかも!
# RandomForestRegressor を RFR としてます。
clf = RFR()
clf.fit(df_Agefill_data, df_Agefill_label)
# 答えをラベルに格納(代入)
age_answer = clf.predict(df_Agenull_data)
df_Agenull_label['Age'] = age_answer
df_Agenull_label
#結果
Age
5 37.987776
17 31.422079
19 26.808000
26 32.879936
28 20.253988
... ...
859 24.929030
863 15.495167
868 25.716969
878 27.344498
888 7.838333
Ageはクラス分類とは異なるので回帰分析ツールのRandomForestRegressorを使います。
浮動小数点ではありますが、なんかいい感じじゃないでしょうか??
ただこれ答えがどこにもないんで合ってるかわかんないんですけどね。。
【結果】Ageを補完したものをランダムフォレストにかけてみる
Ageを補完したデータを元のデータと合体させます。
# ラベルとデータを合体
df_Agenull['Age'] = df_Agenull_label
# 元々Ageが入ってたデータ、新しく入れたデータ、そして敬称で省いたデータを合体
df_Age = pd.concat([df_Agefill, df_Agenull, df_unneed_name])
# このままだとindexがバラバラなので元に戻す
df_Age = df_Age.sort_index()
df_Age.info()
# 結果
<class 'pandas.core.frame.DataFrame'>
Int64Index: 891 entries, 0 to 890
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null int64
5 Age 891 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 891 non-null float64
12 Honorific 891 non-null object
dtypes: float64(3), int64(6), object(4)
memory usage: 97.5+ KB
新たにHonorificがついた以外は元に戻りました。
Ageも欠損なしです。
さて、ではランダムフォレストで正解率を見てみよう!!
# 【基準】Ageを平均値補完 の処理と全く同じ
df_data = df_Age[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']]
df_label = df_Age[['Survived']]
(train_data, test_data, train_label, test_label) = tts(df_data, df_label, test_size=0.3, random_state=0)
clf = RFC()
clf.fit(train_data, train_label)
clf.score(test_data, test_label)
結果は・・・・・・
0.832089552238806
めっちゃ微妙!!
でもちょっと上がった・・・かな??
この頑張りに見合うほどの上昇率じゃなかったけど
まぁまぁ、ちょっと上がっただけでも嬉しい!(笑
えみちゃん、微妙に上がったよ~!(漫才王)