【 專案:信用卡客戶使用者畫像 及 貸款違約預測模型 】

helaoshi發表於2020-06-26

【以下為本專案分析思維導圖:】

專案工具:Pycharm + Navicat 或 線上工具freego

Image Name

接下來進行程式碼演示

【專案 ? ?(綜合版):信用卡客戶使用者畫像 及 貸款違約預測模型】

--【 資料理解 】--

(一) 匯入資料 並進行初步的資料觀察

1.改變工作目錄到 資料集所在資料夾

In [ ]:
# -*- coding:utf-8 -*-import  pandas as pd    # 用於 資料清洗 和 資料整理import os   # 工作路徑 及 資料夾操作import datetimepd.set_option('display.max_columns',None)# 分別設定好 資料集路徑 和 之後儲存生成的檔案 的路徑data_path = r'D:\Data_Sets_of_Analysis\Project_1_Analysis_of_Credit_Card_User_Portrait_and_Personal_Loan_Overdue\Data_Set_of_Credit_Card_User_Portrait_and_Personal_Loan_Overdue'save_file_path = r'D:\Data_Sets_of_Analysis\Project_1_Analysis_of_Credit_Card_User_Portrait_and_Personal_Loan_Overdue'os.chdir(data_path)load_file = os.listdir()print('\n' + '-'*25 + '列印目錄列表' + '-'*25)   # 提示性分割線,方便閱讀print(load_file)    # 列印目錄列表print('\n'*2 + '='*100)  # 列印模組分割線,方便閱讀

2.利用pandas讀取csv檔案

In [2]:
'''【 #%% 】 開啟 View → Scientific Model 後點選其左邊的綠色剪頭 可分塊執行'''# # 因檔案較多,故採用for迴圈結合locals函式動態生成多個變數table_name = []columns_name_express = []columns_name = []sql_statement = []dataframe_table_columns_name = pd.DataFrame(columns=['表名','列名'])    # 生成一個空的DataFrame,其列名為'表名'和'列名'for i in load_file:
    # 僅讀取字尾為csv的表
    if i.split('.')[1] == 'csv':
        # locals()方法動態生成多個變數
        locals()[i.split('.')[0]] = pd.read_csv(i,encoding='gbk',error_bad_lines=False,low_memory=False)
        # 將 每一個表的名字 都新增到 table_name 列表中
        table_name.append(i.split('.')[0])
        # 將 每一個表的名字 連同 每一列的名字都新增到 columns_name_express 和 columns_name 列表中
        columns_name.append(list(locals()[i.split('.')[0]]))
        columns_name_express.append([i.split('.')[0] + '表的列名如下:' + str(list(locals()[i.split('.')[0]])) + '\n' + '-'*125])
        '''        i.split('.')[0] → 每一個表的名字        str(list(locals()[i.split('.')[0]])) → 1.locals[]中填入表示 表名 的i.split('.')[0],表示選中該locals中的該表                                               2.list(DataFrame)表示DataFrame的列名,故list(locals()[i.split('.')[0]])                                                 表示的是 取 表名 為i.split('.')[0] 的 表 的 列名                                               3.最後因為要連線字串,所以用str()函式將以上進行轉換        '\n'  → 表示換行        '-'*125 → 將字串- 乘以125 表示列印 - 125次  作用為畫一個分割線,方便觀察,不易序列        最後將以上欄位轉換為列表,用append新增到 columns_name 中        '''
        # 為方便用線上工具畫ER圖,在此順便生成sql語句
        tn_str =''
        for j in range(len(list(locals()[i.split('.')[0]]))):
            tn_str += '\n' + list(locals()[i.split('.')[0]])[j] + ' ' + 'TEXT' + ','
        sql_statement.append('CREATE TABLE' + ' ' + i.split('.')[0] + '\n' + '(' + tn_str.strip(',') + '\n' + ');')
        # 生成一個 表格、列名透視表 並輸出excel檔案 方便作 列名分析
        dataframe_table_columns_name = dataframe_table_columns_name.append(pd.DataFrame({'表名':i.split('.')[0],'列名':columns_name[-1]}))

3.生成sql語句

