kaggle风控(二)——lending club
本案例数据取自kaggle竞赛lending club(美国最大的P2P公司)。
导入包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 解决坐标轴刻度负号乱码
plt.rcParams['axes.unicode_minus'] = False
# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['Simhei']
import warnings
warnings.filterwarnings("ignore")
#显示全部特征
pd.set_option('display.max_columns', None)
读入数据
data=pd.read_csv('data_all_values.csv')
data.head()
查看数据维度
data.shape
数据维度是(103434, 19)。
查看数据类型
data.info()
可以看到,只有“贷款目的”是字符型变量。
查看标签分布
data['target'].value_counts()
y的分布大致为10:1的比例,存在数据不平衡的情况。
1、LightGBM重要性选择变量
我们采用LightGBM对特征进行筛选(本案例总共只有18个特征,其实可以不需要做特征筛选;如果实际工程中有成百上千的特征,可以按照这个步骤筛选出重要特征)。
查看每个变量的取值个数用以判断是否为分类变量。
data_copy=data.copy()
allFeatures = list(data.columns)
allFeatures.remove('target')
for i in allFeatures:
print('变量 [{}] 的不同水平值有 {} 个'.format(i,len(data[i].unique())))
可以看出,取值个数少的变量为分类变量,将连续变量和分类变量分别拆开。
# 分类变量
categorical_var=['贷款期限', '贷款等级', '工作年限', '房屋所有权', '收入是否由LC验证', '贷款目的',
'过去6个月内被查询次数', '留置税数量']
# 连续变量
continuous_var=['贷款金额', '利率', '每月还款金额', '年收入', '月负债比', '过去两年借款人逾期30天以上的数字',
'摧毁公共记录的数量', '额度循环使用率', '总贷款笔数', '拖欠的逾期款项']
查看分类变量的取值范围。
# 查看分类变量数据
for var in categorical_var:
print('变量 {} 的取值为:{}'.format(var,set(data[var])))
将连续变量标准化
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
data[continuous_var] = sc.fit_transform(data[continuous_var])
将数值型的分类变量转为整型
# 字符分类变量
string_var=list(data.select_dtypes(include=["object"]).columns)
# 从分类变量中减去字符分类变量就是数值分类变量
col=list(set(categorical_var)-set(string_var))
data[col]=data[col].astype(int)
字符分类变量按照坏样本率进行编码(之前我们已经看过了,字符型的变量只有“贷款目的”)
def Encoder(df, col, target):
encoder = {}
for v in set(df[col]):
if v == v:
subDf = df[df[col] == v]
else:
xList = list(df[col])
nanInd = [i for i in range(len(xList)) if xList[i] != xList[i]]
subDf = df.loc[nanInd]
encoder[v] = sum(subDf[target])*1.0/subDf.shape[0]
newCol = [encoder[i] for i in df[col]]
return newCol
string_var=list(data.select_dtypes(include=["object"]).columns)
col=list(set(categorical_var)&set(string_var))
for i in col:
data[i] = Encoder(data, i, 'target')
data['贷款目的'].value_counts()
此时再查看一下我们的数据集data。
LighGBM模型有个非常好的功能,就是能指定分类变量,省去了one-hot编码的步骤。
# 保存变量和文件
import pickle
f =open('lgb_col.pkl','wb')
pickle.dump(col,f)
f.close()
# 建模
allFeatures = list(data.columns)
allFeatures.remove('target')
X = data[allFeatures]
y = data['target']
from sklearn.model_selection import train_test_split as sp
X_train, X_test, y_train, y_test = sp(X, y, test_size=0.3, random_state=1)
# 加载分类变量
f =open('lgb_col.pkl','rb')
col = pickle.load(f)
f.close()
# lightgbm建模
import lightgbm as LGB
params = {
'objective': 'binary',
"boosting" : "gbdt",
'num_leaves': 4,
'min_data_in_leaf': 20,
"subsample": 0.9,
"colsample_bytree": 0.8,
'learning_rate':0.09,
'tree_learner': 'voting',
'metric': 'auc'
}
dtrain = LGB.Dataset(X_train, y_train, categorical_feature=col)
dtest = LGB.Dataset(X_test, y_test, reference=dtrain, categorical_feature=col)
lgb = LGB.train(params, dtrain, valid_sets=[dtrain, dtest],
num_boost_round=3000, early_stopping_rounds=100, verbose_eval=10)
训练好LightGMB模型后,计算变量重要性。
# lightgbm重要性选择变量
importace = list(lgb.feature_importance())
allFeatures=list(lgb.feature_name())
featureImportance = zip(allFeatures,importace)
featureImportanceSorted = sorted(featureImportance, key=lambda k: k[1],reverse=True)
plt.figure(figsize = (5, 10))
sns.barplot(x=[k[1] for k in featureImportanceSorted],y=[k[0] for k in featureImportanceSorted])
plt.xticks(rotation='vertical')
plt.show()
我们从18个特征中选取重要性排名前13的变量。
feature_selection_lgb = [k[0] for k in featureImportanceSorted[:13]]
2、分箱
分箱方法有手动、等频、等宽、卡方和决策树分箱5种。本案例选择手动分箱的方法。
为避免在原数据集上修改,我们将数据集复制一份。
data=data_copy.copy()
data=data[feature_selection_lgb+['target']]
data_copy=data.copy()
2.1 分类变量的分箱
分类变量不用做离散化,可以先拿来分箱。
# 定义计算WOE的函数
def Ln_odds(df, col, target):
# 每个箱子总人数
total = df.groupby([col])[target].count()
total = pd.DataFrame({'total': total})
# 每个箱子坏样本个数
bad = df.groupby([col])[target].sum()
bad = pd.DataFrame({'bad': bad})
# 合并
regroup = total.merge(bad, left_index=True, right_index=True, how='left')
regroup.reset_index(level=0, inplace=True)
# 计算总人数和总坏客户数
N = sum(regroup['total'])
B = sum(regroup['bad'])
# 每个箱子好样本个数
regroup['good'] = regroup['total'] - regroup['bad']
# 好样本总人数
G = N - B
# 计算每个箱子的WOE值
regroup['bad_pcnt'] = regroup['bad'].map(lambda x: x*1.0/B)
regroup['good_pcnt'] = regroup['good'].map(lambda x: x * 1.0 / G)
regroup[col+'_WOE'] = regroup.apply(lambda x: np.log(x.good_pcnt*1.0/x.bad_pcnt),axis = 1)
# 合并,得到总的WOE表
df=pd.merge(df, regroup[[col,col+'_WOE']], on=col, how='left')
return df
# 对分类变量进行分箱
categorical_var=list(set(categorical_var)&set(feature_selection_lgb))
categorical_var=['贷款期限', '房屋所有权', '贷款等级', '过去6个月内被查询次数','工作年限']
for i in categorical_var:
data=Ln_odds(data, i, 'target')
sns.pointplot(x=i, y=i+'_WOE', data=data)
plt.xticks(rotation=0)
plt.show()
一般分箱会遵循以下3个原则:
①分箱个数不宜过多,一般不超过5个
②每个箱子中的样本比例不宜过少,一般不少于整体个数的5%
③分箱曲线尽量呈现单调性
我们可以看到,只有“贷款等级”和“过去6个月内被查询次数”是单调的,我们需要对每个特征进行逐一地调整分箱。
2.1.1 贷款等级
data=data_copy.copy()
data['贷款等级'].value_counts()
可以看到,贷款等级最后3个箱子样本个数过少。
i='贷款等级'
data[i]=data[i].apply(lambda x: 2 if 2<=x<5 else x)
data[i]=data[i].apply(lambda x: 3 if x>=5 else x)
data=Ln_odds(data, i, 'target')
sns.pointplot(x=i, y=i+'_WOE', data=data)
plt.xticks(rotation=0)
plt.show()
下面左图为调整分箱前,右图为调整分箱后的WOE曲线(后同)。
2.1.2 过去6个月内被查询次数
i='过去6个月内被查询次数'
data[i].value_counts()
直接将分类3之后的箱子合并
data[i]=data[i].apply(lambda x: 4 if x>3 else x)
data=Ln_odds(data, i, 'target')
sns.pointplot(x=i, y=i+'_WOE', data=data)
plt.xticks(rotation=0)
plt.show()
2.1.3 工作年限
i='工作年限'
data[i].value_counts()
将分类1-4合并为一箱,5-10合并为一箱。
data[i]=data[i].apply(lambda x: 1 if 1<=x<5 else x)
data[i]=data[i].apply(lambda x: 2 if x>=5 else x)
data=Ln_odds(data, i, 'target')
sns.pointplot(x=i, y=i+'_WOE', data=data)
plt.xticks(rotation=0)
plt.show()
可以看一下此时的数据集:
'''保存文件'''
col=[]
for i in list(data.columns):
if i.find('_WOE')<0:
col.append(i)
data=data[col]
data.to_csv('categorical_bins.csv', index=False, encoding='utf-8')
2.2 连续变量的分箱
先看一下连续型变量的描述性统计。
data=pd.read_csv('categorical_bins.csv')
data.head()
continuous_var=list(set(continuous_var)&set(feature_selection_lgb))
continuous_var=['总贷款笔数', '每月还款金额', '过去两年借款人逾期30天以上的数字',
'贷款金额', '年收入', '利率', '月负债比', '额度循环使用率']
describe=data[continuous_var].describe().T[['max','min']]
describe
2.2.1 总贷款笔数
i='总贷款笔数'
sns.distplot(data[i][data['target'] == 0].dropna(),color='blue')
sns.distplot(data[i][data['target'] == 1].dropna(),color='red')
plt.show()
选取20和30两个分割点
bins=[-1, 20, 30, 200] #左开右闭,如果可能出现0,那么需要以-1开始
cats=pd.cut(list(data[i]), bins, precision=0) #指定分组区间
cats.value_counts()
data[i+'组别']=pd.Series(cats)
data=Ln_odds(data, i+'组别', 'target')
sns.pointplot(x=i+'组别', y=i+'组别_WOE', data=data.sort_values(i+'组别_WOE',ascending=False))
plt.show()
2.2.2 每月还款金额
i='每月还款金额'
sns.distplot(data[i][data['target'] == 0].dropna(),color='blue')
sns.distplot(data[i][data['target'] == 1].dropna(),color='red')
plt.show()
选择300和750两个分割点
bins=[-1, 300, 750, 2000]
cats=pd.cut(list(data[i]), bins, precision=0) #指定分组区间
cats.value_counts()
data[i+'组别']=pd.Series(cats)
data=Ln_odds(data, i+'组别', 'target')
sns.pointplot(x=i+'组别', y=i+'组别_WOE', data=data.sort_values(i+'组别_WOE',ascending=False))
plt.show()
2.2.3 过去两年借款人逾期30天以上的数字
i='过去两年借款人逾期30天以上的数字'
sns.distplot(data[i][data['target'] == 0].dropna(),color='blue')
sns.distplot(data[i][data['target'] == 1].dropna(),color='red')
plt.show()
分布呈现严重的长尾,选择1作为分割点将特征分为两箱。
bins=[-1, 1, 50]
cats=pd.cut(list(data[i]), bins, precision=0) #指定分组区间
cats.value_counts()
data[i+'组别']=pd.Series(cats)
data=Ln_odds(data, i+'组别', 'target')
sns.pointplot(x=i+'组别', y=i+'组别_WOE', data=data.sort_values(i+'组别_WOE',ascending=False))
plt.show()
2.2.4 贷款金额
i='贷款金额'
sns.distplot(data[i][data['target'] == 0].dropna(),color='blue')
sns.distplot(data[i][data['target'] == 1].dropna(),color='red')
plt.show()
选择10000和20000作为分割点
bins=[-1, 10000, 20000, 50000]
cats=pd.cut(list(data[i]), bins, precision=0) #指定分组区间
cats.value_counts()
data[i+'组别']=pd.Series(cats)
data=Ln_odds(data, i+'组别', 'target')
sns.pointplot(x=i+'组别', y=i+'组别_WOE', data=data.sort_values(i+'组别_WOE',ascending=False))
plt.show()
2.2.5 年收入
i='年收入'
sns.distplot(data[i][data['target'] == 0].dropna(),color='blue')
sns.distplot(data[i][data['target'] == 1].dropna(),color='red')
plt.xlim([0,1000000])
plt.show()
选择150000和300000作为分割点
bins=[-1, 150000, 300000, 10000000]
cats=pd.cut(list(data[i]), bins, precision=0) #指定分组区间
cats.value_counts()
data[i+'组别']=pd.Series(cats)
data=Ln_odds(data, i+'组别', 'target')
sns.pointplot(x=i+'组别', y=i+'组别_WOE', data=data.sort_values(i+'组别_WOE',ascending=False))
plt.show()
2.2.6 利率
i='利率'
sns.distplot(data[i][data['target'] == 0].dropna(),color='blue')
sns.distplot(data[i][data['target'] == 1].dropna(),color='red')
plt.show()
选择8和13作为分割点
bins=[0, 8, 13, 50]
cats=pd.cut(list(data[i]), bins, precision=0) #指定分组区间
cats.value_counts()
data[i+'组别']=pd.Series(cats)
data=Ln_odds(data, i+'组别', 'target')
sns.pointplot(x=i+'组别', y=i+'组别_WOE', data=data.sort_values(i+'组别_WOE',ascending=False))
plt.show()
2.2.7 月负债比
i='月负债比'
sns.distplot(data[i][data['target'] == 0].dropna(),color='blue')
sns.distplot(data[i][data['target'] == 1].dropna(),color='red')
plt.xlim([0,200])
plt.show()
选择20作为分割点
bins=[-1, 20, 1000]
cats=pd.cut(list(data[i]), bins, precision=0) #指定分组区间
cats.value_counts()
data[i+'组别']=pd.Series(cats)
data=Ln_odds(data, i+'组别', 'target')
sns.pointplot(x=i+'组别', y=i+'组别_WOE', data=data.sort_values(i+'组别_WOE',ascending=False))
plt.show()
2.2.8 额度循环使用率
i='额度循环使用率'
sns.distplot(data[i][data['target'] == 0].dropna(),color='blue')
sns.distplot(data[i][data['target'] == 1].dropna(),color='red')
plt.show()
选择50作为分割点
bins=[-1, 50, 200]
cats=pd.cut(list(data[i]), bins, precision=0) #指定分组区间
cats.value_counts()
data[i+'组别']=pd.Series(cats)
data=Ln_odds(data, i+'组别', 'target')
sns.pointplot(x=i+'组别', y=i+'组别_WOE', data=data.sort_values(i+'组别_WOE',ascending=False))
plt.show()
'''保存文件'''
col=[]
for i in list(data.columns):
if i.find('_WOE')<0:
col.append(i)
data=data[col]
data.to_csv('cut_bins.csv', index=False, encoding='utf-8')
查看一下此时的数据集data,可以看到,连续变量已经分箱了。
2.3 计算WOE和IV值
# 定义函数计算WOE和IV
def CalcWOE(df, col, target):
# 每个箱子总人数
total = df.groupby([col])[target].count()
total = pd.DataFrame({'total': total})
# 每个箱子坏样本数
bad = df.groupby([col])[target].sum()
bad = pd.DataFrame({'bad': bad})
# 合并
regroup = total.merge(bad, left_index=True, right_index=True, how='left')
regroup.reset_index(level=0, inplace=True)
# 计算总人数和总坏样本数
N = sum(regroup['total'])
B = sum(regroup['bad'])
# 计算好客户个数
regroup['good'] = regroup['total'] - regroup['bad']
G = N - B
# 计算好客户比例和坏客户比例,并算出WOE
regroup['bad_pcnt'] = regroup['bad'].map(lambda x: x*1.0/B)
regroup['good_pcnt'] = regroup['good'].map(lambda x: x * 1.0 / G)
regroup['WOE'] = regroup.apply(lambda x: np.log(x.good_pcnt*1.0/x.bad_pcnt),axis = 1)
WOE_dict = regroup[[col,'WOE']].set_index(col).to_dict(orient='index')
for k, v in WOE_dict.items():
WOE_dict[k] = v['WOE']
# 计算每个变量的IV值
IV = regroup.apply(lambda x: (x.good_pcnt-x.bad_pcnt)*np.log(x.good_pcnt*1.0/x.bad_pcnt),axis = 1)
IV = sum(IV)
return {"WOE": WOE_dict, 'IV':IV}
# 取出经过分箱的连续变量和分类变量
all_var=[]
for i in list(data.columns):
if i.find('组别')>0:
all_var.append(i)
all_var=all_var+categorical_var
# 得到每个特征变量的WOE和IV值
WOE_dict = {}
IV_dict = {}
for var in all_var:
woe_iv = CalcWOE(data, var, 'target')
WOE_dict[var] = woe_iv['WOE']
IV_dict[var] = woe_iv['IV']
# 查看每个特征变量的IV值
pd.Series(IV_dict).sort_values(ascending=False)
3、LR建模
3.1 IV特征筛选
IV值同样可以对特征重要性进行筛选:
IV<0.02 无作用
0.02-0.1 弱作用
0.1-0.3 中等
0.3-0.5 强作用
IV>0.5 过强,需要检查是否有问题
'''选取IV>=0.02的变量'''
IV_dict_sorted = sorted(IV_dict.items(), key=lambda x: x[1], reverse=True)
IV_values = [i[1] for i in IV_dict_sorted]
IV_name = [i[0] for i in IV_dict_sorted]
high_IV = {k:v for k, v in IV_dict.items() if v >= 0.02}
high_IV_sorted = sorted(high_IV.items(),key=lambda x:x[1],reverse=True)
print ('总共',len(high_IV_sorted),'个变量IV >= 0.02')
总共 6 个变量IV >= 0.02
3.2 WOE映射
将IV值筛选出来的6个特征做WOE映射,将各自的每个箱子替换成对应的WOE值。
# short_list记录6个特征名,short_list_2记录6个新的WOE特征名
short_list = high_IV.keys()
short_list_2 = []
for var in short_list:
newVar = var + '_WOE'
data[newVar] = data[var].map(lambda x:WOE_dict[var][x])
short_list_2.append(newVar)
dataWOE = data[short_list_2]
dataWOE.head()
3.3 共线性检验
做LR建模需要检验变量之间的共线性。
# 生成相关性矩阵
corr = round(dataWOE.corr(),2)
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True
plt.figure(figsize = (5, 5))
cmap = sns.diverging_palette(220, 10, as_cmap=True)
sns.heatmap(corr, mask=mask, cmap=cmap, center=0, annot =True, cbar_kws={"shrink": .5})
plt.show()
计算相关系数之后,还需要考虑多重共线性问题,即计算VIF值。VIF值越大,代表共线性越严重。VIF值:
0<VIF<10,不存在多重共线性
10<=VIF<100,存在较强的多重共线性
VIF>=100,存在严重的多重共线性
# 选择方差共线性<10的变量
from statsmodels.stats.outliers_influence import variance_inflation_factor as vif
col = np.array(data[short_list_2],dtype='float')
for i in range(len(short_list_2)):
print ('{} VIF是{}'.format(short_list_2[i], vif(col, i)))
可以看到,6个特征之间不存在多重共线性。
3.4 逻辑回归
在确定变量之间不存在多重共线性之后,就要对变量进行显著性分析,删除p值不显著的变量。
# 构建X和y
X = data[short_list_2]
for col in short_list_2:
X[col]=X[col].astype('float')
X['intercept'] = [1]*X.shape[0]
y = data['target']
# 建模
import statsmodels.api as sm
lr_sm=sm.Logit(y, X).fit()
glmodel = sm.GLM(y,X,family=sm.families.Binomial()).fit()
glmodel.summary()
可以看到,6个变量都是显著的。
用机器学习中的LR建模。
from sklearn.model_selection import train_test_split as sp
X_train, X_test, y_train, y_test = sp(X, y, test_size=0.3, random_state=1)
from sklearn.linear_model import LogisticRegression as LR
lr=LR(random_state=1)
lr.fit(X_train, y_train)
from sklearn import metrics
y_test_label = lr.predict(X_test)
y_test_value = lr.predict_proba(X_test)[:, 1]
print("测试集准确率是: {:.2%}".format(metrics.accuracy_score(y_test, y_test_label)))
print("测试集AUC是: {:.4}".format(metrics.roc_auc_score(y_test, y_test_value)))
4、创建评分卡
在确定模型的准确度和AUC值的可接受范围后,可以对每个入模变量进行单变量得分计算,从而求出变量不同取值下的得分。
计算评分卡的公式:
其中A叫做“补偿”,B叫做“刻度”,log(odds)代表了一个人违约的可能性。A、B两个常数可以通过两个假设的分值带入公式求出。这两个假设分别是:
①某个特定的违约概率下的预期分值
②指定的违约概率翻倍的分数(PDO)
b=lr.intercept_ #截距
coe=lr.coef_ #系数
a0 = coe[0][0] #每月还款金额组别 系数
a1 = coe[0][1] #贷款金额组别 系数
a2 = coe[0][2] #利率组别 系数
a3 = coe[0][3] #月负债比组别 系数
a4 = coe[0][4] #贷款等级 系数
a5 = coe[0][5] #过去6个月内被查询次数 系数
A = 500
PDO = 20 #每增加20分,odds(好坏比)增加1倍
B=PDO/np.log(2)
接下来对6个变量做单变量得分。
4.1 每月还款金额
WOE_dict['每月还款金额组别'] #获取字典key,即变量水平值
woe1 = WOE_dict['每月还款金额组别']['(-1, 300]']
score1 = -(B * a0* woe1) + (A-B*b)/dataWOE.shape[0]
woe2 = WOE_dict['每月还款金额组别']['(300, 750]']
score2 = -(B * a0 * woe2) + (A-B*b)/dataWOE.shape[0]
woe3 = WOE_dict['每月还款金额组别']['(750, 2000]']
score3 = -(B * a0 * woe3) + (A-B*b)/dataWOE.shape[0]
4.2 贷款金额
WOE_dict['贷款金额组别']
woe1 = WOE_dict['贷款金额组别']['(-1, 10000]']
score1 = -(B * a1* woe1) + (A-B*b)/dataWOE.shape[0]
woe2 = WOE_dict['贷款金额组别']['(10000, 20000]']
score2 = -(B * a1 * woe2) + (A-B*b)/dataWOE.shape[0]
woe3 = WOE_dict['贷款金额组别']['(20000, 50000]']
score3 = -(B * a1 * woe3) + (A-B*b)/dataWOE.shape[0]
4.3 利率
WOE_dict['利率组别']
woe1 = WOE_dict['利率组别']['(0, 8]']
score1 = -(B * a2* woe1) + (A-B*b)/dataWOE.shape[0]
woe2 = WOE_dict['利率组别']['(8, 13]']
score2 = -(B * a2 * woe2) + (A-B*b)/dataWOE.shape[0]
woe3 = WOE_dict['利率组别']['(13, 50]']
score3 = -(B * a2 * woe3) + (A-B*b)/dataWOE.shape[0]
4.4 月负债比
WOE_dict['月负债比组别']
woe1 = WOE_dict['月负债比组别']['(-1, 20]']
score1 = -(B * a3* woe1) + (A-B*b)/dataWOE.shape[0]
woe2 = WOE_dict['月负债比组别']['(20, 1000]']
score2 = -(B * a3 * woe2) + (A-B*b)/dataWOE.shape[0]
4.5 贷款等级
WOE_dict['贷款等级']
woe1 = WOE_dict['贷款等级'][1]
score1 = -(B * a4* woe1) + (A-B*b)/dataWOE.shape[0]
woe2 = WOE_dict['贷款等级'][2]
score2 = -(B * a4 * woe2) + (A-B*b)/dataWOE.shape[0]
woe3 = WOE_dict['贷款等级'][3]
score3 = -(B * a4 * woe3) + (A-B*b)/dataWOE.shape[0]
4.6 过去6个月内被查询次数
WOE_dict['过去6个月内被查询次数']
woe1 = WOE_dict['过去6个月内被查询次数'][0]
score1 = -(B * a5* woe1) + (A-B*b)/dataWOE.shape[0]
woe2 = WOE_dict['过去6个月内被查询次数'][1]
score2 = -(B * a5 * woe2) + (A-B*b)/dataWOE.shape[0]
woe3 = WOE_dict['过去6个月内被查询次数'][2]
score3 = -(B * a5 * woe3) + (A-B*b)/dataWOE.shape[0]
woe4 = WOE_dict['过去6个月内被查询次数'][3]
score4 = -(B * a5 * woe4) + (A-B*b)/dataWOE.shape[0]
woe5 = WOE_dict['过去6个月内被查询次数'][4]
score5 = -(B * a5 * woe5) + (A-B*b)/dataWOE.shape[0]