目录
一、逻辑回归原理
一个样本可以理解为发生的一次事件,对于0/1分类问题来讲,产生的结果有两种可能,符合伯努利试验的概率假设。因此,我们可以说样本的生成过程即为伯努利试验过程,产生的结果(0/1)服从伯努利分布,那么对于第i个样本,概率公式表示如下:
将上面两个公式合并在一起,可以得到第i个样本正确预测的概率:
对于所有的样本,假设每条样本生成过程独立,在整个样本空间中(N个样本)的概率分布(即似然函数)为
接下来我们就可以通过极大似然估计方法求概率参数。
g(z)为sigmoid函数,它的输入范围为−∞→+∞,而值域刚好为(0,1),正好满足概率分布为(0,1)的要求。而且它是一个单调上升的函数,具有良好的连续性,不存在不连续点,可导。
为了方便参数求解,对这个公式取对数,可得对数似然函数
使用随机梯度下降的方法,对参数进行更新:
迭代下述公式可救的参数:
a表示学习率。
LR其数学目的是求解能够让模型对数据拟合程度最高的参数的值,以此构建预测函数 ,然后将特征矩阵输入预测函数来计算出逻辑回归的结果y。
二、逻辑回归优缺点
优点:
1. 结构简单,回归计算快,可解释性强,对线性关系的拟合效果好
2.支持增量训练
3. 逻辑回归返回的分类结果不是固定的0,1,而是以小数形式呈现的类概率数字
缺点:
1. 预测精度一般,对变量要求高,非数值需要编码,不能容忍缺失值,需要归一化。
三、LR评分卡的构建
原始代码git地址:
1. 计算登彔日期到放款日期之间的间隔天数,可以看到绝大部分的天数在180天以内.
data1['logInfo'] = data1['LogInfo3'].map(lambda x: datetime.datetime.strptime(x,'%Y-%m-%d'))
data1['Listinginfo'] = data1['Listinginfo1'].map(lambda x: datetime.datetime.strptime(x,'%Y-%m-%d'))
data1['ListingGap'] = data1[['logInfo','Listinginfo']].apply(lambda x: (x[1]-x[0]).days,axis = 1)
plt.hist(data1['ListingGap'],bins=200)
plt.xlim(xmin = 0, xmax = 400)
plt.title('Days between login date and listing date')
图片一
2. 由于绝大部分观测样本的时间跨度在半年内,所以我们选取半年内的时间切片,考虑以月为单位的时间切片,则可以衍生出30天、60天、90天、120天、150天、180天等多种选择。
- 时间切片内的登录的次数
- 时间切片内不同的登录方式的个数
- 时间切片内不同登录方式的平均个数
'''
使用180天作为最大的时间窗口计算新特征
所有可以使用的时间窗口可以有7 days, 30 days, 60 days, 90 days, 120 days, 150 days and 180 days.
在每个时间窗口内,计算总的登录次数,不同的登录方式,以及每种登录方式的平均次数
'''
time_window = [7, 30, 60, 90, 120, 150, 180]
var_list = ['LogInfo1','LogInfo2']
data1GroupbyIdx = pd.DataFrame({'Idx':data1['Idx'].drop_duplicates()})
data1GroupbyIdx.head()
for tw in time_window:
data1['TruncatedLogInfo'] = data1['Listinginfo'].map(lambda x: x + datetime.timedelta(-tw))
temp = data1.loc[data1['logInfo'] >= data1['TruncatedLogInfo']]
for var in var_list:
#count the frequences of LogInfo1 and LogInfo2
count_stats = temp.groupby(['Idx'])[var].count().to_dict()
data1GroupbyIdx[str(var)+'_'+str(tw)+'_count'] = data1GroupbyIdx['Idx'].map(lambda x: count_stats.get(x,0))
# count the distinct value of LogInfo1 and LogInfo2
Idx_UserupdateInfo1 = temp[['Idx', var]].drop_duplicates()
print(temp[['Idx', var]].shape, Idx_UserupdateInfo1.shape)
uniq_stats = Idx_UserupdateInfo1.groupby(['Idx'])[var].count().to_dict()
data1GroupbyIdx[str(var) + '_' + str(tw) + '_unique'] = data1GroupbyIdx['Idx'].map(lambda x: uniq_stats.get(x,0))
# calculate the average count of each value in LogInfo1 and LogInfo2
data1GroupbyIdx[str(var) + '_' + str(tw) + '_avg_count'] = data1GroupbyIdx[[str(var)+'_'+str(tw)+'_count',str(var) + '_' + str(tw) + '_unique']].\
apply(lambda x: DeivdedByZero(x[0],x[1]), axis=1)
data1GroupbyIdx.head()
3. 在信用风控模型的开发中,数据集中度是常见的问题。 即在变量中,某单一数值的占比就占了全部样本值的 绝大多数。例如,在一批训练样本中,学历为本科的样本占了全部样本的90%。具有极高的集中度的字段。
col_most_values_df = pd.DataFrame.from_dict(col_most_values, orient = 'index')
col_most_values_df.columns = ['max percent']
display(col_most_values_df.head())
col_most_values_df = col_most_values_df.sort_values(by = 'max percent', ascending = False)
pcnt = list(col_most_values_df[:500]['max percent'])
vars = list(col_most_values_df[:500].index)
plt.bar(range(len(pcnt)), height = pcnt)
plt.title('Largest Percentage of Single Value in Each Variable')
large_percent_cols = list(col_most_values_df[col_most_values_df['max percent']>=0.9].index)
bad_rate_diff = {}
for col in large_percent_cols:
large_value = col_large_value[col]
temp = allData[[col,'target']]
temp[col] = temp.apply(lambda x: int(x[col]==large_value),axis=1)
bad_rate = temp.groupby(col).mean()
if bad_rate.iloc[0]['target'] == 0:
bad_rate_diff[col] = 0
continue
bad_rate_diff[col] = np.log(bad_rate.iloc[0]['target']/bad_rate.iloc[1]['target'])
bad_rate_diff_sorted = sorted(bad_rate_diff.items(),key=lambda x: x[1], reverse=True)
bad_rate_diff_sorted_values = [x[1] for x in bad_rate_diff_sorted]
plt.bar(x = range(len(bad_rate_diff_sorted_values)), height = bad_rate_diff_sorted_values)
for col in large_percent_cols:
if col in numerical_var:
numerical_var.remove(col)
else:
categorical_var.remove(col)
del allData[col]
图四
4.数据的质量检验-数据缺失(data missing)
数据缺失度是数据质量检验的一个重要项。需要从两个维度检验数据缺失度:
- 字段维度,即某个字段在全部样本上的缺失值个数的占比
- 样本维度,即某条样本在所有字段上的缺失值的占比
一般而言,字段维度的缺失程度会大于样本维度的缺失程度缺失值处理
- 舍弃该字段戒该条记彔:缺失占比太高
- 补缺:缺失占比丌高,可用均值法、众数法、回归法等
- 作为特殊值:将缺失看成一种特殊值
'''
对类别型变量,如果缺失超过80%, 就删除,否则当成特殊的状态
'''
def MissingCategorial(df,x):
missing_vals = df[x].map(lambda x: int(x!=x))
return sum(missing_vals)*1.0/df.shape[0]
missing_pcnt_threshould_1 = 0.8
for col in categorical_var:
missingRate = MissingCategorial(allData,col)
print('{0} has missing rate as {1}'.format(col,missingRate))
if missingRate > missing_pcnt_threshould_1:
categorical_var.remove(col)
del allData[col]
if 0 < missingRate < missing_pcnt_threshould_1:
# In this way we convert NaN to NAN, which is a string instead of np.nan
allData[col] = allData[col].map(lambda x: str(x).upper())
allData_bk = allData.copy()
'''
检查数值型变量, 缺失数值随机采样。
'''
def MissingContinuous(df,x):
missing_vals = df[x].map(lambda x: int(np.isnan(x)))
return sum(missing_vals) * 1.0 / df.shape[0]
missing_pcnt_threshould_2 = 0.8
deleted_var = []
for col in numerical_var:
missingRate = MissingContinuous(allData, col)
print('{0} has missing rate as {1}'.format(col, missingRate))
if missingRate > missing_pcnt_threshould_2:
deleted_var.append(col)
print('we delete variable {} because of its high missing rate'.format(col))
else:
if missingRate > 0:
not_missing = allData.loc[allData[col] == allData[col]][col]
#makeuped = allData[col].map(lambda x: MakeupRandom(x, list(not_missing)))
missing_position = allData.loc[allData[col] != allData[col]][col].index
not_missing_sample = random.sample(list(not_missing), len(missing_position))
print(len(not_missing_sample),not_missing_sample[:10])
allData.loc[missing_position,col] = not_missing_sample
#del allData[col]
#allData[col] = makeuped
missingRate2 = MissingContinuous(allData, col)
print('missing rate after making up is:{}'.format(str(missingRate2)))
if deleted_var != []:
for col in deleted_var:
numerical_var.remove(col)
del allData[col]
5. 特征分箱+woe+iv
分箱操作的优点
- 稳定:分箱后,变量原始值在一定范围内的波动不会影响到评分结果
- 缺失值处理:缺失值可以作为一个单独的箱,或者与其他值进行合并作为一个箱
- 异常值处理:异常值可以和其他值合并作为一个箱
- 无需归一化:从数值型变为类别型,没有尺度的差异
分箱操作的缺点
- 有一定的信息丢失:数值型变量在分箱后,变为取值有限的几个箱
- 需要编码:分箱后的变量是类别型,不能直接带入逻辑回归模型中,需要进行一次数值编码
上一节,具体讲过chimerge分箱,本章就不展开说了。
样本均匀性
基于卡方分箱法对变量进行分箱
对不同类型的变量,分箱的处理是不同的:
(1)数值型变量可直接分箱
(2)取值个数较多的类别型变量,需要用bad rate做编码转换成数值型变量,再分箱
(3)取值个数较少的类别型变量不需要分箱,但是要检查是否每个类别都有好坏样本。如果有类别只有好或坏,需要合并
'''
对于类别型变量,按照以下方式处理
1,如果变量的取值个数超过5,计算bad rate进行编码,保留。
2,除此之外,其他任何类别型变量如果有某个取值中,对应的样本全部是坏样本或者是好样本,进行合并, 对bad_rate从小到大排列,从前遍历,对为0数据合并,如果连着三个都是负样本会合并到一起,最大箱占比>0.9也会剔除。。
'''
deleted_features = [] #将处理过的变量删除,防止对后面建模的干扰
encoded_features = {} #将bad rate编码方式保存下来,在以后的测试和生产环境中需要使用
merged_features = {} #将类别型变量合并方案保留下来
var_IV = {} #save the IV values for binned features #将IV值保留和WOE值
var_WOE = {}
for col in categorical_var:
print('we are processing {}'.format(col))
if len(set(trainData[col]))>5:
print('{} is encoded with bad rate'.format(col))
col0 = str(col)+'_encoding'
#(1), 计算坏样本率并进行编码
encoding_result = BadRateEncoding(trainData, col, 'target')
trainData[col0], br_encoding = encoding_result['encoding'],encoding_result['bad_rate']
#(2), 将(1)中的编码后的变量也加入数值型变量列表中,为后面的卡方分箱做准备
numerical_var.append(col0)
#(3), 保存编码结果
encoded_features[col] = [col0, br_encoding]
#(4), 删除原始值
deleted_features.append(col)
else:
bad_bin = trainData.groupby([col])['target'].sum()
#对于类别数少于5个,但是出现0坏样本的特征需要做处理
if min(bad_bin) == 0:
print('{} has 0 bad sample!'.format(col))
col1 = str(col) + '_mergeByBadRate'
#(1), 找出最优合并方式,使得每一箱同时包含好坏样本
mergeBin = MergeBad0(trainData, col, 'target')
#(2), 依照(1)的结果对值进行合并
trainData[col1] = trainData[col].map(mergeBin)
maxPcnt = MaximumBinPcnt(trainData, col1)
#如果合并后导致有箱占比超过90%,就删除。
if maxPcnt > 0.9:
print('{} is deleted because of large percentage of single bin'.format(col))
deleted_features.append(col)
categorical_var.remove(col)
del trainData[col]
continue
#(3) 如果合并后的新的变量满足要求,就保留下来
merged_features[col] = [col1, mergeBin]
WOE_IV = CalcWOE(trainData, col1, 'target')
var_WOE[col1] = WOE_IV['WOE']
var_IV[col1] = WOE_IV['IV']
#del trainData[col]
deleted_features.append(col)
else:
WOE_IV = CalcWOE(trainData, col, 'target')
var_WOE[col] = WOE_IV['WOE']
var_IV[col] = WOE_IV['IV']
对于连续型变量,处理方式如下:
1,利用卡方分箱法将变量分成5个箱
2,检查坏样本率的单带性,如果发现单调性不满足,就进行合并,直到满足单调性
''''''
ChiMerge函数的 技术细节:
1. 可以设置缺失值或者异常值不进行分箱。
2. 等分数据集时,根据比例对应的数据。
3. 多箱合并时,chi2考虑最小合并,从0到N-1遍历,(0,1),(1,2)组队,对最小值替换,对合并值剔除。
4. 多项合并,检查是否有分箱没有分到坏样本,根据分箱的个数,确定索引,然后找分箱号前后,计算chi2进行合并。
5. 可选,对分箱的均匀性进行合并。
6. 最后返回所有分箱的分位点。Monotone_Merge函数的 技术细节:
1. 获取当前特征的非单调的个数和索引。
2. 遍历所有的非单调的个数,
其中每个判断与前合并,与后合并,非单调点个数的减少,均匀性进行判断。得到单个点的情况。
综合考虑每个非单调点的个数或者均匀情况。
3. 满足单调性,或者总箱数等于2,结束。
var_cutoff = {}
for col in numerical_var:
print("{} is in processing".format(col))
col1 = str(col) + '_Bin'
#(1),用卡方分箱法进行分箱,并且保存每一个分割的端点。例如端点=[10,20,30]表示将变量分为x<10,10<x<20,20<x<30和x>30.
#特别地,缺失值-1不参与分箱
if -1 in set(trainData[col]):
special_attribute = [-1]
else:
special_attribute = []
cutOffPoints = ChiMerge(trainData, col, 'target',special_attribute=special_attribute)
print("cutOffPoints:",cutOffPoints)
var_cutoff[col] = cutOffPoints
trainData[col1] = trainData[col].map(lambda x: AssignBin(x, cutOffPoints,special_attribute=special_attribute))
#(2), check whether the bad rate is monotone
BRM = BadRateMonotone(trainData, col1, 'target',special_attribute=special_attribute)
if not BRM:
if special_attribute == []:
bin_merged = Monotone_Merge(trainData, 'target', col1)
removed_index = []
for bin in bin_merged:
if len(bin)>1:
indices = [int(b.replace('Bin ','')) for b in bin]
removed_index = removed_index+indices[0:-1]
removed_point = [cutOffPoints[k] for k in removed_index]
for p in removed_point:
cutOffPoints.remove(p)
var_cutoff[col] = cutOffPoints
trainData[col1] = trainData[col].map(lambda x: AssignBin(x, cutOffPoints, special_attribute=special_attribute))
else:
cutOffPoints2 = [i for i in cutOffPoints if i not in special_attribute]
temp = trainData.loc[~trainData[col].isin(special_attribute)]
bin_merged = Monotone_Merge(temp, 'target', col1)
removed_index = []
for bin in bin_merged:
if len(bin) > 1:
indices = [int(b.replace('Bin ', '')) for b in bin]
removed_index = removed_index + indices[0:-1]
removed_point = [cutOffPoints2[k] for k in removed_index]
for p in removed_point:
cutOffPoints2.remove(p)
cutOffPoints2 = cutOffPoints2 + special_attribute
var_cutoff[col] = cutOffPoints2
trainData[col1] = trainData[col].map(lambda x: AssignBin(x, cutOffPoints2, special_attribute=special_attribute))
#(3), 分箱后再次检查是否有单一的值占比超过90%。如果有,删除该变量
maxPcnt = MaximumBinPcnt(trainData, col1)
if maxPcnt > 0.9:
# del trainData[col1]
deleted_features.append(col)
numerical_var.remove(col)
print('we delete {} because the maximum bin occupies more than 90%'.format(col))
continue
WOE_IV = CalcWOE(trainData, col1, 'target')
var_IV[col] = WOE_IV['IV']
var_WOE[col] = WOE_IV['WOE']
#del trainData[col]
6. 单变量分析
完成变量分箱、WOE编码与IV计算后,我们需要做单变量分析。
两个角度进行分析:
1. 变量的重要性。变量的重要性可以从IV值的判断出发。不同的IV值反映出变量不同程度的重要性。
2.变量分布的稳定性。合适的变量,各箱的占比不会很悬殊。如果某变量有一箱的占比远低于其他箱,则该变量的稳定性也较弱。
单变量分析是从重要性及分布的稳定性两个角度来考虑。通常先选择IV高于阈值(如0.2)的变量,再挑选出分箱较均匀的变量。
for col in var_WOE.keys():
print(col)
col2 = str(col)+"_WOE"
if col in var_cutoff.keys():
cutOffPoints = var_cutoff[col]
special_attribute = []
if - 1 in cutOffPoints:
special_attribute = [-1]
binValue = trainData[col].map(lambda x: AssignBin(x, cutOffPoints,special_attribute=special_attribute))
trainData[col2] = binValue.map(lambda x: var_WOE[col][x])
else:
trainData[col2] = trainData[col].map(lambda x: var_WOE[col][x])
trainData.to_csv(folderOfData+'allData_3.csv', header=True,encoding='gbk', columns = trainData.columns, index=False)
### (i) select the features with IV above the thresould
trainData = pd.read_csv(folderOfData+'allData_3.csv', header=0,encoding='gbk')
all_IV = list(var_IV.values())
all_IV = sorted(all_IV, reverse=True)
plt.bar(x=range(len(all_IV)), height = all_IV)
iv_threshould = 0.02
varByIV = [k for k, v in var_IV.items() if v > iv_threshould]
7. 多变量分析
完成单变量分析后,我们还需要对变量的整体性做把控,利用多变量分析的技术进一步缩减变量规模,形成全局更优的变量体系。多变量分析从以下两个角度分析变量的特性并完成挑选工作:
- 变量间的两两线性相关性
- 变量间的多重共线性
变量间不允许存在太强的两两线性相关性。主要原因是:
- 若变量f_1和变量f_2的两两线性相关性较强,说明这两个变量间存在一定的信息冗余。同时保留在模型里,即无必要,同时也增加了模型开发、部署与维护的负担
- 较强的线性相关性甚至会影响回归模型的参数估计。在回归模型的参数估计中,当两个变量间存在较强的线性相关性时,参数的估计会有较大的偏差
变量的两两线性相关性检验可以通过相关性矩阵来判断:
当两个变量间存在较强的线性相关性时,通常保留IV值较高的一个。
于是整个流程是:
- 将变量按照IV进行降序排列:f_1,f_2,…,f_p
- 令i=1,计算相关系数: σ(f_i,f_i+1), σ(f_i,f_i+2),…. σ(f_i,f_p)
- 如果σ(f_i,f_j)较大,则剔除f_j
- i=i+1 重复2~4,直到剩余变量中不存在较高的线性相关性。 相关系数通常用较高的阈值来对比,可以用0.7~0.9之间的数。
我们还需要检验是否存在多重共线性(multicolinearity)。多重共线性是指,一组变量中,某一个变量与其他变量的线性组合存在较强的线性相关性。同样地,存在较强的多重共线性意味着存在信息冗余,且对模型的参数估计产生影响,多重共线性通常用方差膨胀因子(VIF)来衡量。
var_IV_selected = {k:var_IV[k] for k in varByIV}
var_IV_sorted = sorted(var_IV_selected.items(), key=lambda d:d[1], reverse = True)
var_IV_sorted = [i[0] for i in var_IV_sorted]
removed_var = []
roh_thresould = 0.6
for i in range(len(var_IV_sorted)-1):
if var_IV_sorted[i] not in removed_var:
x1 = var_IV_sorted[i]+"_WOE"
for j in range(i+1,len(var_IV_sorted)):
if var_IV_sorted[j] not in removed_var:
x2 = var_IV_sorted[j] + "_WOE"
roh = np.corrcoef([trainData[x1], trainData[x2]])[0, 1]
if abs(roh) >= roh_thresould:
print('the correlation coeffient between {0} and {1} is {2}'.format(x1, x2, str(roh)))
if var_IV[var_IV_sorted[i]] > var_IV[var_IV_sorted[j]]:
removed_var.append(var_IV_sorted[j])
else:
removed_var.append(var_IV_sorted[i])
var_IV_sortet_2 = [i for i in var_IV_sorted if i not in removed_var]
### (iii) check the multi-colinearity according to VIF > 10
for i in range(len(var_IV_sortet_2)):
x0 = trainData[var_IV_sortet_2[i]+'_WOE']
x0 = np.array(x0)
X_Col = [k+'_WOE' for k in var_IV_sortet_2 if k != var_IV_sortet_2[i]]
X = trainData[X_Col]
X = np.matrix(X)
regr = LinearRegression()
clr= regr.fit(X, x0)
x_pred = clr.predict(X)
R2 = 1 - ((x_pred - x0) ** 2).sum() / ((x0 - x0.mean()) ** 2).sum()
vif = 1/(1-R2)
if vif > 10:
print("Warning: the vif for {0} is {1}".format(var_IV_sortet_2[i], vif))
一般而言,我们用10来衡量是否存在多重共线性。对于VIF>10,可以认为变量间存在多重共线性。此时,需要逐步从f_1,f_2,…,f_j剔除一个变量,剩余的变量与f_i 计算VIF。如果发现当剔除f_k后剩余变量对f_i的VIF低于10,则从f_i与f_k中剔除IV较低的一个。如果每次剔除一个变量还不能降低VIF,则每次剔除2个变量,直至变量间不存在多重共线性。
7. lr对变量的要求
变量间不存在较强的线性相关性和多重共线性
- 变量具有显著性
- 变量具有合理的业务含义,即变量对于风控业务是正确的。
第1点已经在单变量分析与多变量分析中得到一定的约束,但是未必充分。 关于第2点,需要从系数的p值进行检验 关于第3点,需要从系数的符号进行检验。
其中变量显著性变量显著性 为了获取与目标变量有较高相关性的变量,我们要求最终入模的变量的系数的p值很小,例如低于0.1。如果发现模型中某些变量不显著,需要检验一下两种可能性:1.该变量本身不显著2.该变量显著,但是由于有一定的线性相关性或者多重共线性,导致该变量在多元回归下不显著 先检验1的可能性,如果排除,再检验2.检验1的方法:将该变量单独与目标变量做逻辑回归模型,如果在单变量回归的情况下系数的p值仍然较高,即表明该变量本身的显著性很低。注: 对于IV较高的变量,1的可能性较低 。
#########################
# Step 5: 应用逻辑回归模型#
#########################
LR = sm.Logit(y, X).fit()
summary = LR.summary2()
pvals = LR.pvalues.to_dict()
params = LR.params.to_dict()
#发现有变量不显著,因此需要单独检验显著性
varLargeP = {k: v for k,v in pvals.items() if v >= 0.1}
varLargeP = sorted(varLargeP.items(), key=lambda d:d[1], reverse = True)
varLargeP = [i[0] for i in varLargeP]
p_value_list = {}
for var in varLargeP:
X_temp = trainData[var].copy().to_frame()
X_temp['intercept'] = [1] * X_temp.shape[0]
LR = sm.Logit(y, X_temp).fit()
p_value_list[var] = LR.pvalues[var]
for k,v in p_value_list.items():
print("{0} has p-value of {1} in univariate regression".format(k,v))
#发现有变量的系数为正,因此需要单独检验正确性
varPositive = [k for k,v in params.items() if v >= 0]
coef_list = {}
for var in varPositive:
X_temp = trainData[var].copy().to_frame()
X_temp['intercept'] = [1] * X_temp.shape[0]
LR = sm.Logit(y, X_temp).fit()
coef_list[var] = LR.params[var]
for k,v in coef_list.items():
print("{0} has coefficient of {1} in univariate regression".format(k,v))
selected_var = [multi_analysis[0]]
for var in multi_analysis[1:]:
try_vars = selected_var+[var]
X_temp = trainData[try_vars].copy()
X_temp['intercept'] = [1] * X_temp.shape[0]
LR = sm.Logit(y, X_temp).fit()
#summary = LR.summary2()
pvals, params = LR.pvalues, LR.params
del params['intercept']
if max(pvals)<0.1 and max(params)<0:
selected_var.append(var)
LR.summary2()
y_pred = LR.predict(X_temp)
roc_auc_score(trainData['target'], y_pred)
8.尺度化
得到符合要求的逻辑回归模型后,通常还需要将概率转化成分数。分数的单调性与概率相反,即分数越高表明违约的概率越低,信用资质越好。在评分卡模型中,上述过程称为“尺度化”,转换公式为:
其中,
Base Point:基准分
PDO:Point-to-Double Odds,好坏比每升高一倍,分数升高PDO个单位。具体反应在P这个因子中。
def Prob2Score(prob, basePoint, PDO):
#将概率转化成分数且为正整数
y = np.log(prob/(1-prob))
y2 = basePoint+PDO/np.log(2)*(-y)
score = y2.astype("int")
return score
#########################
# 尺度化与性能检验#
#########################
scores = Prob2Score(y_pred, 200, 100)
scorecard = pd.DataFrame({'y_pred':y_pred, 'y_real':list(trainData['target']),'score':scores})
KS(scorecard,'score','y_real')
plt.hist(score,bins=100)
ROC_AUC(df, score, target)
# 也可用sklearn带的函数
roc_auc_score(trainData['target'], y_pred)
四、参考文献
- 菜菜机器学习--lr构建评分卡
- 天善智能--如何构建评分卡
- 小象学院--金融信贷风控中的机器学习