數據分析中的變量選擇——德國信貸數據集(variable selection in data analysis-German credit datasets)


 

最近看了一本《Python金融大數據風控建模實戰:基於機器學習》(機械工業出版社)這本書,看了其中第7章:變量選擇 內容,總結了主要內容以及做了代碼詳解,分享給大家。

1. 主要知識點

變量選擇是特征工程中非常重要的一部分。特征工程是一個先升維后降維的過程。升維的過程是結合業務理解盡可能多地加工特征,是一個非常耗時且需要發散思維的過程。而變量選擇就是降維的過程,因為傳統評分卡模型為了保證模型的穩定性與Logisitc回歸模型的准確性,往往對入模變量有非常嚴格的考量,並希望入模變量最好不要超過20個,且要與業務強相關。這時變量選擇顯得尤為重要,特征加工階段往往可以得到幾百維或更高維度的特征,而從諸多特征中只保留最有意義的特征也是重要且耗時的過程。
變量選擇的方法很多,常用的方法有過濾法(Filter)、包裝法(Wrapper)、嵌入法(Embedding),並且在上述方法中又有單變量選擇、多變量選擇、有監督選擇、無監督選擇。

2. 代碼

數據的使用還是德國信貸數據集,具體數據集介紹和獲取方法請看 數據清洗與預處理代碼詳解——德國信貸數據集(data cleaning and preprocessing - German credit datasets)

注意:

import variable_bin_methods as varbin_meth
import variable_encode as var_encode

中 variable_bin_methods 和 variable_encode 分別是 第5章 變量編碼第6章 變量分箱 中的代碼。

