題目鏈接:https://cloud.189.cn/t/ri2uUb7BRVJr
前言
又是一年數據挖掘題型,第一次接觸這種題型還是在去年的mathorcup上,這種題的難度就在於指標的建立和數據的處理上。后面會出一份關於數據挖掘題型,我的相關經驗,常用的工具和代碼。
下面的一,二問實際都在解決
- 貸不貸款?
- 貸款金額多少?年利率多少?
數據清洗
這道題的附件數據沒有出現缺省或者異常數據,因此對於數據的預處理,更多的是根據問題的需求來做的。
-
將是否違約,違約設置為1,不違約設置為0
-
信譽等級ABCD分別對應4,3,2,1
-
發票狀態,有效發票為a,作廢發票為b
-
我將銷項和進項所有數據,以公司代碼為區別,提取到了不同的sheet當中,對於該公司
有效發票數
,作廢發票數
,負數發票數
,方便對數據觀察。
# 遍歷所有sheet數據
for xsn in sn.sheet_names[1:]:
# 讀取文件
datas = pd.read_excel(file_pos, sheet_name=xsn)
datas['date']=pd.to_datetime(datas['date'],format='%Y/%m/%d')
datas.set_index('date', drop=True)
# 找到全部公司名稱代號
code_list = list(set(list((datas['code']))))
for name in code_list:
tmp_datas = datas[datas['code'] == name]
tmp_datas.index = range(len(tmp_datas))
# 轉換日期未object類型
tmp_datas['date'] = [x.strftime('%Y/%m/%d') for x in tmp_datas['date']]
count1 = tmp_datas['tax_status'].value_counts()
tmp_datas['a_count'] = list(count1)[0]
if(len(count1) > 1):
tmp_datas['b_count'] = list(count1)[1]
tmp2 = tmp_datas[tmp_datas['cost'] < 0]
tmp_datas['neg_value_tax'] = len(tmp2)
if xsn == sn.sheet_names[1]:
tmp_datas.to_excel(writer1,sheet_name=name,index=False)
else:
tmp_datas.to_excel(writer2,sheet_name=name,index=False)
負數發票:在之前購買的物品,並開具了相關正向發票,后來退貨所以開具了值為負數的發票,抵消前面正數發票的值。
提取到信息:
- 部分公司數據記錄很少,或者時間跨度大,需要綜合數據指標,抵消數據數量和跨度大的影響
- 有些負數發票,在之前找不到對應的正數發票,可能是因為在數據記錄日期之前購買的,在之后退款,因此在附件中找不到記錄。
問題一
※ 信譽等級為D的公司,我們采取不貸款策略,但是在實際模型分析中,需要將D等級的公司代入分析,簡稱工具人。
建立指標
進項發票作廢率
,進項負數發票率
,進項每月平均交易額
,進項每月交易次數
,
銷項發票作廢率
,銷項負數發票率
,銷項每月平均交易額
,銷項每月交易次數
,銷售收入增長率
提取出相關指標到附件
for xsn in sn.sheet_names[1:]:
# 讀取文件
datas = pd.read_excel(file_pos, sheet_name=xsn)
code_list = list(set(list((datas['code']))))
for name in code_list:
tmp_datas = datas[datas['code'] == name]
tmp_datas.index = range(len(tmp_datas))
insert_datas.append(name)
# 作廢數
cacel_count = len(tmp_datas[tmp_datas['tax_status'] == 'b'])
# 有效數
valid_count = len(tmp_datas[tmp_datas['tax_status'] == 'a'])
# 發票作廢率
count1 = (cacel_count / (cacel_count + valid_count))*100
# 負數發票數
neg_count = len(tmp_datas[tmp_datas['cost'] < 0])
# 負數發票率
count2 = (neg_count / valid_count) * 100
# 轉換時間
tmp_datas['date'] = [x.strftime('%Y/%m/%d') for x in tmp_datas['date']]
# 時間最大值
max_time = tmp_datas.iloc[0:,1].max()
# 時間最小值
min_time = tmp_datas.iloc[0:,1].min()
# 時間差
diff_time = months(max_time, min_time) + 1
# 有效票
valid_tax = tmp_datas[tmp_datas['tax_status'] == 'a']
# 平均月交易額
avg_money = valid_tax['totle_cost'].sum() / diff_time
# 平均每月交易次數
trans_count = len(tmp_datas) / diff_time
insert_datas += [count1, count2, avg_money, trans_count,]
if flag:
df1.loc[len(df1)] = insert_datas
df1.to_excel(writer1,sheet_name='進項信息',index=False)
else:
merge_time = tmp_datas.groupby(tmp_datas['date']).sum()
# 銷售收入增長率
income_info= list((merge_time['cost'] - merge_time['cost'].shift(1)).fillna(1))
diff_time_day = days(max_time,min_time)
income_tax = (sum(income_info) / diff_time_day)*100
insert_datas.append(income_tax)
df2.loc[len(df2)] = insert_datas
df2.to_excel(writer1,sheet_name='銷項信息',index=False)
insert_datas = []
flag = False
並將是否違約插入到最后一列
# 提取是否違約的列表
m = []
for name in code_list:
m.append(datas[datas['code']==name]['break_contract'].tolist()[0])
df1.loc[:,len(df1)] = m
df1.to_excel(writer3,sheet_name='sheet1',index=False)
建立模型
Logistics違約率預測模型
使用Logistics違約預測模型
,代入所有的指標數據為自變量,是否違約為因變量,預測出違約率。
X=datas[['進項發票作廢率','進項負數發票率','進項每月平均交易額','進項每月交易次數','銷項發票作廢率','銷項負數發票率','銷項每月平均交易額','銷項每月交易次數','銷售收入增長率']]
y=datas['是否違約']
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2,random_state=2020)
X_validation, X_test, y_validation, y_test = train_test_split(X_test,y_test, test_size=0.1,random_state=2020)
model = LogisticRegression()
model.fit(X_train,y_train)
a=model.predict_proba(X_validation)
result=[]
for i in range(len(a)):
if a[i][1]>0.5:
result.append(1)
else:
result.append(0)
from sklearn import metrics
print('誤差: %.4f' % (1-metrics.recall_score(y_validation,result,average='weighted')))
最終得到一張我們的分析表格
通過預測是否違約,我們就能解決貸不貸款的問題。
貸款金額
貸款金額的確認,根據該公司不違約率在所有公司中的權重,乘以總貸款金額確認:
因此,我們得到的貸款金額是違約率和貸款總金額組成的關系式,這在第二問中能起到重要作用。
貸款年利率
繪制出年利率與客戶流失率圖,可以分析出兩者應該是有關系的。利用SPSS擬合出不同信譽等級,年利率與客戶流失率的關系式。
信譽等級 | R平方 | 關系式 |
---|---|---|
A | 0.9977 | y = 37.97x3-258.57*x2+640.944x -1.121 |
B | 0.9982 | y = 33.995x3-225.051*x2+552.829x-1.017 |
C | 0.9982 | y = 32.157x3-207.386*x2+504.717x-0.973 |
銀行獲利=貸款金額x貸款年利率x(1-利率對於信譽評級客戶流失率)
在貸款金額確認,貸款年利率范圍在0.4~1.5的情況下,利用上面擬合的關系式,我們能夠暴力跑出最優年利率。
double turnover_rate(double x, char ch) {
double y = 0, result = 0;
switch (ch) {
case 'A':
y = 37.969520 * pow(x, 3) - 258.570452 * pow(x, 2) + 640.944427 * x - 1.121484;
result = x * (1 - y / 100.0);
break;
case 'B':
y = 33.994698 * pow(x, 3) - 225.050538 * pow(x, 2) + 552.829151 * x - 1.016503;
result = x * (1 - y / 100.0);
break;
case 'C':
y = 32.156864 * pow(x, 3) - 207.385880 * pow(x, 2) + 504.716993 * x - 0.973497;
result = x * (1 - y / 100.0);
break;
default:
cout << "輸出有誤!" << ch << endl;
}
return result;
}
問題二
- 利用代碼,重新計算出各指標數據
- 代入Logistics違約率預測模型,預測出各公司的違約率
- 根據
標准普爾評級
建立,主標尺,對不同違約率進行A~D等級划分,信譽等級D不予貸款 - 將違約率代入,之前得到的公式,得到具體貸款金額
- 最優年利率沿用上一問
# 信用等級
cs = []
# 最優年利率,客戶流失率,利率值
tax = []
for i in m:
if i <= 0.0069264:
cs.append('A')
tax.append([0.083,0.503173,0.0412366])
elif i > 0.0069264 and i <= 0.22619:
cs.append('B')
tax.append([0.097,0.505215,0.0479942])
elif i > 0.22619 and i <= 0.509915:
cs.append('C')
tax.append([0.1069,0.506501,0.052755])
elif i > 0.509915:
cs.append('D')
tax.append([0.15,0,0])
else:
print('違規')
parr = []
for arr in list(a):
parr.append(list(arr)[0])
sum_val = sum(parr)
amount = []
for ival in parr:
tmp = ival / sum_val * 100000000
if ival < 1 - 0.509915:
amount.append(0)
else:
amount.append(tmp)
可以看到,貸款金額也都在10w~100w之內。
問題三
這一問,我們做得有些匆忙了,有其他想法的可以按照自己的想法做做,這里只拿我們的做參照。
疫情對公司影響最大的就是每月平均銷售額
和每月平均銷售數量
,因此,
- 對
每月平均銷售額
和每月平均銷售數量
,分別取隨機數,數量取10w組,其他指標數據值不變,是否違約數據根據第二問結果,設為初始值 - 代入Logistics模型中,預測出每一組的違約率
- 判斷每個公司違約率的變化情況,根據變化情況來增/減貸款金額和年利率。
rand_num = pro_rand()
data_form = {'E377':[], 'E311':[], 'E297':[], 'E386':[], 'E233':[], 'E367':[], 'E194':[], 'E196':[], 'E249':[], 'E205':[], 'E159':[], 'E300':[], 'E346':[], 'E395':[], 'E360':[], 'E200':[], 'E195':[], 'E247':[], 'E317':[], 'E303':[], 'E135':[], 'E217':[], 'E298':[], 'E325':[], 'E335':[], 'E157':[], 'E287':[], 'E270':[], 'E286':[], 'E212':[], 'E261':[], 'E423':[], 'E390':[], 'E189':[], 'E129':[], 'E366':[], 'E191':[], 'E246':[], 'E406':[], 'E357':[], 'E349':[], 'E387':[], 'E372':[], 'E385':[], 'E209':[], 'E267':[], 'E414':[], 'E316':[], 'E341':[], 'E281':[], 'E206':[], 'E363':[], 'E166':[], 'E190':[], 'E140':[], 'E130':[], 'E225':[], 'E347':[], 'E356':[], 'E185':[], 'E192':[], 'E207':[], 'E234':[], 'E136':[], 'E379':[], 'E274':[], 'E383':[], 'E242':[], 'E361':[], 'E408':[], 'E198':[], 'E204':[], 'E413':[], 'E389':[], 'E253':[], 'E226':[], 'E231':[], 'E182':[], 'E318':[], 'E392':[], 'E275':[], 'E425':[], 'E388':[], 'E305':[], 'E155':[], 'E348':[], 'E400':[], 'E256':[], 'E351':[], 'E201':[], 'E345':[], 'E278':[], 'E306':[], 'E308':[], 'E215':[], 'E382':[], 'E407':[], 'E376':[], 'E291':[], 'E369':[], 'E139':[], 'E296':[], 'E260':[], 'E145':[], 'E257':[], 'E216':[], 'E125':[], 'E312':[], 'E354':[], 'E380':[], 'E326':[], 'E268':[], 'E186':[], 'E179':[], 'E223':[], 'E319':[], 'E422':[], 'E224':[], 'E151':[], 'E293':[], 'E302':[], 'E309':[], 'E378':[], 'E373':[], 'E364':[], 'E144':[], 'E162':[], 'E208':[], 'E399':[], 'E355':[], 'E197':[], 'E375':[], 'E368':[], 'E334':[], 'E172':[], 'E254':[], 'E352':[], 'E230':[], 'E313':[], 'E255':[], 'E273':[], 'E220':[], 'E169':[], 'E126':[], 'E237':[], 'E153':[], 'E164':[], 'E344':[], 'E284':[], 'E320':[], 'E328':[], 'E143':[], 'E338':[], 'E290':[], 'E251':[], 'E314':[], 'E330':[], 'E424':[], 'E183':[], 'E248':[], 'E181':[], 'E271':[], 'E403':[], 'E359':[], 'E324':[], 'E304':[], 'E213':[], 'E412':[], 'E337':[], 'E235':[], 'E371':[], 'E283':[], 'E391':[], 'E370':[], 'E173':[], 'E158':[], 'E171':[], 'E299':[], 'E310':[], 'E174':[], 'E295':[], 'E402':[], 'E419':[], 'E327':[], 'E294':[], 'E339':[], 'E203':[], 'E285':[], 'E410':[], 'E241':[], 'E152':[], 'E228':[], 'E245':[], 'E263':[], 'E404':[], 'E160':[], 'E292':[], 'E397':[], 'E329':[], 'E163':[], 'E301':[], 'E365':[], 'E178':[], 'E288':[], 'E175':[], 'E211':[], 'E252':[], 'E276':[], 'E156':[], 'E232':[], 'E321':[], 'E128':[], 'E150':[], 'E148':[], 'E161':[], 'E222':[], 'E142':[], 'E134':[], 'E238':[], 'E396':[], 'E239':[], 'E227':[], 'E147':[], 'E188':[], 'E243':[], 'E401':[], 'E362':[], 'E418':[], 'E272':[], 'E333':[], 'E405':[], 'E416':[], 'E358':[], 'E202':[], 'E259':[], 'E180':[], 'E409':[], 'E244':[], 'E394':[], 'E282':[], 'E210':[], 'E124':[], 'E398':[], 'E374':[], 'E277':[], 'E265':[], 'E421':[], 'E342':[], 'E322':[], 'E165':[], 'E340':[], 'E381':[], 'E331':[], 'E280':[], 'E393':[], 'E353':[], 'E221':[], 'E219':[], 'E323':[], 'E336':[], 'E199':[], 'E170':[], 'E137':[], 'E168':[], 'E350':[], 'E141':[], 'E214':[], 'E177':[], 'E250':[], 'E269':[], 'E167':[], 'E307':[], 'E420':[], 'E411':[], 'E262':[], 'E176':[], 'E218':[], 'E229':[], 'E315':[], 'E264':[], 'E127':[], 'E132':[], 'E154':[], 'E133':[], 'E332':[], 'E138':[], 'E258':[], 'E266':[], 'E236':[], 'E415':[], 'E146':[], 'E193':[], 'E187':[], 'E384':[], 'E417':[], 'E289':[], 'E279':[], 'E131':[], 'E184':[], 'E240':[], 'E149':[], 'E343':[]}
df = DataFrame(data_form)
for val in rand_num:
a = rate_func(val)
rate_list = get_rate(a)
df.loc[len(df)] = rate_list
df.to_excel(writer,sheet_name='違約率變化',index=False)
銷售數量和銷售金額的隨機數范圍是:0~MAX
最后
因為國賽結果還未出來,因此完整代碼暫時不提供,后續我會在公眾號公布完整代碼以及結果,歡迎大家來關注我的公眾號,也歡迎大家來互相交流┗( ▔, ▔ )┛
完整代碼已更新到公眾號