一 :項目目的
研究Lending Club 貸款的風險特征,並提出建模方案。
二:數據獲取
數據集來自Lending Club平台發生借貸的業務數據,2017年第一季度,具體數據集可以從Lending Club官網下載,需要先用郵箱注冊一個賬號。
三:數據探索
1.導入需要用到的工具
import pandas as pd import numpy as np import matplotlib.pyplot as plt plt.style.use('ggplot') #風格設置 import seaborn as sns sns.set_style('whitegrid') %matplotlib inline import warnings warnings.filterwarnings('ignore') plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默認字體 plt.rcParams['axes.unicode_minus'] = False # 解決保存圖像是負號'-'顯示為方塊的問題
2.導入數據
data=pd.read_csv("LoanStats3d_securev1.csv",encoding='latin-1',skiprows = 1) data.head()
各個變量的解釋也可以在Lending Club 官網找到,直接下載是EXCEL格式的
#看一下目標特征 data.loan_status.value_counts()
Fully Paid 307831 Charged Off 77884 Current 33584 Late (31-120 days) 1006 In Grace Period 436 Late (16-30 days) 287 Default 67 Name: loan_status, dtype: int64
Fully Paid:已結清 ,Charged Off:壞賬 ,Current:當前已還款 , Late (31-120 days):預期30-120天
#In Grace Period :已逾期但在寬限期類 , Default:逾期超過90天
#參考:https://help.bitbond.com/article/20-the-10-loan-status-variants-explained·
從結果看出 我們的正反案列存在嚴重的正反案列不均衡問題,后續建模需要處理以下
3.先把標簽處理一下
#封裝一個替換函數 def coding(col, codeDict):
colCoded = pd.Series(col, copy=True)#創建一個和loan_status一樣的 Series for key, value in codeDict.items():#返回可遍歷的(鍵, 值) 元組數組 colCoded.replace(key, value, inplace=True)#替換原有數據 return colCoded
##把貸款狀態LoanStatus編碼為違約=1, 正常=0: dict1={'Current':0,'Fully Paid':0,'Charged Off':1,'Late (31-120 days)':1,'Late (16-30 days)':1,'In Grace Period':1,"Default":1}data["loan_status_class"]=coding(data["loan_status"],dict1)
data.loan_status_class.value_counts()
0 341415 1 79680 Name: loan_status_class, dtype: int64
3.處理缺失值
#查看缺失值 for i in data.columns: miss=data[i].isnull().sum() print(i,"\t",miss)
截圖不完整
發現很多變量全為空,這種數據對我們沒有任何價值,先處理掉這些無用數據。
#刪除缺失別列0.8以上的列 half_count = len(data)*0.8 # 設定閥值 data = data.dropna(thresh = half_count, axis = 1 ) #若某一列數據缺失的數量超過閥值就會被刪除 data.shape
#data = data.drop(['desc', 'url','id'], axis = 1) #刪除了一些無用列
(421095, 93)
#剩下93 列
4.數據描述
由於數據特征較多,這里篩選一些變量做描述
col=["loan_amnt","term","int_rate","grade","emp_length","annual_inc","verification_status","loan_status","purpose","dti","delinq_2yrs","inq_last_6mths",'open_acc',"pub_rec","revol_bal","total_acc","total_rev_hi_lim","addr_state","home_ownership","emp_title","loan_status_class"] df=data[col]
df.columns=["申請額度","借款期限","利率","評級","工作年限","年收入","收入來源是否核實","借款狀態","借款目的","負債率","近兩年逾期30天以上的次數","近6個月征信查詢次數","未結清借款數","負面記錄","未結清借款總額","剩余信用額度","總授信額度","所在地","住房狀態","職位","分類"]
#描述分類屬性依據好壞樣本的分布情況 cla=["借款期限","評級","工作年限","收入來源是否核實","借款目的","住房狀態"] for i in cla: pvt=pd.pivot_table(df[["分類",i]],index=i,columns="分類",aggfunc=len) pvt.plot(kind="bar")
由圖可知:
1.(左上)大部分人選擇36期貸款,少部分選擇60期,但是60期逾期百分比明顯高於36期,並且高達37%,借款時間越長,風險越大。
2.(右上)LC自評等級,這個評級與利息相關的,隨着評級下降風險越來越高,利息越來越高,我們可以認為相應的逾期率較大,本圖也反饋了LC 評級的優異性能
3.(左下)值得注意的是,工作年限10年以上的借款人相對較多,這與我們的一般認知不符合,除此以外工作1-9年的人群隨着工作年限加長,借款需求相對減少,可能是收入相對穩定了,
(題外話:如果這個假設成立,為什么我自己工作年限越長,就越窮呢,是因為能力么,我太難了=。=)
4.(右下)收入來源是否經過核實,大部分是經過核實的,並且經過核實的違約概率相對較低
5.借款目的 債務整合,家具裝修,還信用卡,三類最多(還是比較誠實,這也側面說明 這個特征可能用處不大)
6.住房狀態上 按揭 與租房 最多,相對的 租房違約率較高 (符合一般家庭情況)
對數值型數據進行描述
cel=[i for i in df1.columns if df1[i].dtypes =="float"] for i ,j in enumerate(cel): plt.figure(figsize=(8,5*len(cel))) plt.subplot(len(cel),1,i+1) sns.distplot(df1[j][df1.分類==0],color="b") sns.distplot(df1[j][df1.分類==1],color="r")
由圖可知:
1.(左上)借款額度呈正太分布,稍有一點左偏,表明業務多集中在中小額度上面,且額度越高逾期率相對有所增加。
2.(右上)年收入集中在15萬以內,個別極高收入達到400萬。
3.(左下)負債率呈現正太分布,多集中在40%以內
4.(右下)近兩年逾期30天以上的次數,說明即使一次逾期記錄也沒有,客戶也是可能逾期的
由圖可知:
1.征信查詢,查的越多越容易逾期
2.沒有負面記錄也是會逾期的,但是有負面記錄的人逾期率要高得多。
用詞雲圖看看,申請地與職位的頻率
text=["職位","所在地"] str_list=["",""] for i,j in enumerate(text): for k in df1[j].values: str_list[i]+=str(k) + " " print(str_list)
#分別設置了背景顏色,寬度,與高度 from wordcloud import WordCloud wordcloud=WordCloud(background_color="white",width=1000, height=860, margin=2).generate(str_list[0]) plt.imshow(wordcloud) plt.axis("off") wordcloud=WordCloud(background_color="white",width=1000, height=860, margin=2).generate(str_list[1]) plt.imshow(wordcloud) plt.axis("off")
#在制作詞雲圖時,文本之間提前預留空格,作圖時間會非常快,相當於自己提前分詞
1.借款人 職務 大多是公司職員,
2.借款人主要集中在,加利福利亞,紐約 德克薩斯州(該公司中部在加州)
探索借款用途與利率之間的關系
df['int_rate_num'] = df['int_rate'].str.rstrip("%").astype("float")# 刪除 利率后面的百分號,並且轉換成 浮點型數據 sns.boxplot(y="purpose",x="int_rate_num",data=df)
借款用途為 small_business 的借款利率最高
探索探索利率 收入 工作年限 和借款狀態之間的關系
#替換數據的第二種方法 mapping_dict = { "emp_length": { "10+ years": 10, "9 years": 9, "8 years": 8, "7 years": 7, "6 years": 6, "5 years": 5, "4 years": 4, "3 years": 3, "2 years": 2, "1 year": 1, "< 1 year": 0, "n/a": 0 } } df = df.replace(mapping_dict) #轉換 df["annual_inc"]=df["annual_inc"].astype("float") #把收入中odjest 轉換成float sns.pairplot(df, vars=["int_rate_num","annual_inc", "emp_length"],hue="loan_status_class", diag_kind="kde" ,kind="reg", size = 3)
可理解為 工作年限越長,收入越高違約情況相對較低,相應的享受更低的利息
簡單看看相關性
sns.heatmap(df1.corr())
除去對角線以外顏色越淺相關信息越高
5.建模准備工作
1.查看缺失值具體情況,並決定填充策略
#查看缺失值情況並決定哪些需要刪除
data_defect=[i for i in data.columns if (data[i].isnull().sum())/data.shape[0] != 0]
for i in data_defect:
defect=data[i].isnull().sum()/data.shape[0]
print( i , defect)
data=data.drop(["mths_since_recent_bc","mths_since_recent_inq"],axis=1)
#眾數填充 fil=["emp_title","emp_length","title","dti","num_rev_accts","num_tl_120dpd_2m","percent_bc_gt_75"] from scipy.stats import mode # 計算眾數模塊 for i in fil: data[i][data[i].isnull()]=mode(data[i][data[i].notnull()])[0][0]
#再看看缺失值情況
objectcolumns=[i for i in data.columns if data[i].dtype=="object"]
data[objectcolumns].isnull().sum().sort_values(ascending=False)
data[objectcolumns].head()
#發現 int_rate 與revol_util 實際數數值,但是含有% 被識別為字符,借款周期需要處理,工齡需要處理 data.int_rate= data.int_rate.str.rstrip('%').astype('float') data.revol_util= data.revol_util.str.rstrip('%').astype('float')#刪除末尾指定字符,並轉化成數值 data["term"]=data["term"].str.rstrip("months").astype("float") objectcolumns=[i for i in data.columns if data[i].dtypes=="object"] data[objectcolumns].isnull().sum().sort_values()
#數據過濾
var = data[objectcolumns].columns for v in var: print('\nFrequency count for variable {0}'.format(v)) print(data[v].value_counts()) data[objectcolumns].shape
drop_list=["sub_grade","title","zip_code","last_pymnt_d","last_credit_pull_d"] data.drop(drop_list,axis=1,inplace=True)
#創建一個vacancy 類型,填充缺失值 objectcolumns=[i for i in data.columns if data[i].dtype=="object"] data[objectcolumns]=data[objectcolumns].fillna("vacanct")
import missingno as msno # 缺失值可視化 msno.matrix(data[objectcolumns])
#查看float數據類型缺失情況 floatcolumns=[i for i in data.columns if data[i].dtype=="float"] data[floatcolumns].isnull().sum().sort_values(ascending=False)
對於數值型數據我們先采用均值填補
from sklearn.preprocessing import Imputer imp = Imputer(missing_values=np.nan , strategy='mean',copy=False, axis=0) imp=imp.fit(data[floatcolumns]) data[floatcolumns]=imp.transform(data[floatcolumns])
極端值這里暫時不做處理,因為是做評分卡,后續會做分箱操作
對object數據再次進行數據過濾,看看是否需要篩選
objectColumns = [i for i in data.columns if data[i].dtype=="object"] var = data[objectColumns].columns for v in var: print('\nFrequency count for variable {0}'.format(v)) print(data[v].value_counts())
data_drop=data[["sub_grade","pymnt_plan","title","last_pymnt_d","last_pymnt_d","last_credit_pull_d","application_type","hardship_flag", "debt_settlement_flag"]] data=data.drop(data_drop,axis=1)
2.特征抽象
這里我們優先使用類別標簽,暫時不用啞變量,后續看模型效果也可以嘗試啞變量
data_list={ "grade":{"A":1,"B":2,"C":3,"D":4,"E":5,"F":6,"G":7}, "emp_length":{"10+ years":11,"2 years":2,"< 1 year":0,"3 years":3,"1 year":1,"5 years":5,"4 years":6,"vacanct":0, "8 years":8,"7 years":7,"6 years":6,"9 years":9 }, "home_ownership":{"MORTGAGE":1,"RENT":2,"OWN":3,"ANY":4 }, "verification_status":{"Source Verified":1,"Verified":2,"Not Verified":3}, "loan_status":{'Current':0,'Fully Paid':0,'Charged Off':1,'Late (31-120 days)':1,'Late (16-30 days)':1,'In Grace Period':1,"Default":1}, "purpose":{"debt_consolidation":1,"credit_card":2,"home_improvement":3,"other":4,"major_purchase":5,"medical":6,"car":7, "small_business":8,"moving":9,"vacation":10,"house":11,"renewable_energy":12,"wedding":13,"educational":14}, "initial_list_status":{"w":1,"f":2}, "term":{36.0:1,60.0:2} } data=data.replace(data_list)#映射

