Feature Selection in Python

NUTHDANAI WANGPRATHAM
QUANT I LOVE U
Published in
4 min readJun 16, 2021

Feature Selection เป็นกระบวนการที่สำคัญในการทำงานกับข้อมูลรวมถึง Machine learning หากเราไม่สามารถเลือก Feature ได้อย่างถูกต้องการยากที่จะได้แบบจำลองที่ดี

วันนี้เราจะลองมาทำ Feature Selection ด้วย Python ไปด้วยกัน โดยข้อมูลเราจะใช้ข้อมูลจาก Lending Club data บน Kaggle ผมโหลดบน excell แล้วสามารถโหลดได้ที่ลิงค์นี้เลย รายละเอียดของตัวแปรดังนี้

ก่อนที่จะเริ่มอยากให้ทุกคนตั้งค่าการแสดงผลก่อนไม่อย่างนั้นหน้าจอจะแสดงอย่างจำกัด

pd.options.display.max_columns = None

คำสั่งปกติในการ Exploratory Data เลย df.head() เพื่อดูหัวตาราง

df.head()

อย่างลองสุ่มดูข้อมูล

df.sample()

ลองดูจำนวนข้อมูล

df.info()

ดูจำนวนข้อมูลที่เป็น NA

df.isnull().sum()

ถึงตรงนีเราจะ drop ข้อมูลที่มี NA มากกว่า 70 ออก

# get a list of columns that have more than 70% null valuesna_values = df.isnull().mean()na_values[na_values>0.7]# drop columns with more than 70% null valuesdf.dropna(thresh = df.shape[0]*0.3, how = 'all', axis = 1, inplace = True)

จัดการตัวแปรเป้าหมาย

ตัวแปรเป้าหมายของเราคือ loan_status สิ่งที่เราทำคือโดยใช้คำสั่ง counts

จะเห็นได้ว่าจำนวนข้อมูลมีหลายประเภทมากปัญหาของชุดข้อมูลนี้คือการแยกประเภทเราออกจะต้องทำเป็น dummy สองค่า

โอเคใช่ได้แล้ว

Data cleaning

บ้างคอลัมน์ของข้อมูลเราจะเห็นว่ามันมีตัวอักษรบนกันอยู่เราก็จะต้องลบตัวหนังสืออออก

ต่อมาจัดการข้อมูลที่เป็นวันที่ให้มีรูปแบบข้อมูลที่เหมือนกันให้หมด

เราจะแยกข้อมูล traain test ก่อนตรงนี้เลย

Feature Selection

มาถึงส่วนที่เป็นไฮไลน์ของเราแล้วคือ Feature Selection เราจะแบ่งข้อมูลที่เป็นตัวเลขกับตัวอักษรแยกออกจากกัน

Chi-squared statistic for categorical features

ข้อมูลที่เป็นประเภทหรือ categorical เราจะทดสอบ Chi-squared และเลือก Featureที่ให้ค่าที่สูง

ANOVA

และเราจะทดสอบ ANOVA สำหรับ feature ที่เป็นตัวเลข

เราเลือก 25 ตัวแปรที่สำคัญที่สุด

Pair wise correlations to detect multicollinearity

ส่วนต่อมาเราจะมาดูความสำพันธ์กันของตัวแปรเพื่อจัดการปัญหา multicollinearity

corrmat = X_train_num[top_num_features].corr()plt.figure(figsize=(10,10))sns.heatmap(corrmat);

จะเห็นได้ว่าตัวแปร out_prncp_inv และ total_pymnt_inv มีความสัมพันธ์เราจึงลบออก

drop_columns_list = ANOVA_F_table.iloc[20:, 0].to_list()drop_columns_list.extend(chi2_result.iloc[4:, 0].to_list())drop_columns_list.extend(['out_prncp_inv', 'total_pymnt_inv'])def col_to_drop(df, columns_list):df.drop(columns = columns_list, inplace = True)col_to_drop(X_train, drop_columns_list)

WOE & VI

