数据分箱的重要性离散特征的增加和减少都很容易,易于模型的快速迭代;

稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;

离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;

逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;

离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;

特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;

特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。

可以将缺失作为独立的一类带入模型。

将所有变量变换到相似的尺度上。

数据分箱的方法有:

1.等距分箱

def bin_frequency(x,y,n=10):

total=y.count()

bad=y.sum()

good=total-bad

d1=pd.DataFrame({"x":x,"y":y,'bin':pd.qcut(x,n)})

d2=d1.groupby('bin',as_index=True)

d3=pd.DataFrame()

d3['total']=d2.y.count() ##每个箱中的总样本数

d3['bad']=d2.y.sum() ##每个箱中的坏样本数

d3['good']=d3['total']-d3['bad'] ##每个箱中的好样本数

d3['bad_rate']=d3['bad']/d3['total']*100 ##每个箱中的坏样本率

d3['%bad']=d3['bad']/bad*100 ##每个箱中的坏样本占总坏样本的比重

d3['%good']=d3['good']/good*100 ##每个箱中的好样本占总好样本的比重

d3['woe']=np.log(d3['%bad']/d3['%good'])

iv=((d3['%bad']-d3['%good'])*d3['woe']).sum()

d3.reset_index(inplace=True)

print(d3,iv)

2.等频分箱

def bin_distance(x,y,n=10): ##主要woe有可能为-inf

total=y.count()

bad=y.sum()

good=total-bad

d1=pd.DataFrame({"x":x,"y":y,'bin':pd.cut(x,n)}) ##等距分箱

d2=d1.groupby('bin',as_index=True)

d3=pd.DataFrame()

d3['total']=d2.y.count() ##每个箱中的总样本数

d3['bad']=d2.y.sum() ##每个箱中的坏样本数

d3['good']=d3['total']-d3['bad'] ##每个箱中的好样本数

d3['bad_rate']=d3['bad']/d3['total']*100 ##每个箱中的坏样本率

d3['%bad']=d3['bad']/bad*100 ##每个箱中的坏样本占总坏样本的比重

d3['%good']=d3['good']/good*100 ##每个箱中的好样本占总好样本的比重

d3['woe']=np.log(d3['%bad']/d3['%good'])

iv=((d3['%bad']-d3['%good'])*d3['woe']).sum()

d3.reset_index(inplace=True)

print(d3,iv)

3.自定义分箱

def bin_self(x,y,cut): ##cut:自定义分箱(list)

total=y.count()

bad=y.sum()

good=total-bad

d1=pd.DataFrame({"x":x,"y":y,'bin':pd.cut(x,cut)}) ##等距分箱

d2=d1.groupby('bin',as_index=True)

d3=pd.DataFrame()

d3['total']=d2.y.count() ##每个箱中的总样本数

d3['bad']=d2.y.sum() ##每个箱中的坏样本数

d3['good']=d3['total']-d3['bad'] ##每个箱中的好样本数

d3['bad_rate']=d3['bad']/d3['total']*100 ##每个箱中的坏样本率

d3['%bad']=d3['bad']/bad*100 ##每个箱中的坏样本占总坏样本的比重

d3['%good']=d3['good']/good*100 ##每个箱中的好样本占总好样本的比重

d3['woe']=np.log(d3['%bad']/d3['%good'])

iv=((d3['%bad']-d3['%good'])*d3['woe']).sum()

d3.reset_index(inplace=True)

print(d3,iv)

bin_self(X['age'],Y,[-10,20,40,60,80,110])

4.有监督分箱--决策树分箱

先使用有监督学习决策树算法得到决策树的叶子节点,把叶子节点的值作为分箱的边界,再使用自定义分箱得到分箱结果。

from sklearn.tree import DecisionTreeClassifier

def decision_tree_bin(x,y):

'''利用决策树获得最优分箱的边界值列表'''

boundary = [] # 待return的分箱边界值列表

x = x.fillna(-999).values # 填充缺失值

y = y.values

clf = DecisionTreeClassifier(criterion='entropy', #“信息熵”最小化准则划分

max_leaf_nodes=6, # 最大叶子节点数

min_samples_leaf=0.05) # 叶子节点样本数量最小占比

clf.fit(x.reshape(-1, 1), y) # 训练决策树

n_nodes = clf.tree_.node_count

children_left = clf.tree_.children_left

children_right = clf.tree_.children_right