In [ ]:
# 生成sql語句,複製後進入freego透過匯入MySQL DDL自動生成ER表格,再新增關係線即可生成ER圖# # 註釋:列屬性在此統一設定為【 TEXT 】僅為方便觀察使用print('線上MySQL語句繪製ER圖連結:複製以下生成的sql語句')for i in sql_statement:
    print(i)

Image Name

4.生成一個 表格、列名透視表 並輸出excel檔案 方便作 列名分析

In [ ]:
dataframe_table_columns_name['列名含義分析'] = 0  # 為作pivot圖,增加全為0的數值列,之後再用replace(),將0全部替換為空warning = '? 注意:.to_excel重複執行會覆蓋原有表,故在正式編輯excel時應注意另存為新表,或建立一個副本'   # 增加一列提示,方便表格使用pivot_table_column_name = pd.pivot_table(dataframe_table_columns_name,index=['表名','列名']).replace(0,'').append(pd.DataFrame(columns=[warning]))print('\n'*2 + '='*100)  # 列印模組分割線,方便閱讀# 將以上得到的表格儲存到路徑為最開始已經設定好的【 save_file_path 】中,表格名稱為【 列名含義分析表(請另存).xls 】try:
    pivot_table_column_name.to_excel(save_file_path +r'\列名含義分析表(請另存).xls')
    print('表格已儲存完畢')except Exception:
    print('錯誤,‘列名含義分析表(請另存).xls’未能成功儲存,請檢查報錯原因(如:重複執行時,原先檔案開啟未關閉,則後一次執行程式碼時,無法覆蓋原檔案報錯)')

Image Name

5. 列出剛剛讀取的所有表名

In [ ]:
print('\n'*2 + '='*100)  # 列印模組分割線,方便閱讀print('所有表格的名稱如下:' + str(table_name))

6.將每個表的列名(column)分別列印出來

In [ ]:
print('\n'*2 + '='*100)  # 列印模組分割線,方便閱讀for i in range(len(columns_name_express)):
    print(columns_name_express[i][0])

7.將每個表格的前5行列印出來,初步觀察資料

In [ ]:
print('\n'*2 + '='*100)  # 列印模組分割線,方便閱讀for tn in load_file:
    col_num = len(list(locals()[tn.split('.')[0]]))
    pd.set_option('display.max_columns', col_num)   # 設定顯示最大列數為 表的列數
    print('\n' + '-'*25 + '以下為%s表'% tn.split('.')[0] + '-'*25)
    print(locals()[tn.split('.')[0]].head())    # 預設列印前五行
    print(locals()[tn.split('.')[0]].count())   # 列印各列資料數

8.建立一個函式用於 查詢可選取值、空值

In [ ]:
# 建立一個函式用於 有選擇性地 查詢 某個表 中 某個列 的 可選取值(透過該函式還能檢視是否有空值)# # 1) 因def中無法直接引用local,故將所有的表格儲存到一個字典中,鍵 為 表名,值 為 表dict_of_tables = dict()for i in load_file:
    dict_of_tables[i.split('.')[0]] = locals()[i.split('.')[0]]     # 使用for迴圈將每一個表都增加到dict_of_tables中# # 2) 進行函式定義def check_distinct_column_value():
    while True:
        t_name = input('請輸入 表名 :')
        c_name = input('請輸入 列名 :')
        try:
            import sqlite3
            con = sqlite3.connect(':memory:')
            dict_of_tables[t_name].to_sql(t_name,con)
            c_value = pd.read_sql_query('select distinct %s from %s' % (c_name, t_name), con)
            print(c_value)
            break
        except Exception:
            print('發生錯誤,請檢查所輸入的 表名 和 列名 及其對應關係 是否正確,並重新輸入')# # 3) 選擇是否呼叫該查詢函式while True:
    answer = input('是否呼叫“列名取值檢視”函式?回答 Y 或 N:')
    if answer == 'Y':
        check_distinct_column_value()   #函式呼叫
    elif answer == 'N':
        break
    else:
        print('輸入有誤,請重新輸入回答,僅可回答Y 或 N')

-- 【 Part 1 信用卡使用者畫像 】 --