ส่วนสุดท้ายคือในส่วนของ Weight of Evidence จะบอกเราถึงพลังการทำนายของตัวแปรอิสระที่สัมพันธ์กับตัวแปรตาม เนื่องจากวิวัฒนาการมาจากโลกของการให้คะแนนเครดิต จึงมักถูกอธิบายว่าเป็นตัวชี้วัดการแยกลูกค้าที่ดีและไม่ดีออกจากกัน “ลูกค้าไม่ดี” หมายถึงลูกค้าที่ผิดนัดเงินกู้ และ “ลูกค้าดี” หมายถึง ลูกค้าที่ชำระคืนเงินกู้โดยมีสมการดังนี้

WOE & VI

เราก็สามารถประกาศฟังก์ชันได้

ขั้นตอนการคำนวณ WOE
สำหรับตัวแปรต่อเนื่อง ให้แบ่งข้อมูลออกเป็น 10 ส่วน (หรือน้อยกว่านั้นขึ้นอยู่กับการกระจาย)
คำนวณจำนวนเหตุการณ์และไม่ใช่เหตุการณ์ในแต่ละกลุ่ม (bin)
คำนวณ % ของเหตุการณ์และ % ของสิ่งที่ไม่ใช่เหตุการณ์ในแต่ละกลุ่ม
คำนวณ WOE โดยการแบ่ง % ของเหตุการณ์ที่ไม่ใช่และ % ของเหตุการณ์

ส่วน Information Value เป็นหนึ่งในเทคนิคที่มีประโยชน์ที่สุดในการเลือกตัวแปรที่สำคัญในแบบจำลองการทำนาย ช่วยจัดอันดับตัวแปรตามความสำคัญ IV คำนวณโดยใช้สูตรต่อไปนี้:

IV = ∑ (% of non-events — % of events) * WOE

มีวิธีการอ่านค่าตารางดังต่อไปนี้

