def chi_bins(df,col,target,confidence=3.841,bins=20): # 设定自由度为1,卡方阈值为3.841,最大分箱数20
total = df[target].count() #计算总样本数
bad = df[target].sum() # 计算坏样本总数
good = total - bad # 计算好样本总数
total_bin = df.groupby([col])[target].count() # 计算每个箱体总样本数
total_bin_table = pd.DataFrame({'total':total_bin}) #创建一个数据框保存结果
bad_bin = df.groupby([col])[target].sum() # 计算每个箱体的坏样本数
bad_bin_table = pd.DataFrame({'bad':bad_bin}) #创建一个数据框保存结果
regroup = pd.merge(total_bin_table,bad_bin_table,left_index=True,right_index=True,how='inner') #组合total_bin 和 bad_bin
regroup.reset_index(inplace=True)
regroup['good'] = regroup['total']-regroup['bad'] #计算每个箱体的好样本数
regroup = regroup.drop(['total'],axis=1) #删除total
np_regroup = np.array(regroup) # 将regroup转为numpy
# 处理连续没有正样本和负样本的区间,进行合并,以免卡方报错
i = 0
while (i <= np_regroup.shape[0] - 2):
if ((np_regroup[i, 1] == 0 and np_regroup[i + 1, 1] == 0) or ( np_regroup[i, 2] == 0 and np_regroup[i + 1, 2] == 0)):
np_regroup[i, 1] = np_regroup[i, 1] + np_regroup[i + 1, 1] # 正样本
np_regroup[i, 2] = np_regroup[i, 2] + np_regroup[i + 1, 2] # 负样本
np_regroup[i, 0] = np_regroup[i + 1, 0]
np_regroup = np.delete(np_regroup, i + 1, 0)
i = i - 1
i = i + 1
# 对相邻两个区间的值进行卡方计算
chi_table = np.array([]) # 创建一个数组保存相邻两个区间的卡方值
for i in np.arange(np_regroup.shape[0]-1):
chi = ((np_regroup[i,1]*np_regroup[i+1,2]-np_regroup[i,2]*np_regroup[i+1,1])**2*\
(np_regroup[i,1]+np_regroup[i,2]+np_regroup[i+1,1]+np_regroup[i+1,2]))/\
((np_regroup[i,1]+np_regroup[i,2])*(np_regroup[i+1,1]+np_regroup[i+1,2])*\
(np_regroup[i,1]+np_regroup[i+1,1])*(np_regroup[i,2]+np_regroup[i+1,2]))
chi_table = np.append(chi_table,chi)
# 将卡方值最小的两个区间进行合并
while(1): #除非设置break,否则会一直循环下去
if(len(chi_table)<=(bins-1) or min(chi_table)>=confidence):
break # 当chi_table的值个数小于等于(箱体数-1) 或 最小的卡方值大于等于卡方阈值时,循环停止
chi_min_index = np.where(chi_table == min(chi_table))[0] # 找出卡方最小值的索引
np_regroup[chi_min_index,1] = np_regroup[chi_min_index,1] + np_regroup[chi_min_index+1,1]
np_regroup[chi_min_index,2] = np_regroup[chi_min_index,2] + np_regroup[chi_min_index+1,2]
np_regroup[chi_min_index,0] = np_regroup[chi_min_index+1,0]
np_regroup = np.delete(np_regroup,chi_min_index+1,axis=0)
if (chi_min_index == np_regroup.shape[0]-1): # 当卡方最小值是最后两个区间时,计算当前区间和前一个区间的卡方值并替换
chi_table[chi_min_index-1] = ((np_regroup[chi_min_index-1,1]*np_regroup[chi_min_index,2]-np_regroup[chi_min_index-1,2]\
*np_regroup[chi_min_index,1])**2*(np_regroup[chi_min_index-1,1]+np_regroup[chi_min_index-1,2]\
+np_regroup[chi_min_index,1]+np_regroup[chi_min_index,2]))/((np_regroup[chi_min_index-1,1]+\
np_regroup[chi_min_index-1,2])*(np_regroup[chi_min_index,1]+np_regroup[chi_min_index,2])*\
(np_regroup[chi_min_index-1,1]+np_regroup[chi_min_index,1])*(np_regroup[chi_min_index-1,2]+\
np_regroup[chi_min_index,2]))
chi_table = np.delete(chi_table,chi_min_index,axis=0) #删除替换前的卡方值
else:
# 计算合并后当前区间和前一个区间的卡方值并替换
chi_table[chi_min_index-1] = ((np_regroup[chi_min_index-1,1]*np_regroup[chi_min_index,2]-np_regroup[chi_min_index-1,2]\
*np_regroup[chi_min_index,1])**2*(np_regroup[chi_min_index-1,1]+np_regroup[chi_min_index-1,2]\
+np_regroup[chi_min_index,1]+np_regroup[chi_min_index,2]))/((np_regroup[chi_min_index-1,1]+\
np_regroup[chi_min_index-1,2])*(np_regroup[chi_min_index,1]+np_regroup[chi_min_index,2])*\
(np_regroup[chi_min_index-1,1]+np_regroup[chi_min_index,1])*(np_regroup[chi_min_index-1,2]+\
np_regroup[chi_min_index,2]))
# 计算合并后当前区间和后一个区间的卡方值并替换
chi_table[chi_min_index] = ((np_regroup[chi_min_index,1]*np_regroup[chi_min_index+1,2]-np_regroup[chi_min_index,2]\
*np_regroup[chi_min_index+1,1])**2*(np_regroup[chi_min_index,1]+np_regroup[chi_min_index,2]\
+np_regroup[chi_min_index+1,1]+np_regroup[chi_min_index+1,2]))/((np_regroup[chi_min_index,1]+\
np_regroup[chi_min_index,2])*(np_regroup[chi_min_index+1,1]+np_regroup[chi_min_index+1,2])*\
(np_regroup[chi_min_index,1]+np_regroup[chi_min_index+1,1])*(np_regroup[chi_min_index,2]+\
np_regroup[chi_min_index+1,2]))
chi_table = np.delete(chi_table,chi_min_index+1,axis=0) #删除替换前的卡方值
# 将结果保存为一个数据框
result_data = pd.DataFrame()
result_data['col'] = [col]*np_regroup.shape[0] #结果第一列为变量名
list_temp=[] # 创建一个空白的分组列
for i in np.arange(np_regroup.shape[0]):
if i==0: # 当为第一个箱体时
x= '0' + ',' + str(np_regroup[i,0])
elif i == np_regroup.shape[0]-1: # 当为最后一个箱体时
x = str(np_regroup[i-1,0]) + '+'
else:
x = str(np_regroup[i-1,0]) + ',' + str(np_regroup[i,0])
list_temp.append(x)
result_data['bin'] = list_temp
result_data['bad'] = np_regroup[:,1]
result_data['good'] = np_regroup[:,2]
result_data['bad_rate'] = result_data['bad']/(total) #计算每个箱体坏样本所占总样本比例
result_data['badattr'] = result_data['bad']/(bad) #计算每个箱体坏样本所占坏样本总数的比例
result_data['goodattr'] = result_data['good']/(good) #计算每个箱体好样本所占好样本总数的比例
result_data['woe'] = np.log(result_data['goodattr']/(result_data['badattr'])) #计算每个箱体的woe值
iv = ((result_data['goodattr']-result_data['badattr'])*result_data['woe']).sum() #计算每个变量的iv值
print('分箱结果:')
print(result_data)
print('IV值为:')
print(iv)
return result_data,iv