threshold = clf.tree_.threshold

for i in range(n_nodes):

if children_left[i] != children_right[i]: # 获得决策树节点上的划分边界值

boundary.append(threshold[i])

boundary.sort()

min_x = x.min()

max_x = x.max() + 0.1 # +0.1是为了考虑后续groupby操作时,能包含特征最大值的样本

boundary = [min_x] + boundary + [max_x]

return boundary

boundary_list=decision_tree_bin(X['age'],Y)

bin_self(X['age'],Y,boundary_list)

5.无监督分箱--卡方分箱

卡方分箱是再实际工作中常用的分箱方法,这里我先介绍一下卡方检验,卡方检验是检验两个分类变量之间的关联关系,其根本思想就是在于比较理论频数和实际频数的吻合程度或拟合优度问题。

卡方检验的H0:观察频数与期望频数没有差别。

然后计算卡方值:计算公式为:

其中A为实际值,T为理论值,如果卡方值越大,越能拒绝原假设。

卡方分箱采取自底向上不断合并的方法完成分箱操作。在每一步的合并过程中,依靠最小的卡方值来寻找最优的合并项。其核心思想是,如果某两个区间可以被合并,那么这两个区间的坏样本需要有最接近的分布,进而意味着两个区间的卡方值是最小的。于是ChiMerge的步骤如下:将数值变量A排序后分成区间较多的若干组,设为A_1,A_2,…,A_n

计算A_1 与A_2合并后的卡方值,A_2 与A_3合并后的卡方值,直至A_(n-1) 与A_n合并后的卡方值

找出上一步所有合并后的卡方值中最小的一个,假设为A_(i-1) 与A_i,将其合并形成新的A_(i-1)

不断重复2和3,直至满足终止条件

import math

import pandas as pd

import numpy as np

# 先用卡方分箱输出变量的分割点

def split_data(df,col,split_num):

"""df: 原始数据集col:需要分箱的变量split_num:分割点的数量"""

df2 = df.copy()

count = df2.shape[0] # 总样本数

n = math.floor(count/split_num) # 按照分割点数目等分后每组的样本数

split_index = [i*n for i in range(1,split_num)] # 分割点的索引

values = sorted(list(df2[col])) # 对变量的值从小到大进行排序

split_value = [values[i] for i in split_index] # 分割点对应的value

split_value = sorted(list(set(split_value))) # 分割点的value去重排序

return split_value

def assign_group(x,split_bin):

"""x:变量的valuesplit_bin:split_data得出的分割点list"""

n = len(split_bin)

if x<=min(split_bin):

return min(split_bin) # 如果x小于分割点的最小值,则x映射为分割点的最小值

elif x>max(split_bin): # 如果x大于分割点的最大值,则x映射为分割点的最大值

return 10e10

else:

for i in range(n-1):

if split_bin[i]

return split_bin[i+1]

def bin_bad_rate(df,col,target,grantRateIndicator=0):

"""df:原始数据集col:原始变量/变量映射后的字段target:目标变量的字段grantRateIndicator:是否输出总体的违约率"""

total = df.groupby([col])[target].count()

bad = df.groupby([col])[target].sum()

total_df = pd.DataFrame({'total':total})

bad_df = pd.DataFrame({'bad':bad})

regroup = pd.merge(total_df,bad_df,left_index=True,right_index=True,how='left')

regroup = regroup.reset_index()

regroup['bad_rate'] = regroup['bad']/regroup['total'] # 计算根据col分组后每组的违约率

dict_bad = dict(zip(regroup[col],regroup['bad_rate'])) # 转为字典形式

if grantRateIndicator==0:

return (dict_bad,regroup)

total_all= df.shape[0]

bad_all = df[target].sum()

all_bad_rate = bad_all/total_all # 计算总体的违约率

return (dict_bad,regroup,all_bad_rate)

def cal_chi2(df,all_bad_rate):

"""df:bin_bad_rate得出的regroupall_bad_rate:bin_bad_rate得出的总体违约率"""

df2 = df.copy()

df2['expected'] = df2['total']*all_bad_rate # 计算每组的坏用户期望数量

combined = zip(df2['expected'],df2['bad']) # 遍历每组的坏用户期望数量和实际数量

chi = [(i[0]-i[1])**2/i[0] for i in combined] # 计算每组的卡方值

chi2 = sum(chi) # 计算总的卡方值

