Python数据分析案例61——信贷风控评分卡模型(A卡)(scorecardpy 全面解析)

案例背景

虽然在效果上,传统的逻辑回归模型通常不如现代的机器学习模型,但在风控领域,解释性至关重要。逻辑回归的解释性是这些“黑箱”模型所无法比拟的,因此,研究传统的评分卡模型依然是有意义的。 传统的评分卡模型与机器学习模型的主要差异在于特征工程的处理。机器学习模型通常不需要对数据进行大幅度的调整,而传统评分卡模型则需要对数据进行分箱、编码、计算WOE(Weight of Evidence)等处理。

并且由于评分卡模型是线性模型,还需要进行一定的变量筛选。因此,评分卡模型的特征工程相对更加复杂,但解释性更强。 至于哪种模型效果更好,取决于具体的数据鄂使用场景(老板非要看解释性那就得评分卡了,客户非要效果那就得LGBM)。在这个案例中,我们使用一个之前测试过的信贷评分数据集来构建评分卡模型,并与主流机器学习模型进行对比,以评估其效果。这个案例也展示了构建全面的A卡(授信行为)和信贷风控评分模型的整体流程,同时解析了ScoreCardPy包的用法,方便未来快速应用于相关项目。

scorecardpy 的这些用法都会演示,全面解析 数据集划分 (split_df)

变量筛选(iv, var_filter)

变量分箱(woebin, woebin_plot, woebin_adj, woebin_ply)

分数转换(scorecard, scorecard_ply)

效果评估(perf_eva, perf_psi)


数据介绍

大部分的教学,一提到ScoreCardPy包应用,马上就要掏出了他内置的德国信用卡数据集。这个其实对新手小白不是很友好的,他们也不知道这个数据集,咋样读取的,怎么做清洗。我下面会从数据读取开始一步步的进行.

本次数据是这样的:

 

最后一列一变量就代表这个人是否会违约,也就是是不是要给他贷款的和响应变量y。所以这是一个a卡模型。

当然需要本次演示的所有代码文件和数据的同学还是可以参考:信贷评分卡


代码实现

首先导入数据科学常用的包

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt 
import seaborn as sns
import scorecardpy as sc
import shapplt.rcParams ['font.sans-serif'] ='SimHei'               #显示中文
plt.rcParams ['axes.unicode_minus']=False               #显示负号

然后读取数据

读取数据

train=pd.read_csv('train.csv').set_index('Id')
test=pd.read_csv('test.csv').set_index('Id')
print(train.shape,test.shape)
train.head()

可以看到训练集7500条,测试集2500条。变量维度为十六个x,一个y。

查看数据基本信息

train.info()

我们可以看到数据的缺失率参差不齐,并且有一些object的对象这些不能放入模型直接训练,得进行一个转化。

变量的含义的简介:

查看一下标签 分布

train['Credit Default'].value_counts()

可以看到大概的比例是好样本为五,坏样本为二,比例还算较为均衡,没有那么极端。

非数值型变量查看其描述性统计。

train.select_dtypes(exclude=['number']).describe()

可以看到这几个类别变量的类别都不是很多,我们可以直接进行因子化,label encoder。

我们自定义一个函数,不仅训练集要进行同样的转化,测试集也要同样进行类别变量的转化。

def ensure_category_consistency(train, test, columns):for col in columns:# 找到所有可能的类别categories = pd.Categorical(train[col]).categories  #.append(test[col])# 定义一致的类别顺序train[col] = pd.Categorical(train[col], categories=categories)test[col] = pd.Categorical(test[col], categories=categories)# 转换为category数据类型train[col] = train[col].astype('category')test[col] = test[col].astype('category')return train, test### 类别变量转化
columns_to_convert = train.select_dtypes(exclude=['number']).columns.to_list()
train, test = ensure_category_consistency(train, test, columns_to_convert)

查看数据信息

train.info()

现在可以看到所有的数据基本上都是数值型和类别型变量,可以直接进行模型计算。


变量筛选

做机器学习其实大体上是不用进行变量筛选的,变量越多越好,但是做传统的评分卡模型,由于它是线性模型,所以它不能用那么多变量,它得进行一定的筛选,得把一些解释性不好的变量,效果不好的变量给它进行去掉。

其主要用法解析:

