3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

債務不履行リスクの低減

Last updated at Posted at 2021-06-05
1 / 28

概要

SIGNATEの債務不履行リスクの低減の予測モデル作成を行ます。

借入総額や返済期間、金利、借入目的などの顧客データを使って、債務不履行リスクを予測するモデルを構築していただきます。
金融会社では個人や法人にお金を貸す、いわゆる融資を行い、返済額に利子を上乗せすることで利益を得ています。
しかし、様々な理由から貸したのに返済されない、貸し倒れというケースが発生します。貸し倒れは金融会社として大きな損失であるため、できる限り避けたいですが、
一定確率で貸し倒れが起きることは避けられないのが現状です。したがって金融会社は、貸し倒れのリスクを可能な限り減らしたり、貸し倒れても利益がでるように適切に金利を設定したりしたいと考えています。
そこで今回は、借入総額や返済期間、金利、借入目的などの顧客データを使って、債務不履行リスクを予測するモデルの構築にチャレンジしてみましょう。

他にも「Learn Python In Seminar」のシリーズとして書いているので、よければご覧ください!


前提


インポート

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from imblearn.under_sampling import RandomUnderSampler

データの読み込みと整形

  • トレインデータの読み込み
  • 欠損値と重複の削除
  • FullyPaidを0、ChargedOffを1に設定
  • その他データを数値データに変換
# 読み込み
df = pd.read_csv('data/train.csv')

# 重複部分の削除
df.drop_duplicates(inplace=True)

# 重複部分の削除の後に新しいインデックスを取得
df.reset_index(drop=True, inplace=True)

# 欠損値を削除
df = df.dropna()

# id列を削除
del df['id']

# 「years、year」を削除 ex.「3 years」→「3」
df["term"] = df["term"].str.replace('years', '')
df["employment_length"] = df["employment_length"].str.replace(
    'years', '').str.replace(
    'year', '')

# FullyPaidを0、ChargedOffを1に設定
df["loan_status"] = df["loan_status"].str.replace(
    'FullyPaid', '0').str.replace('ChargedOff', '1')

df["term"] = df["term"].astype(int)
df["employment_length"] = df["employment_length"].astype(int)
df["loan_status"] = df["loan_status"].astype(int)

# データフレームの分離
col_categoric = ["grade", "purpose", "application_type", "loan_status"]
df_numeric = df.drop(col_categoric, axis=1)
df_categoric = df[col_categoric]

# df_categoric内の"loan_status"列と、df_numericの列を横結合する
df_tmp = pd.concat([df_categoric["loan_status"], df_numeric], axis=1)

データの可視化


FullyPaidとChargedOff

FullyPaidとChargedOffの数を棒グラフで可視化

# データ数を取得
counts_loan_status = df["loan_status"].value_counts()

# 棒グラフの作成
counts_loan_status.plot(kind='bar')

# グラフの表示
plt.show()

loan-status-bar.png

  • FullyPaidの数がChargedOffの数の3倍以上

量的データの可視化

loan_amnt、interested_rate、credit_scoreをヒストグラムで可視化

# ヒストグラムの作成(※figsizeはグラフのサイズを指定)
df_numeric.hist(figsize=(8, 6)) # df_numericは前処理で定義

# ラベルが重ならないように調整
plt.tight_layout()

# グラフの表示
plt.show()

numeric-hist.png

  • 全体的に左寄り

目的変数ごとに可視化

目的変数ごとのヒストグラムを重ねて可視化

# グラフの整形
plt.figure(figsize=(12, 12))

for ncol, colname in enumerate(df_numeric.columns):
    plt.subplot(3, 3, ncol+1) # plt.subplot(縦のプロット数, 横のプロット数, プロット番号)
    sns.distplot(df_tmp.query("loan_status==0")[colname])
    sns.distplot(df_tmp.query("loan_status==1")[colname]) # 重ねる
    plt.legend(labels=["FullyPaid", "ChargedOff"], loc='upper right') # 凡例

