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