在实际的工程建模中,有时会引入大量的特征,以便可以从更多角度来刻画特征。但是当特征太多时,难免会引入一些无效特征。无效特征不仅会给模型带来噪音,并且还会增加模型的训练难度。因此在建模之前常常有必要进行一个初步的特征筛选,以便过滤掉那些无用特征。
iv值全称是information value。通过计算不同特征的iv值,可以来判断不同特征的重要程度。通常而言,某个特征的iv值越大,则该特征越重要。由于特征主要分为连续型特征和离散型特征,在特征入模之前,我们常常对连续型的特征进行归一化,对离散型的特征进行哑变量化(onehot)。我们需要分别对这两种不同类型的特征来计算iv值。
(1)iv值计算公式
对某个特征计算其iv值时首先需要对该特征进行分桶,设某特征被分为n个桶,每个分桶都能计算出一个iv值。设第i个桶对应的iv值为:
x1 x 1 | y |
---|---|
1.0 | 1 |
2.0 | 0 |
3.0 | 1 |
4.0 | 0 |
5.0 | 0 |
6.0 | 1 |
7.0 | 0 |
8.0 | 1 |
9.0 | 0 |
10.0 | 1 |
11.0 | 0 |
12.0 | 0 |
13.0 | 1 |
14.0 | 0 |
演示数据集中特征 x1 x 1 的最大值 max m a x =14.0,最小值 min m i n =1.0,将其分为5个桶 [1.0,3.6) [ 1.0 , 3.6 ) , [3.6,6.2) [ 3.6 , 6.2 ) , [6.2,8.8) [ 6.2 , 8.8 ) , [8.8,11.4) [ 8.8 , 11.4 ) , [11.4,14.0] [ 11.4 , 14.0 ] ,分别将这5个桶编号为1,2,3,4,5。所有样本中有6个正样本,8个负样本,因此 yall=6 y a l l = 6 , nall=8 n a l l = 8 。对于第一个桶而言,有三条样本(1.0,1),(2.0,0),(3.0,1),其中 1.0⩽x1=1.0,2.0,3.0<3.6 1.0 ⩽ x 1 = 1.0 , 2.0 , 3.0 < 3.6 。这三条数据中有两条正样本,一条负样本,因此:
#需要处理的dataframe,该dataframe中x1,x2为连续型特征(连续型特征用float类型表示),x3为离散型特征(离散型特征用int类型表示),y为label。
df = spark.createDataFrame(
[
(1.0, 2.0, 0,1),
(2.0, 3.0, 0,0),
(3.0, 4.0, 0,1),
(4.0, 5.0, 0,0),
(5.0, 6.0, 0,0),
(6.0, 7.0, 0,1),
(7.0, 8.0, 0,0),
(8.0, 9.0, 1,1),
(9.0, 10.0, 1,0),
(10.0, 11.0, 1,1),
(11.0, 12.0, 1,0),
(12.0, 13.0, 1,0),
(13.0, 14.0, 1,1),
(14.0, 15.0, 1,0)
],
("x1", "x2", "x3","y"))
#计算iv值
#连续型变量是float数据类型。对连续型变量进行iv值计算时,需要对其进行分箱操作。我们将其等分为KIND_NUM=5份。
#离散型变量是in数据类型。我们默认离散型变量已经经过onthot处理 ,取值只有0和1。
#标签列label必须是最后一列
import math
def information_value(df):
#将桶号和数据绑定
def map_1(row,min_array,max_array):
res = []
for i in range(len(row)-1):
if(isinstance(row[i],float)):
KIND_NUM = 5
diff = (float(max_array[i])-float(min_array[i]))/KIND_NUM
for j in range(KIND_NUM):
if(j==4):
res.append((j,row[i]))
elif(row[i]>=float(min_array[i])+j*diff and row[i]1)*diff):
res.append((j,row[i]))
break
else:
res.append((row[i],row[i]))
res.append(row[len(row)-1])
return res
#添加正负样本计数器
def map_2(row,col_num):
kind = row[col_num][0]
label = row[-1]
pos_num = 0
neg_num = 0
if(label==1):
pos_num = 1
else:
neg_num = 1
res = (kind,(pos_num,neg_num))
return res
summary = df.describe()
min_array = summary.filter(summary["summary"]=="min").rdd.take(1)[0][1:]
max_array = summary.filter(summary["summary"]=="max").rdd.take(1)[0][1:]
rdd1 = df.rdd.map(lambda x:map_1(x,min_array,max_array))
ivs = []
col_name = df.columns[0:-1]
pos_all_num = df.filter(df["y"]==1).count()
neg_all_num = df.filter(df["y"]==0).count()
for i in range(len(df.columns)-1):
num_array = rdd1.map(lambda x:map_2(x,i)).reduceByKey(lambda a,b:(a[0]+b[0],a[1]+b[1])).collect()
iv = 0
for temp in num_array:
#当条数为0时,将其设为1,避免除以0(大数据环境下很少出现这种情况)
y_temp = temp[1][0] if temp[1][0]!=0 else 1
n_temp = temp[1][1] if temp[1][1]!=0 else 1
pos_all_num = pos_all_num if pos_all_num!=0 else 1
neg_all_num = neg_all_num if neg_all_num!=0 else 1
py = float(y_temp)/pos_all_num
pn = float(n_temp)/neg_all_num
sub_iv = (py-pn)*math.log(py/pn)
iv += sub_iv
ivs.append(iv)
return (ivs,col_name)
res = information_value(df)
res[0]
res[1]
结果如下:
>>>res[0]:
[0.31769245775664157, 0.31769245775664157, 0.0]
>>>res[1]:
['x1', 'x2', 'x3']