sklearn論理回帰パラメータの詳細、および論理回帰でスコアカードを作成する
1ガイド、データ取得
2探索データとデータ前処理
このステップでは、欠落値の表示、アウトラインが統一されているかどうか、ダミー変数が必要かどうかなど、サンプル全体の概要を説明します.実はデータの探索とデータの前処理は完全に分離されているわけではなく、必ずしもどちらを先にしなければならないわけではないので、この順序は参考にしているだけです.
2.1重複値の除去
現実的なデータ、特に銀行業のデータでは、サンプルが重複している可能性があります.つまり、1行以上のサンプルが表示されているすべての特徴が同じです.時には人為的に入力が重複したり、システム入力が重複したりすることがあります.要するに、データを再処理しなければなりません.2つのサンプルの特徴がそっくりだと言う人もいるかもしれませんが、彼らは2つのサンプルですか?例えば、二人、同じ名前、年齢、性別、学歴、給料......特徴量が少ない場合、これは確かに可能ですが、家族数、月収、借りた不動産ローンの数など、ほとんど同じではありません.特に銀行業のデータは常に数百の特徴であり、すべての特徴が同じ可能性は微々たるものである.本当にこのような極端な状況が発生しても,少量の情報損失と見なし,この記録を繰返し値として除去することができる.
2.2補充欠損値
2つ目に直面する問題は、値の欠落です.ここで私たちが埋めなければならない特徴は「収入」と「家族数」です.「家族数」の欠落は少なく、約2.5%しか欠落していません.直接削除するか、平均値を使用して埋めることも考えられます.「収入」はほぼ20%不足しており、「収入」は必然的に信用スコアにとって重要な要素であるため、この特徴を埋めなければならないことを知っています.ここでは、平均値を使用して「家族数」を埋めます.では、フィールドの「収益」はどうしますか?銀行のデータにとって、お金を借りに来た人は、「高収入」や「安定収入」が彼/彼女自身にとってローンを申請する過程の助力になることを知っているはずだ.そのため、収入が安定している人は、自分の収入状況を書く傾向があるに違いない.「収入」欄が欠けている人は、収入状況が不安定だったり、収入が低い人かもしれません.このような判断に基づいて、私たちは例えば、4分の1の桁数で欠損値を補充し、すべての収入が空いているお客様を低所得者と見なすことができます.もちろん、これらの欠落は銀行のデータ収集過程のミスである可能性もあり、なぜ収入欄が欠落しているのか判断できないため、私たちの推定も正しくない可能性があります.具体的にどのような手段で欠損値を記入するかは、業務員とコミュニケーションし、欠損値がどのように発生したかを観察しなければならない.ここでは、ランダムな森を使って「収入」を埋めます.ランダムな森で欠損値を埋めた例を覚えていますか?ランダム森林は「A,B,Cを用いてZを予測できる以上,A,C,Zを用いてBを予測することもできる」という考え方を利用して欠損値を補う.n個の特徴を持つデータに対して,特徴Tに欠落値がある場合,特徴Tをラベルとし,他のn−1個の特徴と元のラベルから新しい特徴行列を構成する.それはTにとって欠けていない部分であり、私たちのY_です.train、このデータにはラベルも特徴もありますが、欠落している部分は、特徴だけがラベルを持っていないので、予測が必要な部分です.特徴Tが欠落しない値に対応する他のn-1個の特徴+本来のラベル:X_trainフィーチャーTが欠落しない値:Y_trainフィーチャーT欠落値に対応する他のn-1個のフィーチャー+本来のラベル:X_testフィーチャーTが欠落している値:不明、予測Y_が必要testというやり方は,ある特徴が大量に欠けているのに,他の特徴が完全である場合に非常に適している.
以前に行ったランダム森林充填欠損値のケースでは,データセット全体で複数の特徴が欠落している場合に直面しているため,まず特徴をソートし,すべての特徴を遍歴して埋めなければならない.今回は「収入」の特徴を埋めるだけで、循環するほど面倒ではなく、この列を直接埋めることができます.任意の列を埋める関数を書きます.
関数に必要なパラメータを作成し、パラメータを関数にインポート
2.3記述的統計処理異常値
現実のデータには常にいくつかの異常値があります.まず、私たちは彼らを捕まえて、彼らの性質を観察します.注意、私たちはすべての異常値を排除するのではなく、逆に多くの場合、異常値は私たちの重点研究対象であり、例えば、双十一の中で購入量が高いブランドや、教室で多くの学生を興奮させる課題であり、これらは私たちが重点的に研究観察しなければならない.日常処理異常値、箱線図や法則を使用して異常値を見つけます(目に依存することは言うまでもありませんが、データマイニングエンジニアであり、ビジネス理解のほかに方法があります).しかし、銀行データでは、除外したい「異常値」いくつかの超高または超低の数字ではなく、いくつかの常識に合わないデータです.例えば、収入は負数ではありませんが、超高レベルの収入は合理的で、存在することができます.そのため、銀行業界では、通常の記述的な統計を使用して、データの異常の有無とデータの分布状況を観察することが多い.この方法は,特徴量が限られている場合にのみ行うことができ,数百の特徴があっても次元を下げることに成功しないか,特徴選択が役に立たない場合は,やはり用いたほうがよい.
2.4なぜアウトラインを統一せず、データを標準化しないのか
記述的統計結果では,論理回帰はデータに対して分布要件を持たないが,データが正規分布に従うと勾配降下がより速く収束できることを知ったデータアウトラインが著しく不統一であり,一部の極偏分布が存在することを観測できた.しかし、ここでは、データを標準化したり、アウトラインを統一したりしません.なぜですか.アルゴリズムにどのような規定があっても、統計学にどのような要求があっても、私たちの最終的な目的はビジネスに奉仕することです.今、私たちはスコアカードを作ります.スコアカードは、新しいお客様が記入した各種情報に基づいてお客様に採点するカードです.このカードを作るために、私たちのデータに対して「スコア」を行う必要があります.例えば、年齢2030歳が1つ、年齢3050歳が1つ、月収1 W以上が1つ、5000~1 Wが1つです.ランクごとに点数が違います.
データをアウトラインに統一したり、標準化したりすると、データのサイズや範囲が変わり、統計結果はきれいになりますが、ビジネス関係者にとっては、標準化後の年齢が0.00328-0.00467の間でどのような意味を持っているのか理解できません.また、新しいお客様が記入した情報は、生まれながらにして量綱が統一されていないので、私たちは確かにすべての情報を入力した後、統一して標準化し、アルゴリズム計算を導入することができますが、最終的には業務員の手に落ちて判断すると、何が入力された情報が統計的に美しいが、実際には読めない数字になったのか全く理解できません.業務上の要求により、スコアカードを作成する際には、できるだけデータの原形を維持しなければならない.年齢は8~110の数字で、収入は0より大きく、最大値は無限の数字で、アウトラインが統一されていなくても、データを標準化しない.
2.5サンプルの不均衡問題
サンプルの深刻な不均衡がうかがえる.信用リスクの防止に努めているが、実際に違約している人は多くない.そして、銀行は本当に違約するすべての人を殺すことはありません.多くの人はお金を返すことができます.ただ、返済日を忘れただけで、多くの人はお金を借りたくありませんでした.しかし、当時は本当に困難で、資金の回転ができなかったので、期限を過ぎました.しかし、お金があれば、彼はお金を交換します.銀行にとって、あなたが最後にお金を返すことができる限り、私はあなたにお金を借りたいです.私が貸してあげると収入(利息)があるので、銀行にとって、本当に判別されたいのは「悪意のある違約」です」という人は、この部分の人数が非常に少なく、サンプルが不均衡になります.これは銀行業のモデリングの痛点です.私たちは常に少数のクラスをキャプチャしたいと思っています.前提として、論理回帰で最も多く使われているのは、サンプリング方法でサンプルをバランスさせることです.
2.6分トレーニングセットとテストセット
3ボックス
3.1等周波数分割箱
3.2箱ごとに0、1があることを確認する
3.3 WOEとIV関数の定義
3.4チャック検査、箱体を合併し、IV曲線を描く
3.5最適な箱数で箱を分け、箱の結果を検証する
連結ボックスの部分を関数として定義し、ボックス分割を行います.
最適なボックス数を選択するプロセスを関数としてパッケージ
%matplotlib inline
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression as LR
# , 。 。
data = pd.read_csv(r"C:\work\learnbetter\micro-class\week 5 logit regression\ranking
card\card\data\rankingcard.csv",index_col=0)
2探索データとデータ前処理
このステップでは、欠落値の表示、アウトラインが統一されているかどうか、ダミー変数が必要かどうかなど、サンプル全体の概要を説明します.実はデータの探索とデータの前処理は完全に分離されているわけではなく、必ずしもどちらを先にしなければならないわけではないので、この順序は参考にしているだけです.
#
data.head()
#
data.shape()
data.info()
2.1重複値の除去
現実的なデータ、特に銀行業のデータでは、サンプルが重複している可能性があります.つまり、1行以上のサンプルが表示されているすべての特徴が同じです.時には人為的に入力が重複したり、システム入力が重複したりすることがあります.要するに、データを再処理しなければなりません.2つのサンプルの特徴がそっくりだと言う人もいるかもしれませんが、彼らは2つのサンプルですか?例えば、二人、同じ名前、年齢、性別、学歴、給料......特徴量が少ない場合、これは確かに可能ですが、家族数、月収、借りた不動産ローンの数など、ほとんど同じではありません.特に銀行業のデータは常に数百の特徴であり、すべての特徴が同じ可能性は微々たるものである.本当にこのような極端な状況が発生しても,少量の情報損失と見なし,この記録を繰返し値として除去することができる.
#
data.drop_duplicates(inplace=True)
data.info()
# ,
data.index
2.2補充欠損値
#
data.info()
data.isnull().sum()/data.shape[0]
#data.isnull().mean()
2つ目に直面する問題は、値の欠落です.ここで私たちが埋めなければならない特徴は「収入」と「家族数」です.「家族数」の欠落は少なく、約2.5%しか欠落していません.直接削除するか、平均値を使用して埋めることも考えられます.「収入」はほぼ20%不足しており、「収入」は必然的に信用スコアにとって重要な要素であるため、この特徴を埋めなければならないことを知っています.ここでは、平均値を使用して「家族数」を埋めます.では、フィールドの「収益」はどうしますか?銀行のデータにとって、お金を借りに来た人は、「高収入」や「安定収入」が彼/彼女自身にとってローンを申請する過程の助力になることを知っているはずだ.そのため、収入が安定している人は、自分の収入状況を書く傾向があるに違いない.「収入」欄が欠けている人は、収入状況が不安定だったり、収入が低い人かもしれません.このような判断に基づいて、私たちは例えば、4分の1の桁数で欠損値を補充し、すべての収入が空いているお客様を低所得者と見なすことができます.もちろん、これらの欠落は銀行のデータ収集過程のミスである可能性もあり、なぜ収入欄が欠落しているのか判断できないため、私たちの推定も正しくない可能性があります.具体的にどのような手段で欠損値を記入するかは、業務員とコミュニケーションし、欠損値がどのように発生したかを観察しなければならない.ここでは、ランダムな森を使って「収入」を埋めます.ランダムな森で欠損値を埋めた例を覚えていますか?ランダム森林は「A,B,Cを用いてZを予測できる以上,A,C,Zを用いてBを予測することもできる」という考え方を利用して欠損値を補う.n個の特徴を持つデータに対して,特徴Tに欠落値がある場合,特徴Tをラベルとし,他のn−1個の特徴と元のラベルから新しい特徴行列を構成する.それはTにとって欠けていない部分であり、私たちのY_です.train、このデータにはラベルも特徴もありますが、欠落している部分は、特徴だけがラベルを持っていないので、予測が必要な部分です.特徴Tが欠落しない値に対応する他のn-1個の特徴+本来のラベル:X_trainフィーチャーTが欠落しない値:Y_trainフィーチャーT欠落値に対応する他のn-1個のフィーチャー+本来のラベル:X_testフィーチャーTが欠落している値:不明、予測Y_が必要testというやり方は,ある特徴が大量に欠けているのに,他の特徴が完全である場合に非常に適している.
data["NumberOfDependents"].fillna(int(data["NumberOfDependents"].mean()),inplace=True)
# 2.5% , ~
data.info()
data.isnull().sum()/data.shape[0]
以前に行ったランダム森林充填欠損値のケースでは,データセット全体で複数の特徴が欠落している場合に直面しているため,まず特徴をソートし,すべての特徴を遍歴して埋めなければならない.今回は「収入」の特徴を埋めるだけで、循環するほど面倒ではなく、この列を直接埋めることができます.任意の列を埋める関数を書きます.
def fill_missing_rf(X,y,to_fill):
"""
:
X:
y: ,
to_fill: ,
"""
#
df = X.copy()
fill = df.loc[:,to_fill]
df = pd.concat([df.loc[:,df.columns != to_fill],pd.DataFrame(y)],axis=1)
#
Ytrain = fill[fill.notnull()]
Ytest = fill[fill.isnull()]
Xtrain = df.iloc[Ytrain.index,:]
Xtest = df.iloc[Ytest.index,:]
#
from sklearn.ensemble import RandomForestRegressor as rfr
rfr = rfr(n_estimators=100)
rfr = rfr.fit(Xtrain, Ytrain)
Ypredict = rfr.predict(Xtest)
return Ypredict
関数に必要なパラメータを作成し、パラメータを関数にインポート
X = data.iloc[:,1:]
y = data["SeriousDlqin2yrs"]
X.shape
#=====【TIME WARNING:1 min】=====#
y_pred = fill_missing_rf(X,y,"MonthlyIncome")
# ,
data.loc[data.loc[:,"MonthlyIncome"].isnull(),"MonthlyIncome"] = y_pred
2.3記述的統計処理異常値
現実のデータには常にいくつかの異常値があります.まず、私たちは彼らを捕まえて、彼らの性質を観察します.注意、私たちはすべての異常値を排除するのではなく、逆に多くの場合、異常値は私たちの重点研究対象であり、例えば、双十一の中で購入量が高いブランドや、教室で多くの学生を興奮させる課題であり、これらは私たちが重点的に研究観察しなければならない.日常処理異常値、箱線図や法則を使用して異常値を見つけます(目に依存することは言うまでもありませんが、データマイニングエンジニアであり、ビジネス理解のほかに方法があります).しかし、銀行データでは、除外したい「異常値」いくつかの超高または超低の数字ではなく、いくつかの常識に合わないデータです.例えば、収入は負数ではありませんが、超高レベルの収入は合理的で、存在することができます.そのため、銀行業界では、通常の記述的な統計を使用して、データの異常の有無とデータの分布状況を観察することが多い.この方法は,特徴量が限られている場合にのみ行うことができ,数百の特徴があっても次元を下げることに成功しないか,特徴選択が役に立たない場合は,やはり用いたほうがよい.
#
data.describe([0.01,0.1,0.25,.5,.75,.9,.99]).T
# , 0, , 8 , 0
(data["age"] == 0).sum()
# 0, , ,
data = data[data["age"] != 0]
"""
, :
"NumberOfTime30-59DaysPastDueNotWorse"
"NumberOfTime60-89DaysPastDueNotWorse"
"NumberOfTimes90DaysLate"
“ 35-59 ”,“ 60-89 ”,“ 90 ”。 , 99% 2, 98, 。 35~59 98 , 6 60 , 98 ?
, 。 , 98 , 。 , :
"""
data[data.loc[:,"NumberOfTimes90DaysLate"] > 90].count()
# 225 , , , 1, 。 , , , 。
data = data[data.loc[:,"NumberOfTimes90DaysLate"] < 90]
#
data.index = range(data.shape[0])
data.info()
2.4なぜアウトラインを統一せず、データを標準化しないのか
記述的統計結果では,論理回帰はデータに対して分布要件を持たないが,データが正規分布に従うと勾配降下がより速く収束できることを知ったデータアウトラインが著しく不統一であり,一部の極偏分布が存在することを観測できた.しかし、ここでは、データを標準化したり、アウトラインを統一したりしません.なぜですか.アルゴリズムにどのような規定があっても、統計学にどのような要求があっても、私たちの最終的な目的はビジネスに奉仕することです.今、私たちはスコアカードを作ります.スコアカードは、新しいお客様が記入した各種情報に基づいてお客様に採点するカードです.このカードを作るために、私たちのデータに対して「スコア」を行う必要があります.例えば、年齢2030歳が1つ、年齢3050歳が1つ、月収1 W以上が1つ、5000~1 Wが1つです.ランクごとに点数が違います.
データをアウトラインに統一したり、標準化したりすると、データのサイズや範囲が変わり、統計結果はきれいになりますが、ビジネス関係者にとっては、標準化後の年齢が0.00328-0.00467の間でどのような意味を持っているのか理解できません.また、新しいお客様が記入した情報は、生まれながらにして量綱が統一されていないので、私たちは確かにすべての情報を入力した後、統一して標準化し、アルゴリズム計算を導入することができますが、最終的には業務員の手に落ちて判断すると、何が入力された情報が統計的に美しいが、実際には読めない数字になったのか全く理解できません.業務上の要求により、スコアカードを作成する際には、できるだけデータの原形を維持しなければならない.年齢は8~110の数字で、収入は0より大きく、最大値は無限の数字で、アウトラインが統一されていなくても、データを標準化しない.
2.5サンプルの不均衡問題
#
X = data.iloc[:,1:]
y = data.iloc[:,0]
y.value_counts()
n_sample = X.shape[0]
n_1_sample = y.value_counts()[1]
n_0_sample = y.value_counts()[0]
print(' :{}; 1 {:.2%}; 0
{:.2%}'.format(n_sample,n_1_sample/n_sample,n_0_sample/n_sample))
サンプルの深刻な不均衡がうかがえる.信用リスクの防止に努めているが、実際に違約している人は多くない.そして、銀行は本当に違約するすべての人を殺すことはありません.多くの人はお金を返すことができます.ただ、返済日を忘れただけで、多くの人はお金を借りたくありませんでした.しかし、当時は本当に困難で、資金の回転ができなかったので、期限を過ぎました.しかし、お金があれば、彼はお金を交換します.銀行にとって、あなたが最後にお金を返すことができる限り、私はあなたにお金を借りたいです.私が貸してあげると収入(利息)があるので、銀行にとって、本当に判別されたいのは「悪意のある違約」です」という人は、この部分の人数が非常に少なく、サンプルが不均衡になります.これは銀行業のモデリングの痛点です.私たちは常に少数のクラスをキャプチャしたいと思っています.前提として、論理回帰で最も多く使われているのは、サンプリング方法でサンプルをバランスさせることです.
# , prompt :pip install imblearn
import imblearn
#imblearn , sklearn
#imblearn , ,fit , sklearn
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=42) #
X,y = sm.fit_sample(X,y)
n_sample_ = X.shape[0]
pd.Series(y).value_counts()
n_1_sample = pd.Series(y).value_counts()[1]
n_0_sample = pd.Series(y).value_counts()[0]
print(' :{}; 1 {:.2%}; 0
{:.2%}'.format(n_sample_,n_1_sample/n_sample_,n_0_sample/n_sample_))
2.6分トレーニングセットとテストセット
from sklearn.model_selection import train_test_split
X = pd.DataFrame(X)
y = pd.DataFrame(y)
X_train, X_vali, Y_train, Y_vali = train_test_split(X,y,test_size=0.3,random_state=420)
model_data = pd.concat([Y_train, X_train], axis=1)
model_data.index = range(model_data.shape[0])
model_data.columns = data.columns
vali_data = pd.concat([Y_vali, X_vali], axis=1)
vali_data.index = range(vali_data.shape[0])
vali_data.columns = data.columns
model_data.to_csv(r"C:\work\learnbetter\micro-class\week 5 logit
regression\model_data.csv")
vali_data.to_csv(r"C:\work\learnbetter\micro-class\week 5 logit
regression\vali_data.csv")
3ボックス
3.1等周波数分割箱
model_data["qcut"], updown = pd.qcut(model_data["age"], retbins=True, q=20)
"""
pd.qcut, ,
。
q:
retbins=True , Series
: ,
"""
# model_data “ ”,
model_data["qcut"]
#
updown
# 0 1
# groupby
coount_y0 = model_data[model_data["SeriousDlqin2yrs"] == 0].groupby(by="qcut").count()
["SeriousDlqin2yrs"]
coount_y1 = model_data[model_data["SeriousDlqin2yrs"] == 1].groupby(by="qcut").count()
["SeriousDlqin2yrs"]
#num_bins , ,0 ,1
num_bins = [*zip(updown,updown[1:],coount_y0,coount_y1)]
# zip
num_bins
3.2箱ごとに0、1があることを確認する
for i in range(20):
# ,
if 0 in num_bins[0][2:]:
num_bins[0:2] = [(
num_bins[0][0],
num_bins[1][1],
num_bins[0][2]+num_bins[1][2],
num_bins[0][3]+num_bins[1][3])]
continue
"""
, ?
, , ,
, ,
continue , , for i in range(20), i+1
, ,
, if , , , 20
, if ,
"""
# , ,
# num_bins , ,
# , num_bins , in range(len(num_bins))
for i in range(len(num_bins)):
if 0 in num_bins[i][2:]:
num_bins[i-1:i+1] = [(
num_bins[i-1][0],
num_bins[i][1],
num_bins[i-1][2]+num_bins[i][2],
num_bins[i-1][3]+num_bins[i][3])]
break
# , if ,
else:
break
"""
break, if
, , for i in range(len(num_bins))
? range(len(num_bins))
,len(num_bins) ,
, num_bins 5 ,for i in range(len(num_bins)) for i in
range(5)
range , num_bins , i [0,1,2,3,4]
,num_bins 4 , =4 , i 4,
, if , , , break
for i in range(20)
, for i in range(len(num_bins))
i ,
"""
3.3 WOEとIV関数の定義
# WOE BAD RATE
#BAD RATE bad%
#BAD RATE , (bad/total)
# bad%
def get_woe(num_bins):
# num_bins woe
columns = ["min","max","count_0","count_1"]
df = pd.DataFrame(num_bins,columns=columns)
df["total"] = df.count_0 + df.count_1
df["percentage"] = df.total / df.total.sum()
df["bad_rate"] = df.count_1 / df.total
df["good%"] = df.count_0/df.count_0.sum()
df["bad%"] = df.count_1/df.count_1.sum()
df["woe"] = np.log(df["good%"] / df["bad%"])
return df
# IV
def get_iv(df):
rate = df["good%"] - df["bad%"]
iv = np.sum(rate * df.woe)
return iv
3.4チャック検査、箱体を合併し、IV曲線を描く
num_bins_ = num_bins.copy()
import matplotlib.pyplot as plt
import scipy
IV = []
axisx = []
while len(num_bins_) > 2:
pvs = []
# num_bins_ ( )
for i in range(len(num_bins_)-1):
x1 = num_bins_[i][2:]
x2 = num_bins_[i+1][2:]
# 0 chi2 ,1 p 。
pv = scipy.stats.chi2_contingency([x1,x2])[1]
# chi2 = scipy.stats.chi2_contingency([x1,x2])[0]
pvs.append(pv)
# p 。 p
i = pvs.index(max(pvs))
num_bins_[i:i+2] = [(
num_bins_[i][0],
num_bins_[i+1][1],
num_bins_[i][2]+num_bins_[i+1][2],
num_bins_[i][3]+num_bins_[i+1][3])]
bins_df = get_woe(num_bins_)
axisx.append(len(num_bins_))
IV.append(get_iv(bins_df))
plt.figure()
plt.plot(axisx,IV)
plt.xticks(axisx)
plt.xlabel("number of box")
plt.ylabel("IV")
plt.show()
3.5最適な箱数で箱を分け、箱の結果を検証する
連結ボックスの部分を関数として定義し、ボックス分割を行います.
def get_bin(num_bins_,n):
while len(num_bins_) > n:
pvs = []
for i in range(len(num_bins_)-1):
x1 = num_bins_[i][2:]
x2 = num_bins_[i+1][2:]
pv = scipy.stats.chi2_contingency([x1,x2])[1]
# chi2 = scipy.stats.chi2_contingency([x1,x2])[0]
pvs.append(pv)
i = pvs.index(max(pvs))
num_bins_[i:i+2] = [(
num_bins_[i][0],
num_bins_[i+1][1],
num_bins_[i][2]+num_bins_[i+1][2],
num_bins_[i][3]+num_bins_[i+1][3])]
return num_bins_
afterbins = get_bin(num_bins,4)
afterbins
bins_df = get_woe(num_bins)
bins_df
最適なボックス数を選択するプロセスを関数としてパッケージ
def graphforbestbin(DF, X, Y, n=5,q=20,graph=True):
"""
,
:
DF:
X:
Y: Y
n:
q:
graph: IV
(]
"""
DF = DF[[X,Y]].copy()
DF["qcut"],bins = pd.qcut(DF[X], retbins=True, q=q,duplicates="drop")
coount_y0 = DF.loc[DF[Y]==0].groupby(by="qcut").count()[Y]
coount_y1 = DF.loc[DF[Y]==1].groupby(by="qcut").count()[Y]
num_bins = [*zip(bins,bins[1:],coount_y0,coount_y1)]
for i in range(q):
if 0 in num_bins[0][2:]:
num_bins[0:2] = [(
num_bins[0][0],
num_bins[1][1],
num_bins[0][2]+num_bins[1][2],
num_bins[0][3]+num_bins[1][3])]
continue
for i in range(len(num_bins)):
if 0 in num_bins[i][2:]:
num_bins[i-1:i+1] = [(
num_bins[i-1][0],
num_bins[i][1],
num_bins[i-1][2]+num_bins[i][2],
num_bins[i-1][3]+num_bins[i][3])]
break
else:
break
def get_woe(num_bins):
columns = ["min","max","count_0","count_1"]
df = pd.DataFrame(num_bins,columns=columns)
df["total"] = df.count_0 + df.count_1
df["percentage"] = df.total / df.total.sum()
df["bad_rate"] = df.count_1 / df.total
df["good%"] = df.count_0/df.count_0.sum()
df["bad%"] = df.count_1/df.count_1.sum()
df["woe"] = np.log(df["good%"] / df["bad%"])
return df
def get_iv(df):
rate = df["good%"] - df["bad%"]
iv = np.sum(rate * df.woe)
return iv
IV = []
axisx = []
while len(num_bins) > n:
pvs = []
for i in range(len(num_bins)-1):
x1 = num_bins[i][2:]
x2 = num_bins[i+1][2:]
pv = scipy.stats.chi2_contingency([x1,x2])[1]
pvs.append(pv)
i = pvs.index(max(pvs))
num_bins[i:i+2] = [(
num_bins[i][0],
num_bins[i+1][1],
num_bins[i][2]+num_bins[i+1][2],
num_bins[i][3]+num_bins[i+1][3])]
bins_df = pd.DataFrame(get_woe(num_bins))
axisx.append(len(num_bins))
IV.append(get_iv(bins_df))
if graph:
plt.figure()
plt.plot(axisx,IV)
plt.xticks(axisx)
plt.show()
return bins_df