默认的参数配置为:iv_limit=0.02, missing_limit=0.95, identical_limit=0.95,即当某个变量的 IV 值小于0.02,或缺失率大于95%,或同值率(除空值外)大于95%,则剔除掉该变量。

此外,该方法还内置了除上述以外的其他参数:

def var_filter(dt, y, x=None, iv_limit=0.02, missing_limit=0.95,  
               identical_limit=0.95, var_rm=None, var_kp=None, 
               return_rm_reason=False, positive='bad|1')
其中各参数含义如下:

varrm可设置强制保留的变量,默认为空;
varkp可设置强制剔除的变量,默认为空;
return_rm_reason可设置是否返回剔除原因,默认为不返回(False);
positive可设置坏样本对应的值,默认为“bad|1”。

下面进行变量筛选,我们使用iv最小为0.02的这个条件。(不懂iv是啥的可以去搜一下)

dt_s = sc.var_filter(train, y="Credit Default",iv_limit=0.02, missing_limit=0.95, identical_limit=0.95,return_rm_reason=True)

我们这里打开了返回原因的这个参数,因为所以它后面会返回一个字典,我们来查看一下这个字典是什么。

dt_s['rm']  ## return_rm_reason=True 返回一个字典

可以看到它移除了5个变量,并且它这个方法返回的rm的这个键对应的值是一个data frame,他清楚地告诉你哪些变量是v小于0.02才导致他们被移除的。

data_train=dt_s['dt']
data_train.info()

现在只剩下十二个变量了,x只有十一个了

我们对测试集也进行同样的变量过滤

### 测试集也进行同样的变量过滤,但是没y
test=test[data_train.columns[:-1]]

这里附赠一个,我自己写的如何能全面的看iv的一个方法吧。

### 变量筛选咋定义看IV的方法
def calculate_pred_proba_bin(true_labels,predictions, bins=10):# 创建分箱区间bin_edges = np.linspace(0, 1, bins + 1)# 分箱bin_labels = [f"{bin_edges[i]:.2f}-{bin_edges[i+1]:.2f}" for i in range(len(bin_edges)-1)]bin_indices = np.digitize(predictions, bin_edges, right=False) - 1# 创建数据框df = pd.DataFrame({ 'bin': [bin_labels[i] for i in bin_indices], 'label': true_labels })# 统计各个分箱的总数、类别为0和1的样本数result = df.groupby('bin')['label'].agg(total='count',count_0=lambda x: (x == 0).sum(),count_1=lambda x: (x == 1).sum()).reset_index()# 计算坏样本率和坏样本在所有坏样本中的比例total_bad_samples = result['count_1'].sum()result['bad_rate'] = result['count_1'] / result['total']result['bad_percent'] = result['count_1'] / total_bad_samples#result=result.sort_values('bin',ascending=False)result['lift']=result['bad_rate']/(total_bad_samples/result['total'].sum())result['cumulative_bad_percent'] = result['bad_percent'][::-1].cumsum()[::-1]return result.style.bar(color='skyblue').format(subset=['bad_rate','bad_percent','lift','cumulative_bad_percent'], precision=4)def scorecardpy_display_bin(bins_info):df_list = []for col, bin_data in bins_info.items():df = pd.DataFrame(bin_data)df_list.append(df)result_df = pd.concat(df_list, ignore_index=True)# 增加 lift 列total_bad = result_df['bad'].sum()   ;   total_count = result_df['count'].sum()overall_bad_rate = total_bad / total_countresult_df['lift'] = result_df['badprob'] / overall_bad_rateresult_df=result_df.sort_values(['total_iv','variable'],ascending=False).set_index(['variable','total_iv','bin'])[['count_distr','count','good','bad','badprob','lift','bin_iv','woe']]return  result_df.style.format(subset=['count','good','bad'], precision=0).format(subset=['count_distr', 'bad','lift','badprob','woe','bin_iv'], precision=4).bar(subset=['badprob','bin_iv','lift'], color=['#d65f5f', '#5fba7d'])
train_miss=train.copy()
train_miss['Years in current job']=train_miss['Years in current job'].astype('str').fillna('missing').astype('category')
bins_adj = sc.woebin(train_miss, y="Credit Default")
scorecardpy_display_bin(bins_adj)