n_columns = ["home_ownership","verification_status","purpose","application_type"] dummy_df = pd.get_dummies(data[n_columns])# 用get_dummies進行one hot編碼 loans = pd.concat([data, dummy_df], axis=1) #當axis = 1的時候,concat就是行對齊,然后將不同列名稱的兩張表合並 data = data.drop(n_columns, axis=1) #清除原來的分類變量
同值信息處理
from scipy.stats import mode equ_fea=[] for i in data1.columns: mode_value=mode(data1[i])[0][0] mode_rate=mode(data1[i])[1][0]/data1.shape[0] if mode_rate >0.9: equ_fea.append([i,mode_value,mode_rate]) dt=pd.DataFrame(equ_fea,columns=["name","value","equi"]) dt.sort_values(by="equi")
再剔除信息泄露屬性
drop_data_leakage=data[["recoveries","last_pymnt_amnt","funded_amnt","funded_amnt_inv","total_pymnt","total_pymnt_inv","total_rec_prncp", "total_rec_int"]] a=data.drop(drop_data_leakage,axis=1,inplace=True)
特征衍生
#我們呢把'annual_inc'年收入/12 得到客戶月收入,然后在用"installment" 除以月收入得到得到每月還款與月收入的比,值越大客戶還款壓力越大 data["installment_feat"]=data["installment"] / (data["annual_inc"]/12) #把時序變量變成月份值,用借款發放時間 - 首次使用信用卡時間,作為一個新變量,表示信用歷史 a=(data["issue_d"]-data["earliest_cr_line"])/30 data["cre_hist"]=a data.drop(["issue_d","earliest_cr_line"],axis=1,inplace=True)
data.to_csv("2017q1_2.csv",index=False)
連續變量分箱:
分箱方法包括有監督的 卡方分箱 KS分箱和決策樹分箱,無監督的 等寬 等頻等分箱
一開始打算采用卡方分箱,但是有的數據莫名其妙出錯,要么就跑一晚上沒有反應,以為是正太分布的問題,半天也沒有解決,最后改用決策樹分箱。