(二) 資料清洗及繪圖 之 信用卡使用者畫像

1.將匯入的檔案註冊到sql裡

In [ ]:
'''透過 信用卡客戶畫像 的 目標拆解 和 ER圖,需要用到 card、clients、disp(連線關係用)、trans根據之前的資料檢視,這三個表中均無缺失的情況'''import sqlite3con = sqlite3.connect(':memory:')locals()['card'].to_sql('card',con)locals()['clients'].to_sql('clients',con)locals()['disp'].to_sql('disp',con)locals()['trans'].to_sql('trans',con)

2.寫sql語句透過disp表,將card和client兩個表連線起來

In [ ]:
sql_card_client = '''SELECT cd.*,ctdp.birth_date,ctdp.district_id,ctdp.sexFROM card AS cd JOIN (SELECT ct.birth_date,ct.district_id,ct.sex,dp.disp_id FROM clients AS ctJOIN disp AS dp ON ct.client_id = dp.client_id WHERE dp.type == "所有者")AS ctdpON cd.disp_id = ctdp.disp_id'''card_client = pd.read_sql_query(sql_card_client,con)print(card_client)

3.作圖 → 信用卡業務總體描述

1) 髮卡總量 隨 時間 的變化趨勢

In [ ]:
# # 因作圖可能涉及到中文,在此先設定字型from pylab import mplmpl.rcParams['font.sans-serif'] = ['SimHei']   # 指定預設字型mpl.rcParams['axes.unicode_minus'] = False      # 解決儲存影像負號'-'顯示為方塊的問題# # # 探究信用卡總量 不同年份 的總量變化import datetimeimport matplotlib.pyplot as plt# # # 新增一列‘issue_year’,提取出年份card_client['issue_year'] = pd.to_datetime(card_client['issued']).map(lambda x:x.year)# # # 建立一個交叉表,顯示不同類別卡,不同年份的發行數量cross_tab = pd.crosstab(card_client.issue_year,card_client.type)print(cross_tab)# # # 畫趨勢面積圖labels = ['青年卡','普通卡','金卡']y1 = cross_tab.loc[:,'青年卡'].astype('int')  # 將'青年卡'這列的每行的字元都轉換成inty2 = cross_tab.loc[:,'普通卡'].astype('int')y3 = cross_tab.loc[:,'金卡'].astype('int')x = cross_tab.index  # 將index列,也就是issue_year轉換成intplt.stackplot(x,y1,y2,y3,labels=labels,colors=['#f89588','#3b6291','#f8cb7f'])plt.title('信用卡髮卡量隨年份的時間變化趨勢圖')plt.legend(loc = 'upper left')  # 設定圖例位置在左上角plt.ylim(0,500)     # 設定y軸刻度最大值# # # 設定 數字標籤,表明對應的髮卡量for a,b in zip(x,y1):
    plt.text(a,0.5*b-10,b,ha='center', va= 'bottom',fontsize=7)for a,b in zip(x,y2):
    plt.text(a,0.5*b+list(y1)[list(x).index(a)]-10,b,ha='center', va= 'bottom',fontsize=7)for a,b in zip(x,y3):
    plt.text(a,0.5*b+list(y1)[list(x).index(a)]+list(y2)[list(x).index(a)]-10,b,ha='center', va= 'bottom',fontsize=7)plt.show()'''設定 數字標籤,格式示例:    for a,b in zip(x,y):        plt.text(a, b+0.05, '%.0f' % b, ha='center', va= 'bottom',fontsize=7)    a:數字標籤的橫軸座標    b+0.05:數字標籤的縱軸座標    '%.0f' % b:格式化的數字標籤(保留一位小數)    ha='center':horizontalalignment(水平對齊)    va= 'bottom':verticalalignment(垂直對齊)的方式    fontsize:文字大小本案例中的例項說明:    因本例為堆疊圖,故對數字標籤的縱軸座標相應做了值疊加    y1對應的數字標籤的縱座標放中間,並下移10(下移10是為了看起來更美觀)    y2對應的數字標籤的縱座標,要使其同樣放中間,則需用y2對應的b值乘以0.5再加上y1對應的b值,再下移10    y3對應的數字標籤的縱座標,要使其同樣放中間,則需用y3對應的b值乘以0.5再加上y1和y2對應的b值,再下移10'''