return chi2

def assign_bin(x,cutoffpoints):

"""x:变量的valuecutoffpoints:分箱的切割点"""

bin_num = len(cutoffpoints)+1 # 箱体个数

if x<=cutoffpoints[0]: # 如果x小于最小的cutoff点,则映射为Bin 0

return 'Bin 0'

elif x>cutoffpoints[-1]: # 如果x大于最大的cutoff点,则映射为Bin(bin_num-1)

return 'Bin{}'.format(bin_num-1)

else:

for i in range(0,bin_num-1):

if cutoffpoints[i]

return 'Bin{}'.format(i+1)

def ChiMerge(df,col,target,max_bin=5,min_binpct=0):

col_unique = sorted(list(set(df[col]))) # 变量的唯一值并排序

n = len(col_unique) # 变量唯一值得个数

df2 = df.copy()

if n>100: # 如果变量的唯一值数目超过100,则将通过split_data和assign_group将x映射为split对应的value

split_col = split_data(df2,col,100) # 通过这个目的将变量的唯一值数目人为设定为100

df2['col_map'] = df2[col].map(lambda x:assign_group(x,split_col))

else:

df2['col_map'] = df2[col] # 变量的唯一值数目没有超过100,则不用做映射

# 生成dict_bad,regroup,all_bad_rate的元组

(dict_bad,regroup,all_bad_rate) = bin_bad_rate(df2,'col_map',target,grantRateIndicator=1)

col_map_unique = sorted(list(set(df2['col_map']))) # 对变量映射后的value进行去重排序

group_interval = [[i] for i in col_map_unique] # 对col_map_unique中每个值创建list并存储在group_interval中

while (len(group_interval)>max_bin): # 当group_interval的长度大于max_bin时,执行while循环

chi_list=[]

for i in range(len(group_interval)-1):

temp_group = group_interval[i]+group_interval[i+1] # temp_group 为生成的区间,list形式,例如[1,3]

chi_df = regroup[regroup['col_map'].isin(temp_group)]

chi_value = cal_chi2(chi_df,all_bad_rate) # 计算每一对相邻区间的卡方值

chi_list.append(chi_value)

best_combined = chi_list.index(min(chi_list)) # 最小的卡方值的索引

# 将卡方值最小的一对区间进行合并

group_interval[best_combined] = group_interval[best_combined]+group_interval[best_combined+1]

# 删除合并前的右区间

group_interval.remove(group_interval[best_combined+1])

# 对合并后每个区间进行排序

group_interval = [sorted(i) for i in group_interval]

# cutoff点为每个区间的最大值

cutoffpoints = [max(i) for i in group_interval[:-1]]

# 检查是否有箱只有好样本或者只有坏样本

df2['col_map_bin'] = df2['col_map'].apply(lambda x:assign_bin(x,cutoffpoints)) # 将col_map映射为对应的区间Bin

# 计算每个区间的违约率

(dict_bad,regroup) = bin_bad_rate(df2,'col_map_bin',target)

# 计算最小和最大的违约率

[min_bad_rate,max_bad_rate] = [min(dict_bad.values()),max(dict_bad.values())]

# 当最小的违约率等于0,说明区间内只有好样本,当最大的违约率等于1,说明区间内只有坏样本

while min_bad_rate==0 or max_bad_rate==1:

bad01_index = regroup[regroup['bad_rate'].isin([0,1])].col_map_bin.tolist()# 违约率为1或0的区间

bad01_bin = bad01_index[0]

if bad01_bin==max(regroup.col_map_bin):

cutoffpoints = cutoffpoints[:-1] # 当bad01_bin是最大的区间时,删除最大的cutoff点

elif bad01_bin==min(regroup.col_map_bin):

cutoffpoints = cutoffpoints[1:] # 当bad01_bin是最小的区间时,删除最小的cutoff点

else:

bad01_bin_index = list(regroup.col_map_bin).index(bad01_bin) # 找出bad01_bin的索引

prev_bin = list(regroup.col_map_bin)[bad01_bin_index-1] # bad01_bin前一个区间

df3 = df2[df2.col_map_bin.isin([prev_bin,bad01_bin])]

(dict_bad,regroup1) = bin_bad_rate(df3,'col_map_bin',target)

chi1 = cal_chi2(regroup1,all_bad_rate) # 计算前一个区间和bad01_bin的卡方值

