题目链接: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
最后
因为国赛结果还未出来,因此完整代码暂时不提供,后续我会在公众号公布完整代码以及结果,欢迎大家来关注我的公众号,也欢迎大家来互相交流┗( ▔, ▔ )┛
完整代码已更新到公众号