md
零基礎入門金融風控-貸款違約預測 Task03 特征工程
Task03目的:
- 學習特征預處理/缺失值處理/異常值處理/數據分桶等特征處理方法
- 學習特征交互/特征編碼/特征選擇的相應方法
0.0 導包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
from tqdm import tqdm
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.preprocessing import MinMaxScaler
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostRegressor
from sklearn.model_selection import StratifiedKFold, KFold
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss
import warnings
warnings.filterwarnings('ignore')
0.1 公共變量
linux_file_path = '/plus/阿里雲開發者-天池比賽/02_零基礎入門金融風控_貸款違約預測/'
win_file_path = 'E:\\阿里雲開發者-天池比賽\\02_零基礎入門金融風控_貸款違約預測\\'
file_path = linux_file_path
train_file_path = file_path+'train.csv'
testA_file_path = file_path+'testA.csv'
now = datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
0.2 數據讀取pandas
data_train = pd.read_csv(train_file_path)
data_test_a = pd.read_csv(testA_file_path)
# print('Train Data shape 行*列:',data_train.shape)
# print('TestA Data shape 行*列:',data_test_a.shape)
print('易得,結果列 isDefault\n'
'testA相較於train多出兩列: \'n2.2\' \'n2.3\' ')
3.3.2 特征預處理
3.3.2.0 解析出數字屬性列和類別屬性列
# 這里介紹了數據缺失值的填充,時間格式特征的轉化處理,某些對象類別特征的處理
numerical_fea = list(data_train.select_dtypes(exclude=['object']).columns)
# filter(function or None, iterable)
category_fea = list(filter(lambda x: x not in numerical_fea,list(data_train.columns)))
# 結果了
label = 'isDefault'
numerical_fea.remove(label)
print('numerical_fea:', numerical_fea)
print('category_fea:', category_fea)
3.3.2.1 缺失值填充
#查看缺失值情況
# data_train.isnull().sum()
# 1. 把所有缺失值替換為指定的值0
# data_train = data_train.fillna(0)
# 2. 向用缺失值上面的值替換缺失值
# data_train = data_train.fillna(axis=0,method='ffill')
# 3. 縱向用缺失值下面的值替換缺失值,且設置最多只填充兩個連續的缺失值
# data_train = data_train.fillna(axis=0,method='bfill',limit=2)
#按照平均數填充數值型特征
data_train[numerical_fea] = data_train[numerical_fea].fillna(data_train[numerical_fea].median())
data_test_a[numerical_fea] = data_test_a[numerical_fea].fillna(data_train[numerical_fea].median())
# #按照眾數填充類別型特征
data_train[category_fea] = data_train[category_fea].fillna(data_train[category_fea].mode())
data_test_a[category_fea] = data_test_a[category_fea].fillna(data_train[category_fea].mode())
data_train.isnull().sum()
# 輸出 employmentLength 46799 , 其他列都已經可以填充完畢
3.3.2.3 對象類型特征轉換到數值 特指 employmentLength(就業年限(年))列
print('data_train: ', data_train['employmentLength'].head(10))
print('employmentLength: \n', data_train['employmentLength'].drop_duplicates())
def employmentLength_2_int(s):
if pd.isnull(s):
return s
elif(str(s).count(' ') > 0):
return np.int8(s.split()[0])
else:
return s
for data in [data_train, data_test_a]:
data['employmentLength'].replace('10+ years', value='10 years', inplace=True)
data['employmentLength'].replace('< 1 year', value='0 years', inplace=True)
data['employmentLength'] = data['employmentLength'].apply(employmentLength_2_int)
# 打印處理后的結果
x= data['employmentLength'].value_counts(dropna=False).sort_index()
print(x)
3.3.2.3 對象類型特征轉換到數值 對earliesCreditLine(借款人最早報告的信用額度開立的月份)進行預處理
data_train['earliesCreditLine'].sample(5)
"""
212023 Jun-2005
699597 Jul-2004
428151 Jul-1993
175922 Sep-2011
756776 Jul-2008
"""
# 截取后四位數字
for data in [data_train, data_test_a]:
data['earliesCreditLine'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:]))
類別特征處理
部分類別特征
cate_features = category_fea
for f in cate_features:
print(f, '類型數:', data[f].nunique())
# 像等級這種類別特征,是有優先級的可以label encode或者自映射
for data in [data_train, data_test_a]:
data['grade'] = data['grade'].map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})
# 類型數在2之上,又不是高維稀疏的,且純分類特征 [label二進制編碼形式] get_dummies: 意為獲得偽造數
for data in [data_train, data_test_a]:
data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
3.3.3 異常值處理
3.3.3.1 檢測異常的方法一:均方差
# 正態分布, 近似68%的數據在一個u +- std, 近似95%的數據在u+=std*2的范圍內, 近似99.7%的數據會在三個標准差+-u的范圍內
def find_outliers_by_3segama(data, fea):
data_std = np.std(data[fea])
data_mean = np.mean(data[fea])
outliers_cut_off = data_std * 3
lower_rule = data_mean - outliers_cut_off
upper_rule = data_mean + outliers_cut_off
data[fea+'_outliers'] = data[fea].apply(lambda x:str('異常值') if x > upper_rule or x < lower_rule else '正常值')
return data
# 1. 得到特征的異常值后可以進一步分析變量異常值和目標變量的關系
data_train_copy = data_train.copy()
for fea in numerical_fea:
data_train_copy = find_outliers_by_3segama(data_train_copy, fea)
fea_out_name = fea+'_outliers'
print(data_train_copy[fea_out_name].value_counts())
print(data_train_copy.groupby(fea_out_name)['isDefault'].sum())
print('*'*10)
Q1: 例如可以看到異常值在兩個變量上的分布幾乎復合整體的分布,如果異常值都屬於為1的用戶數據里面代表什
# 么呢?
print('常值都屬於為1的用戶數據, 那么這就不是異常值,可以建議強關聯')
刪除異常值
for fea in numerical_fea:
data_train = data_train_copy[data_train_copy[fea+'_outliers']=='正常值']
data_train = data_train.reset_index(drop=True)
print('data_train 異常值清除完成')
3.3.3.2 檢測異常的方法二:箱型圖
# TODO ???
# 總結一句話:四分位數會將數據分為三個點和四個區間,IQR = Q3 -Q1,下觸須=Q1 − 1.5x IQR,上觸須=Q3+ 1.5x IQR;
3.3.4 數據分桶(特征分箱)
"""
1. 特征分箱的目的:
a. 從模型效果上來看,特征分箱主要是為了降低變量的復雜性,減少變量噪音對模型的影響,提高自變量
和因變量的相關度。從而使模型更加穩定。
2. 數據分桶的對象:
a. 將連續變量離散化
b. 將多狀態的離散變量合並成少狀態
3. 分箱的原因:
a. 數據的特征內的值跨度可能比較大,對有監督和無監督中如k-均值聚類它使用歐氏距離作為相似度函數來
測量數據點之間的相似度。都會造成大吃小的影響,其中一種解決方法是對計數值進行區間量化即數據
分桶也叫做數據分箱,然后使用量化后的結果。
分箱的優點:
a. 處理缺失值:當數據源可能存在缺失值,此時可以把null單獨作為一個分箱。
b. 處理異常值:當數據中存在離群點時,可以把其通過分箱離散化處理,從而提高變量的魯棒性(抗干擾
能力)。例如,age若出現200這種異常值,可分入“age > 60”這個分箱里,排除影響。
c. 業務解釋性:我們習慣於線性判斷變量的作用,當x越來越大,y就越來越大。但實際x與y之間經常存在
着非線性關系,此時可經過WOE變換。
4.特別要注意一下分箱的基本原則:
a. (1)最小分箱占比不低於5%
b. (2)箱內不能全部是好客戶
c. (3)連續箱單調
"""
# 1. 實踐 固定寬度分箱
# 當數值橫跨多個數量級時,最好按照 10 的冪(或任何常數的冪)來進行分組:09、1099、100999、10009999,等
# 等。固定寬度分箱非常容易計算,但如果計數值中有比較大的缺口,就會產生很多沒有任何數據的空箱子。
# 通過除法映射到間隔均勻的分箱中,每個分箱的取值范圍都是loanAmnt/1000
data['loanAmnt_bin1'] = np.floor_divide(data['loanAmnt'], 1000)
# ## 通過對數函數映射到指數寬度分箱
data['loanAmnt_bin2'] = np.floor(np.log10(data['loanAmnt']))
# 2. 分位數分箱 (10等分)
data['loanAmnt_bin3'] = pd.qcut(data['loanAmnt'], 10, labels=False)
# 3. 卡方分箱及其他分箱方法的嘗試
# 1. 這一部分屬於進階部分,學有余力的同學可以自行搜索嘗試。
# TODO
3.3.5 特征交互
# 交互特征的構造非常簡單,使用起來卻代價不菲。如果線性模型中包含有交互特征對,那它的訓練時間和評
# 分時間就會從 O(n) 增加到 O(n2),其中 n 是單一特征的數量。
for col in ['grade', 'subGrade']:
temp_dict = data_train.groupby([col])
['isDefault'].agg(['mean']).reset_index().rename(columns={'mean': col + '_target_mean'})
temp_dict.index = temp_dict[col].values
temp_dict = temp_dict[col + '_target_mean'].to_dict()
data_train[col + '_target_mean'] = data_train[col].map(temp_dict)
data_test_a[col + '_target_mean'] = data_test_a[col].map(temp_dict)
# 其他衍生變量 mean 和 std
external_cols = ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']
for df in [data_train, data_test_a]:
for item in external_cols:
# transform('sum') == transform(lambda x: x /sum(x))
df['grade_to_mean_' + item] = df['grade'] / df.groupby([item])['grade'].transform('mean')
df['grade_to_std_' + item] = df['grade'] / df.groupby([item]) ['grade'].transform('std')
# TODO 搞一個無限跑的特征匹配計划
3.3.6 特征編碼
### 3.3.6.1 labelEncode 直接放入樹模型中
#label-encode:subGrade,postCode,title
# 高維類別特征需要進行轉換
for col in tqdm(['employmentTitle', 'postCode', 'title','subGrade']):
le = LabelEncoder()
le.fit(list(data_train[col].astype(str).values) + list(data_test_a[col].astype(str).values))
data_train[col] = le.transform(list(data_train[col].astype(str).values))
data_test_a[col] = le.transform(list(data_test_a[col].astype(str).values))
print('Label Encoding 完成')
3.3.6.2 邏輯回歸等模型 要單獨增加的特征工程
"""
什么是歸一化?
1. 對特征做歸一化,去除相關性高的特征
2. 歸一化目的是讓訓練過程更好更快的收斂,避免特征大吃小的問題
3. 去除相關性是增加模型的可解釋性,加快預測過程。
"""
# 舉例歸一化過程 #偽代碼
for fea in external_cols:
data[fea] = ((data[fea] - np.min(data[fea])) / (np.max(data[fea]) - np.min(data[fea])))
%% 3.3.7 特征選擇/特征篩選/特征精簡
"""
1.1 Filter
a. 方差選擇法
b. 相關系數法(pearson 相關系數)
c. 卡方檢驗
d. 互信息法
2.2 Wrapper (RFE) a. 遞歸特征消除法
3.3 Embedded
a. 基於懲罰項的特征選擇法
b. 基於樹模型的特征選擇
"""
3.3.7.1 Filter
# 基於特征間的關系進行篩選 方差選擇法
# 方差選擇法中,先要計算各個特征的方差,然后根據設定的閾值,選擇方差大於閾值的特征
from sklearn.feature_selection import VarianceThreshold
#其中參數threshold為方差的閾值
VarianceThreshold(threshold=3).fit_transform(train, target_train)
# 相關系數法
# Pearson 相關系數
# 皮爾森相關系數是一種最簡單的,可以幫助理解特征和響應變量之間關系的方法,該方法衡量的是變量之間的線性相關性。
# 結果的取值區間為 [-1,1] , -1 表示完全的負相關, +1表示完全的正相關,0 表示沒有線性相關。
from sklearn.feature_selection import SelectKBest
from scipy.stats import pearsonr
#選擇K個最好的特征,返回選擇特征后的數據
# #第一個參數為計算評估特征是否好的函數,該函數輸入特征矩陣和目標向量,
# #輸出二元組(評分,P值)的數組,數組第i項為第i個特征的評分和P值。在此定義為計算相關系數
# #參數k為選擇的特征個數
SelectKBest(k=5).fit_transform(train, target_train)
# 卡方檢驗
# 1. 經典的卡方檢驗是用於檢驗自變量對因變量的相關性。 假設自變量有N種取值,因變量有M種取值,考慮自變
# 量等於i且因變量等於j的樣本頻數的觀察值與期望的差距。 其統計量如下: χ2=∑(A−T)2T,其中A為實際值,
# T為理論值
# 2. (注:卡方只能運用在正定矩陣上,否則會報錯Input X must be non-negative)
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2 #參數k為選擇的特征個數
SelectKBest(chi2, k=5).fit_transform(train, target_train)
# 互信息法
# 經典的互信息也是評價自變量對因變量的相關性的。 在feature_selection庫的SelectKBest類結合最大信息系數
# 法可以用於選擇特征,
from sklearn.feature_selection import SelectKBest
from minepy import MINE
#由於MINE的設計不是函數式的,定義mic方法將其為函數式的,
# #返回一個二元組,二元組的第2項設置成固定的P值0.5
def mic(x, y):
m = MINE()
m.compute_score(x, y)
return (m.mic(), 0.5) #參數k為選擇的特征個數
SelectKBest(lambda X, Y: np.array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(train,target_train)
3.3.7.2 Wrapper (Recursive feature elimination,RFE)遞歸特征消除法
"""
遞歸特征消除法 遞歸消除特征法使用一個基模型來進行多輪訓練,每輪訓練后,消除若干權值系數的特征,
再基於新的特征集進行下一輪訓練。 在feature_selection庫的RFE類可以用於選擇特征,相關代碼如下(以邏輯
回歸為例):
"""
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression #遞歸特征消除法,返回特征選擇后的數據 #參數estimator為基模型 #參數
# n_features_to_select為選擇的特征個數
RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(train, target_train)
3.3.7.3 Embedded (漢譯:嵌入式的/基模型)
# 基於懲罰項的特征選擇法 使用帶懲罰項的基模型,除了篩選出特征外,同時也進行了降維。 在
# feature_selection庫的SelectFromModel類結合邏輯回歸模型可以用於選擇特征,相關代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression #帶L1懲罰項的邏輯回歸作為基模型的特征選擇
SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(train, target_train)
# 基於樹模型的特征選擇 樹模型中GBDT也可用來作為基模型進行特征選擇。 在feature_selection庫的
# SelectFromModel類結合GBDT模型可以用於選擇特征,相關代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier #GBDT作為基模型的特征選擇
SelectFromModel(GradientBoostingClassifier()).fit_transform(train, target_train)
3.3.7.4 數據處理
"""
本數據集中我們刪除非入模特征后,並對缺失值填充,然后用計算協方差的方式看一下特征間相關性,然后進 行模型訓練
"""
# 刪除不需要的數據
for data in [data_train, data_test_a]:
data.drop(['issueDate','id'], axis=1,inplace=True)
"縱向用缺失值上面的值替換缺失值"
data_train = data_train.fillna(axis=0,method='ffill')
x_train = data_train.drop(['isDefault','id'], axis=1) #計算協方差
data_corr = x_train.corrwith(data_train.isDefault) #計算相關性
result = pd.DataFrame(columns=['features', 'corr'])
result['features'] = data_corr.index
result['corr'] = data_corr.values # 當然也可以直接看圖.
data_numeric = data_train[numerical_fea]
correlation = data_numeric.corr()
f,ax = plt.subplots(figsize = (7, 7))
plt.title('Correlation of Numeric Features with Price',y=1,size=16)
sns.heatmap(correlation,square = True, vmax=0.8)
features = [f for f in data_train.columns if f not in ['id','issueDate','isDefault'] and '_outliers' not in f]
x_train = data_train[features]
x_test = data_test_a[features]
y_train = data_train['isDefault']
3.4 總結