Image Name

2) 不同型別卡總髮行量佔比情況(餅圖)

In [ ]:
cross_tab1 = cross_tabcross_tab1.loc['Sum'] = 0for i in list(cross_tab):
    cross_tab1.loc['Sum'][i] = sum(cross_tab1[i])print(cross_tab1)plt.pie(cross_tab1.loc['Sum'],labels=list(cross_tab1),autopct='%1.1f%%',startangle=100,colors=['#3b6291','#f8cb7f','#f89588'])plt.title('不同種類卡的佔比情況')plt.show()

Image Name

補充stack2dim包的程式碼

  • ? ? 【 這裡補充下接下來所引用的stack2dim包的程式碼:
In [ ]:
# 如遇中文顯示問題可加入以下程式碼from pylab import mplmpl.rcParams['font.sans-serif'] = ['SimHei']  # 指定預設字型mpl.rcParams['axes.unicode_minus'] = False  # 解決儲存影像是負號'-'顯示為方塊的問題def stack2dim(raw, i, j, rotation=0, location='upper right'):
    '''    此函式是為了畫兩個維度標準化的堆積柱狀圖    raw為pandas的DataFrame資料框    i、j為兩個分類變數的變數名稱,要求帶引號,比如"school"    rotation:水平標籤旋轉角度,預設水平方向,如標籤過長,可設定一定角度,比如設定rotation = 40    location:分類標籤的位置,如果被主體圖形擋住,可更改為'upper left'    '''
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    import math
    data_raw = pd.crosstab(raw[i], raw[j])
    data = data_raw.div(data_raw.sum(1), axis=0)  # 交叉錶轉換成比率,為得到標準化堆積柱狀圖
    # 計算x座標,及bar寬度
    createVar = locals()
    x = [0]  # 每個bar的中心x軸座標
    width = []  # bar的寬度
    k = 0
    for n in range(len(data)):
        # 根據頻數計算每一列bar的寬度
        createVar['width' + str(n)] = list(data_raw.sum(axis=1))[n] / sum(data_raw.sum(axis=1))
        width.append(createVar['width' + str(n)])
        if n == 0:
            continue
        else:
            k += createVar['width' + str(n - 1)] / 2 + createVar['width' + str(n)] / 2 + 0.05
            x.append(k)
    # 以下是透過頻率交叉表矩陣生成一串對應堆積圖每一塊位置資料的陣列,再把陣列轉化為矩陣
    y_mat = []
    n = 0
    y_level = len(data.columns)
    for p in range(data.shape[0]):
        for q in range(data.shape[1]):
            n += 1
            y_mat.append(data.iloc[p, q])
            if n == data.shape[0] * data.shape[1]:
                break
            elif n % y_level != 0:
                y_mat.extend([0] * (len(data) - 1))
            elif n % y_level == 0:
                y_mat.extend([0] * len(data))
    y_mat = np.array(y_mat).reshape(-1, len(data))
    y_mat = pd.DataFrame(y_mat)  # bar圖中的y變數矩陣,每一行是一個y變數
    # 透過x,y_mat中的每一行y,依次繪製每一塊堆積圖中的每一塊圖
    from matplotlib import cm
    cm_subsection = [level for level in range(y_level)]
    colors = [cm.Pastel1(color) for color in cm_subsection]
    bottom = [0] * y_mat.shape[1]
    createVar = locals()
    for row in range(len(y_mat)):
        createVar['a' + str(row)] = y_mat.iloc[row, :]
        color = colors[row % y_level]
        if row % y_level == 0:
            bottom = bottom = [0] * y_mat.shape[1]
            if math.floor(row / y_level) == 0:
                label = data.columns.name + ': ' + str(data.columns[row])
                plt.bar(x, createVar['a' + str(row)],
                        width=width[math.floor(row / y_level)], label=label, color=color)
            else:
                plt.bar(x, createVar['a' + str(row)],
                        width=width[math.floor(row / y_level)], color=color)
        else:
            if math.floor(row / y_level) == 0:
                label = data.columns.name + ': ' + str(data.columns[row])
                plt.bar(x, createVar['a' + str(row)], bottom=bottom,
                        width=width[math.floor(row / y_level)], label=label, color=color)
            else:
                plt.bar(x, createVar['a' + str(row)], bottom=bottom,
                        width=width[math.floor(row / y_level)], color=color)
        bottom += createVar['a' + str(row)]
    plt.title(j + ' vs ' + i)
    group_labels = [str(name) for name in data.index]
    plt.xticks(x, group_labels, rotation=rotation)
    plt.ylabel(j)
    plt.legend(shadow=True, loc=location)
    plt.show()
  • 補充結束 】 ? ?
    ## 4.基本屬性特徵