def Chi2(df, total_col, bad_col,overallRate): ''' #此函數計算卡方值 :df dataFrame :total_col 每個值得總數量 :bad_col 每個值的壞數據數量 :overallRate 壞數據的占比 : return 卡方值 ''' df2=df.copy() df2['expected']=df[total_col].apply(lambda x: x*overallRate) combined=zip(df2['expected'], df2[bad_col]) chi=[(i[0]-i[1])**2/i[0] for i in combined] chi2=sum(chi) return chi2 #最大分箱數分箱 def ChiMerge_MaxInterval_Original(df, col, target,max_interval=5): ''' : df dataframe : col 要被分項的特征 : target 目標值 0,1 值 1 為反 0 為正 : max_interval 最大箱數 :return 箱體 ''' colLevels=set(df[col]) colLevels=sorted(list(colLevels)) N_distinct=len(colLevels) if N_distinct <= max_interval: print ("the row is cann't be less than interval numbers") return colLevels[:-1] else: total=df.groupby([col])[target].count() total=pd.DataFrame({'total':total}) bad=df.groupby([col])[target].sum() bad=pd.DataFrame({'bad':bad}) regroup=total.merge(bad, left_index=True, right_index=True, how='left') regroup.reset_index(level=0, inplace=True) N=sum(regroup['total']) B=sum(regroup['bad']) overallRate=B*1.0/N groupIntervals=[[i] for i in colLevels] groupNum=len(groupIntervals) while(len(groupIntervals)>max_interval): chisqList=[] for interval in groupIntervals: df2=regroup.loc[regroup[col].isin(interval)] chisq=Chi2(df2,'total','bad',overallRate) chisqList.append(chisq) min_position=chisqList.index(min(chisqList)) if min_position==0: combinedPosition=1 elif min_position==groupNum-1: combinedPosition=min_position-1 else: if chisqList[min_position-1]<=chisqList[min_position + 1]: combinedPosition=min_position-1 else: combinedPosition=min_position+1 #合並箱體 groupIntervals[min_position]=groupIntervals[min_position]+groupIntervals[combinedPosition] groupIntervals.remove(groupIntervals[combinedPosition]) groupNum=len(groupIntervals) groupIntervals=[sorted(i) for i in groupIntervals] print (groupIntervals) cutOffPoints=[i[-1] for i in groupIntervals[:-1]] return cutOffPoints #返回最佳切分點array