这个是经过排序的,可以清楚看到哪些变量,他们对应的IV是多少,他们的分箱情况是怎么样的,并且每一箱的换样本浓度还有提升度以及他们对iv的贡献都在上面,可能一目了然。


数据集划分

虽然sklearn库有它自带的划分训练集的方式,但是我们的sc包它也有。划分训练集和验证集的方法,我们就用他的好了。

train, val = sc.split_df(data_train, 'Credit Default', ratio=0.7, seed=186).values()
print(train.shape, val.shape )

可以看到我们的训练集大概有5250条数据,12个变量,其中11个是x,一个是y,我们的验证集是2250条。也是12个变量。


变量分箱

可通过woebin()函数对全部变量进行自动分箱,并基于woe_bin的结果,使用woebin_plot对各变量分箱的count distribution和bad probability进行可视化,可观察是否存在单调性:

woebin()函数包括如下参数:

def woebin(dt, y, x=None, 
           var_skip=None, breaks_list=None, special_values=None, 
           stop_limit=0.1, count_distr_limit=0.05, bin_num_limit=8, 
           # min_perc_fine_bin=0.02, min_perc_coarse_bin=0.05, max_num_bin=8, 
           positive="bad|1", no_cores=None, print_step=0, method="tree",
           ignore_const_cols=True, ignore_datetime_cols=True, 
           check_cate_num=True, replace_blank=True, 
           save_breaks_list=None, **kwargs)
woebin()可针对数值型和类别型变量生成最优分箱结果,方法可选择决策树分箱、卡方分箱或自定义分箱。其他各参数的含义如下:

var_skip: 设置需要跳过分箱操作的变量;
breaks_list: 切分点列表,默认为空。如果非空,则按设置的切分点进行分箱处理;
special_values: 设置需要单独分箱的值,默认为空;
count_distr_limit: 设置分箱占比的最小值,一般可接受范围为0.01-0.2,默认值为0.05;
method: 设置分箱方法,可设置"tree"(决策树)或"chimerge"(卡方),默认值为"tree";

stop_limit: 当IV值的增长率小于所设置的stop_limit,或卡方值小于qchisq(1-stoplimit, 1)时,停止分箱。一般可接受范围为0-0.5,默认值为0.1;
bin_num_limit: 该参数为整数,代表最大分箱数。
positive: 指定样本中正样本对应的标签,默认为"bad|1";
no_cores: 设置用于并行计算的 CPU 数目;
print_step: 该参数为非负数,默认值为1。若print_step>0,每次迭代会输出变量名。若iteration=0或no_cores>1,不会输出任何信息;

ignore_const_cols: 是否忽略常数列,默认值为True,即忽略常数列;
ignore_datetime_cols: 是否忽略日期列,默认值为True,即忽略日期列;
check_cate_num: 检查类别变量中枚举值数目是否大于50,默认值为True,即自动进行检查。若枚举值过多,会影响分箱过程的速度;
replace_blank: 设置是否将空值填为None,默认为True。
若对自动分箱结果不满意,还可手动自定义分箱:

breaks_adj = {'age.in.years': [26, 35, 40],
    'other.debtors.or.guarantors': ["none", "co-applicant%,%guarantor"] }
bins_adj = sc.woebin(dt_s, y="creditability", breaks_list=breaks_adj)

其实使用很简单,就下面一行代码,然后再画图看一看就行。

bins = sc.woebin(data_train, y="Credit Default" )
sc.woebin_plot(bins)

所有变量都有一张图,但是我这里篇幅限制就不放那么多了,只放这一个变量。

从这个图中我们可以清楚的看到它的分箱的情况,每一箱里面的好坏的数量,比例以及坏样本的浓度是怎么样变化的。

## 可以看到 Credit Score 这个字段有点问题,按理来说分数越高黑样本浓度越低 ,750分以上黑样本这么高明显有问题

我们画拉出明细来看一下

bins['Credit Score']#.plot.hist()

这个明细其实就是上面的图,但是他并没有展示750以上的这个数据的分布长什么样,我们直接筛选来看一下。

##查看到750以上的,部分是比750大一点,很多是5000以上的分数,明显有问题,这个切割点需要重新划分,
data_train[data_train["Credit Score"]>800]["Credit Score"].plot.hist()