主代碼:

  1 # -*- coding: utf-8 -*-
  2 """
  3 第7章:變量選擇
  4 數據獲取
  5 """
  6 import os
  7 import pandas as pd
  8 import numpy as np
  9 from sklearn.model_selection import train_test_split
 10 import variable_bin_methods as varbin_meth
 11 import variable_encode as var_encode
 12 import matplotlib
 13 import matplotlib.pyplot as plt
 14 # matplotlib.use('Qt5Agg')
 15 matplotlib.rcParams['font.sans-serif'] = ['SimHei']
 16 matplotlib.rcParams['axes.unicode_minus'] = False
 17 from sklearn.linear_model import LogisticRegression
 18 from sklearn.feature_selection import VarianceThreshold
 19 from sklearn.feature_selection import SelectKBest, f_classif
 20 from sklearn.feature_selection import RFECV
 21 from sklearn.svm import SVR
 22 from sklearn.feature_selection import SelectFromModel
 23 import seaborn as sns
 24 from sklearn.tree import DecisionTreeClassifier
 25 from feature_selector import FeatureSelector
 26 import warnings
 27 warnings.filterwarnings("ignore")  # 忽略警告
 28 
 29 
 30 # 數據讀取
 31 def data_read(data_path, file_name):
 32     df = pd.read_csv(os.path.join(data_path, file_name), delim_whitespace=True, header=None)
 33     # 變量重命名
 34     columns = [
 35         'status_account', 'duration', 'credit_history', 'purpose', 'amount',
 36         'svaing_account', 'present_emp', 'income_rate', 'personal_status',
 37         'other_debtors', 'residence_info', 'property', 'age', 'inst_plans',
 38         'housing', 'num_credits', 'job', 'dependents', 'telephone',
 39         'foreign_worker', 'target'
 40     ]
 41     df.columns = columns
 42     # 將標簽變量由狀態1,2轉為0,1;0表示好用戶,1表示壞用戶
 43     df.target = df.target - 1
 44     # 數據分為data_train和 data_test兩部分,訓練集用於得到編碼函數,驗證集用已知的編碼規則對驗證集編碼
 45     data_train, data_test = train_test_split(df, test_size=0.2, random_state=0, stratify=df.target)
 46     return data_train, data_test
 47 
 48 
 49 # 離散變量與連續變量區分
 50 def category_continue_separation(df, feature_names):
 51     categorical_var = []
 52     numerical_var = []
 53     if 'target' in feature_names:
 54         feature_names.remove('target')
 55     # 先判斷類型,如果是int或float就直接作為連續變量
 56     numerical_var = list(df[feature_names].select_dtypes(
 57         include=['int', 'float', 'int32', 'float32', 'int64', 'float64']).columns.values)
 58     categorical_var = [x for x in feature_names if x not in numerical_var]
 59     return categorical_var, numerical_var
 60 
 61 
 62 if __name__ == '__main__':
 63     path = os.getcwd()
 64     data_path = os.path.join(path, 'data')
 65     file_name = 'german.csv'
 66     # 讀取數據
 67     data_train, data_test = data_read(data_path, file_name)
 68 
 69     print("訓練集中好樣本數 = ", sum(data_train.target == 0))
 70     print("訓練集中壞樣本數 = ", data_train.target.sum())
 71 
 72     # 區分離散變量與連續變量
 73     feature_names = list(data_train.columns)
 74     feature_names.remove('target')
 75     # 通過判斷輸入數據的類型來區分連續變量和連續變量
 76     categorical_var, numerical_var = category_continue_separation(data_train, feature_names)
 77 
 78     print("連續變量個數 = ", len(numerical_var))
 79     print("離散變量個數 = ", len(categorical_var))
 80     for s in set(numerical_var):
 81         print('變量 ' + s + ' 可能取值數量 = ' + str(len(data_train[s].unique())))
 82         # 如果連續變量的取值個數 <= 10,那個就把它列入到離散變量中
 83         if len(data_train[s].unique()) <= 10:
 84             categorical_var.append(s)
 85             numerical_var.remove(s)
 86             # 同時將后加的數值變量轉為字符串
 87             # 這里返回的是true和false,數據類型是series
 88             index_1 = data_train[s].isnull()
 89             if sum(index_1) > 0:
 90                 data_train.loc[~index_1, s] = data_train.loc[~index_1, s].astype('str')
 91             else:
 92                 data_train[s] = data_train[s].astype('str')
 93             index_2 = data_test[s].isnull()
 94             if sum(index_2) > 0:
 95                 data_test.loc[~index_2, s] = data_test.loc[~index_2, s].astype('str')
 96             else:
 97                 data_test[s] = data_test[s].astype('str')
 98     print("現連續變量個數 = ", len(numerical_var))
 99     print("現離散變量個數 = ", len(categorical_var))
