今日は欠損データを補完する手法を学んでいく。

前記事はこちらをご確認ください。。

 ①データの整備(続編)

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

これでデータの処理を完了。次回からはモデリングの構築に進めていきたい。