800以上都是直接跳到了6000这种最高分了,下面重新划分这个变量的分箱阈值

breaks_adj = {'Credit Score': [678,696,728,740,800] }
bins_adj = sc.woebin(data_train, y="Credit Default", breaks_list=breaks_adj)
sc.woebin_plot(bins_adj)

这里就使用到了break list,就是可以自己手工定义分享的节点阈值的。参数我觉得还是挺好用的,可以调整这个分箱,然后重新再看我们的这个变量的情况。

由于分箱多了一箱,所以可能有点密集重叠,但是我们可以明显的看到。信用分数这一项,随着分数变高,它的换样本浓度是明显的下降的只有在800分以上。6000分以上的这种才全是黑样本,这应该是数据的异常情况。

可以看到 iv已经升到了1.6119级,信用卡评分在800分以上的基本全部是黑样本。这应该是数据的一些错误之类的,因为信用卡评分也不可能到五六千分以上。

重新划分后,800分以上的,黑样本100浓度,IV超高。分箱完成后,使用woebin_ply()函数对变量进行woe变换,之后需要把所得到的woe值作为模型的输入。


WOE转化

分箱分好之后,我们就要进行woe的转化编码,在这个包里面使用很简单。

train_woe = sc.woebin_ply(train, bins_adj)
val_woe = sc.woebin_ply(val, bins_adj)
test_woe = sc.woebin_ply(test, bins_adj)

数据现在就准备好了我们可以进行训练了。


模型训练

我们取出x跟y训练集和验证集,还有测试集。

y_train = train_woe.loc[:,'Credit Default']
X_train = train_woe.loc[:,train_woe.columns != 'Credit Default']
y_val = val_woe.loc[:,'Credit Default']
X_val = val_woe.loc[:,train_woe.columns != 'Credit Default']X_test = test_woe.loc[:,train_woe.columns[1:] != 'Credit Default']
print(X_train.shape,y_train.shape,X_val.shape,y_val.shape, X_test.shape)

使用逻辑回归进行学习器

# 逻辑回归 ------
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(penalty='l1', C=0.9, solver='saga', n_jobs=-1)
lr.fit(X_train, y_train)
lr.coef_,lr.intercept_

可以看到回归的系数项和截距项。

然后我们可以直接进行预测概率。

# 预测
train_pred = lr.predict_proba(X_train)[:,1]
val_pred = lr.predict_proba(X_val)[:,1]test_pred = lr.predict_proba(X_test)[:,1]


效果评估

可使用perf_eva()函数对模型效果进行计算及可视化,基于预测的概率值和label值,提供KS(kolmogorov-smirnow), ROC, lift以及precision-recall四种评估指标:

该函数还包括以下参数:

def perf_eva(label, pred, title=None, groupnum=None, plot_type=["ks", "roc"], 
             show_plot=True, positive="bad|1", seed=186)
参数plot_type可设置为:"ks", "lift", "roc", "pr",默认为["ks", "roc"]。
 

他的评估看ks跟auc特别简单,一行代码就可以了。

train_perf = sc.perf_eva(y_train, train_pred, title = "train")
val_perf = sc.perf_eva(y_val, val_pred, title = "val")

训练集KS=0.38,AUC=0.77,验证集KS=0.3716,AUC=0.765,没有过拟合,很好的效果。

到这里可能就有小聪明要问了,为什么不用分类问题常用的评价指标,准确率,精准度,召回率f1值呢。

呃,因为在信贷模型里面大部分的时候都是样本不平衡的,这4个指标在样本不平衡的情况下效果非常差,信贷模型主要还是看ks跟auc。


分数转换

然后基于’woebin'的结果和sklearn.linear_model的LogisticRegression,创建scorecard()函数,用于构建评分卡,只需一行代码:

scorecard()包括以下参数:

def scorecard(bins, model, xcolumns, points0=600, odds0=1/19, 
              pdo=50, basepoints_eq0=False, digits=0)
各参数含义如下:

bins:由`woebin`得到的分箱信息;
model:LogisticRegression模型对象;
points0:基准分数,默认值为600;
odds0: 基准 Odds(好坏比),与真实违约概率对应,可换算得到违约概率,Odds = p/(1-p)。默认值为 1/19;
pdo: Points toDouble theOdds,即Odds变成2倍时,所增加的信用分。默认值为50;
basepoints_eq0:设置是否要把basepoints均分给每个变量的得分,默认为False,即不进行均分。但大多数评分卡倾向于所有分数均为正数,所以可手动改为True。

使用很简单

card = sc.scorecard(bins_adj, lr, X_train.columns)

然后基于scorecard的结果,用scorecard_ply()函数计算train和test数据集的信用分数:

train_score = sc.scorecard_ply(train, card, print_step=0)
val_score = sc.scorecard_ply(val, card, print_step=0)
test_score = sc.scorecard_ply(test, card, print_step=0)

最后用perf_psi()得到该评分卡在测试数据集上的表现。

但是这个方法好像有时候版本问题容易报错,所以我们就自己自定义了一个怎么算psi的方法。

train.shape,val.shape,train_score.shape,val_score.shape , test_score.shapedef calculate_psi(expected, actual, bins=10, epsilon=1e-10):# 分箱breakpoints = np.linspace(0, 1, bins + 1) * (max(expected.max(), actual.max()) - min(expected.min(), actual.min())) + min(expected.min(), actual.min())expected_percents = np.histogram(expected, bins=breakpoints)[0] / len(expected)actual_percents = np.histogram(actual, bins=breakpoints)[0] / len(actual)# 增加一个小的正数,避免零值expected_percents = np.where(expected_percents == 0, epsilon, expected_percents)actual_percents = np.where(actual_percents == 0, epsilon, actual_percents)# 计算PSIpsi_value = np.sum((expected_percents - actual_percents) * np.log(expected_percents / actual_percents))return psi_value
print(f'训练集和验证集的PSI{calculate_psi(train_score.to_numpy().reshape(-1,), val_score.to_numpy().reshape(-1,))}') 
print(f'训练集测试集的PSI{calculate_psi(train_score.to_numpy().reshape(-1,), test_score.to_numpy().reshape(-1,)) }')

可以看到训练集和验证集,还有训练集和测试集的预测的分数PSI变化都不大,因此预测出来的分布都较为稳定,模型性能应该都挺良好的。

plt.figure(figsize=(7, 3),dpi=128)
sns.kdeplot(train_score, bw_adjust=1.5,label='Train Score', fill=True, palette="Set1", color='gold', alpha=0.3)#
sns.kdeplot(val_score,bw_adjust=1.5,label='Validation Score',fill=True,  palette="Set2", alpha=0.3)#
sns.kdeplot(test_score, label='Test Score', fill=True,palette="Set3", alpha=0.3) #plt.title('Kernel Density Plot of Scores')
plt.xlabel('Score')
plt.ylabel('Density')
plt.legend()
plt.show()

高度重叠,效果很好,测试集也没偏移

test_score是信用卡评分,是概率转化后的分数,test_pred是概率,现在重新预测为变成枚举值后 可以储存,然后提交

test_pred = lr.predict(X_test)#[:,1]
df_pred=pd.DataFrame(test_pred,index=[test.index],columns=['Credit Default'])
df_pred.head()

df_pred.to_csv('评分卡模型预测结果.csv')

进行储存,我们测试集上的这些申请人,就知道要不要给他贷款了。


和机器学习模型对比

上面的评分卡模型和机器学习模型到底谁效果好呢?我们测试一下

(之前有全面用这个数据集做过一篇关于机器学习及其可解释性可视化分析的一个案例链接在这儿)

这里的机器学习就是简单的算一个评价指标ks,做一下对比。

重新读取数据

train=pd.read_csv('train.csv').set_index('Id')
test=pd.read_csv('test.csv').set_index('Id')
columns_to_convert = train.select_dtypes(exclude=['number']).columns.to_list()
train, test = ensure_category_consistency(train, test, columns_to_convert)

划分训练集和验证集

X=train.iloc[:,:-1]   ;  y=train.iloc[:,-1]
from sklearn.model_selection import train_test_split
X_train,X_val,y_train,y_val=train_test_split(X,y,stratify=y,test_size=0.3,random_state=186)

定义分类问题常用的四个评价指标