100 
101     # 連續變量分箱
102     dict_cont_bin = {}
103     for i in numerical_var:
104         # print(i)
105         dict_cont_bin[i], gain_value_save, gain_rate_save = varbin_meth.cont_var_bin(
106                 data_train[i],
107                 data_train.target,
108                 method=2,
109                 mmin=3,
110                 mmax=12,
111                 bin_rate=0.01,
112                 stop_limit=0.05,
113                 bin_min_num=20)
114 
115     # 離散變量分箱
116     dict_disc_bin = {}
117     del_key = []
118     for i in categorical_var:
119         dict_disc_bin[i], gain_value_save, gain_rate_save, del_key_1 = varbin_meth.disc_var_bin(
120                 data_train[i],
121                 data_train.target,
122                 method=2,
123                 mmin=3,
124                 mmax=8,
125                 stop_limit=0.05,
126                 bin_min_num=20)
127         if len(del_key_1) > 0:
128             del_key.extend(del_key_1)
129     # 刪除分箱數只有1個的變量
130     if len(del_key) > 0:
131         for j in del_key:
132             del dict_disc_bin[j]
133 
134     # ---------------------- 訓練數據分箱 ------------------- #
135     # 連續變量分箱映射
136     df_cont_bin_train = pd.DataFrame()
137     for i in dict_cont_bin.keys():
138         df_cont_bin_train = pd.concat([
139             df_cont_bin_train,
140             varbin_meth.cont_var_bin_map(data_train[i], dict_cont_bin[i])], axis=1)
141     # 離散變量分箱映射
142     df_disc_bin_train = pd.DataFrame()
143     for i in dict_disc_bin.keys():
144         df_disc_bin_train = pd.concat([
145             df_disc_bin_train,
146             varbin_meth.disc_var_bin_map(data_train[i], dict_disc_bin[i])], axis=1)
147 
148     # --------------------- 測試數據分箱 --------------------- #
149     # 連續變量分箱映射
150     df_cont_bin_test = pd.DataFrame()
151     for i in dict_cont_bin.keys():
152         df_cont_bin_test = pd.concat([
153             df_cont_bin_test,
154             varbin_meth.cont_var_bin_map(data_test[i], dict_cont_bin[i])], axis=1)
155 
156     # 離散變量分箱映射
157     df_disc_bin_test = pd.DataFrame()
158     for i in dict_disc_bin.keys():
159         df_disc_bin_test = pd.concat([
160             df_disc_bin_test,
161             varbin_meth.disc_var_bin_map(data_test[i], dict_disc_bin[i])], axis=1)
162 
163     # 組成分箱后的訓練集與測試集
164     df_disc_bin_train['target'] = data_train.target
165     data_train_bin = pd.concat([df_cont_bin_train, df_disc_bin_train], axis=1)
166     df_disc_bin_test['target'] = data_test.target
167     data_test_bin = pd.concat([df_cont_bin_test, df_disc_bin_test], axis=1)
168 
169     data_train_bin.reset_index(inplace=True, drop=True)
170     data_test_bin.reset_index(inplace=True, drop=True)
171 
172     # #WOE編碼
173     var_all_bin = list(data_train_bin.columns)
174     var_all_bin.remove('target')
175 
176     # 訓練集WOE編碼
177     df_train_woe, dict_woe_map, dict_iv_values, var_woe_name = var_encode.woe_encode(
178         data_train_bin,
179         data_path,
180         var_all_bin,
181         data_train_bin.target,
182         'dict_woe_map',
183         flag='train')
184 
185     # 測試集WOE編碼
186     df_test_woe, var_woe_name = var_encode.woe_encode(data_test_bin,
187                                                       data_path,
188                                                       var_all_bin,
189                                                       data_test_bin.target,
190                                                       'dict_woe_map',
191                                                       flag='test')
192     y = np.array(data_train_bin.target)
193 
194     # ------------------------ 過濾法特征選擇 ---------------------------- #
195     # --------------- 方差篩選 ----------------- #
196     # 獲取woe編碼后的數據
197     df_train_woe = df_train_woe[var_woe_name]
198     # 得到進行woe編碼后的變量個數
199     len_1 = df_train_woe.shape[1]
200     # VarianceThreshold 方差閾值法,用於特征選擇,過濾器法的一種,去掉那些方差沒有達到閾值的特征。默認情況下,刪除零方差的特征
201     # 實例化一個方差篩選的選擇器,閾值設置為0.01
202     select_var = VarianceThreshold(threshold=0.01)
203     # 讓這個選擇器擬合df_train_woe數據
204     select_var_model = select_var.fit(df_train_woe)
205     # 將df_train_woe數據縮小為選定的特征
206     df_1 = pd.DataFrame(select_var_model.transform(df_train_woe))
207     # 獲取所選特征的掩碼或整數索引,保留的索引,即可以看到哪些特征被保留
208     save_index = select_var.get_support(True)
209     print("保留下來的索引: = ", save_index)
210 
211     # 獲取保留下來的變量名字
212     var_columns = [list(df_train_woe.columns)[x] for x in save_index]
213     df_1.columns = var_columns
214     # 刪除變量的方差
215     var_delete_variance = select_var.variances_[[x for x in range(len_1) if x not in save_index]]
216     print("刪除變量的方差值 = ", var_delete_variance)
217     var_delete_variance_columns = list((df_train_woe.columns)[x] for x in range(len_1) if x not in save_index)
218     print("刪除變量的列名 = ", var_delete_variance_columns)
219 
220     # ------------------------ 單變量篩選 ------------------------- #
221     # 參數:SelectKBest(score_func= f_classif, k=10)
222     # score_func:特征選擇要使用的方法,默認適合分類問題的F檢驗分類:f_classif。 k :取得分最高的前k個特征,默認10個。
223     # f_calssif計算ANOVA中的f值。方差分析ANOVA F用於分類任務的標簽和/特征之間的值
224     # 當樣本xx屬於正類時,xixi會取某些特定的值(視作集合S+S+),當樣本xx屬於負類時,xixi會取另一些特定的值(S−S−)。
225     # 我們當然希望集合S+S+與S−S−呈現出巨大差異,這樣特征xixi對類別的預測能力就越強。落實到剛才的方差分析問題上,就變成了我們需要檢驗假設H0:μS+=μS−H0:μS+=μS− ,我們當然希望拒絕H0H0,所以我們希望構造出來的ff值越大越好。也就是說ff值越大,我們拒絕H0H0的把握也越大,我們越有理由相信μS+≠μS−μS+≠μS−,越有把握認為集合S+S+與S−S−呈現出巨大差異,也就說xixi這個特征對預測類別的幫助也越大!
226     # 我們可以根據樣本的某個特征xi的f值來判斷特征xi對預測類別的幫助,f值越大,預測能力也就越強,相關性就越大,從而基於此可以進行特征選擇。
227     select_uinvar = SelectKBest(score_func=f_classif, k=15)
228     # 傳入特征集df_train_woe和標簽y擬合數據
229     select_uinvar_model = select_uinvar.fit(df_train_woe, y)
230     # 轉換數據,返回特征過濾后保留下的特征數據集
231     df_1 = select_uinvar_model.transform(df_train_woe)
232     # 看得分
233     len_1 = len(select_uinvar_model.scores_)
234     # 得到原始列名
235     var_name = [str(x).split('_BIN_woe')[0] for x in list(df_train_woe.columns)]
236     # 畫圖
237     plt.figure(figsize=(10, 6))
238     fontsize_1 = 14
239     # barh 函數用於繪制水平條形圖
240     plt.barh(np.arange(0, len_1), select_uinvar_model.scores_, color='c', tick_label=var_name)
241     plt.xticks(fontsize=fontsize_1)
242     plt.yticks(fontsize=fontsize_1)
243     plt.xlabel('得分', fontsize=fontsize_1)
244     plt.show()
245 
246     # ------------------------- 分析變量相關性 ------------------------ #
247     # 計算相關矩陣。 dataFrame.corr可以返回各類型之間的相關系數DataFrame表格
248     correlations = abs(df_train_woe.corr())
249     # 相關性繪圖
250     fig = plt.figure(figsize=(10, 6))
251     fontsize_1 = 10
252     # 繪制熱力圖
253     sns.heatmap(correlations,
254                 cmap=plt.cm.Greys,
255                 linewidths=0.05,
256                 vmax=1,
257                 vmin=0,
258                 annot=True,
259                 annot_kws={'size': 6, 'weight': 'bold'})
260     plt.xticks(np.arange(len(var_name)) + 0.5, var_name, fontsize=fontsize_1, rotation=20)
261     plt.yticks(np.arange(len(var_name)) + 0.5, var_name, fontsize=fontsize_1)
262     plt.title('相關性分析')
263     #    plt.xlabel('得分',fontsize=fontsize_1)
264     plt.show()
265 
266     # -------------------- 包裝法變量選擇:遞歸消除法 -------------------- #
267     # 給定學習器, Epsilon-Support Vector Regression.實例化一個SVR估算器
268     estimator = SVR(kernel="linear")
269     # 遞歸消除法, REFCV 具有遞歸特征消除和交叉驗證選擇最佳特征數的特征排序。用來挑選特征
270     # REF(Recursive feature elimination) 就是使用機器學習模型不斷的去訓練模型,每訓練一個模型,就去掉一個最不重要的特征,直到特征達到指定的數量
271     # sklearn.feature_selection.RFECV(estimator, *, step=1, min_features_to_select=1, cv=None, scoring=None, verbose=0, n_jobs=None)
272     # estimator: 一種監督學習估計器。
273     # step: 如果大於或等於1,則step對應於每次迭代要刪除的個特征個數。如果在(0.0,1.0)之內,則step對應於每次迭代要刪除的特征的百分比(向下舍入)。
274     # cv: 交叉驗證拆分策略
275     select_rfecv = RFECV(estimator, step=1, cv=3)
276     # Fit the SVM model according to the given training data.
277     select_rfecv_model = select_rfecv.fit(df_train_woe, y)
278     df_1 = pd.DataFrame(select_rfecv_model.transform(df_train_woe))
279     # 查看結果
280     # 選定特征的掩碼。哪些特征入選最后特征,true表示入選
281     print("SVR support_ = ", select_rfecv_model.support_)
282     # 利用交叉驗證所選特征的數量。挑選了幾個特征
283     print("SVR n_features_ = ", select_rfecv_model.n_features_)
284     # 特征排序,使ranking_[i]對應第i個特征的排序位置。選擇的(即估計的最佳)特征被排在第1位。
285     # 每個特征的得分排名,特征得分越低(1最好),表示特征越好
286     print("SVR ranking = ", select_rfecv_model.ranking_)
287 
288     # --------------------- 嵌入法變量選擇 -------------------------- #
289     # 選擇學習器
290     # C    正則化強度 浮點型,默認:1.0;其值等於正則化強度的倒數,為正的浮點數。數值越小表示正則化越強。
291     # penalty 是正則化類型
292     lr = LogisticRegression(C=0.1, penalty='l2')
293     # 嵌入法變量選擇
294     # SelectFromModel(estimator, *, threshold=None, prefit=False, norm_order=1, max_features=None)
295     # estimator用來構建變壓器的基本估算器
296     # prefit: bool, default False,預設模型是否期望直接傳遞給構造函數。如果為True,transform必須直接調用和SelectFromModel不能使用cross_val_score,
297     # GridSearchCV而且克隆估計類似的實用程序。否則,使用訓練模型fit,然后transform進行特征選擇
298     # threshold 是用於特征選擇的閾值
299     select_lr = SelectFromModel(lr, prefit=False, threshold='mean')
300     select_lr_model = select_lr.fit(df_train_woe, y)
301     df_1 = pd.DataFrame(select_lr_model.transform(df_train_woe))
302     # 查看結果,.threshold_ 是用於特征選擇的閾值
303     print("邏輯回歸 threshold_ = ", select_lr_model.threshold_)
304     # get_support 獲取選出的特征的索引序列或mask
305     print("邏輯回歸 get_support = ", select_lr_model.get_support(True))
306 
307     # -------------- 基學習器選擇預訓練的決策樹來進行變量選擇 -------------- #
308     # 先訓練決策樹
309     # riterion = gini/entropy 可以用來選擇用基尼指數或者熵來做損失函數。
310     # max_depth = int 用來控制決策樹的最大深度,防止模型出現過擬合。
311     # fit需要訓練數據和類別標簽
312     cart_model = DecisionTreeClassifier(criterion='gini', max_depth=3).fit(df_train_woe, y)
313     # Return the feature importances.
314     print("決策樹 feature_importances_ = ", cart_model.feature_importances_)
315     # 用預訓練模型進行變量選擇
316     select_dt_model = SelectFromModel(cart_model, prefit=True)
317     df_1 = pd.DataFrame(select_dt_model.transform(df_train_woe))
318     # 查看結果
319     print("決策樹 get_support(True) = ", select_dt_model.get_support(True))

 

 

 

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM