特征类别
常见的特征类型有:数值特征、类别特征、序列特征、k-v特征、embedding特征、交叉特征等。
1. 数值特征
数值特征最为常见,如一些统计类特征:ctr、click_num等,不同的业务场景,数值特征量不同,数值特征从特征获得方式上面又可以分成两类:
- 一类是基础的统计特征
- 一类是根据业务场景,按照一定规则计算产出的复合特征
通常情况下,复合特征包含的信息会更多,更有效。下面对数值特征的常见处理方式进行总结。
1.1 特征分桶
像点击量、点赞量、收藏量这类连续型统计特征,直接拿去编码会造成编码空间的极大浪费,于是先进行分桶,同一个桶内的特征共用一个编码索引,既能够降低编码空间,又可以实现特征的高效处理。如此操作,将连续型特征映射到N个桶内,直接进行one-hot编码,简单方便。
分桶规则:即分桶阈值的确定,如果分桶阈值设置的不合理,就会导致一些特征全部集中在几个桶里,这样会大大降低特征的区分度
-
等距分桶。但这种方式需要注意两个点:
- 这个特征的数值分布是否平均,如果长尾严重,显然不合理;
- 特征异常值对分桶的影响是否巨大,比如点击量中出现某个断层的最大值,会直接将其他的特征挤压到前面的桶里,后面的桶则很稀疏。
-
等频分桶:先对数据的等分点进行统计,然后根据每个特征等分点的情况大致确定分桶阈值,这样可以基本保证以下两个原则:
- 每个桶内的特征量级差不多
- 每个桶内的特征具有一定的区分度
1.2 特征截断
特征中可能会出现异常值,这些异常值可能直接影响特征处理之后的结果,这种情况下,使用特征截断,移除异常值,可以使特征的准确性更高,截断这里涉及到异常值检测。
1.3 数据平滑
对于比值型特征,小分母计算得到的特征值更不稳定,置信度低,这时候需要将数据进行平滑处理,使其更接近真实值。
- wilson平滑
比如对于ctr特征,有以下三个case:- case1:曝光100,点击3
- case2:曝光10000,点击300
- case3:曝光100000,点击3000
很明显,case3计算得到的ctr会更接近真实值,因为曝光基数足够大;而case1计算的数值最不稳定,多一个点击,ctr就变成了4%,因此,需要对case1和case2进行平滑操作。
使用wilson平滑就可以对这个数值进行修正,其计算公式如下:
- 贝叶斯平滑
1.4 数据标准化
- 最大最小标准化
- z-score标准化
2. 分桶实现的基础知识
2.1 p分位数
统计学上的四分为函数
原则上p是可以取0到1之间的任意值的,但是有一个四分位数是p分位数中较为有名的。所谓四分位数;即把数值由小到大排列并分成四等份,处于三个分割点位置的数值就是四分位数。即:p=0.25, 0.5 ,0.75
确定p分位的位置的方法:
方法1: pos= (n+1)*p
方法2: pos = 1+ (n-1)*p
实现:通过pandas
中的quantile
函数实现
函数形式:
DataFrame.quantile(q=0.5, axis=0, numeric_only=True, interpolation='linear')
参数:
- q: float or array-like,the quantile(s) to compute
- axis:0,1,‘index’,‘columns’,其中 0,‘index’ 是逐行(row-wise)统计, 1,‘columns’ 逐列计算(column-wise)
- numeric_only:如果为 False,则还将计算 datetime 和 timedelta 数据的分位数。
- interpolation(插值方法):
- linear 线性插值: i+(j-i)* fraction,fraction由计算得到的pos的小数部分
- lower:i
- higher: j
- nearest: i or j whichever is nearest
- midpoint : (i+j)/2
df = pd.DataFrame(np.array([[1, 1], [2, 10], [3, 100], [4, 100]]),
columns=['a', 'b'])
print(df.quantile(0.1))
# 返回值, columns of self and the values are the quantiles.
a 1.3
b 3.7
# 计算逻辑:pos = 1 + (4 - 1)*0.1 = 1.3 ,fraction = 0.3,ret = 1 + (2 - 1) * 0.3 = 1.3
print(df.quantile([0.2, 0.5]))
# 返回值:index is q, the columns are the columns of self, and the values are the quantiles.
a b
0.2 1.6 6.4
0.5 2.5 55.0
2.2 等宽/等频分桶策略:
等频分桶,也称为按分位数分桶,为了计算分位数和映射数据到分位数箱中,可以通过pandas库来实现。
-
pd.qcut()
:根据这些值的频率来选择箱子的均匀间隔,即每个箱子中含有的数的数量是相同的
pd.qcut(x, q, labels=None, retbins=False, precision=3, duplicates='raise')
- q :int or list-like of float
Number of quantiles. 10 for deciles, 4 for quartiles, etc. Alternately array of quantiles, e.g. [0, .25, .5, .75, 1.]
for quartiles.
# 传入q参数
factors = np.random.randn(9)
res = pd.qcut(factors, 3) # 返回每个数对应的分组
cnt = pd.qcut(factors, 3).value_counts() # 计算每个分组中含有的数的数量
# 传入lable参数
print(pd.qcut(factors, 3, labels=["a", "b", "c"])) # 返回每个数对应的分组,但分组名称由label指示)
print(pd.qcut(factors, 3, labels=False)) # 返回每个数对应的分组,但仅显示分组下标
# 传入retbins参数
print(pd.qcut(factors, 3, retbins=True)) # 返回每个数对应的分组,且额外返回bins,即每个边界值
参数 | 说明 |
---|---|
x | ndarray 或Series |
q | integer ,指示划分的数组 |
labels | array 或bool,默认为None。当传入数组时,分组的名称由label指示;当传入False时,仅显示分组下标 |
retbins | bool,是否返回bins,默认为False。当传入True时,额外返回bins,即每个边界值 |
precision | int,精度,默认为3 |
等宽分桶,根据值本身来选择箱子均匀间隔,即每个箱子的间距都是相同的
# 传入bins参数
factors = np.random.randn(9)
print(pd.cut(factors, 3)) # 返回每个数对应的分组
print(pd.cut(factors, bins=[-3, -2, -1, 0, 1, 2, 3]))
print(pd.cut(factors, 3).value_counts()) # 计算每个分组中含有的数的数量
# 传入label参数
print(pd.cut(factors, 3, labels=["a", "b", "c"])) # 返回每个数对应的分组,但分组名称由label指示
print(pd.cut(factors, 3, labels=False)) # 返回每个数对应的分组,但仅显示分组下标
参数 | 说明 |
---|---|
x | array,仅能使用一维数组 |
q | integer 或sequence of scalars,指示划分的数组或指定组距 |
labels | array 或bool,默认为None。当传入数组时,分组的名称由label指示;当传入False时,仅显示分组下标 |
retbins | bool,是否返回bins,默认为False。当传入True时,额外返回bins,即每个边界值 |
precision | int,精度,默认为3 |
python_等频分箱_等距分箱
data_temp = data
# 分箱:等距等频分箱
# 等距分箱
#bins=10 分箱数
data_temp['deposit_cur_balance_bins'] = binning(df = data_temp,col = 'deposit_cur_balance',method='frequency',bins=10)
data_temp['deposit_year_total_balance_bins'] = binning(df = data_temp,col = 'deposit_year_total_balance',method='frequency',bins=10)
def binning(df,col,method,bins):
# 需要判断类型
uniqs=df[col].nunique()
if uniqs<=bins:
raise KeyError('nunique is smaller than bins: '+col)
# print('nunique is smaller than bins: '+col)
return
# 左开右闭
def ff(x,fre_list):
if x<=fre_list[0]:
return 0
elif x>fre_list[-1]:
return len(fre_list)-1
else :
for i in range(len(fre_list)-1):
if x>fre_list[i] and x<=fre_list[i+1]:
return i
# 等距分箱
if method=='distance':
umax=np.percentile(df[col],99.99)
umin=np.percentile(df[col],0.01)
step=(umax-umin)/bins
fre_list=[umin+i*step for i in range(bins+1)]
return df[col].map(lambda x:ff(x,fre_list))
#等频分箱
elif method=='frequency' :
fre_list=[np.percentile(df[col],100/bins*i) for i in range(bins+1)]
fre_list=sorted(list(set(fre_list)))
return df[col].map(lambda x:ff(x,fre_list))
SQL_等频分箱_等距分箱
等距分箱/等宽分箱:将变量的取值范围分为k个等宽的区间,每个区间当作一个分箱。
-- 数学运算:对col进行0.1(即按照等距分成10分)宽度的分箱,方法,先将数据归一到0-1的区间内,然后根据分桶的数据量对特征划分到对应的桶内
---- 等距分桶 [0.100000,0.590000,1.080000,1.570000,2.060000,2.550000,3.040000,3.530000,4.020000,4.510000,5.000000]
select
col,
(col-mi)/(ma-mi) as rate
from
(
select
col,
(select max(col) from VALUES (5),(4.5), (0.1), (0.15),(0.20), (0.25),(0.3) AS tab(col)) as ma,
(select min(col) from VALUES (5),(4.5), (0.1), (0.15),(0.20), (0.25),(0.3) AS tab(col)) as mi
from VALUES (5),(4.5), (0.1), (0.15),(0.20), (0.25),(0.3) AS tab(col)
)
等频分箱:把观测值按照从小到大的顺序排列,根据观测的个数等分为k部分,每部分当作一个分箱,即百分位数的概念。
-- Ntile(n) over(order by col):分块函数
-- 备注:NULL值的处理,是否需要单独为1组。
select
col,
ntile(3) over( order by col) as group1, -- NULL默认为最小值
if(col is null, null, ntile(3) over( partition by if(col is null, 1, 0) order by col)) as group2 -- 将NULL单独为1组
from VALUES (5), (0.1), (0.15),(0.20), (0.25),(0.3),(null) AS tab(col);
ntile()
函数的作用是等频分箱,把观测值进行有序排列(默认升序),根据观测值的总个数等分为k部分,每部分当作一个分箱,即百分位数的概念,可以根据箱号选取前或后n分之几的数据。
函数方法:ntile(n) over(order by col) as bucket_numn
是指定的分箱数量。如果不能平均分配,则优先分配较小编号的箱,并且各个箱中能放的行数最多相差1备注:NULL值的处理,可以设置单独为一组,或者默认为最小值
补充:
percent_rank() over(order by col):先得出每个值对应的百分位数,再根据实际需求分箱
select col
-- 根据百分位数划分
, if(group1<0.5, 1, if(group1<=1.0, 2, null)) as group1
, if(group2<0.5, 1, if(group2<=1.0, 2, null)) as group2
from(
select col
-- NULL默认为最小值
, percent_rank() over( order by col) as group1
-- 将NULL单独为1组
, if(col is null, null, percent_rank() over( partition by if(col is null, 1, 0) order by col) as group2
from(
select cast(col as int) as col
from(
select stack(5, 'NULL', '1', '2', '3', '4') as col
) as a
) as a
) as a