# define a binning functiondef mono_bin(Y, X, n = max_bin):df1 = pd.DataFrame({"X": X, "Y": Y})justmiss = df1[['X','Y']][df1.X.isnull()]notmiss = df1[['X','Y']][df1.X.notnull()]r = 0while np.abs(r) < 1:try:d1 = pd.DataFrame({"X": notmiss.X, "Y": notmiss.Y, "Bucket": pd.qcut(notmiss.X, n)})d2 = d1.groupby('Bucket', as_index=True)r, p = stats.spearmanr(d2.mean().X, d2.mean().Y)n = n - 1except Exception as e:n = n - 1if len(d2) == 1:n = force_binbins = algos.quantile(notmiss.X, np.linspace(0, 1, n))if len(np.unique(bins)) == 2:bins = np.insert(bins, 0, 1)bins[1] = bins[1]-(bins[1]/2)d1 = pd.DataFrame({"X": notmiss.X, "Y": notmiss.Y, "Bucket": pd.cut(notmiss.X, np.unique(bins),include_lowest=True)})d2 = d1.groupby('Bucket', as_index=True)d3 = pd.DataFrame({},index=[])d3["MIN_VALUE"] = d2.min().Xd3["MAX_VALUE"] = d2.max().Xd3["COUNT"] = d2.count().Yd3["EVENT"] = d2.sum().Yd3["NONEVENT"] = d2.count().Y - d2.sum().Yd3=d3.reset_index(drop=True)if len(justmiss.index) > 0:d4 = pd.DataFrame({'MIN_VALUE':np.nan},index=[0])d4["MAX_VALUE"] = np.nand4["COUNT"] = justmiss.count().Yd4["EVENT"] = justmiss.sum().Yd4["NONEVENT"] = justmiss.count().Y - justmiss.sum().Yd3 = d3.append(d4,ignore_index=True)d3["EVENT_RATE"] = d3.EVENT/d3.COUNTd3["NON_EVENT_RATE"] = d3.NONEVENT/d3.COUNTd3["DIST_EVENT"] = d3.EVENT/d3.sum().EVENTd3["DIST_NON_EVENT"] = d3.NONEVENT/d3.sum().NONEVENTd3["WOE"] = np.log(d3.DIST_EVENT/d3.DIST_NON_EVENT)d3["IV"] = (d3.DIST_EVENT-d3.DIST_NON_EVENT)*np.log(d3.DIST_EVENT/d3.DIST_NON_EVENT)d3["VAR_NAME"] = "VAR"d3 = d3[['VAR_NAME','MIN_VALUE', 'MAX_VALUE', 'COUNT', 'EVENT', 'EVENT_RATE', 'NONEVENT', 'NON_EVENT_RATE', 'DIST_EVENT','DIST_NON_EVENT','WOE', 'IV']]d3 = d3.replace([np.inf, -np.inf], 0)d3.IV = d3.IV.sum()return(d3)def char_bin(Y, X):df1 = pd.DataFrame({"X": X, "Y": Y})justmiss = df1[['X','Y']][df1.X.isnull()]notmiss = df1[['X','Y']][df1.X.notnull()]df2 = notmiss.groupby('X',as_index=True)d3 = pd.DataFrame({},index=[])d3["COUNT"] = df2.count().Yd3["MIN_VALUE"] = df2.sum().Y.indexd3["MAX_VALUE"] = d3["MIN_VALUE"]d3["EVENT"] = df2.sum().Yd3["NONEVENT"] = df2.count().Y - df2.sum().Yif len(justmiss.index) > 0:d4 = pd.DataFrame({'MIN_VALUE':np.nan},index=[0])d4["MAX_VALUE"] = np.nand4["COUNT"] = justmiss.count().Yd4["EVENT"] = justmiss.sum().Yd4["NONEVENT"] = justmiss.count().Y - justmiss.sum().Yd3 = d3.append(d4,ignore_index=True)d3["EVENT_RATE"] = d3.EVENT/d3.COUNTd3["NON_EVENT_RATE"] = d3.NONEVENT/d3.COUNTd3["DIST_EVENT"] = d3.EVENT/d3.sum().EVENTd3["DIST_NON_EVENT"] = d3.NONEVENT/d3.sum().NONEVENTd3["WOE"] = np.log(d3.DIST_EVENT/d3.DIST_NON_EVENT)d3["IV"] = (d3.DIST_EVENT-d3.DIST_NON_EVENT)*np.log(d3.DIST_EVENT/d3.DIST_NON_EVENT)d3["VAR_NAME"] = "VAR"d3 = d3[['VAR_NAME','MIN_VALUE', 'MAX_VALUE', 'COUNT', 'EVENT', 'EVENT_RATE', 'NONEVENT', 'NON_EVENT_RATE', 'DIST_EVENT','DIST_NON_EVENT','WOE', 'IV']]d3 = d3.replace([np.inf, -np.inf], 0)d3.IV = d3.IV.sum()d3 = d3.reset_index(drop=True)return(d3)def data_vars(df1, target):stack = traceback.extract_stack()filename, lineno, function_name, code = stack[-2]vars_name = re.compile(r'\((.*?)\).*$').search(code).groups()[0]final = (re.findall(r"[\w']+", vars_name))[-1]x = df1.dtypes.indexcount = -1for i in x:if i.upper() not in (final.upper()):if np.issubdtype(df1[i], np.number) and len(Series.unique(df1[i])) > 2:conv = mono_bin(target, df1[i])conv["VAR_NAME"] = icount = count + 1else:conv = char_bin(target, df1[i])conv["VAR_NAME"] = icount = count + 1if count == 0:iv_df = convelse:iv_df = iv_df.append(conv,ignore_index=True)iv = pd.DataFrame({'IV':iv_df.groupby('VAR_NAME').IV.max()})iv = iv.reset_index()return(iv_df,iv)return(iv_df,iv)

https://colab.research.google.com/drive/1ui54MpXrZ53EsKdHArm-N7ISL85nIiiO?usp=sharing

--

--

NUTHDANAI WANGPRATHAM
QUANT I LOVE U

I am a learner and have a multipotential life. You can contact me at nutdnuy@gmail.com