1) 不同卡型別的性別比較堆積圖

In [ ]:
from stack2dim import *stack2dim(card_client,'type','sex')

Image Name

2) 不同卡型別的年齡比較

In [ ]:
import seaborn as snsimport timecard_client['age']=(pd.to_datetime(card_client['issued'])-pd.to_datetime(card_client['birth_date']))card_client['age1']=card_client['age'].map(lambda x:x.days/365)ax_age = sns.boxplot(x = 'type', y = 'age1', data = card_client,palette=sns.xkcd_palette(['gold','windows blue','coral']))ax_age.set_title('不同卡型別的 年齡 比較')plt.show()

Image Name

3) 不同型別卡的持卡人在辦卡前一年內的平均帳戶餘額對比

In [ ]:
sql_card_client_trans = '''select a.card_id,a.issued,a.type,c.type as t_type,c.amount,c.balance,c.date as t_date  from card as a  left join disp as b on a.disp_id=b.disp_id  left join trans as c on b.account_id=c.account_id  where b.type="所有者"  order by a.card_id,c.date'''card_client_trans = pd.read_sql_query(sql_card_client_trans,con)# print(card_client_trans.head())# # 標準化日期card_client_trans['issued']=pd.to_datetime(card_client_trans['issued'])card_client_trans['t_date']=pd.to_datetime(card_client_trans['t_date'])print(card_client_trans)# # 對帳戶餘額進行清洗:去掉金額單位和逗號分隔,便於計算card_client_trans['balance_1'] = card_client_trans['balance'].map(lambda x:int(x.strip('$').replace(',','')))print(card_client_trans)# # 篩選出開卡前一年的資料card_client_trans_1 = card_client_trans[card_client_trans.issued > card_client_trans.t_date][
    card_client_trans.t_date >= card_client_trans.issued-datetime.timedelta(days=365)]print(card_client_trans_1)# #  分組計算餘額均值card_client_trans_1['avg_balance'] = card_client_trans_1.groupby('card_id')['balance_1'].mean()card_client_trans_2 = card_client_trans_1.groupby(['type','card_id'])['balance_1'].agg([('avg_balance','mean')])# print(card_client_trans_1)# print(card_client_trans_2)card_client_trans_2.to_sql('card_client_trans_2',con)card_client_trans_3 = card_client_trans_2.reset_index()card_client_trans_3 = pd.read_sql('select * from card_client_trans_2', con)colors = ['windows blue','gold','coral']ax_balance = sns.boxplot(x='type',y='avg_balance',data=card_client_trans_3,palette=sns.xkcd_palette(colors))ax_balance.set_title('不同型別卡的持卡人在辦卡前一年內的 平均帳戶餘額 對比')plt.show()

Image Name

4) 不同型別持卡人在辦卡前一年內的平均收入和平均支出對比

