特征工程->趋势->规律
特征工程要做的工作包括:
- 数据预处理:包括缺失值填补,类别型特征编码等;
- 特征构建:构造新特征;
- 特征选择:选取最重要的特征或者实现特征降维。(本文章暂不涉及特征选择)
目录:
1.数据预处理
1.1.缺失值填补
缺失值填补策略:
- 当缺失值少于20%时,数值型特征可以用均值或中位数填补,类别型特征可以用众数填补或把缺失值单算一类;
- 当缺失值处于20%-80%时,填补方式同上,但数值型特征要生成一个指示哑变量参与后续的建模;
- 当缺失值多于80%时,有缺失值的变量生成一个指示哑变量即可,原始变量不再使用。
- 使用pd.DataFrame自带的fillna函数对数据进行缺失值填补,fillna可以填充统计量(如均值、中位数、众数)或指定一个常数。
- 数值型特征使用中位数填补
- 类别型特征把缺失值单算一类
1.1.1.筛选有缺失值的特征
#选定缺失值少于20%的特征名
missing_columns_under_20=missing_values_result[missing_values_result['% of Total Values']<=20].index
#选定缺失值在20%-80%之间的特征名
missing_columns_over_20=missing_values_result[missing_values_result['% of Total Values']>20].index
1.1.2.筛选出缺失值为数值型的特征
#缺失值少于20%的数值型特征名
numeric_missing_columns_under_20=df_train[missing_columns_under_20].select_dtypes(exclude='object').columns
#缺失值在20%-80%的数值型特征名
numeric_missing_columns_over_20=df_train[missing_columns_over_20].select_dtypes(exlude='object').columns
1.1.3.获取指示哑变量的特征名
# 获取指示哑变量特征名
numeric_missing_columns_over_20_isnan=list(map(lambda x: x+'_ISNAN',numeric_missing_columns_over_20))
# 缺失占比在20%-80%之间
# 获取中位数
median_values_over_20=df_train[numeric_missing_columns_over_20].median()
# 设置指示哑变量
df_train[numeric_missing_columns_over_20_isnan]=df_train[numeric_missing_columns_over_20].isnull().astype(int)
df_test[numeric_missing_columns_over_20_isnan]=df_test[numeric_missing_columns_over_20].isnull().astype(int)
1.1.4.用中位数填补缺失值
# 对训练集和测试集分别用中位数填补
df_train[numeric_missing_columns_over_20]=df_train[numeric_missing_columns_over_20].fillna(median_values_over_20)
df_test[numeric_missing_columns_over_20]=df_test[numeric_missing_columns_over_20].fillna(median_values_over_20)
# 缺失占比少于20%
# 获取中位数
median_values_under_20=df_train[numeric_missing_columns_under_20].median()
# 对训练集和测试集分别用中位数填补
df_train[numeric_missing_columns_under_20]=df_train[numeric_missing_columns_under_20].fillna(median_values_under_20)
df_test[numeric_missing_columns_under_20]=df_test[numeric_missing_columns_under_20].fillna(median_vvalues_under_20)
到此为止:缺失值为数值型的特征已经填充完成
接下来填补缺失值为类别型特征
1.1.5.筛选出缺失值特征名
missing_value_columns=missing_value_result.index
1.1.6.在此基础上筛选出类别型特征
categorical_missing_columns=df_train['missing_value_columns'].select_dtypes(include='object').columns
1.1.7.统一用字符串’NaN’填补缺失值
df_train[categorical_missing_columns]=df_train[categorical_missing_columns].replace({
np.nan:'NaN'})
df_test[categorical_missing_columns]=df_train[categorical_missing_columns].replace({
np.nan:'NaN'})
1.1.8.查看是否还有缺失值
missing_values(df_train)
# 查看输出:
SK_ID_CURR 0
TARGET 0
NAME_CONTRACT_TYPE 0
CODE_GENDER 0
FLAG_OWN_CAR 0
..
FLOORSMAX_MODE_ISNAN 0
YEARS_BEGINEXPLUATATION_AVG_ISNAN 0
YEARS_BEGINEXPLUATATION_MEDI_ISNAN 0
YEARS_BEGINEXPLUATATION_MODE_ISNAN 0
TOTALAREA_MODE_ISNAN 0
Length: 167, dtype: int64
1.2.类别型特征编码
1.2.1.自然数编码
自然数编码(Label Encoding)就是给特征每个字符取一个自然数,使用preprocessing.LabelEncoder实现数字编码
自然数编码的缺点:字符编码的顺序是认为设定的,没有既定的规则。
1.2.2.独热编码
独热编码(One-hot Encoding)会对特征的每个字符创建一个新列。
- 独热编码常用的函数是pandas中的get_dummies()。
- 独热编码的缺点:特征取值过多会导致编码后的维度暴增。
- 类别型特征编码建议都使用独热编码。
- 对训练集和测试集做独热编码:
df_train=pd.get_dummies(df_train)
df_test=pd.get_dummies(df_test)
# 输出结果:
print('Training Feature shape:',df_train.shape)
print('Testing Feature shape:',df_train.shape)
独热编码后,训练集和测试集的特征可能会有出入,因为训练集的类别型特征的取值在测试集中可能没有,所以需要做数据对齐操作,也就是去掉独热编码后训练集但测试集没有的特征,以保持训练集和测试集有相同的特征。
- 数据对齐调用的是pandas中align函数。
# 数据对齐会把训练集的TARGET去掉,所以要提前保存
train_labels=df_train['TARGET']
# join='inner'表示数据按照重叠部分的索引做数据对齐
# axis=1表示按列索引做数据对齐
df_train.align(df_test,join='inner',axis=1)
df_train['TARGET']=train_labels
# 输出结果:
print('Training Features shape:',df_train.shape)
print('Testing Features shape',df_test.shape)
2.构建新特征
2.1.多项式特征
多项式特征就是多个特征以某种方式(常用的是相乘,如EXT_SOURCE_1EXT_SOURCE_2),或取高阶项,如EXT_SOURCE^2,
或两者的组合EXT_SOURCE_1EXT_SOURCE^2)组合而成的新特征。
可以尝试一部分这样的组合特征来看是否会对模型预测用户还款有很大帮助。
通过sklearn的PolynomialFeature函数对EXT_SOURCE和DAYS_BIRTH 做多项式特征。
这里限制组合特征的阶数不高于3(阶数太高导致组合特征太多,模型容易过拟合)。
2.1.1.构造3阶以内的多项式特征
from sklearn.preprocessing import PolynomialFeatures
# 选取想要的特征
columns_select=['EXT_SOURCE_1','EXT_SOURCE_2','EXT_SOURCE_3','DAYS_BIRTH']
df_train_select=df_train[columns_select]
df_test_select=df_test[columns_select]
# 构造阶数为3的PolynomialFeatures对象
poly_transform=PolynomialFeatures(degree=3,include_bias=False)
# 根据训练集特征,获取构造的多项式特征
poly_transformer.fit(df_train_select)
# 对训练集和测试集构造多项式特征
poly_features_train=poly_transformer.transform(df_train_select)
poly_features_test=poly_transformer.transform(df_test_select)
print('Polynomial Feature shape:'poly_features_train.shape)
2.2.领域知识特征
如果熟悉信用卡业务,还可以根据业务构建领域知识特征,比如:
- CREDIT_INCOME_PERCENT:贷款金额占用户收入的比例
- ANNUITY_INCOME_PERCENT:贷款年金(每年还款金额)占用户收入的比例
- CREDIT_TERM:还款期数
- DAYS_EMPLOYED_PERCENT:工作年限与用户年龄的比值
领域 知识特征具体构造的方式为:
- CREDIT_INCOME_PERCENT=AMT_CREDIT/AMT_INCOME_TOTAL
- ANNUITY_INCOME_PERCENT=AMT_ANNUITY/AMT_INCOME_TOTAL
- CREDIT_TERM=AMT_CREDIT/AMT_ANNUITY
- DAYS_EMPLOYED_PERCENT=DAYS_EMPLOYED/DAYS_BIRTH
其中的特征含义如下:
- AMT_CREDIT:贷款金额
- AMT_INCOME_TOTAL:用户收入
- AMT_ANNUITY:年金
df_train_domain=df_train.copy()
df_test_domain=df_test.copy()
对于训练集:
df_train_domain['CREDIT_INCOME_PERCENT']=df_train_domain['AMT_CREDIT']/df_train_domain['AMT_INCOME_TOTAL']
df_train_domain['ANNUITY_INCOME_PERCENT']=df_train_domain['AMT_ANNUITY']/df_train_domain['AMT_INCOME_TOTAL']
df_train['CREDIT_TERM']=df_train_domain['AMT_CREDIT']/df_train['AMT_ANNUITY']
df_train_domain['DAYS_EMPLOYED_PERCENT']=df_train_domain['DAYS_EMPLOYED']/df_train_domain['DAYS_BIRTH']
对于测试集:
df_test_domain['CREDIT_INCOME_PERCENT']=df_test_domain['AMT_CREDIT']/df_test_domain['AMT_INCOME_TOTAL']
df_test_domain['ANNUITY_INCOME_PERCENT']=df_test_domain['AMT_ANNUITY']/df_test_domain['AMT_INCOME_TOTAL']
df_test_domain['CREDIT_TERM']=df_test_domain['AMT_CREDIT']/df_test_domain['AMT_ANNUITY']
df_test_domain['DAYS_EMPLOYED_PERCENT']=df_test_domain['DAYS_EMPLOYED']/df_test_domain['DAYS_BIRTH']
可视化新特征:
画出不同标签下领域知识特征的概率密度分布图
plt.figure(figsize=(12,20))
# 循环查看特征
for i,feature in enumerate(['CREDIT_INCOME_PERCENT','ANNUITY_INCOME_PERCENT','CREDIT_TERM','DAYS_EMPLOYED_PERCENT']):
#创建子图
plt.subplot(4,1,i+1)
#画出还款用户的领域知识特征的概率密度分布
sns.kdeplot(df_train_domain.iloc[df_train_domain['TARGET']==0,feature],label='TARGET==0')
#画出违约用户的 领域知识 特征的概率密度分布
sns.kdeplot(df_train_domain.iloc[df_train_domain['TARGET']==1,feature],label='TARGET==1')
#设置标题和坐标轴
plt.title('Distribution of %s by Target Value' %feature)
plt.xlabel('%s' %feature)
plt.ylabel('Density')
plt.tight_layout(h_pad=2.5)
感觉这些特征对标签有很大的作用,当然最后还是得要看它们输入模型后的结果。
- 整个过程下来,涉及大量对特征的操作,无论是对脑力、心力还是体力都是极大的挑战。
- 但洞见趋势系列还没结束,下一篇需要构建模型