特征工程系列:聚合特征构造以及转换特征构造
本文为数据茶水间群友原创,经授权在本公众号发表。
关于作者:JunLiang,一个热爱挖掘的数据从业者,勤学好问、动手达人,期待与大家一起交流探讨机器学习相关内容~
0x00 前言
数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。由此可见,特征工程在机器学习中占有相当重要的地位。在实际应用当中,可以说特征工程是机器学习成功的关键。
那特征工程是什么?
特征工程是利用数据领域的相关知识来创建能够使机器学习算法达到最佳性能的特征的过程。
特征工程又包含了 Data PreProcessing(数据预处理)、Feature Extraction(特征提取)、Feature Selection(特征选择)和 Feature construction(特征构造)等子问题,本章内容主要讨论特征构造的方法。
创造新的特征是一件十分困难的事情,需要丰富的专业知识和大量的时间。机器学习应用的本质基本上就是特征工程。 ——Andrew Ng
0x01 特征构造介绍
特征构造意味着从现有的数据中构造额外特征,这些特征通常分布在多张相关的表中。特征构造需要从数据中提取相关信息并将其存入单张表格中,然后被用来训练机器学习模型。这需要我们花大量的时间去研究真实的数据样本,思考问题的潜在形式和数据结构,同时能够更好地应用到预测模型中。
特征构建需要很强的洞察力和分析能力,要求我们能够从原始数据中找出一些具有物理意义的特征。对于表格数据,特征构建意味着将特征进行混合或组合以得到新的特征,或通过对特征进行分解或切分来构造新的特征;对于文本数据,特征够自己按意味着设计出针对特定问题的文本指标;对于图像数据,这意味着自动过滤,得到相关的结构。
特征构造是一个非常耗时的过程,因为每个新的特征通常需要几步才能构造,特别是当使用多张表的信息时。我们可以将特征构造的操作分为两类:“转换”和“聚合”。
以下将介绍聚合特征构造以及简单变换特征构造的方法。
0x02 聚合特征构造
通常基于 id 值(用户id、商品id等)或类别特征的某个类别计算数值特征的一些统计量,一般在多个表好操作一些。
用 N1 和 N2 表示数值特征,用C1和C2表示类别特征(C2还可以是连续值特征离散化后的取值),利用 pandas 的 groupby 操作,可以创造出以下几种有意义的新特征:
代码语言:javascript复制display(df.head(10))
# 输出
C1 C2 N1 N2
0 A a 1 1.1
1 A a 1 2.2
2 A a 2 3.3
3 B a 2 4.4
4 B a 3 5.5
5 C b 3 6.6
6 C b 4 7.7
7 C b 4 8.8
8 D b 5 9.9
9 D b 5 10.0
1.分组统计特征
1)分组统计中位数
median(N1)_by(C1)
例子:不同公司职员工资的中位数。
代码语言:javascript复制df.groupby(['C1']).agg({'N1': 'median'})
# 输出
N1
C1
A 1.0
B 2.5
C 4.0
D 5.0
2)分组统计算术平均数
mean(N1)_by(C1)
例子:顾客平均每次的购买金额。
代码语言:javascript复制df.groupby(['C1']).agg({'N1': 'mean'})
# 输出
N1
C1
A 1.333333
B 2.500000
C 3.666667
D 5.000000
3)分组统计众数
mode(N1)_by(C1)
例子:购买商品类型的众数。
代码语言:javascript复制df.groupby(['C1'])['N1'].agg(lambda x: stats.mode(x)[0][0])
# 输出
C1
A 1
B 2
C 4
D 5
4)分组统计最小值
min(N1)_by(C1)
例子:购买行为至今天数最小值(最近一次购买至今天数)。
代码语言:javascript复制df.groupby(['C1']).agg({'N1': 'min'})
# 输出
N1
C1
A 1
B 2
C 3
D 5
5)分组统计最大值
max(N1)_by(C1)
例子:理财用户的历史最大在投金额。
代码语言:javascript复制df.groupby(['C1']).agg({'N1': 'max'})
# 输出
N1
C1
A 2
B 3
C 4
D 5
6)分组统计标准差
std(N1)_by(C1)
代码语言:javascript复制df.groupby(['C1']).agg({'N1': 'std'})
# 输出
N1
C1
A 0.577350
B 0.707107
C 0.577350
D 0.000000
7)分组统计方差
var(N1)_by(C1)
代码语言:javascript复制df.groupby(['C1']).agg({'N1': 'var'})
# 输出
N1
C1
A 0.333333
B 0.500000
C 0.333333
D 0.000000
8)分组统计频数
freq(C2)_by(C1)
例子:每个月购买商品次数。
代码语言:javascript复制df.groupby(['C1']).agg({'C2': 'count'})
# 输出
C2
C1
A 3
B 2
C 3
D 2
仅仅将已有的类别和数值特征进行以上的有效组合,就能够大量增加优秀的可用特征。
2.统计频数构造特征
freq(C1) ,直接统计类别特征的频数,这个不需要 groupby 也有意义。
代码语言:javascript复制df['C1'].count()
# 输出:10
3.分组统计和基础特征工程方法结合
将这种方法和线性组合等基础特征工程方法结合(仅用于决策树),可以得到更多有意义的特征,例如:
中位数分组和线性组合结合
- N1 median(N1)_by(C1)
- N1 - median(N1)_by(C1)
- N1 * median(N1)_by(C1)
- N1 / median(N1)_by(C1)
df = pd.merge(df, df.groupby(['C1'])['N1'].median().reset_index().rename(columns={'N1': 'N1_Median'}),
on='C1', how='left')
df['N1 Median(C1)'] = df['N1'] df['N1_Median']
df['N1-Median(C1)'] = df['N1'] - df['N1_Median']
df['N1*Median(C1)'] = df['N1'] * df['N1_Median']
df['N1/Median(C1)'] = df['N1'] / df['N1_Median']
display(df.head(20))
# 输出:
C1 C2 N1 N2 N1_Median N1 Median(C1) N1-Median(C1) N1*Median(C1) N1/Median(C1)
0 A a 1 1.1 1.0 2.0 0.0 1.0 1.00
1 A a 1 2.2 1.0 2.0 0.0 1.0 1.00
2 A a 2 3.3 1.0 3.0 1.0 2.0 2.00
3 B a 2 4.4 2.5 4.5 -0.5 5.0 0.80
4 B a 3 5.5 2.5 5.5 0.5 7.5 1.20
5 C b 3 6.6 4.0 7.0 -1.0 12.0 0.75
6 C b 4 7.7 4.0 8.0 0.0 16.0 1.00
7 C b 4 8.8 4.0 8.0 0.0 16.0 1.00
8 D b 5 9.9 5.0 10.0 0.0 25.0 1.00
9 D b 5 10.0 5.0 10.0 0.0 25.0 1.00
均值分组和线性组合结合
- N1 mean(N1)_by(C1)
- N1 - mean(N1)_by(C1)
- N1 * mean(N1)_by(C1)
- N1 / mean(N1)_by(C1)
df = pd.merge(df, df.groupby(['C1'])['N1'].mean().reset_index().rename(columns={'N1': 'N1_Mean'}),
on='C1', how='left')
df['N1 Mean(C1)'] = df['N1'] df['N1_Mean']
df['N1-Mean(C1)'] = df['N1'] - df['N1_Mean']
df['N1*Mean(C1)'] = df['N1'] * df['N1_Mean']
df['N1/Mean(C1)'] = df['N1'] / df['N1_Mean']
display(df.head(20))
# 输出:
C1 C2 N1 N2 N1_Mean N1 Mean(C1) N1-Mean(C1) N1*Mean(C1) N1/Mean(C1)
0 A a 1 1.1 1.333333 2.333333 -0.333333 1.333333 0.750000
1 A a 1 2.2 1.333333 2.333333 -0.333333 1.333333 0.750000
2 A a 2 3.3 1.333333 3.333333 0.666667 2.666667 1.500000
3 B a 2 4.4 2.500000 4.500000 -0.500000 5.000000 0.800000
4 B a 3 5.5 2.500000 5.500000 0.500000 7.500000 1.200000
5 C b 3 6.6 3.666667 6.666667 -0.666667 11.000000 0.818182
6 C b 4 7.7 3.666667 7.666667 0.333333 14.666667 1.090909
7 C b 4 8.8 3.666667 7.666667 0.333333 14.666667 1.090909
8 D b 5 9.9 5.000000 10.000000 0.000000 25.000000 1.000000
9 D b 5 10.0 5.000000 10.000000 0.000000 25.000000 1.000000
0x03 简单转换特征构造
1.单列特征加/减/乘/除一个常数
对于创造新的有用特征毫无用处;只能作为对已有特征的处理。
程序实现
代码语言:javascript复制df['Feature'] = df['Feature'] n
df['Feature'] = df['Feature'] - n
df['Feature'] = df['Feature'] * n
df['Feature'] = df['Feature'] / n
2.单列特征单调变换
如果 u(x1, y1) > u(x2, y2),则 v(x1, y1) = f(u(x1, y1)) > v(x2, y2)=f(u(x2, y2)) ,那么 f() 就是(正)单调变化V是U的单调变换。设u为效用函数,f(u) 是其单调变换。
例子:幂变换
适用范围:不适用于决策树类算法。
程序实现
代码语言:javascript复制import numpy as np
# 计算n次方
df['Feature'] = df['Feature']**2
# 计算log变换
df['Feature'] = np.log(df['Feature'])
3.线性组合(linear combination)
- [A X B]: 将两个特征的值相乘形成的特征组合;
- [A x B x C x D x E]: 将五个特征的值相乘形成的特征组合;
- [A x A]: 对单个特征的值求平方形成的特征组合。
借助特征组合,线性学习器可以很好扩展到大量数据,并有助于构建复杂模型解决非线性问题。
适用场景:仅适用于决策树以及基于决策树的 ensemble 算法,因为常见的 axis-aligned split function 不擅长捕获不同特征之间的相关性;不适用于 SVM 、线性回归、神经网络等。
程序实现
代码语言:javascript复制df['Feature'] = df['A'] * df['B']
df['Feature'] = df['A'] * df['B'] * df['C'] * df['D'] * df['E']
df['Feature'] = df['A'] * df['A']
4.多项式特征(polynomial feature)
当两个变量各自与 y 的关系并不强时候,把它们结合成为一个新的变量可能更会容易体现出它们与 y 的关系。
特征a和特征b的多项式输出是:[1, a, b, a^2, ab, b^2]或者[1, a, b, ab]。
程序实现
代码语言:javascript复制import numpy as np
from sklearn.preprocessing import PolynomialFeatures
X = np.arange(6).reshape(3, 2)
print(X)
# 输出:array([[0, 1],
[2, 3],
[4, 5]])
# 设置多项式阶数为2
poly = PolynomialFeatures(2)
print(poly.fit_transform(X))
# 输出:array([[ 1., 0., 1., 0., 0., 1.],
[ 1., 2., 3., 4., 6., 9.],
[ 1., 4., 5., 16., 20., 25.]])
#默认的阶数是2,同时设置交互关系为true
poly = PolynomialFeatures(interaction_only=True)
print(poly.fit_transform(X))
# 输出:array([[ 1., 0., 1., 0.],
[ 1., 2., 3., 6.],
[ 1., 4., 5., 20.]])
5.比例特征(ratio feature)
计算两个特征的数值比例:X1/X2。
例子:购物车转化比特征。
程序实现
代码语言:javascript复制df['Feature'] = df['X1']/df['X2']
6.绝对值特征(absolute value)
计算特征值的绝对值:|X|。
例子:某数据的相关系数特征。
程序实现
代码语言:javascript复制import numpy as np
df['Feature'] = np.abs(df['Feature'])
7.最大值特征
max(X1, X2)
例子:最近两个月的最大购买金额。
程序实现
代码语言:javascript复制# 最大值
df['Feature'] = df.apply(lambda x: max(x['X1'], x['X2']), axis=1)
8.最小值特征
min(X1, X2)
例子:公司地址和宿舍地址距离商业中心的最小距离。
程序实现
代码语言:javascript复制# 最小值
df['Feature'] = df.apply(lambda x: min(x['X1'], x['X2']), axis=1)
9.排名编码特征
按特征值对全体样本进行排序,以排序序号作为特征。这种特征对异常点不敏感,不会导致特征值冲突。
例子:广告历史曝光量排名。
程序实现
代码语言:javascript复制X = [10, 9, 9, 8, 7]
df = pd.DataFrame({'X': X,})
df['num'] = df['X'].rank(ascending=0, method='dense')
display(df.head())
# 输出
X num
0 10 1.0
1 9 2.0
2 9 2.0
3 8 3.0
4 7 4.0
10.异或值特征
计算两特征的异或值:X1 xor X2。
实际应用中也可以先进行二值化,然后再进行异或运算。
程序实现
代码语言:javascript复制# 按位进行异或运算
df['Feature'] = df.apply(lambda x: x['X1'] ^ x['X2'], axis=1)
0x0FF 总结
特征构造是一个非常耗时的过程,因为每个新的特征通常需要几步才能构造,特别是当使用多张表的信息时。我们可以将特征构造的操作分为两类:“转换”和“聚合”。
很多机器学习比赛都是直接给出了训练集(特征 类标),我们可以对给出的特征进行“转换”操作,构造更多的特征。而在实际的工作中,很多时候我们都没有现成的特征,需要自己进行“聚合”操作从多个原始数据表中构造出模型所需要的特征。
例如,用户行为数据表中每条记录为某个用户的一次浏览行为或一次点击行为,我们需要通过“聚合”操作构造出用户的行为特征(如:用户最近一次浏览的时长、用户最近一次登录的点击次数等特征),然后再使用“转换”操作来构造更多特征,最后再使用这些特征训练模型。
预告:下一篇文章将介绍笛卡尔乘积特征构造以及遗传编程特征构造。
参考文献
[1]https://machinelearning-notes.readthedocs.io/zh_CN/latest/feature/特征工程——时间.html [2] https://www.cnblogs.com/nxf-rabbit75/p/11141944.html#_nav_12 [3] https://gplearn.readthedocs.io/en/stable/examples.html#symbolic-classifier [4] 利用 gplearn 进行特征工程. https://bigquant.com/community/t/topic/120709 [5] Practical Lessons from Predicting Clicks on Ads at Facebook. https://pdfs.semanticscholar.org/daf9/ed5dc6c6bad5367d7fd8561527da30e9b8dd.pdf [6] Feature Tools:可自动构造机器学习特征的Python库. https://www.jiqizhixin.com/articles/2018-06-21-2
[7] 各种聚类算法的系统介绍和比较. https://blog.csdn.net/abc200941410128/article/details/78541273