In [ ]:
# # # # # 先將 借、貸 轉換成 更易理解的 out、incometype_dict = {'借':'out','貸':'income'}card_client_trans_1['type_1'] = card_client_trans_1.t_type.map(type_dict)# # # 將amount金額列的 金額單位 和 逗號分隔 去掉card_client_trans_1['amount_1'] = card_client_trans_1['amount'].apply(lambda x:int(x.strip('$').replace(',','')))card_client_trans_4 = card_client_trans_1.groupby(['type','card_id','type_1'])[['amount_1']].sum()card_client_trans_4.head()card_client_trans_4.to_sql('card_client_trans_4',con)card_client_trans_5 = card_client_trans_4.reset_index()card_client_trans_5.to_sql('card_client_trans_5',con)card_client_trans_6 = pd.read_sql_query('select * from card_client_trans_5 where type_1 = "income"',con)ax_amount_income = sns.boxplot(x='type',y='amount_1',data=card_client_trans_6,palette=sns.xkcd_palette(['gold','windows blue','coral']))ax_amount_income.set_title('不同型別持卡人在辦卡前一年內的 平均收入 對比')plt.show()card_client_trans_7 = pd.read_sql_query('select * from card_client_trans_5 where type_1 = "out"',con)ax_amount_out = sns.boxplot(x='type',y='amount_1',data=card_client_trans_6,palette=sns.xkcd_palette(['gold','windows blue','coral']))ax_amount_out.set_title('不同型別持卡人在辦卡前一年內的 平均支出 對比')plt.show()

Image Name

Image Name

In [ ]:
'''【信用卡客戶畫像總結分析】:(一)總體趨勢(近六年):    1.逐年髮卡量:金卡、普通卡均呈逐年上升趨勢,青年卡在1997年的發行量同比降低,但總體為上升趨勢;                逐年發行量佔比排名為:普通卡 > 青年卡 > 金卡    2.總髮卡量:總體髮卡量佔比排名為:普通卡 > 青年卡 > 金卡,其中普通卡佔比接近總髮卡量的3/4(二)基本屬性特徵    1.不同卡型別的 性別 比較:        普通卡和青年卡 男女性比例較為均衡,基本為1:1;金卡的男性持有者比例相較女性持有者明顯更多    2.不同卡型別的 年齡 比較:        普通卡和金卡的持有者年齡主要集中在30~60歲之間;而青年卡則普遍集中在25歲以內,卡型別設計與目標物件相符    3.不同型別卡的持卡人在辦卡前一年內的 平均帳戶餘額 對比:        金卡持有者的辦卡前一年的 平均餘額 是要顯著高於 普通卡 和 青年卡 的,卡型別設計與目標物件相符    4.不同型別持卡人在辦卡前一年內的 平均收入和平均支出 對比:        三種型別的 平均收入、平均支出 排序均符合:金卡 > 普通卡 > 青年卡,金卡的持有人群為收入較高的群體,        同樣其支出情況也相應高於普通持卡人群,而青年卡,由於其持卡人群多為年齡層較小的人群,收入支出均較低,        卡型別設計與目標物件情況相符'''

-- 【 Part 2 貸款違約預測模型 】 --

(三) 資料清洗 之 貸款違約預測模型

'''
1.時間點的選擇:選取放款時間點之前的一年,觀察使用者是否有逾期行為
2.loans表中的貸款狀態說明:A代表合同終止,沒問題;B代表合同終止,貸款沒有支付;
C代表合同處於執行期,至今正常;D代表合同處於執行期,欠債狀態。
A貸款正常還款,B、D有問題,C待定
'''

1.使用者資訊 → 將 性別、年齡、信用卡資訊 新增到loans表中

In [ ]:
# # 1)在loans表中增加一列,用數字來代替貸款狀態,方便後續分析loan_status = {'B':1,'D':1,'A':0,'C':2}locals()['loans']['loan_status'] = locals()['loans']['status'].map(loan_status)print(locals()['loans'])# # 2) 進行列新增'''透過disp表連線loans表和clients表'''data_1 = pd.merge(locals()['loans'],locals()['disp'],>

2.狀態資訊 → 將 相關列新增

In [ ]:
# # 1)新增地區狀態資訊data_5 = pd.merge(data_4,locals()['district'],left_on='district_id',right_on='A1',how='left')# # # 將需要的列篩選出來trans_clients_district = data_5[['account_id','amount','duration','payments','loan_status','type_y','sex',
                                 'age', 'district_id','GDP','A4','A10','A11','A12','A13','A14','A15','a16']]# # 2) 客戶個人經濟狀況資訊trans_loans = pd.merge(locals()['trans'],locals()['loans'],on='account_id')print(trans_loans.head())print(list(trans_loans))'''合併後出現的列名後帶_x和_y是因為合併的兩張表中有有相同的列名,為示區分加上的字尾;_x表示的是合併前'trans'表中的列,_y表示的是合併前'loans'表中的列'''