from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.metrics import cohen_kappa_scoredef evaluation(y_test, y_predict):accuracy=classification_report(y_test, y_predict,output_dict=True)['accuracy']s=classification_report(y_test, y_predict,output_dict=True)['weighted avg']precision=s['precision']recall=s['recall']f1_score=s['f1-score']#kappa=cohen_kappa_score(y_test, y_predict)return accuracy,precision,recall,f1_score #, kappa

构建Lgbml模型

from lightgbm import LGBMClassifier
model=LGBMClassifier(objective='binary',random_state=1,verbose=-1,max_depth=6,n_estimators=100,eta=0.05)
model.fit(X_train, y_train)
y_pred=model.predict(X_val)
evaluation(y_val,y_pred)

可以看到这4个指标的数据的情况,但是信贷模型一般不关注他们,所以就不是很重要了。

画出pr曲线和roc图。

from sklearn.metrics import roc_curve, auc, precision_recall_curve
y_pred_proba = model.predict_proba(X_val)[:, 1]
# 计算ROC曲线和AUC值
fpr, tpr, _ = roc_curve(y_val, y_pred_proba)
roc_auc = auc(fpr, tpr)
# 计算PR曲线
precision, recall, _ = precision_recall_curve(y_val, y_pred_proba)# 创建1*2的子图
plt.figure(figsize=(10, 4),dpi=128)# 绘制ROC曲线
plt.subplot(1, 2, 1)
plt.plot(fpr, tpr, color='tomato', lw=2, label='AUC = %0.2f' % roc_auc)
plt.plot([0, 1], [0, 1], color='k', lw=1, linestyle='--')
plt.xlim([0.0, 1.0]) ;  plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')  ;  plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc="lower right")# 绘制PR曲线
plt.subplot(1, 2, 2)
plt.plot(recall, precision, color='skyblue', lw=2)
plt.xlim([0.0, 1.0])  ;  plt.ylim([0.0, 1.05])
plt.xlabel('Recall')  ;  plt.ylabel('Precision')
plt.title('Precision-Recall (PR) Curve')# 显示图像
plt.tight_layout()
plt.show()

画出ks的图

import scikitplot as skplt
skplt.metrics.plot_ks_statistic(y_val,model.predict_proba(X_val))
plt.show()

Lgbm模型验证集上的KS=0.369,AUC=0.77 ,比上面的评分卡模型验证集KS=0.3716,AUC=0.765差不多

Lgbm模型处理简单,但是没太多解释性,评分卡逻辑回归要做很多woe处理,解释性强,效果都差不多,各有优缺点吧

预测结果储存

test_pred=model.predict(test)
df_pred=pd.DataFrame(test_pred,index=[test.index],columns=['Credit Default'])
df_pred.head()
df_pred.to_csv('LGBM模型预测结果.csv')

那么到底谁的结果好呢?我们把测试集上用两种不同模型得到的结果分别提交到kaggle上去进行一个打分。

 

测试集上传到kaggle上计算F1值的对比,高下立见!

看来还是现代的机器学习效果更好啊!


下面是PS,补充学习内容

评分卡转化的影响

我们模型输出的是概率要转化为评分卡的这个分数,要经过一定的几率转化,这个转化有一些参数,我们可以研究一下它对分数的分布的影响。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns# 生成1000个正态分布的随机概率,假设均值在0.5,标准差较小以确保大部分集中在0到1之间
np.random.seed(42)  # 固定随机种子以保证结果可重复
random_probs = np.clip(np.random.normal(loc=0.5, scale=0.1, size=1000), 0, 1)import numpy as npdef cal_score(pred, pdo=0.2, base_score=0.5, base_odds=1.0):factor = pdo / np.log(2)  # 调整这部分offset = base_score - factor * np.log(base_odds)odds = (1 - pred) / predscore = offset + factor * np.log(odds)# 限制分数在0到1之间#score = np.clip(score, score_min, score_max)return score# 应用分数转换
scores = cal_score(random_probs)

调整 pdo 参数:pdo(每翻一倍的分数差)决定了分数随赔率变化的敏感度。通过降低 pdo 值,你可以增加分数对概率变化的敏感度,从而增加区分度。

调整 base_odds 参数:base_odds 是基准的赔率。调整这个值可以影响分数的整体偏移。例如,如果你将基准赔率稍微增大或减小,可能会得到偏离中心的分数。