import numpy as np from scipy.stats import kstest kstest(b, 'norm') #正太分布檢驗 p值大於0.05 表示符合正太分布
#先切分下需要分箱的數據 x_data=data["open_acc"] x1_data=x_data[:,np.newaxis] #sklearn要求x,至少是二維數據,所以需要增加一維,np.newaxis 的位置決定了增加維度的位置 x1_data
#做單變量決策樹
from sklearn.tree import DecisionTreeClassifier model = DecisionTreeClassifier( max_depth=3,min_samples_leaf=21054).fit(x1_data,data["loan_status"])
#顯示圖形 from sklearn import tree import graphviz dot_data = tree.export_graphviz(model, out_file=None) graphviz.Source(dot_data)
略過圖形展示
#通過決策樹得到所有切分點,並轉換成字典 num_box=["loan_amnt","int_rate","dti","fico_range_low","installment","annual_inc","fico_range_high","open_acc"] cut_list=[ [4012,5987,7012,9012,10012,19987,20012,23987,28112], [6.905,8.045,10.565,11.76,12.49,13.665,15.88,17.915,19.94], [7.445,10.075,12.625,14.855,20.195,21.795,25.135,30.115,34.275], [667.5,677,683,687,692,697,707,727,747], [161,197,251,503,602,880], [42800,55101,65732,85085,104499,120287,150486], [671.5,686.5,691.5,701.5,711.5,731.5,751.5], [5.5,7.5,8.5,10.5,17.5,22.5]] cut_dict={} for i in range(len(num_box)): cut_dict[num_box[i]]=cut_list[i]
#采用pd.cut()划分數據 def box_col_to_df(to_box,col,num_b):#數據集 需要轉換的數據列 切割點LISI bins=[-100.0]+num_b+[1000000000.0] #因為pd.cun()是封閉的,這里把bins的上下區間擴大 to_box[col]=pd.cut(to_box[col],bins=bins,include_lowest=True,labels=range(len(bins)-1))
box_col_to_df(data,"open_acc",cut_dict["open_acc"])
完成后的數據如下
用隨機森林對變量重要程度排序
已經提前切分了 X與 y
from sklearn.ensemble import RandomForestClassifier clf=RandomForestClassifier().fit(x,y)
一開始這里出現錯誤,顯示 x存在 空值或者無窮大
#找到無窮值 inf_list= np.isinf(data).sum().tolist()#把每一列的無窮值個數加起來 sum(inf_list)#如果sum(nan_inf) 為0,則不存在無窮值;如果不為0,則存在。
#定位無窮值 abnormal_index = [ [inf_list.index(i)] for i in inf_list if i != 0 ]#遍歷列表,找到所有非0值的索引。 print(data.columns[abnormal_index])
只有2個,所以刪除相應行就可以了
再跑一次隨機森林
然后輸出變量重要程度
#輸出變量重要程度排序 importance = clf.feature_importances_ indices = np.argsort(importance)[::-1] features = x.columns name=[] degree=[] for f in range(x.shape[1]): name.append(features[f]) degree.append(importance[indices[f]]) zy=pd.DataFrame({"name":name,"degree":degree}) print(zy)
先截取前15個變量看效果
degree_list=df.loc[:15,"name"] df=data[degree_list]
計算woe值與IV值
#封裝woe與IV值計算函數 def Calcwoe(data,col,target): total=data.groupby([col])[target].count() total=pd.DataFrame({"total":total}) bad=data.groupby([col])[target].sum() bad=pd.DataFrame({"bad":bad}) regroup=total.merge(bad,left_index=True,right_index=True,how="left") regroup.reset_index(level=0,inplace=True) n=sum(regroup["total"]) b=sum(regroup["bad"]) regroup["good"]=regroup["total"]-regroup["bad"] g=n-b regroup["bad_pcnt"]=regroup["bad"].map(lambda x: x*1.0/b) regroup["good_pcnt"]=regroup["good"].map(lambda x : x*1.0/g) regroup["woe"]=regroup.apply(lambda x: np.log(x.good_pcnt*1.0/x.bad_pcnt),axis=1) woe_dict=regroup[[col,"woe"]].set_index(col).to_dict() IV=regroup.apply(lambda x:(x.good_pcnt-x.bad_pcnt)*np.log(x.good_pcnt*1.0/x.bad_pcnt),axis=1) IV_SUM=sum(IV) return {"woe":woe_dict,"IV_SUM":IV_SUM,"IV":IV}
計算IV值 df=data.copy() woe_dist={} IV_list=[] for i in df.columns: iv_dict=Calcwoe(df,i,"loan_status") IV_list.append(iv_dict["IV_SUM"]) woe_dist[i]=iv_dist["woe"] DF_IV=pd.DataFrame({"iv_name":df.columns.values,"IV":IV_list}) DF_IV.sort_values(by="IV",ascending=False)
iv值出現(無窮大)表明 特征中的某些屬性缺失 某一類樣本,這種情況下需要從新分箱,合並屬性
再次查看IV值
計算IV值
df=data.copy() woe_dist={} IV_list=[] for i in df.columns: iv_dict=Calcwoe(df,i,"loan_status") IV_list.append(iv_dict["IV_SUM"]) woe_dist[i]=iv_dist["woe"] DF_IV=pd.DataFrame({"iv_name":df.columns.values,"IV":IV_list}) DF_IV.sort_values(by="IV",ascending=False)
之后可以保留IV值大於0.015變量,也可以保留大於0.02的變量,看實際情況
下面利VIF(方差膨脹系數)檢驗多重共線性,既用其他特征擬合這一特征,如果解釋性很強,說明他們存在共線性
#利用VIF(方差膨脹系數)檢驗多重共線性 from statsmodels.stats.outliers_influence import variance_inflation_factor as VIF VIF_ls=[] n=df.columns for i in range(len(n)): VIF_ls.append([n[i],int(VIF(df.values,i))]) df_vif=pd.DataFrame(VIF_ls,columns=["name","vif"]) print(df_vif)
#利用協方差計算線性相關性 cor=data[num_box].corr() cor.iloc[:,:]=np.tril(cor.values,k=-1) cor=cor.stack() cor[np.abs(cor)>0.7]
# VIF 大於 10 cor 大於0.7 變量之間存在相關性 這里我們逐一刪除,如當刪除 installment 之后,vif小於10,那么installment和 loan_amnt #選擇iv值大的哪一個 df.drop("fico_range_high",axis=1,inplace=True ) df.drop("installment",axis=1,inplace=True) df.drop("grade",axis=1,inplace=True)
valid_feas=DF_IV[DF_IV.IV > 0.015].iv_name.tolist()
valid_feas
df=df[valid_feas]
df.head()
#用熱力圖看看相關性 colormap = plt.cm.viridis plt.figure(figsize=(12,12)) plt.title('Pearson Correlation of Features', y=1.05, size=15) sns.heatmap(df.corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True)
解決樣本不均衡問題,方法有,過采樣,和欠采樣,以及有放回隨機抽樣等方法,本次采用過采樣平衡正反樣本。
#利用過采樣方法,解決樣本不均衡問題 #划分x和y x_list=list(df.columns) x_list.remove("loan_status") #再x_list中剔除 loan_status 變量 x=df[x_list] y=df["loan_status"]
n_sample=y.shape[0] n_pos_sample=y[y==0].shape[0] n_neg_sample=y[y==1].shape[0] print("樣本個數:{},正樣本占比:{:.2%},負樣本占比:{:.2%}".format(n_sample, n_pos_sample/n_sample, n_neg_sample/n_sample))
from imblearn.over_sampling import SMOTE # 導入SMOTE算法模塊 # 處理不平衡數據 sm = SMOTE(random_state=42) # 處理過采樣的方法 x, y = sm.fit_sample(x, y) print('通過SMOTE方法平衡正負樣本后') n_sample = y.shape[0] n_pos_sample = y[y == 0].shape[0] n_neg_sample = y[y == 1].shape[0] print('樣本個數:{}; 正樣本占{:.2%}; 負樣本占{:.2%}'.format(n_sample, n_pos_sample / n_sample, n_neg_sample / n_sample))
df.to_csv("2017q1_df.csv",index=False)
# 用woe編碼替換原屬性值,這樣可以讓系數正則化 for i in range(len(x.columns)): x[x.columns[i]].replace(woe_dict[x.columns[i]],inplace=True)
開始訓練模型
#x增加一列全為1,得到方程截距 import statsmodels.api as sm x1=sm.add_constant(x_train)
x_train,x_text,y_train,y_test=train_test_split(x1,y,test_size=0.2,random_state=1991)# 切分比列為2-8,切分,並設置隨機數種子
#利用交叉驗證和網格搜索 from sklearn.model_selection import GridSearchCV #網格搜索 from sklearn.linear_model import LogisticRegression # 邏輯回歸 from sklearn.model_selection import train_test_split # 測試集與訓練集划分
#構建網格參數組合 param_test1={"C":[0.01,0.1,1.0,10.0,20.0,30.0,100.0,200.0,300.0,1000.0], #正則化系數 "penalty":["l1","l2"] #正則化參數 "max_iter":[100,200,300,400,500]} #算法收斂的最大迭代次數 gsearch1=GridSearchCV(LogisticRegression(),param_grid=param_test1,cv=10) gsearch1.fit(x_train,y_train) #訓練模型
gsearch1.best_params_, gsearch1.best_score_ #查看評分最高的參數組合與最佳評分
gsearch1.best_estimator_ # 最佳參數分類器
利用網格搜索得到的最佳參數訓練模型
from sklearn.linear_model import LogisticRegression flt= LogisticRegression(penalty='l2',C=0.01) flt.fit(x_train,y_train)
用驗證集查看模型效果
auc=roc_auc_score(flt.predict(x_text),y_test) fpr,tpr,thre=roc_curve(flt.predict(x_text),y_test) ks=max(tpr-fpr) print("auc:{} ks:{}".format(auc,ks))
#查看准確率 from sklearn.metrics import accuracy_score print("准確率:{:.4%}".format(accuracy_score(flt.predict(x_text),y_test)))
flt.coef_ #查看系數
ks值大於0.3說明是一個基本能用的模型
輸出評分卡:
#輸出評分卡 #假設比率為1/20 時 分值是500,比率每翻倍一次的20分 B=20/np.log(2) A=500+B*np.log(1/20) basescore=round(A-B*flt.coef_[0][0],0) #基准分四舍五入取整 scorecard={} for i,j in enumerate(x.columns): woe=woe_dict[j]["woe"] interval=[] scores=[] for key,value in woe.items(): score=round(-(value*flt.coef_[0][i+1]*B)) scores.append(score) interval.append(key) data=(pd.DataFrame({"interval":interval,"scores":scores})).set_index("interval").to_dict() scorecard[j]=data print(scorecard)
整理之后得到評分卡。
得到評分卡之后我們通常需要計算出最佳的分數切割點,可以用ROC曲線,找到拐點的值,帶入評分卡方程就是我們的最佳切割分數
也可以利用,卡方分箱,或者決策樹,將評分分箱,計算每一箱的逾期率,根據業務情況選擇切割分數。
總結
1.每一種方法沒有好壞的區分,只有適合與不適合,更多時候我們需要都用一邊,才知道某一種算法適合什么數據。
2.制作模型本身是一個不斷迭代尋找最優的過程,當我們構建出一個模型之后如果效果不理想,那么需要我們從數據清洗開始從新來做,比如缺失 值填充是用均值還是眾數?分類變量使用 標簽法,還是做啞變量呢,這些我們都要一一嘗試不斷迭代,得到我們的最終模型。
3.變量選擇 很重要,人們常說數據決定的模型的頂點,而算法用於逼近頂點,可見再特征選擇上我們要盡量的貼合業務實際情況,要想得到好的模 型最終還是要在數據上下功夫,這說明數據清洗與准備過程,再整個建模流程中是比較重要的。
參考:
https://zhuanlan.zhihu.com/p/39780207
https://blog.csdn.net/zs15321583801/article/details/89485951