scorecardpy 是一款专门用于评分卡模型开发的 Python 库,由谢士晨博士开发,该软件包是R软件包评分卡的Python版本。量级较轻,依赖更少,旨在简化传统信用风险计分卡模型的开发过程,使这些模型的构建更加高效且易于操作。
本文主要讲解 scorecardpy 库的变量分箱 woebin 函数的使用,让你了解函数中每个入参的使用姿势,快速高效地进行评分卡建模分析。分箱的原理在之前的 sklearn-逻辑回归-制作评分卡 中有讲过,可自行跳转了解。
目录
scorecardpy安装
scorecardpy提供的功能
woebin 函数定义
woebin 函数参数解析
参数1:dt
参数2:y
check_y 函数对标签值进行检测
代码解析
参数3:x
参数4:var_skip
参数5:breaks_list
参数6:special_values
参数7:count_distr_limit
参数8:stop_limit
IV增长率
卡方值
参数9:bin_num_limit
参数10:positive
positive 参数的设置
参数11:no_cores
参数12:print_step
参数取值详解
使用建议
参数13:method
支持的分箱方法
1、tree(决策树分箱):
2、chimerge(卡方分箱):
使用建议
参数14:ignore_const_cols
参数15:ignore_datetime_cols
参数16:check_cate_num
参数17:replace_blank
参数18:save_breaks_list
scorecardpy安装
使用 Python 包管理器 pip 进行安装
pip install scorecardpy
scorecardpy提供的功能
为了使评分卡建模流程更加便捷,scorecardpy 库对建模过程中的关键步骤都封装好了函数,在不同环节可以调用不同的函数
- 数据集划分:通过
split_df
函数将数据集分割成训练集和测试集 - 变量筛选:使用
var_filter
函数根据变量的缺失率、IV值、同值性等因素进行筛选 - 变量分箱:提供
woebin
函数进行变量分箱,并可以生成分箱的可视化图表 - 评分转换:使用
scorecard
函数进行评分转换 - 效果评估:包括性能评估(
perf_eva
)和PSI(Population Stability Index)评估(perf_psi
)
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):pass
woebin 函数适用于对特征进行分箱(bining)和权重证据转换(weight of evidence)的工具,它使用决策树分箱或者卡方分箱的方法对变量进行最佳分箱。
默认 woe 计算是 ln(Distr_Bad_i/Distr_Good_i),如果需要实现 ln(Distr_Good_i/Distr_Bad_i),需要将入参 positive 设置为想反的值,比如 0 或者 'good'。
woebin
函数参数解析
参数1:dt
类型是 pandas.DataFrame,包含要分箱的特征和标签变量 y 的 DataFrame
参数2:y
标签值的变量名称
传入的 dt 中,标签 y 列的取值不能有空的情况,如果检测到空,对应的行将会被删除,同时会提示 “There are NaNs in \'{}\' column”
# remove na in yif dat[y].isnull().any():warnings.warn("There are NaNs in \'{}\' column. The rows with NaN in \'{}\' were removed from dat.".format(y,y))dat = dat.dropna(subset=[y])# dat = dat[pd.notna(dat[y])]
check_y 函数对标签值进行检测
check_y() 函数会对传入数据的 y 值进行检测,确保都是合理的,入参包含 dat, y, positive.
代码解析
def check_y(dat, y, positive):""":param dat: 数据集,pd.DataFrame 类型:param y: 标签y列名,string 类型,或者只有1个元素的 list 类型:param positive: 目标变量,string 类型:return: 数据集 dat"""positive = str(positive)# 数据集 dat 必须属于 pd.DataFrame 类型数据,且至少有2列,(一列特征,一列标签)if not isinstance(dat, pd.DataFrame):raise Exception("Incorrect inputs; dat should be a DataFrame.")elif dat.shape[1] <= 1:raise Exception("Incorrect inputs; dat should be a DataFrame with at least two columns.")# 如果 y 入参是 string 类型,在这里会被处理成只有1个元素的 list 类型数据y = str_to_list(y)# y 入参必须是只有1个元素,即一个数据集只能有一个标签列if len(y) != 1:raise Exception("Incorrect inputs; the length of y should be one")y = y[0]# y 标签列必须出现在数据集中if y not in dat.columns:raise Exception("Incorrect inputs; there is no \'{}\' column in dat.".format(y))# 如果有数据的标签取值为空,则该数据会被删除,不参与分箱评分if dat[y].isnull().any():warnings.warn("There are NaNs in \'{}\' column. The rows with NaN in \'{}\' were removed from dat.".format(y, y))dat = dat.dropna(subset=[y])# y 列数据转换成 int 类型数据if is_numeric_dtype(dat[y]):dat.loc[:, y] = dat[y].apply(lambda x: x if pd.isnull(x) else int(x)) # dat[y].astype(int)# y 的取值枚举必须是2中,否则抛出异常unique_y = np.unique(dat[y].values)if len(unique_y) == 2:# unique_y 存储了y取值的枚举, positive 传值必须有一个值属于 unique_y 的某一个枚举# re.search() 函数第一个入参是正则表达式,第二个入参是要搜索的字符串# positive 默认值为 'bad|1',表示搜索字符串中的 "bad" 或 "1"。# 假如 unique_y 取值为 [0, 1],positive取默认值,if 条件即判断 True 是否包含在[False, True] 中if True in [bool(re.search(positive, str(v))) for v in unique_y]:y1 = dat[y]# re.split() 函数第一个入参是分隔符,第二个入参是字符串,代表使用分隔符将字符串分成一个列表# 因为 '|'是特殊字符,代表'或',因此前面要加转义字符'\'# lambda 接收一个 dat[y] 中的取值,判断这个取值是否出现在 positive 列表中,出现则为1,否则为0y2 = dat[y].apply(lambda x: 1 if str(x) in re.split('\|', positive) else 0)# y1 和 y2 两个Series对象中的每个对应元素是否不相等。# 如果至少有一个元素不相等,.any()方法会返回True;如果所有元素都相等,则返回Falseif (y1 != y2).any():# 如果有不相等的,则将 y2 赋值给 dat[y] 列# loc() 函数接受两个参数:第一个参数是行的选择器,第二个参数是列的选择器# [:, y]:这是.loc[]属性中的选择器参数。冒号:表示选择所有的行,而y表示选择名为y的列dat.loc[:, y] = y2 # dat[y] = y2warnings.warn("The positive value in \"{}\" was replaced by 1 and negative value by 0.".format(y))else:raise Exception("Incorrect inputs; the positive value in \"{}\" is not specified".format(y))else:raise Exception("Incorrect inputs; the length of unique values in y column \'{}\' != 2.".format(y))return dat
参数3:x
要分箱的特征列表,默认值是 None,如果该参数不传,将会默认 dt 中所有的除了y的列,都会参与分箱
参数4:var_skip
不参与分箱的特征列表,如果传入的是 string 类型,会自动转换成一个元素的 list 类型,并参与分箱特征的排除
参数5:breaks_list
分箱的边界值,是个 list 类型,默认一般是 None,如果该参数传入了值,则会根据传入的边界值进行分箱。假设传入的是 [0,10,20,30],使用左开右闭进行分箱,则会被分成 4 个箱子,即 (0,10],(10,20],(20,30],(30,+∞)
参数6:special_values
特殊值列表,默认是 None。如果传入该参数,那么取值在该参数列表中的元素,将会被分到独立的箱子中
假设传入 [-90,-9999],那个取值为-90,-9999的都会在一个特殊箱子里
参数7:count_distr_limit
每个箱内的样本数量占总样本数量的最小占比,默认值是 0.05,即最小占比 5%
该参数可以确保分箱结果更加合理和实用,特别是在要处理不平衡数据集或需要严格控制复杂度时
通过限制每个箱内的最小样本数,可以减少过拟合的风险,并提高模型在新数据上的泛化能力
需要注意的是,该参数的具体行为和效果可能受到其它参数(如分箱方法,分箱数量等)的影响,在使用时需要合理设置
参数8:stop_limit
控制分箱停止条件的参数,当统计量(比如IV增长率,卡方值)的增长率小于设置的 stop_limit 参数时,停止继续分箱,取值范围是 0-0.5,默认值是 0.1
该参数主要用于决定何时停止进一步的分箱操作,以避免过拟合或生成过多不必要的箱
该参数默认值设置为0.1,是一个比较小的数值,这意味着只有当统计量的增长率显著时,分箱才会继续。通过调整 stop_limit
的值,用户可以在分箱的数量和模型的复杂度之间找到平衡。
需要注意的是,该参数只是控制分箱停止条件的参数之一,在使用时需要合理结合设置,以确保分箱既有效又高效。
IV增长率
如果 woebin
函数使用信息值(IV)作为分箱的依据,stop_limit
可以设定为一个阈值,当相邻两次分箱后IV值的增长率小于这个阈值时,分箱停止
卡方值
在一些实现中,stop_limit
也可能与卡方值相关。当卡方值小于某个基于 stop_limit
计算出的临界值时,分箱也会停止
参数9:bin_num_limit
整数类型,可以分的最大箱子数量,默认值是 8
该参数是限制分箱算法可以生成的最大箱子数量,从而避免过度分箱导致的模型复杂度过高或数据过拟合问题。
当 woebin
函数对变量进行分箱时,它会考虑这个限制,并尝试在不超过 bin_num_limit
设定的箱数的前提下,找到最优的分箱方案
-
如果
bin_num_limit
设定为一个较小的值,分箱算法会倾向于生成较少的、包含较多样本的箱,这可能会简化模型并减少过拟合的风险。 -
如果
bin_num_limit
设定为一个较大的值,分箱算法则有更多的自由度来生成更多的、包含较少样本的箱,这可能会提高模型的精细度,但同时也可能增加模型的复杂度和过拟合的风险
在使用 bin_num_limit
参数时,需要根据具体的数据集和建模需求来选择合适的值。如果数据集较大且变量分布复杂,可能需要更多的箱来捕捉数据的细节特征;而如果数据集较小或变量分布相对简单,则较少的箱可能就足够了
该参数与 stop_limit 参数、count_distr_limit参数结合控制分箱数量,以共同控制分箱的过程和结果
参数10:positive
用于指定目标变量是好类别的标签,通过该参数的设置,用来检测 dt 中的 y 列取值是否规范,如果不规范,将会被check函数检测出来,抛出异常终止建模。
该入参默认值是 "bad|1"
在信用评分卡建模中,这通常指的是那些我们希望模型能够识别并预测出的正面事件,比如客户会偿还贷款(即“好”客户)的情况
positive
参数的设置
positive
参数应该设置为目标变量中代表正面事件的唯一值或值的列表。这个参数对于函数来说很重要,因为它决定了如何计算诸如好坏比率(Good/Bad Ratio)、信息值(IV)等关键指标,这些指标在信用评分卡的开发中至关重要。
- 如果目标变量是二元的(比如,只有“好”和“坏”两种可能),
positive
参数就应该设置为表示“好”类别的那个值。 - 如果目标变量有多个类别,但其中只有一个被视为正面事件,那么
positive
参数同样应该设置为那个代表正面事件的值。 - 在某些情况下,如果目标变量使用了不同的编码方式(比如,用1表示“好”,用0表示“坏”),那么
positive
参数就应该设置为对应的编码值。
参数11:no_cores
并发的CPU核数。默认值是None,如果该参数传的是None,会看 x 特征变量的数量,如果小于10个特征,则使用 1 核 CPU,如果大于等于 10 个特征,则使用全部的 CPU。
参数12:print_step
该参数控制函数在执行分箱(binning)过程中的信息打印级别
默认值为 0 或者 False
参数取值详解
-
当
print_step
= 0 或 False 时:- 函数将不会打印任何分箱过程中的步骤信息。
- 这适用于不希望看到详细执行过程,只关心最终结果的用户。
-
当
print_step
> 0 或 True 时:- 函数将打印分箱过程中的一些关键步骤信息,如每个变量的分箱结果、每个分箱的坏账率(Bad Rate)、权重(Weight of Evidence, WoE)等。
- 打印的信息量可能随着
print_step
值的增加而增加,但具体行为取决于函数的实现。 - 这对于调试、理解分箱过程或查看中间结果非常有用。
使用建议
- 在初次使用
woebin
函数或对新数据进行分箱时,可以将print_step
设置为一个大于 0 的值或 True,以便查看分箱过程中的详细信息,确保分箱结果符合预期。 - 如果已经熟悉分箱过程,并且只关心最终结果,可以将
print_step
设置为 0 或 False,以减少不必要的输出信息。 - 该参数的使用根据个人实际情况即可。
参数13:method
用于指定分箱的方法,默认值为 tree
支持的分箱方法
1、tree
(决策树分箱):
- 决策树分箱是一种基于决策树算法的分箱方法。它通过递归地划分数据集来生成最优的分箱结果。
- 决策树分箱的优点是能够处理连续型变量和类别型变量,并且通常能够生成较为均衡的分箱结果。
- 缺点是计算复杂度较高,可能需要较长的计算时间,尤其是在处理大数据集时。
def woebin2_tree(dtm, init_count_distr=0.02, count_distr_limit=0.05,stop_limit=0.1, bin_num_limit=8, breaks=None, spl_val=None):# initial binningbin_list = woebin2_init_bin(dtm, init_count_distr=init_count_distr, breaks=breaks, spl_val=spl_val)initial_binning = bin_list['initial_binning']binning_sv = bin_list['binning_sv']if len(initial_binning.index) == 1:return {'binning_sv': binning_sv, 'binning': initial_binning}# initialize parameterslen_brks = len(initial_binning.index)bestbreaks = NoneIVt1 = IVt2 = 1e-10IVchg = 1 ## IV gain ratiostep_num = 1# best breaks from three to n+1 binsbinning_tree = Nonewhile (IVchg >= stop_limit) and (step_num + 1 <= min([bin_num_limit, len_brks])):binning_tree = woebin2_tree_add_1brkp(dtm, initial_binning, count_distr_limit, bestbreaks)# best breaksbestbreaks = binning_tree.loc[lambda x: x['bstbrkp'] != float('-inf'), 'bstbrkp'].tolist()# information valueIVt2 = binning_tree['total_iv'].tolist()[0]IVchg = IVt2 / IVt1 - 1 ## ratio gainIVt1 = IVt2# step_numstep_num = step_num + 1if binning_tree is None: binning_tree = initial_binning# return return {'binning_sv': binning_sv, 'binning': binning_tree}
2、chimerge
(卡方分箱):
- 卡方分箱是一种基于卡方统计量的分箱方法。它通过合并相邻的区间来减少区间数量,直到满足某个停止条件为止。
- 卡方分箱的优点是能够处理连续型变量,并且生成的分箱结果通常具有较好的单调性。
- 缺点是可能无法处理类别型变量,并且需要指定分箱的数量或停止条件。
卡方算法参考文献:
ChiMerge算法详解:数据离散化与应用-CSDN博客
ChiMerge:Discretization of numeric attributs
def woebin2_chimerge(dtm, init_count_distr=0.02, count_distr_limit=0.05, stop_limit=0.1, bin_num_limit=8, breaks=None, spl_val=None):# chisq = function(a11, a12, a21, a22) {# A = list(a1 = c(a11, a12), a2 = c(a21, a22))# Adf = do.call(rbind, A)## Edf =# matrix(rowSums(Adf), ncol = 1) %*%# matrix(colSums(Adf), nrow = 1) /# sum(Adf)## sum((Adf-Edf)^2/Edf)# }# function to create a chisq column in initial_binningdef add_chisq(initial_binning):chisq_df = pd.melt(initial_binning, id_vars=["brkp", "variable", "bin"], value_vars=["good", "bad"],var_name='goodbad', value_name='a')\.sort_values(by=['goodbad', 'brkp']).reset_index(drop=True)###chisq_df['a_lag'] = chisq_df.groupby('goodbad')['a'].apply(lambda x: x.shift(1))#.reset_index(drop=True)chisq_df['a_rowsum'] = chisq_df.groupby('brkp')['a'].transform(lambda x: sum(x))#.reset_index(drop=True)chisq_df['a_lag_rowsum'] = chisq_df.groupby('brkp')['a_lag'].transform(lambda x: sum(x))#.reset_index(drop=True)###chisq_df = pd.merge(chisq_df.assign(a_colsum = lambda df: df.a+df.a_lag), chisq_df.groupby('brkp').apply(lambda df: sum(df.a+df.a_lag)).reset_index(name='a_sum'))\.assign(e = lambda df: df.a_rowsum*df.a_colsum/df.a_sum,e_lag = lambda df: df.a_lag_rowsum*df.a_colsum/df.a_sum).assign(ae = lambda df: (df.a-df.e)**2/df.e + (df.a_lag-df.e_lag)**2/df.e_lag).groupby('brkp').apply(lambda x: sum(x.ae)).reset_index(name='chisq')# returnreturn pd.merge(initial_binning.assign(count = lambda x: x['good']+x['bad']), chisq_df, how='left')# initial binningbin_list = woebin2_init_bin(dtm, init_count_distr=init_count_distr, breaks=breaks, spl_val=spl_val)initial_binning = bin_list['initial_binning']binning_sv = bin_list['binning_sv']# return initial binning if its row number equals 1if len(initial_binning.index)==1: return {'binning_sv':binning_sv, 'binning':initial_binning}# dtm_rowsdtm_rows = len(dtm.index) # chisq limitfrom scipy.special import chdtrichisq_limit = chdtri(1, stop_limit)# binning with chisq columnbinning_chisq = add_chisq(initial_binning)# parambin_chisq_min = binning_chisq.chisq.min()bin_count_distr_min = min(binning_chisq['count']/dtm_rows)bin_nrow = len(binning_chisq.index)# remove brkp if chisq < chisq_limitwhile bin_chisq_min < chisq_limit or bin_count_distr_min < count_distr_limit or bin_nrow > bin_num_limit:# brkp needs to be removedif bin_chisq_min < chisq_limit:rm_brkp = binning_chisq.assign(merge_tolead = False).sort_values(by=['chisq', 'count']).iloc[0,]elif bin_count_distr_min < count_distr_limit:rm_brkp = binning_chisq.assign(count_distr = lambda x: x['count']/sum(x['count']),chisq_lead = lambda x: x['chisq'].shift(-1).fillna(float('inf'))).assign(merge_tolead = lambda x: x['chisq'] > x['chisq_lead'])# replace merge_tolead as Truerm_brkp.loc[np.isnan(rm_brkp['chisq']), 'merge_tolead']=True# order select 1strm_brkp = rm_brkp.sort_values(by=['count_distr']).iloc[0,]elif bin_nrow > bin_num_limit:rm_brkp = binning_chisq.assign(merge_tolead = False).sort_values(by=['chisq', 'count']).iloc[0,]else:break# set brkp to lead's or lag'sshift_period = -1 if rm_brkp['merge_tolead'] else 1binning_chisq = binning_chisq.assign(brkp2 = lambda x: x['brkp'].shift(shift_period))\.assign(brkp = lambda x:np.where(x['brkp'] == rm_brkp['brkp'], x['brkp2'], x['brkp']))# groupby brkpbinning_chisq = binning_chisq.groupby('brkp').agg({'variable':lambda x:np.unique(x),'bin': lambda x: '%,%'.join(x),'good': sum,'bad': sum}).assign(badprob = lambda x: x['bad']/(x['good']+x['bad']))\.reset_index()# update## add chisq to new binning dataframebinning_chisq = add_chisq(binning_chisq)## parambin_nrow = len(binning_chisq.index)if bin_nrow == 1:breakbin_chisq_min = binning_chisq.chisq.min()bin_count_distr_min = min(binning_chisq['count']/dtm_rows)# format init_bin # remove (.+\\)%,%\\[.+,)if is_numeric_dtype(dtm['value']):binning_chisq = binning_chisq\.assign(bin = lambda x: [re.sub(r'(?<=,).+%,%.+,', '', i) if ('%,%' in i) else i for i in x['bin']])\.assign(brkp = lambda x: [float(re.match('^\[(.*),.+', i).group(1)) for i in x['bin']])# return return {'binning_sv':binning_sv, 'binning':binning_chisq}
使用建议
在选择分箱方法时,应根据数据的类型(连续型或类别型)、数据的特点(如分布情况、缺失值情况等)以及具体的业务需求来进行选择
- 如果数据中包含较多的连续型变量,并且希望分箱结果具有较好的单调性,可以考虑使用卡方分箱(
chimerge
)。 - 如果数据中包含较多的类别型变量,或者希望分箱过程能够自动处理不同类型的数据,可以考虑使用决策树分箱(
tree
)
参数14:ignore_const_cols
用于控制是否忽略常量列的分箱处理,参数类型为 bool
默认值:True,即忽略常量列的分箱处理
常量列指在整个数据集中所有行的值都相同的列,这些列对于建模通常没有提供有用的信息,因此可以忽略,从而减少不必要的计算,提高分箱的效率。
参数15:ignore_datetime_cols
用于控制是否忽略日期时间列的分箱处理,参数类型为 bool
默认值:True,即忽略日期时间列的分箱处理
日期时间列通常包含时间戳、日期或时间信息,这些信息对于某些类型的分析可能是有用的,但在分箱过程中可能需要特殊的处理。
当需要对日期时间列进行分箱处理,即ignore_datetime_cols
= False
时,这可能需要对日期时间列进行额外的预处理,例如将它们转换为数值型特征或提取特定的日期时间组件(如年、月、日等)。
参数16:check_cate_num
用于控制在对类别型变量(categorical variables)进行分箱时,是否检查并限制每个类别中的样本数量,并在必要时进行合并或处理,参数类型为 bool
默认值:True
如果类别型变量的类别数量超过50,即会给出提示,由用户判断是否继续分箱。如果样本中有很多类别型变量,并且每个变量枚举值都非常多,那么也会非常影响建模效率。
def check_cateCols_uniqueValues(dat, var_skip = None):# character columns with too many unique valueschar_cols = [i for i in list(dat) if not is_numeric_dtype(dat[i])]if var_skip is not None: char_cols = list(set(char_cols) - set(str_to_list(var_skip)))char_cols_too_many_unique = [i for i in char_cols if len(dat[i].unique()) >= 50]if len(char_cols_too_many_unique) > 0:print('>>> There are {} variables have too many unique non-numberic values, which might cause the binning process slow. Please double check the following variables: \n{}'.format(len(char_cols_too_many_unique), ', '.join(char_cols_too_many_unique)))print('>>> Continue the binning process?')print('1: yes \n2: no')cont = int(input("Selection: "))while cont not in [1, 2]:cont = int(input("Selection: "))if cont == 2:raise SystemExit(0)return None
参数17:replace_blank
用于控制如何处理数据中的空白(或缺失)值,参数类型为 bool
默认值:True
def rep_blank_na(dat): # cant replace blank string in categorical value with nan# 如果有重复的索引,则重置索引if dat.index.duplicated().any():dat = dat.reset_index(drop = True)warnings.warn('There are duplicated index in dataset. The index has been reseted.')blank_cols = [i for i in list(dat) if dat[i].astype(str).str.findall(r'^\s*$').apply(lambda x:0 if len(x)==0 else 1).sum()>0]if len(blank_cols) > 0:warnings.warn('There are blank strings in {} columns, which are replaced with NaN. \n (ColumnNames: {})'.format(len(blank_cols), ', '.join(blank_cols)))
# dat[dat == [' ','']] = np.nan
# dat2 = dat.apply(lambda x: x.str.strip()).replace(r'^\s*$', np.nan, regex=True)dat.replace(r'^\s*$', np.nan, regex=True)return dat
参数18:save_breaks_list
用于控制是否将分箱后的断点(或称为区间边界)保存为一个列表。这个参数对于后续的分箱处理、模型训练或结果分析可能非常重要,因为它决定了分箱的具体方式和每个箱子的边界
默认值:None
如果该参数不为 None,传入的是 String 类型数据,则会用于文件名,将分箱信息保存在文件中。具体由 bins_to_breaks 函数实现
def bins_to_breaks(bins, dt, to_string=False, save_string=None):if isinstance(bins, dict):bins = pd.concat(bins, ignore_index=True)# x variablesxs_all = bins['variable'].unique()# dtypes of variablesvars_class = pd.DataFrame({'variable': xs_all,'not_numeric': [not is_numeric_dtype(dt[i]) for i in xs_all]})# breakslist of binsbins_breakslist = bins[~bins['breaks'].isin(["-inf","inf","missing"]) & ~bins['is_special_values']]bins_breakslist = pd.merge(bins_breakslist[['variable', 'breaks']], vars_class, how='left', on='variable')bins_breakslist.loc[bins_breakslist['not_numeric'], 'breaks'] = '\''+bins_breakslist.loc[bins_breakslist['not_numeric'], 'breaks']+'\''bins_breakslist = bins_breakslist.groupby('variable')['breaks'].agg(lambda x: ','.join(x))if to_string:bins_breakslist = "breaks_list={\n"+', \n'.join('\''+bins_breakslist.index[i]+'\': ['+bins_breakslist[i]+']' for i in np.arange(len(bins_breakslist)))+"}"if save_string is not None:brk_lst_name = '{}_{}.py'.format(save_string, time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))with open(brk_lst_name, 'w') as f:f.write(bins_breakslist)print('[INFO] The breaks_list is saved as {}'.format(brk_lst_name))return return bins_breakslist