数据挖掘 Task3:特征工程
1、特征构造
1.1特征构造的目的
对特征(属性)做进一步分析,对数据进行处理,可以对数据进行一些图表总结
1.2 常用操作
1.2.1清除异常值
1.2.1.1 箱型图

在Q3+1.5IQR和Q1-1.5IQR处画两条与中位线一样的线段,这两条线段为异常值截断点,称其为内限;在Q3+3IQR和Q1-3IQR处画两条线段,称其为外限。处于内限以外位置的点表示的数据都是异常值,其中在内限与外限之间的异常值为温和的异常值(mild outliers),在外限以外的为极端的异常值(extreme outliers)。四分位距IQR=Q3-Q1。
def extreme_outliers(data,col,scale):
def box_outliers(data_series,box_scale):#箱型图寻找异常值,box_scale控制异常值范围
iqr=box_scale*(data_series.quantile(0.75)-data_series.quantile(0.25))#
low=data_series.quantile(0.25)-iqr
high=data_series.quantile(0.75)+iqr
rule_low=data_series<low
rule_high=data_series>high
return (rule_low,rule_high),(low,high)#返回的是两个元组,一个是有异常值的行索引,一个是异常值内限
rule,number=box_outliers(data[col],scale)
index=np.arange(data[col].shape[0])[rule[0]|rule[1]]
print('异常数据数量:'+str(len(index)))
index_1=np.delete(np.arange(data[col].shape[0]),index)#删除异常值后的下标
data_p=data.iloc[index_1]#在全数据范围内删除带有异常值的记录
fig,ax=plt.subplots(1,2,figsize=(10,8))
sns.boxplot(y=data[col],ax=ax[0])#原始数据
sns.boxplot(y=data_p[col],ax=ax[1])#异常值删除后的数据
plt.show()
return data_p
extreme_outliers(train,'price',1.5)
函数参考教程,按自己的理解重新写了一遍,就是删除指定倍数的异常值,然后结果能通过箱型图直观地表现出来,删除异常值后的数据更直观更聚集。

