这一部分是特征工程,主要是数据处理以及变量衍生两大块内容。机器学习比赛中,发现一个好的特征,往往就能带来非常大的提升,因而在整个项目周期中,可以安排一半时间考虑特征工程相关的内容,这个步骤是最需要静下心来仔细挖掘的。
一、数据预处理
1.1 缺失值
数据的缺失是经常遇到的情况,尤其在实际业务数据中(比赛数据相对更加饱满,可能已经被人为处理过)。但同时,不可以盲目的进行缺失值填充。数据的缺失也蕴含着一定的信息,不假思索的进行填充,可能导致最终模型效果的下降和数据的污染(毕竟填充数据也是人工经验为主)。
在金融风控中,评分卡模型会单独对 NULL(None/空)等列单独进行WOE计算,视为一个单独的分箱,这样能保留数据缺失背后的含义。
同时在目前流行的集成树模型中,其算法都内置了对于缺失值的处理(会选择使得增益最大的分裂方向),因而我认为现阶段可以不处理缺失值。
可能在时间序列相关模型中,可以尝试进行缺失值填充(用缺失值前一位来填充,表示前1天数据),这个后续可以继续学习一下。
附:一些常见的处理方法
image.png
1.2 异常值
异常值同样是数据处理会经常遇到的情况。对于一些明由于系统异常导致的数据错误情况,且错误数据量非常少,那我们可以直接剔除数据。但更多时候,由于本身数据量较少,简单粗暴的剔除这些看似异常的数据,可能会降低模型预测能力(毕竟数据越多模型一般也会越好一些)。因而我们在保留这些异常数据的前提下,对异常数据进行一些处理。
这些异常情况,包含了数据过大、过小,还有一些数值偏离了本身的业务含义范围,都需要分别考虑。
1.2.1 WOE转换
WOE可以将某一个区间内的数值都映射到一个数值中,例如将年龄区间[60,60+) 范围内的数值都映射到0.12,那么遇到一个年龄99的人,可以直接映射到0.12这个数值。
1.2.2 盖帽?
不知道学名是啥,将大于某个阈值的数值都直接设置该阈值。例如年龄设置了60岁的阈值,那在数据中把超过60岁的情况都直接赋值为60。
附: 异常检测的方法
- 3法则
在统计学中,如果一个数据分布近似正态,那么大约 68% 的数据值会在均值的一个标准差范围内,大约 95% 会在两个标准差范围内,大约 99.7% 会在三个标准差范围内。因而可以将超过这个范围的数值作为异常值
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
-
箱型图
四分位数会将数据分为三个点和四个区间,IQR = Q3 -Q1,下触须=Q1 − 1.5x IQR,上触须=Q3+ 1.5x IQR;超过这个范围的都可以认为是异常值。 -
Isolation Forest
from sklearn.ensemble import IsolationForest
import pandas as pd
clf = IsolationForest(max_samples=100, random_state=42)
table = pd.concat([input_table['Mean(ArrDelay)']], axis=1)
clf.fit(table)
output_table = pd.DataFrame(clf.predict(table))```python
- DBSCAN
DBSCAN聚类方法,有时间再补上
1.3 时间类型处理
1.3.1 issueDate
做时间差,代码如下:
for data in [data_train, data_test_a]:
data['issueDate'] = pd.to_datetime(data['issueDate'],format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
#构造时间特征
data['issueDateDT'] = data['issueDate'].apply(lambda x: x-startdate).dt.days
1.3.2 employmentLength
将2 years 转换为2,去除后缀 years,映射到数值型。
def employmentLength_to_int(s):
if pd.isnull(s):
return s
else:
return np.int8(s.split()[0])
for data in [data_train, data_test_a]:
data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True)
data['employmentLength'].replace('< 1 year', '0 years', inplace=True)
data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int)
1.4 类别特征处理
1.4.1 有序类别
grade/subgrade
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})
1.4.2 无序类别
for data in [data_train, data_test_a]:
data = pd.get_dummies(data, columns=[ 'homeOwnership', 'verificationStatus','purpose', 'regionCode'], drop_first=True)
二、变量衍生
2.1 结合业务含义
使用底层数据来衍生变量时可以事先设计好框架,一般可以按照 时间范围+统计变量+统计指标,然后做笛卡尔积。
但是本次比赛提供的数据,相对比较完善,后续可以用来加工的比较少。
这部分后续在补充。
2.2 特征交互
这种方式相对暴力,直接两两特征选择,然后进行加减乘除,得到交互特征。
此外可以在原始数据基础上,结合均值、标准差、斜度等指标,得到一系列统计指标
for df in [data_train, data_test_a]:
for item in ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']:
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')
附 WOE 转换
说明:https://zhuanlan.zhihu.com/p/80134853/
- 目的
从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量
和因变量的相关度。从而使模型更加稳定。 - 优点
a. 处理缺失值:当数据源可能存在缺失值,此时可以把null单独作为一个分箱。
b. 处理异常值:当数据中存在离群点时,可以把其通过分箱离散化处理,从而提高变量的鲁棒性(抗干扰
能力)。例如,age若出现200这种异常值,可分入“age > 60”这个分箱里,排除影响。
c. 业务解释性:我们习惯于线性判断变量的作用,当x越来越大,y就越来越大。但实际x与y之间经常存在
着非线性关系,此时可经过WOE变换。
分箱方法
- 等频
- 等距
- 卡方分箱
- BestKS分箱(常用)
三、特征选择
3.1 Filter
3.1.1 方差选择法
from sklearn.feature_selection import VarianceThreshold
#其中参数threshold为方差的阈值
VarianceThreshold(threshold=3).fit_transform(train,target_train)
3.1.2 相关系数法
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)
3.1.3 互信息
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: array(map(lambda x:mic(x, Y), X.T)).T,k=2).fit_transform(train,target_train)
3.2 Wrapper
递归特征消除法 递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,
再基于新的特征集进行下一轮训练。在feature_selection库的SelectFromModel类结合逻辑回归模型可以用于选择特征,
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 Embedded
基于惩罚项的特征选择法 使用带惩罚项的基模型,除了筛选出特征外,同时也进行了降维。
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也可用来作为基模型进行特征选择。
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier
#GBDT作为基模型的特征选择
SelectFromModel(GradientBoostingClassifier()).fit_transform(train,target_train)
网友评论