少し間が空いてしまいましたが、ベースモデルを作成しましたので、そのコードと流れを紹介します。下記の手順のうち、3,4,5,6をささっと行い、7を実行したという形になります。
- 明らかにしたい問いや、問題の定義
- 訓練およびテストデータの取得
- データの整形、作成、クレンジング
- パターンの分析、特定、また探索的にデータを分析する
- 問題のモデル化、予測、解決
- 問題解決のステップと最終的な解決方法を視覚化、報告
- 結果の提出
最初にcsvの種類をおさらいです。
train_df = pd.read_csv("application_train.csv")
test_df = pd.read_csv("application_test.csv")
bur = pd.read_csv("bureau.csv")
bur_b = pd.read_csv("bureau_balance.csv")
cd_b = pd.read_csv("credit_card_balance.csv")
ins_p = pd.read_csv("installments_payments.csv")
pos_c = pd.read_csv("POS_CASH_balance.csv")
prv_a = pd.read_csv("previous_application.csv")
上記のうち、まずはtrain_dfとtest_df以外を整形していきます。最初はbureauとbureau_balanceについて。こちらはカードの調査会社の情報のようで二つはSK_ID_BUREAUというIDでつながっているので、これを結合します。今気づきましたが、せっかく作ったのにこのテーブルをtrain, testに結合するの忘れてました。次回修正。。。
bur_com = pd.merge(bur, bur_b, on="SK_ID_BUREAU", how="left")
続いて、残り4つのcredit_card_balance.csv、installments_payments.csv、 POS_CASH_balance.csv、previous_application.csvを整形していきます。これらはSK_ID_CURRとSK_ID_PREVがキーとなっているようです。
一方で、train_df, test_dfはSK_ID_CURRでユニークになっているので後々結合することを考えるとそちらに合わせてSK_ID_CURRでユニークにしておくとやりやすそうです。
また中身も月次残高などなので、SK_ID_CURRで平均化しておきます。ここで数値の情報は平均化されてますが、オブジェクトの情報は破棄されてます。カラムを分割するなどをして情報を有効活用すべきなので、反省ポイント。
cd_b2 = cd_b.groupby(["SK_ID_CURR"], as_index=False).mean()
ins_p2 = ins_p.groupby(["SK_ID_CURR"], as_index=False).mean()
pos_c2 = pos_c.groupby(["SK_ID_CURR"], as_index=False).mean()
prv_a2 = prv_a.groupby(["SK_ID_CURR"], as_index=False).mean()
合わせてtrain, testと結合したときに重複するカラムがあるとカラム名+x, カラム名+yのように、出どころもわからなくなってしまうので、予めカラム名を変更しておきます。さらに結合後のSK_ID_PREVは不要のため削除します。
#SK_ID_PREVはIDで不要のため削除する
cd_b2 = cd_b2.drop("SK_ID_PREV", axis=1)
ins_p2 = ins_p2.drop("SK_ID_PREV", axis=1)
pos_c2 = pos_c2.drop("SK_ID_PREV", axis=1)
prv_a2 = prv_a2.drop("SK_ID_PREV", axis=1)
#カラム名が重複するため、どこのものかわかるように名称変更
cd_b3 = cd_b2.rename(columns={"MONTHS_BALANCE":"MONTHS_BALANCE_CRE", "SK_DPD" : "SK_DPD_CRE", "SK_DPD_DEF" : "SK_DPD_DEF_CRE"})
ここまで作ったテーブルを結合します。下記は修正していますがhowをrightにしていました。rightだと、先に記述したテーブル(下記ではins_p2やdf)を右からくっつけるため大量のNaNが発生してしまいます。後ほど大量の欠損値処理をすることになってます。これまた反省。。。
#結合するために
df = pd.merge(ins_p2, cd_b3, on="SK_ID_CURR", how="left")#重複なし. df.info()でx, yがあるかチェック
df2 = pd.merge(df, pos_c2, on="SK_ID_CURR", how="left")#MONTHS_BALANCE, SK_DPD_x, SK_DPD_DEF_x が重複
df3 = pd.merge(df2, prv_a2, on="SK_ID_CURR", how="left")
続いて、trainとdf3を結合するためにも、改めて重複カラムを処理します。
#カラム名が重複するため、どこのものかわかるように名称変更
#AMT_CREDIT_x, AMT_ANNUITY_x, AMT_GOODS_PRICE_x が重複しているので、結合前に名前を変更しておく
df4 = df3.rename(columns={"AMT_CREDIT":"AMT_CREDIT_PRE", "AMT_AN
NUITY" : "AMT_ANNUITY_PRE", "AMT_GOODS_PRICE" : "AMT_GOODS_PRICE_PRE"})
続いてtrain, testと結合します。下記メモに書いてありますが、howの方向を完全に間違っていた模様です。
#train, testとdf4を結合
#rightにしたらtrainにおいて、TARGETでNAが発生。どうやら最初に書いた方を結合する方向らしい
train_df2 = pd.merge(train_df, df4, on="SK_ID_CURR", how="left")
test_df2 = pd.merge(test_df, df4, on="SK_ID_CURR", how="left")
これで必要な情報をtrain, testに追加することができました。現在考えているモデルはランダムフォレストなので、オブジェクトはラベルコーディング(例えばMALE, FAMELEを0, 1に変換するなど)を行い、数値情報に変換する必要があります。
今回反映させることはできませんでしたが、数値化しても連続性を持たせては行けなかったので、それも対応必要でした。例えば男性:0, 女性:1, 未回答:2というふうに変換したとします。このとき、男性(0)と未回答(2)の平均をとった場合、女性(1)という予測になってしまいます。これではなんのことかわからないので、なんらかの対応を考えなくては行けなかったです。反省ばかりですが、反省。。。
とりあえず無理やり数値に変換しているコードがこちらです。
#オブジェクトを含んでいるカラムを抽出。
train_df2.select_dtypes(include=object)
#どんな種類があるか確認
print(train_df2["NAME_CONTRACT_TYPE"].value_counts())
#Nullがあると変換できない為、Unknownとして置換
train_df2["NAME_CONTRACT_TYPE"] = train_df2["NAME_CONTRACT_TYPE"].fillna("Unknown")
##Cash loansを1, Revolvingを2、Unknownを0として置換
train_df2["NAME_CONTRACT_TYPE"] = train_df2["NAME_CONTRACT_TYPE"].map( {'Unknown': 0, 'Cash loans': 1, 'Revolving loans': 2} ).astype(int)
これをあと15回やるのかと思っていましたが、簡単に実行するコードが見つかったので、そちらを紹介します。
#変換したいカラムをリストに入れます。下記は全てではないので注意。
columns = ["NAME_TYPE_SUITE","NAME_INCOME_TYPE", "NAME_EDUCATION_TYPE","NAME_FAMILY_STATUS", "NAME_HOUSING_TYPE","OCCUPATION_TYPE", "WEEKDAY_APPR_PROCESS_START","ORGANIZATION_TYPE","FONDKAPREMONT_MODE","HOUSETYPE_MODE", "WALLSMATERIAL_MODE", "EMERGENCYSTATE_MODE"]
下記を実行することで、それぞれをエンコードしてくれます。
from sklearn.preprocessing import LabelEncoder
for c in columns:
lbl = LabelEncoder()
lbl.fit(list(train_df2[c].values))
train_df2[c] = lbl.transform(list(train_df2[c].values))
for c in columns:
lbl = LabelEncoder()
lbl.fit(list(test_df2[c].values))
test_df2[c] = lbl.transform(list(test_df2[c].values))
最後にNull確認したところ、途中の結合ミスのせいで大量の欠損値が。。。なんとなく0で置換しちゃってますが、本来全ての欠損値の傾向をみながら物によっては削除も考慮しつつ平均、中央値などの処理が必要です。
#最終的にNullを確認
#とりあえずNulllを全て0で置換
train_df3 = train_df2.fillna(0)
test_df3 = test_df2.fillna(0)
ここまで来たら、欠損値がない+データが全て数値になっているので機械学習をとりあえず回すことができそうです。今回はデータ定義後、ランダムフォレストを実行しています。
from sklearn.ensemble import RandomForestClassifier
#目的であるTARGET以外をX_trainとして定義
X_train = train_df3.drop("TARGET", axis=1)
#目的であるTARGETをY_trainとして定義
Y_train = train_df3["TARGET"]
#testをX_testとして定義
X_test = test_df3
#データ形状を確認。X_trainとX_testでカラム数が整合してないとエラー要因となります。
print(X_train.shape, Y_train.shape,X_test.shape)
#出力結果→171が不一致だと後にエラーになります。(307511, 171) (307511,) (48744, 171)
ランダムフォレストで予測を実行。今回は各種パラメータを調整していません。こちらも後々必要です。
# ランダムフォレストによる分類モデルを作成し、モデルの精度を確認して下さい。
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred_r = random_forest.predict(X_test)
random_forest.score(X_train, Y_train)
acc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)
print(acc_random_forest)
ここまで来ましたら、submissionに合わせてデータを出力します。カラム名称がsample_submissionと違うと提出時にエラーになりますのでご注意。
# CSVファイルに保存
submission = pd.DataFrame({
"SK_ID_CURR": test_df3["SK_ID_CURR"],
"TARGET": Y_pred
})
submission.to_csv('my_submission1.csv', index=False)
ということで提出したところ、スコアは0.50000。終わっているコンペだからか順位は出ず。気になる所がたくさんあるので修正を行っていきたいと思います。
具体的に思いつく修正内容はこちら。
・適した結合の実施->how=leftにしていたので欠損値大量
・平均化した際のオブジェクト情報の扱い->今は全て破棄しちゃってます
・カラムの絞り込み->171のカラムで処理。ヒートマップなどで絞り込みトライ
・ラベルエンコードの適切処理->離散値と連続値の区別
・機械学習モデルとパラメータチューニング->クロスバリデーション などトライ
ということで次回以降で上記を修正していきたいと思います。