1.2.2 构造新特征
1.汽车使用时间
data['time']=(pd.to_datetime(data['creatDate'],format='%Y%m%d',errors='coerce')-
pd.to_datetime(data['regDate'],format='%Y%m%d',errors='coerce')).dt.days
注意点:errors=‘coerce’,无法转换的情况使其成为空值
2.城市信息
data['city']=data['regionCode'].apply(lambda x:str(x)[:-3])
按照教程,regionCode去掉后三位是城市编码
3.品牌销量
data_brand=train.groupby('brand')
all_info={}
for kind,data_kind in data_brand:
info={}
data_kind=data_kind[data_kind['price']>0]
info['brand_amount']=len(data_kind)
info['brand_price_max']=data_kind['price'].max()
info['brand_price_min']=data_kind['price'].min()
info['brand_price_ave']=data_kind['price'].sum()/len(data_kind)
info['brand_price_med']=data_kind['price'].median()
info['brand_price_std']=data_kind['price'].std()
info['brand_price_sum']=data_kind['price'].sum()
all_info[kind]=info
brand_frame=pd.DataFrame(all_info).T.reset_index().rename(columns={"index": "brand"})
data=pd.merge(data,brand_frame,how='inner',on='brand')
注意点:reindex,rename,groupby,merge函数,merge函数里面的参数how的使用
inner:默认,取交集
outer:取并集,数据缺失范围NaN
left:按照左边数据集为参考合并,数据缺失范围NaN right:按照右边数据集为参考合并,数据缺失范围NaN
4.数据分箱
分箱是对数据预处理的手段,好处是内积运算速度快,对异常数据有很强的鲁棒性,提高模型的表达能力,防止过拟合,通过padas里面的cut实现。pd.cut用来把一组数据分割成离散的区间。
bin = [i*10 for i in range(31)]
data['power_bin'] = pd.cut(data['power'], bin, labels=False)
print(data[['power_bin', 'power']].head())
print(pd.value_counts(data['power_bin'])) # 统计每个区间个数
5.删除新特征构造完成之后不需要的数据
data = data.drop(['creatDate', 'regDate', 'regionCode'], axis=1)
1.3 数据标准化
1.3.1 归一化
通过直方图能看到power属性中有异常值,分布区别不明显,异常值最好不删,用长尾分布截断。可以先取log然后再用sklearn里面的preprocessing来进行归一化
from sklearn import preprocessing
min_max_scaler = preprocessing.MinMaxScaler()
data['power'] = np.log(data['power'] + 1)
data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))
data['power'].plot.hist()
data['kilometer'] = ((data['kilometer'] - np.min(data['kilometer'])) /
(np.max(data['kilometer']) - np.min(data['kilometer'])))
data['kilometer'].plot.hist()
plt.show()
def max_min(x):
return (x - np.min(x)) / (np.max(x) - np.min(x))
data['brand_amount'] = ((data['brand_amount'] - np.min(data['brand_amount'])) /
(np.max(data['brand_amount']) - np.min(data['brand_amount'])))
data['brand_price_average'] = ((data['brand_price_average'] - np.min(data['brand_price_average'])) /
(np.max(data['brand_price_average']) - np.min(data['brand_price_average'])))
data['brand_price_max'] = ((data['brand_price_max'] - np.min(data['brand_price_max'])) /
(np.max(data['brand_price_max']) - np.min(data['brand_price_max'])))
data['brand_price_median'] = ((data['brand_price_median'] - np.min(data['brand_price_median'])) /
(np.max(data['brand_price_median']) - np.min(data['brand_price_median'])))
data['brand_price_min'] = ((data['brand_price_min'] - np.min(data['brand_price_min'])) /
(np.max(data['brand_price_min']) - np.min(data['brand_price_min'])))
data['brand_price_std'] = ((data['brand_price_std'] - np.min(data['brand_price_std'])) /
(np.max(data['brand_price_std']) - np.min(data['brand_price_std'])))
data['brand_price_sum'] = ((data['brand_price_sum'] - np.min(data['brand_price_sum'])) /
(np.max(data['brand_price_sum']) - np.min(data['brand_price_sum'])))
对几个数值型的属性进行最大最小归一化
1.3.2 离散化
对类别特征进行OneEncoder
data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType','gearbox', 'notRepairedDamage', 'power_bin'])
小tip: one-hot将离散型特征的每一种取值都看成一种状态,若你的这一特征中有N个不相同的取值,那么我们就可以将该特征抽象成N种不同的状态,one-hot编码保证了每一个取值只会使得一种状态处于“激活态”,也就是说这N种状态中只有一个状态位值为1,其他状态位都是0。
函数原型:pandas.get_dummies(data, prefix=None, prefix_sep=’ ’, dummy_na=False, columns=None, sparse=False, drop_first=False, dtype=None)
主要参数说明:
data : array-like, Series, or DataFrame
prefix : 给输出的列添加前缀,如prefix=“A”,输出的列会显示类似prefix_sep : 设置前缀跟分类的分隔符sepration,默认是下划线""
2、特征过滤
2.1 过滤式
过滤式:先按照某种规则对数据集进行特征选择,然后再训练学习器,特征选择过程与后续学习器无关,这相当于先用特征选择过程对初始特征进行“过滤”,再用过滤后的特征来训练模型。
过滤式有很多种方法,按照教程先选择相关性,其他的后续补充。
2.1.1 相关性分析
print(data['power'].corr(data['price'], method='spearman'))
print(data['kilometer'].corr(data['price'], method='spearman'))
print(data['brand_amount'].corr(data['price'], method='spearman'))
print(data['brand_price_average'].corr(data['price'], method='spearman'))
print(data['brand_price_max'].corr(data['price'], method='spearman'))
print(data['brand_price_median'].corr(data['price'], method='spearman'))
也可以直接画图
data_numeric = data[['power', 'kilometer', 'brand_amount', 'brand_price_average',
'brand_price_max', 'brand_price_median']]
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)
2.2 包裹式
包裹式:特征选择过程与学习器相关,使用学习器的性能作为特征选择的评价准则,选择最有利于学习器性能的特征子集。常用的包裹式特征选择法有递归特征消除法RFE。
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from sklearn.linear_model import LinearRegression
sfs = SFS(LinearRegression(),
k_features=10,
forward=True,
floating=False,
scoring = 'r2',
cv = 0)
x = data.drop(['price'], axis=1)
x = x.fillna(0)
y = data['price']
sfs.fit(x, y)
sfs.k_feature_names_
画图
from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs
import matplotlib.pyplot as plt
fig1 = plot_sfs(sfs.get_metric_dict(), kind='std_dev')
plt.grid()
plt.show()
3、经验总结
特征工程的主要目的还是在于将数据转换为能更好地表示潜在问题的特征,从而提高机器学习的性能。
比如,异常值处理是为了去除噪声,填补缺失值可以加入先验知识等。
特征构造也属于特征工程的一部分,其目的是为了增强数据的表达。有些比赛的特征是匿名特征,这导致我们并不清楚特征相互直接的关联性,这时我们就只有单纯基于特征进行处理,比如装箱,groupby,agg 等这样一些操作进行一些特征统计,此外还可以对特征进行进一步的 log,exp 等变换,或者对多个特征进行四则运算(如上面我们算出的使用时长),多项式组合等然后进行筛选。
由于特性的匿名性其实限制了很多对于特征的处理,当然有些时候用 NN 去提取一些特征也会达到意想不到的良好效果。对于知道特征含义(非匿名)的特征工程,特别是在工业类型比赛中,会基于信号处理,频域提取,丰度,偏度等构建更为有实际意义的特征,这就是结合背景的特征构建,在推荐系统中也是这样的,各种类型点击率统计,各时段统计,加用户属性的统计等等,这样一种特征构建往往要深入分析背后的业务逻辑或者说物理原理,从而才能更好的找到 magic。
当然特征工程其实是和模型结合在一起的,这就是为什么要为 LR NN 做分桶和特征归一化的原因,而对于特征的处理效果和特征重要性等往往要通过模型来验证。
