pyspark实现iv特征筛选

特征筛选和iv值

特征筛选

在实际的工程建模中,有时会引入大量的特征,以便可以从更多角度来刻画特征。但是当特征太多时,难免会引入一些无效特征。无效特征不仅会给模型带来噪音,并且还会增加模型的训练难度。因此在建模之前常常有必要进行一个初步的特征筛选,以便过滤掉那些无用特征。

iv值

iv值全称是information value。通过计算不同特征的iv值,可以来判断不同特征的重要程度。通常而言,某个特征的iv值越大,则该特征越重要。由于特征主要分为连续型特征和离散型特征,在特征入模之前,我们常常对连续型的特征进行归一化,对离散型的特征进行哑变量化(onehot)。我们需要分别对这两种不同类型的特征来计算iv值。
(1)iv值计算公式
对某个特征计算其iv值时首先需要对该特征进行分桶,设某特征被分为n个桶,每个分桶都能计算出一个iv值。设第i个桶对应的iv值为:

ivi=(pyipni)ln(pyipni) i v i = ( p y i − p n i ) ∗ l n ( p y i p n i )

,其中 pyi p y i 是第i个桶中正样本个数在所有正样本中的比列, pni p n i 则是第i个桶中负样本个数在所有负样本中的比列。其表达式为:
pyi=yiyall,pni=ninall p y i = y i y a l l , p n i = n i n a l l

,其中 yi y i ni n i 分别是第i个桶中正负样本的个数, yall y a l l nall n a l l 则分别为所有正负样本的总个数。
(2)对连续型变量计算iv值
由步骤(1)中可知,计算iv值之前需要对特征分桶操作。对于连续型特征我们可以先求得所有样本在这个特征下的最大值 max m a x 和最小值 min m i n ,再将其分为 n=5 n = 5 个桶,则这5个桶的区间分别为: [min,min+(maxmin)5) [ m i n , m i n + ( m a x − m i n ) 5 ) , [min+(maxmin)5,min+2(maxmin)5) [ m i n + ( m a x − m i n ) 5 , m i n + 2 ( m a x − m i n ) 5 ) , [min+2(maxmin)5,min+3(maxmin)5) [ m i n + 2 ( m a x − m i n ) 5 , m i n + 3 ( m a x − m i n ) 5 ) , [min+3(maxmin)5,min+4(maxmin)5) [ m i n + 3 ( m a x − m i n ) 5 , m i n + 4 ∗ ( m a x − m i n ) 5 ) , [min+4(maxmin)5,max] [ m i n + 4 ( m a x − m i n ) 5 , m a x ]
例如有一个一维的数据集:

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.0x1=1.0,2.0,3.0<3.6 1.0 ⩽ x 1 = 1.0 , 2.0 , 3.0 < 3.6 。这三条数据中有两条正样本,一条负样本,因此:

y1=2,n1=1,py1=yiyall=2/6,pn1=ninall=1/8 y 1 = 2 , n 1 = 1 , p y 1 = y i y a l l = 2 / 6 , p n 1 = n i n a l l = 1 / 8
,则
iv1=(py1pn1)ln(py1pn1)=(2/61/8)ln(2/61/8)=0.20433942771077626 i v 1 = ( p y 1 − p n 1 ) ∗ l n ( p y 1 p n 1 ) = ( 2 / 6 − 1 / 8 ) ∗ l n ( 2 / 6 1 / 8 ) = 0.20433942771077626

,同理可得:
iv2=0.03378875900901371iv3=0.011986753018824198,iv4=0.03378875900901371,iv5=0.03378875900901371 i v 2 = 0.03378875900901371 , i v 3 = 0.011986753018824198 , i v 4 = 0.03378875900901371 , i v 5 = 0.03378875900901371

,所以特征 x1 x 1 的iv值为:
iv=iv1+iv2+iv3+iv4+iv5=0.31769245775664157 i v = i v 1 + i v 2 + i v 3 + i v 4 + i v 5 = 0.31769245775664157

(3)对离散特征计算iv值
对离散特征计算iv值时,通常需要先将离散特征进行onehot。进行onehot之后离散型的特征只能取0和1。相当于对其进行了分桶操作,其他步骤和步骤(2)中类似。

pyspark代码

#需要处理的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']

你可能感兴趣的:(特征处理,机器学习,特征选择)