今日は欠損データを補完する手法を学んでいく。
前記事はこちらをご確認ください。。
①データの整備(続編)
4.欠損データを補完
欠損データを一気に補完するために、まずは学習データ・検証データを統合し、全データを作成する。
all_df=pd.concat([train_df,test_df],sort=False).reset_index(drop=True)
次に、説明変数では欠損データがあるかどうかを確認する。
all_df.isnull().sum()
やはりあったね。前の記事では、Age・Fareが関係ありそうと確認していたので、その2つのデータを補完するように進めていく。
1)Fare
Fareでは、欠損データが1個のみなので、先にこちらを補完する。
前の記事では、FareはPclassと比較的に強い相関関係(r=-0.55)を確認した。その為、Pclassに応じたFareの平均値を計算し、欠損データを補完する。
まずは、Fareの平均値(Fare_mean)を計算する。
Fare_mean=all_df[["Pclass","Fare"]].groupby("Pclass").mean().reset_index()
Fare_mean.columns=["Pclass","Fare_mean"]
Fare_mean
次に、Fareの平均値で欠損データの穴埋めをする。具体的手法としては、全データにFare_mean列を追加する。そして、欠損データの位置を特定して、対応しているFare_mean列のセルから値を持っていく。
all_df=all_df.merge(Fare_mean,on="Pclass",how="left")
all_df.loc[all_df["Fare"].isnull(),"Fare"]=all_df["Fare_mean"]
all_df=all_df.drop("Fare_mean",axis=1)
これでFareの欠損データを補完できたと思う。念のためもう一度確認してみるね。
all_df.isnull().sum()
Fareの欠損データ数が0になり、見事に補完できた。
2)Age
AgeはNameの敬称から年齢を推測して補完する。
まずはNameの構造について、サンプルを出して確認する。
all_df["Name"].head()
名前の表現としては「苗字, 敬称. 名前」と記載されていることが分かった。
では、Nameを分解して変数として変換していく。
name_df=all_df["Name"].str.split("[,.]",2,expand=True)
分解した結果を確認してみる。
name_df
Columnのタイトルがデフォルトで0,1,2になっている。分かりにくいので、内容に応じたタイトルに変換し、再度確認する。
name_df.columns=["family_name","honorific","name"]
name_df
ちゃんと欲しい形になった。
また、表には見えないが、実際に各列には空白が入っていたので、その空白を削除するための処理を行う。
name_df["family_name"]=name_df["family_name"].str.strip()
name_df["honorific"]=name_df["honorific"].str.strip()
name_df["name"]=name_df["name"].str.strip()
では、ここで各敬称の人数を計算してみる。
name_df["honorific"].value_counts()
主な敬称はMr、Miss、Mrs、Masterであり、他の敬称は少ないことと分かった。
次に、全データと敬称データを含めたNameデータと統合し、箱ひげ図で各敬称の年齢分布を確認する。
all_df=pd.concat([all_df,name_df],axis=1)
all_df
plt.figure(figsize=(18,5))
sns.boxplot(x="honorific", y="Age", data=all_df)
敬称毎の年齢の平均値(箱ひげ図の棒の真中の線)を数値でも計算してみる。
all_df[["Age","honorific"]].groupby("honorific").mean()
ちゃんとあっていた。
次に、敬称ごとの生存率の違いにつて調べてみる。
train_df=pd.concat([train_df[0:len(train_df)].reset_index(drop=True)],axis=1)
test_df=pd.concat([test_df[len(train_df):].reset_index(drop=True)],axis=1)
honorfic_df=all_df[["honorific","Survived","PassengerId"]].dropna().groupby(["honorific","Survived"]).count().unstack()
honorfic_df.plot.bar(stacked=True)
図によって、以下のことがまとめられる。
①女性にはMrsとMissの生存率が高い
②男性にはMrの死亡率が高い。Masterの生存率は比較的に高い。
次に、Fareの補完と同じ手法で、敬称ごとの平均年齢でAgeを補完する。
honorfic_age_mean=all_df[["honorific","Age"]].groupby("honorific").mean().reset_index()
honorfic_age_mean.columns=["honorific","honorific_Age"]
all_df=all_df.merge(honorfic_age_mean,on="honorific",how="left")
all_df.loc[all_df["Age"].isnull(),"Age"]=all_df["honorific_Age"]
all_df=all_df.drop("honorific_Age",axis=1)
補完結果を確認してみる。
all_df.isnull().sum()
Ageの欠損データ数が0になっている。これでAgeのデータの補完も完了した。
5.データの再整理
以下の二つの考えに従って、不要なデータを削除する。
①Passenger Id・Ticketのような生存性に対する規則性が見だしにくい変数列を削除する。
②Cabinのような欠損が多く、推測しにくい変数列を削除する。
all_df=all_df.drop(["PassengerId","Name","family_name","name","Ticket","Cabin"],axis=1)
all_df.head()
また、Mr、Miss、Mrs、Master敬称以外の敬称の人数が少ないので、otherとして統合する。
all_df.loc[~((all_df["honorific"]=="Mr")|(all_df["honorific"]=="Miss")|(all_df["honorific"]=="Mrs")|(all_df["honorific"]=="Master")),"honorific"]="other"
all_df.honorific.value_counts()
次に、Label Encoding手法でEmbarked・Sex・honorificの文字列を数値に変換する。
from sklearn.preprocessing import LabelEncoder
all_df["Embarked"].fillna("missing",inplace=True)
le=LabelEncoder()
le=le.fit(all_df["Sex"])
all_df["Sex"]=le.transform(all_df["Sex"])
categories = all_df.columns[all_df.dtypes == "object"]
for cat in categories:
le=LabelEncoder()
if all_df[cat].dtypes=="object":
le=le.fit(all_df[cat])
all_df[cat]=le.transform(all_df[cat])
all_df.head()
最後に、全データを学習データと検証データに戻しておく。
train_X=all_df[~all_df["Survived"].isnull()].drop("Survived",axis=1).reset_index(drop=True)
train_Y=train_df["Survived"]
test_X=all_df[all_df["Survived"].isnull()].drop("Survived",axis=1).reset_index(drop=True)
train_X
test_X
これでデータの処理を完了。次回からはモデリングの構築に進めていきたい。