调整 base_score 参数:base_score 是赔率为基准赔率时的分数。通过改变这个值,你可以整体上平移分数分布。

scores2 = cal_score(random_probs,pdo=0.2, base_score=0.5, base_odds=1,)
scores3 = cal_score(random_probs,pdo=0.5, base_score=0.5, base_odds=1,)
scores4 = cal_score(random_probs,pdo=0.2, base_score=0.05, base_odds=1,)
scores5 = cal_score(random_probs,pdo=0.2, base_score=0.5, base_odds=2,)

可视化对比

# 画核密度图
plt.figure(figsize=(7, 4),dpi=128)
sns.kdeplot(random_probs, fill=True, bw_adjust=1.5,label='real')
sns.kdeplot(scores2, fill=True, bw_adjust=1.5,label='pdo=0.2, base_score=0.5, base_odds=1')
sns.kdeplot(scores3, fill=True, bw_adjust=1.5,label='pdo=0.5, base_score=0.5, base_odds=1')
sns.kdeplot(scores4, fill=True, bw_adjust=1.5,label='pdo=0.2, base_score=0.05, base_odds=1')
sns.kdeplot(scores5, fill=True, bw_adjust=1.5,label='pdo=0.2, base_score=0.5, base_odds=2')
plt.title('KDE of Transformed Probabilities')
plt.xlabel('Probability')
plt.ylabel('Density')
plt.legend(fontsize=6)
plt.show()


创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~(需要定制类似的代码可私信)

以往的文章可以在这里查看:数据分析案例合集


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/462450.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免费送源码:Java+Springboot+MySQL Springboot酒店客房管理系统的设计与实现 计算机毕业设计原创定制

摘 要 信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对酒店客房管理等问题,对酒店客房…

力扣每日一题 超级饮料的最大强化能量 动态规划(dp)

来自未来的体育科学家给你两个整数数组 energyDrinkA 和 energyDrinkB,数组长度都等于 n。这两个数组分别代表 A、B 两种不同能量饮料每小时所能提供的强化能量。 你需要每小时饮用一种能量饮料来 最大化 你的总强化能量。然而,如果从一种能量饮料切换到…

Linux高阶——1027—守护进程

1、守护进程的基本流程 1、父进程创建子进程,父进程退出 守护进程是孤儿进程,但是是工程师人为创建的孤儿进程,低开销模式运行,对系统没有压力 2、子进程(守护进程)脱离控制终端,创建新会话 …

抗疫物资管理:SpringBoot技术应用案例

目 录 摘 要 1 前 言 2 第1章 概述 2 1.1 研究背景 3 1.2 研究目的 3 1.3 研究内容 4 第二章 开发技术介绍 5 2.1相关技术 5 2.2 Java技术 6 2.3 MySQL数据库 6 2.4 Tomcat介绍 7 2.5 Spring Boot框架 8 第三章 系统分析 9 3.1 可行性分析 9 3.1.1 技术可行性 9 3.1.2 经济可行…

pandas——数据结构

一、series (一)创建series import pandas as pd#1.使用列表或数组创建Series # 使用列表创建Series,索引默认从0开始 s1 pd.Series([1, 2, 3]) print(s1) # 使用列表和自定义索引创建Series s2 pd.Series([1, 2, 3], index[a, b, c]) pr…

MySQL的SQL语句之触发器的创建和应用

触发器 Trigger 一.触发器 作用:当检测到某种数据表发生数据变化时,自动执行操作,保证数据的完整性,保证数据的一致性。 1.创建一个触发器 如上图所示,查看这个create的帮助信息的时候,这个create trig…

服务器数据恢复—DELL EqualLogic PS6100系列存储简介及如何收集故障信息?

DELL EqualLogic PS6100系列存储采用虚拟ISCSI SAN阵列,支持VMware、Solaris、Linux、Mac、HP-UX、AIX操作系统,提供全套企业级数据保护和管理功能,具有可扩展性和容错功能。DELL EqualLogic PS6100系列存储介绍: 1、上层应用基础…

什么是无限钱包系统?有什么优势?

在数字货币风起云涌的今天,一个名为“无限钱包系统”的创新平台正悄然引领着行业的变革。它不仅重新定义了数字资产的管理方式,更以卓越的安全性、便捷的操作体验以及前瞻性的技术理念,成为了广大数字货币爱好者心中的理想之选。 一、数字货币…