later_bin = list(regroup.col_map_bin)[bad01_bin_index+1] # bin01_bin的后一个区间

df4 = df2[df2.col_map_bin.isin([later_bin,bad01_bin])]

(dict_bad,regroup2) = bin_bad_rate(df4,'col_map_bin',target)

chi2 = cal_chi2(regroup2,all_bad_rate) # 计算后一个区间和bad01_bin的卡方值

if chi1

cutoffpoints.remove(cutoffpoints[bad01_bin_index-1])

else: # 当chi1>=chi2时,删除bin01对应的cutoff点

cutoffpoints.remove(cutoffpoints[bad01_bin_index])

df2['col_map_bin'] = df2['col_map'].apply(lambda x:assign_bin(x,cutoffpoints))

(dict_bad,regroup) = bin_bad_rate(df2,'col_map_bin',target)

# 重新将col_map映射至区间,并计算最小和最大的违约率,直达不再出现违约率为0或1的情况,循环停止

[min_bad_rate,max_bad_rate] = [min(dict_bad.values()),max(dict_bad.values())]

# 检查分箱后的最小占比

if min_binpct>0:

group_values = df2['col_map'].apply(lambda x:assign_bin(x,cutoffpoints))

df2['col_map_bin'] = group_values # 将col_map映射为对应的区间Bin

group_df = group_values.value_counts().to_frame()

group_df['bin_pct'] = group_df['col_map']/n # 计算每个区间的占比

min_pct = group_df.bin_pct.min() # 得出最小的区间占比

while min_pct2: # 当最小的区间占比小于min_pct且cutoff点的个数大于2,执行循环

# 下面的逻辑基本与“检验是否有箱体只有好/坏样本”的一致

min_pct_index = group_df[group_df.bin_pct==min_pct].index.tolist()

min_pct_bin = min_pct_index[0]

if min_pct_bin == max(group_df.index):

cutoffpoints=cutoffpoints[:-1]

elif min_pct_bin == min(group_df.index):

cutoffpoints=cutoffpoints[1:]

else:

minpct_bin_index = list(group_df.index).index(min_pct_bin)

prev_pct_bin = list(group_df.index)[minpct_bin_index-1]

df5 = df2[df2['col_map_bin'].isin([min_pct_bin,prev_pct_bin])]

(dict_bad,regroup3) = bin_bad_rate(df5,'col_map_bin',target)

chi3 = cal_chi2(regroup3,all_bad_rate)

later_pct_bin = list(group_df.index)[minpct_bin_index+1]

df6 = df2[df2['col_map_bin'].isin([min_pct_bin,later_pct_bin])]

(dict_bad,regroup4) = bin_bad_rate(df6,'col_map_bin',target)

chi4 = cal_chi2(regroup4,all_bad_rate)

if chi3

cutoffpoints.remove(cutoffpoints[minpct_bin_index-1])

else:

cutoffpoints.remove(cutoffpoints[minpct_bin_index])

return cutoffpoints

cut=chisqbin.ChiMerge(data,'age','SeriousDlqin2yrs',max_bin=5,min_binpct=0)

def chisqbin_bin(x,y,cutpoints):

inf = float('inf')

ninf = float('-inf')

total=y.count()

bad=y.sum()

good=total-bad

cutpoints.insert(0,ninf)

cutpoints.append(inf)

d1=pd.DataFrame({"x":x,"y":y,'bin':pd.cut(x,cut)}) ##等距分箱

d2=d1.groupby('bin',as_index=True)

d3=pd.DataFrame()

d3['total']=d2.y.count() ##每个箱中的总样本数

d3['bad']=d2.y.sum() ##每个箱中的坏样本数

d3['good']=d3['total']-d3['bad'] ##每个箱中的好样本数

d3['bad_rate']=d3['bad']/d3['total']*100 ##每个箱中的坏样本率

d3['%bad']=d3['bad']/bad*100 ##每个箱中的坏样本占总坏样本的比重

d3['%good']=d3['good']/good*100 ##每个箱中的好样本占总好样本的比重

d3['woe']=np.log(d3['%bad']/d3['%good'])

iv=((d3['%bad']-d3['%good'])*d3['woe']).sum()

print(d3,iv)

return d3,iv

chisqbin_bin(X['age'],Y,cut)