3.篩選資料

In [ ]:
# # 篩選出貸款前一年的交易資料trans_loans['date_x'] = pd.to_datetime(trans_loans['date_x'])trans_loans['date_y'] = pd.to_datetime(trans_loans['date_y'])# # 將 amount_x 和 balance 由字串型別,去掉$符號和逗號分隔,轉化為數值型別trans_loans['amount_x'] = trans_loans['amount_x'].apply(lambda x:int(x.strip('$').replace(',','')))trans_loans['balance'] = trans_loans['balance'].apply(lambda x:int(x.strip('$').replace(',','')))# # 篩選出放款日期按1年內至前一天的交易記錄trans_loans = trans_loans[(trans_loans['date_x']<trans_loans['date_y'])&((trans_loans['date_x']+datetime.timedelta(days=365))>trans_loans['date_y'])]# # 篩選使用者前一年內結息總額trans_loans_1 = trans_loans[trans_loans['k_symbol']=='利息所得']trans_loans_1 = pd.DataFrame(trans_loans_1.groupby('account_id')['amount_x'].sum())trans_loans_1.columns = ['interest']# # 篩選在本行是否由養老金和房屋貸款trans_loans_2 = trans_loans[(trans_loans['k_symbol']=='養老金')|(trans_loans['k_symbol']=='房屋貸款')]# # 標記是否在本行有房屋貸款trans_loans_2 = pd.DataFrame(trans_loans_2.groupby('account_id')['k_symbol'].count())trans_loans_2['house_loan'] = '1'del trans_loans_2['k_symbol']# # 篩選客戶一年內收入和支出(總和)print(trans_loans_2.head())trans_loans_3 = pd.DataFrame(trans_loans.pivot_table(values='amount_x',index='account_id',columns='type'))trans_loans_3.columns = ['out','income']# # 篩選客戶一年內餘額的均值和標準差trans_loans_4 = pd.DataFrame(trans_loans.groupby('account_id')['balance'].agg(['mean','std']))trans_loans_4.columns = ['balance_mean','balance_std']# # 合併資料data_temp = pd.merge(trans_loans_1,trans_loans_2,how='left',left_index=True,right_index=True)data_temp = pd.merge(data_temp,trans_loans_3,left_index=True,right_index=True)data_temp = pd.merge(data_temp,trans_loans_4,left_index=True,right_index=True)print(len(data_temp)) # 檢視資料條數是否與貸款表條數一致data_model = pd.merge(trans_clients_district,data_temp,left_on='account_id',right_index=True)print(data_model)

(四) 模型構建

1. 資料清洗 及 變數選擇

1) 檢視資料缺失情況

In [ ]:
print(data_model.isnull().sum()/len(data_model))'''分析缺失情況、原因及處理:    總共有 列存在缺失資訊    type_y:缺失資訊接近75%,資訊缺失過多,刪除列;    A12 和 A15:A12 1995年失業率 和 A15 1995犯罪率(千人) 有極小部分資料缺失,因為是連續資料,使用中位數填充;    house_loan:是否有房屋貸款,缺失值為沒有房屋貸款,填充字元‘0’'''

2) 資料處理

In [ ]:
del data_model['type_y']data_model['A12'].fillna(data_model['A12'].median(),inplace=True)data_model['A15'].fillna(data_model['A15'].median(),inplace=True)data_model['house_loan'].fillna('0',inplace=True)

3) 對變數進行分類

In [ ]:
# # # <1> 因變數y = 'loan_status'# # # <2> 連續變數var_c =  ['amount', 'duration', 'payments', 'GDP',
       'A4', 'A10', 'A11', 'A12', 'A13', 'A14', 'A15', 'a16', 'age',
       'interest', 'out', 'income', 'balance_mean',
       'balance_std']# # # <3> 分類變數var_d = ['sex','house_loan']# # # # 對 sex 和 house_loan 兩個分類變數二值化,方便分析data_model['sex_kind'] = data_model['sex'].map({'男':1,'女':0})data_model['house_loan_kind'] = data_model['house_loan'].map({'1':1,'0':0})