API网关 - JWT认证 ; 原理概述与具体实践样例

API网关主要提供的能力,就是协议转换,安全,限流等能力。 本文主要是分享 如何基于API网关实现 JWT 认证 。 包含了JWT认证的流程,原理,与具体的配置样例 API网关认证的重要性 在现代Web应用和微服务架构中&#x…

前端加密解密

一、 AES 加密与解密 高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。是一种对称加密算法也就是加密和解密用相同的密钥; 1.1 使用 crypto-js 实现 AES 加密 1.1.1 参数说明 data 要加密的明文key 秘钥iv …

基于知识引导提示的因果概念提取(论文复现)

基于知识引导提示的因果概念提取(论文复现) 本文所涉及所有资源均在传知代码平台可获取 文章目录 基于知识引导提示的因果概念提取(论文复现)论文概述论文方法提示构造器获取典型概念集聚类典型概念构建训练数据训练主题分类器概念提取器输入构造指针网络置信度评分训练损失…

【element ui系列】分享几种实现el-table表格单选的方法

在实际的开发中,经常会用到从表格中选择一条记录的情况,虽然官方给出的例子,但是给人感觉看起来不明显,于是,在此基础上做了改进。接下来,介绍两种常见的实现方法: 1、采用复选框(checkbox)实现…

63 mysql 的 行锁

前言 我们这里来说的就是 我们在 mysql 这边常见的 几种锁 行共享锁, 行排他锁, 表意向共享锁, 表意向排他锁, 表共享锁, 表排他锁 意向共享锁, 意向排他锁, 主要是 为了表粒度的锁获取的同步判断, 提升效率 意向共享锁, 意向排他锁 这边主要的逻辑意义是数据表中是否有任…

江协科技STM32学习- P26 UART串口外设

🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝​…

使用 ADB 在某个特定时间点点击 Android 设备上的某个按钮

前提条件 安装 ADB:确保你已经在计算机上安装了 Android SDK(或单独的 ADB)。并将其添加到系统环境变量中,以便你可以在命令行中运行 adb。 USB调试:确保 Android 设备已启用 USB 调试模式。这可以在设备的“设置” -…

mint-ui Picker 显示异常

mint-ui Picker 显示异常 现象 最近一个老项目页面显示异常&#xff0c;使用mint-ui Picker显示异常,直接显示成了 数据对象&#xff0c;而不是具体travelName 字段 组件 mint-ui Picker 使用方式(vue方式) // template <mt-picker :slots"slots" value-key…

FastAPI性能对比:同步vs异步

大家好&#xff0c;FastAPI已成为构建Python API的最流行框架之一&#xff0c;因其速度和易用性而广受欢迎。但在构建高性能应用程序时&#xff0c;使用同步&#xff08;sync&#xff09;还是异步&#xff08;async&#xff09;代码执行是很重要的问题。本文将通过现实世界的性…

wx.setNavigationBarColor动态设置导航栏颜色无效(亲测有效)

wx.setNavigationBarColor动态设置导航栏颜色无效&#xff08;亲测有效&#xff09; 问题描述问题分析问题解决注意 问题描述 wx.setNavigationBarColor({frontColor: #E6E6E6,backgroundColor: #E6E6E6 })上面的代码设置后导航栏颜色没有变化&#xff0c;查看了app.json 以及…

Blender进阶:贴图与UV

9 UV 9.1 贴图与UV UV&#xff0c;指定每个面顶点在贴图上的坐标 演示&#xff1a; 1、添加物体 2、添加贴图&#xff0c;即图片纹理节点 3、进入UV Edit工作区 4、右边&#xff0c;选择一个面 5、左边&#xff0c;选择一个面&#xff0c;移动这个面 9.2 电子表格 电子…

利用LangChain与LLM打造个性化私有文档搜索系统

我们知道LLM&#xff08;大语言模型&#xff09;的底模是基于已经过期的公开数据训练出来的&#xff0c;对于新的知识或者私有化的数据LLM一般无法作答&#xff0c;此时LLM会出现“幻觉”。针对“幻觉”问题&#xff0c;一般的解决方案是采用RAG做检索增强。 但是我们不可能把…