hist.png

  • 金利が上がると債務不履行が増加
  • 信用スコアが高いと債務不履行が若干減少

相関関係

各変数間の相関関係をヒートマップで可視化

# グラフの整形
plt.figure(figsize=(10, 8))

# ヒートマップの作成
sns.heatmap(df.corr(), vmin=-1.0, vmax=1.0, annot=True,
            cmap='coolwarm', linewidths=0.1) # corr関数で相関関数を算出

heatmap.png

  • 説明変数間での強い相関がいくつか見られる

前処理


  • grade, purpose, application_typeのダミー変数化
  • 説明変数と目的変数に分離
  • トレインデータとテストデータに分割
# ダミー変数化
df = pd.get_dummies(
    df, columns=["grade", "purpose", "application_type"])

# 目的変数のデータフレーム
y = df["loan_status"]
# 説明変数のデータフレーム
X = df.drop(["loan_status"], axis=1)

# データの分割
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.4, random_state=0)

モデル作成


学習と予測

ロジスティック回帰分析:2値(0と1)のどちらに分類されるかを確率的に予測する手法

# モデルの初期化
lr = LogisticRegression()

# モデルの学習
lr.fit(X_train, y_train)

y_pred = lr.predict(X_test)

モデルの評価

  • 混同行列
  • F1 Score
# 混同行列の作成
cm = confusion_matrix(y_true=y_test, y_pred=y_pred)

# 混同行列をデータフレーム化
df_cm = pd.DataFrame(np.rot90(cm, 2), index=["実際のChargedOff", "実際のFullyPaid"], columns=["ChargedOffの予測", "FullyPaidの予測"])
print(df_cm)

# F1 Scoreの算出
f1 = f1_score(y_test, y_pred)
print(f1)

# 混同行列
               ChargedOffの予測  FullyPaidの予測
実際のChargedOff            518         16657
実際のFullyPaid             433         65422

# F1 Score
0.0571554672845636

混同行列の可視化

# heatmapによる混同行列の可視化
sns.heatmap(df_cm, annot=True, fmt="2g", cmap='Blues')
plt.yticks(va='center')

result_heatmap_1.png


結果

  • F1 Scoreは0.057とかなり低い
  • ChargedOffをほとんど予測できていない

モデル改善


ダウンサンプリング

  • データの不均衡を改善
# 初期化
sampler = RandomUnderSampler(random_state=42)

# ダウンサンプリングの適用
X_resampled, y_resampled = sampler.fit_resample(X_train, y_train)

# 返ってきたBoolを数値に変換
df["loan_status"] = df["loan_status"].replace(True, 0).replace(False, 1)
df["loan_status"] = df["loan_status"].astype(int)

学習と予測

# 学習
lr = LogisticRegression()
lr.fit(X_resampled, y_resampled)

# 予測
y_pred = lr.predict(X_test)

評価

df_cm = pd.DataFrame(np.rot90(cm, 2), index=["実際のChargedOff", "実際のFullyPaid"], columns=["ChargedOffの予測", "FullyPaidの予測"])
print(df_cm)

f1 = f1_score(y_test, y_pred)
print(f1)

# 混同行列
               ChargedOffの予測  FullyPaidの予測
実際のChargedOff          11977          5198
実際のFullyPaid           30787         35068

# F1 Score
0.3996396336275213

混同行列の可視化

result_heatmap_2.png


結果

  • F1 Scoreが0.399に大幅改善
  • ChargedOffの予測精度が改善
  • FullyPaidの予測精度が低下

まとめ

  • 最終的にFullyPaidの予測精度が低下してしまったが、債務不履行のリスクを検知するという意味ではChargedOffの予測精度が改善できたので目的に合ったモデルになったのではないか
  • 0.399というF1 ScoreはSIGNATEへの提出者順位の30位と同程度
  • 金利や信用スコアの閾値を設定することでさらに改善ができそう
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?