4) 使用 熱力圖 檢視個變數間的關係

In [ ]:
import matplotlib.pyplot as pltimport seaborn as snscorr = data_model[var_c+var_d].corr()plt.figure(figsize=(12,9))  # 指定寬和高(單位:英寸)sns.heatmap(corr,vmax=1,annot=True) # vmax設定熱力圖顏色取值的最大值;annot設定是否顯示格子數字plt.show()

Image Name

5) 選擇最終模型使用的變數

In [ ]:
'''從熱力圖觀察可知:    貸款資訊、居住地資訊、經濟狀況資訊內各變數具有高相關性,對變數進行篩選及轉換    1. 貸款資訊中:保留amount    2. 居住地資訊:                1) 採用人均GDP,即對變數進行轉換;                2) 採用失業增長率    3. 經濟狀況資訊:                1) 客戶放款前近一年總結息(反應實際存款數額)                2) 收支比(反應客戶消費水平)                3) 可用餘額變異係數(反應客戶生活狀態穩定係數)'''data_model['GDP_per'] = data_model['GDP']/data_model['A4']  # 人均GDP人民生活水平的一個標準data_model['unemployment'] = data_model['A13']/data_model['A12']    # 失業增長率一定程度上反應經濟增長率data_model['out/in'] =  data_model['out']/data_model['income']  # 消費佔收入比重,一定程度反應客戶消費水平data_model['balance_a']  =  data_model['balance_std']/data_model['balance_mean']    # 可用餘額變異係數var = ['account_id','sex_kind','age','amount','GDP_per','unemployment','out/in','balance_a']# print(data_model)# print(list(data_model))

2. 邏輯迴歸構建

In [ ]:
data_model = data_model[var+[y]]for_predict = data_model[data_model[y]==2]  # loan_status 為2表示狀態C,即:待定data_model = data_model[data_model[y]!=2]# # 定義自變數和因變數import numpy as npX = data_model[var]Y = data_model[y]# # 將樣本資料建立訓練集和測試集,測試集取20%的資料from sklearn.model_selection import train_test_splitx_train, x_test, y_train, y_test = train_test_split(X, Y, test_size = 0.2, random_state = 1234)

3. 建模(使用邏輯迴歸L1正則化引數)

In [ ]:
from sklearn.linear_model import LogisticRegressionLR = LogisticRegression(penalty='l1',solver='liblinear')var_temp = ['sex_kind','age','amount','GDP_per','unemployment','out/in','balance_a']x_train_temp = x_train[var_temp]clf = LR.fit(x_train_temp,y_train) # 擬合x_test_temp = x_test[var_temp]y_pred = clf.predict(x_test_temp)   # 預測測試集資料test_result = pd.DataFrame({'account_id':x_test['account_id'],'y_predict':clf.predict(x_test_temp)})new_test_result = test_result.reset_index(drop=True)print(test_result)  # 輸出測試集中 account_id 對應的貸款狀態預測print(new_test_result)  # 輸出測試集中 account_id 對應的貸款狀態預測print(clf.coef_)    #檢視各變數的迴歸係數

4. 建模結果評價

In [ ]:
from sklearn.metrics import classification_reportprint(classification_report(y_test, y_pred))'''模型的精確率0.87,召回率0.84,f1_score為0.82'''

5. 繪製ROC曲線

In [ ]:
from sklearn.metrics import roc_curve, aucfpr, tpr, threshold = roc_curve(y_test, y_pred)roc_auc = auc(fpr, tpr)plt.plot(fpr, tpr, color='darkorange',label='ROC curve (area = %0.2f)' % roc_auc)plt.plot([0, 1], [0, 1], color='navy',  linestyle='--')plt.xlim([0.0, 1.0])plt.ylim([0.0, 1.0])plt.xlabel('False Positive Rate')plt.ylabel('True Positive Rate')plt.title('ROC_curve')plt.legend(loc="lower right")plt.show()

Image Name


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69977871/viewspace-2700681/,如需轉載,請註明出處,否則將追究法律責任。

相關文章