python分箱处理_python数据处理--WOE分箱相关推荐

  1. python woe分箱_python数据处理--WOE分箱

    数据分箱的重要性离散特征的增加和减少都很容易,易于模型的快速迭代: 稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展: 离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1 ...

  2. python画厢式图_Python数据可视化:箱线图多种库画法

    概念 箱线图通过数据的四分位数来展示数据的分布情况.例如:数据的中心位置,数据间的离散程度,是否有异常值等. 把数据从小到大进行排列并等分成四份,第一分位数(Q1),第二分位数(Q2)和第三分位数(Q ...

  3. python分代回收_python 垃圾回收——分代回收 和java有些区别 注意循环引用无法被回收...

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 语言的内存管理是语言设计的一个重要方面.它是决定语言性能的重要因素.无论是C语言的 ...

  4. python如何分成两行_python将文本分每两行一组并保存到文件

    业务需求 需要将文本文件分每两行一组 jb51.txt 1:www.jb51.net 2:www.jb51.net 3:www.jb51.net 4:www.jb51.net 5:www.jb51.n ...

  5. python解复杂方程_Python数据处理篇之Sympy系列(五)---解方程

    前言 sympy不仅在符号运算方面强大,在解方程方面也是很强大. 本章节学习对应官网的:Solvers 官方教程 (一)求解多元一次方程-solve() 1.说明: 解多元一次方程可以使用solve( ...

  6. python解二元一次方程_Python数据处理篇之Sympy系列(五)---解方程

    前言 sympy不仅在符号运算方面强大,在解方程方面也是很强大. 本章节学习对应官网的:Solvers 官方教程 https://docs.sympy.org/latest/tutorial/solv ...

  7. python绘制dca曲线_Python数据处理从零开始----第四章(可视化)(9)线性相关曲线...

    原文:Python数据处理从零开始----第四章(可视化)(9)线性相关曲线​www.jianshu.com ============================================= ...

  8. python处理异常值循环_Python数据处理:异常值处理方法之3σ原则

    原标题:Python数据处理:异常值处理方法之3σ原则 一.3σ原则定义 异常值是指样本中的个别值,其数值明显偏离其余的观测值.异常值也称离群点,异常值的分析也称为离群点的分析. 在进行机器学习过程中 ...

  9. python拟合反比例函数_Python 数据处理(八)—— 应用函数

    6. 函数应用 要将您自己定义的函数或其他第三方库的函数应用于 pandas 对象上,主要包含下面的方法. 具体使用哪种方法需要根据需要,是在整个 DataFrame 还是 Series 上.行或列上 ...

最新文章

  1. react开发心路历程
  2. 2020 年 Node.js 开发者调查报告
  3. java客户端_Java常用的Http client客户端
  4. MySQL数据库“局部”乱码
  5. [19/05/06-星期一] JDBC(Java DataBase Connectivity,java数据库连接)_基本知识
  6. android音频杂音问题_如何消除音频中的噪声?用这个简单好用的音频剪辑软件就够了...
  7. intellij idea 无法识别新装字体的问题的两种解决办法
  8. 功率因数cosφ仪表盘
  9. maya2020 redshift3.0.31demo版安装方法。
  10. 『幽默』网络经典语录1600条~~(可累死我了)
  11. 算法竞赛入门知识干货
  12. 万能和弦与和弦走向浅析
  13. ubuntu20.04开机黑屏解决方法
  14. 双向dijkstra
  15. 2022年计算机考研数学国家线一般多少分?
  16. AlarmManager详解
  17. Metasploit -- 木马生成原理和方法
  18. 快速将多个excel表合并成一个excel表
  19. 关于安装office,出现你已安装32位,无法安装64位问题的解决方法
  20. 5000元性价比高的笔记本_不超过5000元性价比最高的笔记本有那几款?

热门文章

  1. JAVA代码—算法基础:切割钢锯条售卖的问题
  2. mysql修改路径报错_mysql修改数据存储路径报错处理
  3. HTML怎么做成多彩背景,HTML5+Colours.js 彩色格子布料背景生成器
  4. Real-Time Rendering——4.5 Morphing 变形
  5. php用户量剧增导致cpu100%解决办法
  6. 内网渗透思路06之一次完整的渗透测试
  7. 教师节浙大颁出百万大奖,国内编程教育引路人翁恺获殊荣
  8. git切换远程分支和tag操作
  9. python绘制女孩_用Python画小女孩放风筝的示例
  10. 任务协作管理工具 Trello 使用简介