分计算iv值_数据分析变量选择--IV值计算

在进行数据分析和机器学习之前,我们通常的做法,需要选择变量,但在实际业务场景中,变量的个数有可能很多,其中包含连续变量和非连续变量(离散变量),同时也需要将这些自变量和因变量的关联程度在进行分析前,做个初步的了解。

1.为什么需要将连续变量调整为离散变量?

在机器学习过程中,经常会将连续变量离散化,主要是基于以下的考虑:(1)离散特征的增加和减少都很容易,易于模型的快速迭代;(2)稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;(3)离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;(4)单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;(5)离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;(6)特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;(7)特征离散化以后,起到了简化了模型的作用,降低了模型过拟合的风险。

2.如何衡量自变量与因变量的关联程度?

挑选变量过程是个比较复杂的过程,需要考虑的因素很多,比如:变量的预测能力,变量之间的相关性,变量的简单性(容易生成和使用),变量的强壮性(不容易被绕过),变量在业务上的可解释性(被挑战时可以解释的通)等等。但是,其中最主要和最直接的衡量标准是变量的预测能力。

IV的全称是Information Value,中文意思是信息价值,或者信息量。WOE的全称是“Weight of Evidence”,即证据权重。WOE是对原始自变量的一种编码形式。要对一个变量进行WOE编码,需要首先把这个变量进行分组处理(也叫离散化、分箱等等,说的都是一个意思)。分组后,对于第i组,WOE的计算公式如下:

1027b77748bc066e10ff53e05fff1eb3.png

WOE表示的实际上是“当前分组中响应客户占所有响应客户的比例”和“当前分组中没有响应的客户占所有没有响应的客户的比例”的差异。

e4b3be1bac4056219fe62979ee074271.png

3.实例卡方分箱和IV值计算

import pandas as pdimport numpy as npimport math'''exampledata = pd.read_csv('sample_data.csv', sep="", na_values=['', '?'])data = pd.read_csv('E:/breast_cancer.csv', sep=',')temp = data[['radius_mean','diagnosis']]temp2=ChiMerge(temp,'radius_mean' , 'diagnosis', confidenceVal=5.841, bin=5, sample = None)'''# 定义一个卡方分箱(可设置参数置信度水平与箱的个数)停止条件为大于置信水平且小于bin的数目'''    运行前需要 import pandas as pd 和 import numpy as np    df:传入一个数据框仅包含一个需要卡方分箱的变量与正负样本标识(正样本为1,负样本为0)    variable:需要卡方分箱的变量名称(字符串)    flag:正负样本标识的名称(字符串)    confidenceVal:置信度水平(默认是不进行抽样95%)    bin:最多箱的数目    sample: 为抽样的数目(默认是不进行抽样),因为如果观测值过多运行会较慢'''def ChiMerge(df, variable, flag, confidenceVal=3.841, bin=10, sample = None):  #进行是否抽样操作    if sample != None:        df = df.sample(n=sample)    else:        df   #进行数据格式化录入    total_num = df.groupby([variable])[flag].count()  # 统计需分箱变量每个值数目    total_num = pd.DataFrame({'total_num': total_num})  # 创建一个数据框保存之前的结果    positive_class = df.groupby([variable])[flag].sum()  # 统计需分箱变量每个值正样本数    positive_class = pd.DataFrame({'positive_class': positive_class})  # 创建一个数据框保存之前的结果    regroup = pd.merge(total_num, positive_class, left_index=True, right_index=True,                       how='inner')  # 组合total_num与positive_class    regroup.reset_index(inplace=True)    regroup['negative_class'] = regroup['total_num'] - regroup['positive_class']  # 统计需分箱变量每个值负样本数    regroup = regroup.drop('total_num', axis=1)    np_regroup = np.array(regroup)  # 把数据框转化为numpy(提高运行效率)    print('已完成数据读入,正在计算数据初处理')#处理连续没有正样本或负样本的区间,并进行区间的合并(以免卡方值计算报错)    i = 0    while (i <= np_regroup.shape[0] - 2):        if ((np_regroup[i, 1] == 0 and np_regroup[i + 1, 1] == 0) or ( np_regroup[i, 2] == 0 and np_regroup[i + 1, 2] == 0)):            np_regroup[i, 1] = np_regroup[i, 1] + np_regroup[i + 1, 1]  # 正样本            np_regroup[i, 2] = np_regroup[i, 2] + np_regroup[i + 1, 2]  # 负样本            np_regroup[i, 0] = np_regroup[i + 1, 0]            np_regroup = np.delete(np_regroup, i + 1, 0)            i = i - 1        i = i + 1 #对相邻两个区间进行卡方值计算    chi_table = np.array([])  # 创建一个数组保存相邻两个区间的卡方值    for i in np.arange(np_regroup.shape[0] - 1):        chi = (np_regroup[i, 1] * np_regroup[i + 1, 2] - np_regroup[i, 2] * np_regroup[i + 1, 1]) ** 2           * (np_regroup[i, 1] + np_regroup[i, 2] + np_regroup[i + 1, 1] + np_regroup[i + 1, 2]) /           ((np_regroup[i, 1] + np_regroup[i, 2]) * (np_regroup[i + 1, 1] + np_regroup[i + 1, 2]) * (          np_regroup[i, 1] + np_regroup[i + 1, 1]) * (np_regroup[i, 2] + np_regroup[i + 1, 2]))        chi_table = np.append(chi_table, chi)    print('已完成数据初处理,正在进行卡方分箱核心操作')#把卡方值最小的两个区间进行合并(卡方分箱核心)    while (1):        if (len(chi_table) <= (bin - 1) and min(chi_table) >= confidenceVal):            break        chi_min_index = np.argwhere(chi_table == min(chi_table))[0]  # 找出卡方值最小的位置索引        np_regroup[chi_min_index, 1] = np_regroup[chi_min_index, 1] + np_regroup[chi_min_index + 1, 1]        np_regroup[chi_min_index, 2] = np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 2]        np_regroup[chi_min_index, 0] = np_regroup[chi_min_index + 1, 0]        np_regroup = np.delete(np_regroup, chi_min_index + 1, 0)        if (chi_min_index == np_regroup.shape[0] - 1):  # 最小值试最后两个区间的时候            # 计算合并后当前区间与前一个区间的卡方值并替换            chi_table[chi_min_index - 1] = (np_regroup[chi_min_index - 1, 1] * np_regroup[chi_min_index, 2] - np_regroup[chi_min_index - 1, 2] * np_regroup[chi_min_index, 1]) ** 2                                            * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) /                                        ((np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index, 1]) * (np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 2]))            # 删除替换前的卡方值            chi_table = np.delete(chi_table, chi_min_index, axis=0)        else:            # 计算合并后当前区间与前一个区间的卡方值并替换            chi_table[chi_min_index - 1] = (np_regroup[chi_min_index - 1, 1] * np_regroup[chi_min_index, 2] - np_regroup[chi_min_index - 1, 2] * np_regroup[chi_min_index, 1]) ** 2                                        * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) /                                        ((np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index, 1]) * (np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 2]))            # 计算合并后当前区间与后一个区间的卡方值并替换            chi_table[chi_min_index] = (np_regroup[chi_min_index, 1] * np_regroup[chi_min_index + 1, 2] - np_regroup[chi_min_index, 2] * np_regroup[chi_min_index + 1, 1]) ** 2                                        * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 1] + np_regroup[chi_min_index + 1, 2]) /                                    ((np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index + 1, 1] + np_regroup[chi_min_index + 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index + 1, 1]) * (np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 2]))            # 删除替换前的卡方值            chi_table = np.delete(chi_table, chi_min_index + 1, axis=0)    print('已完成卡方分箱核心操作,正在保存结果')#把结果保存成一个数据框    result_data = pd.DataFrame()  # 创建一个保存结果的数据框    result_data['variable'] = [variable] * np_regroup.shape[0]  # 结果表第一列:变量名    list_temp = []    for i in np.arange(np_regroup.shape[0]):        if i == 0:            x = '0' + ',' + str(np_regroup[i, 0])        elif i == np_regroup.shape[0] - 1:            x = str(np_regroup[i - 1, 0]) + '+'        else:            x = str(np_regroup[i - 1, 0]) + ',' + str(np_regroup[i, 0])        list_temp.append(x)    result_data['interval'] = list_temp  # 结果表第二列:区间    result_data['flag_0'] = np_regroup[:, 2]  # 结果表第三列:负样本数目    result_data['flag_1'] = np_regroup[:, 1]  # 结果表第四列:正样本数目    return result_datadef iv_value(file,feature,sep,target):    ###sep格式为(10,15,20)    data = pd.read_csv(file, sep=',')    #data = pd.read_csv('E:/breast_cancer.csv', sep=',')    # woe    #sep_value = sep.split(',')    sep_value =str(sep).replace('(','').replace(')','').split(',')        sep_len = len(sep_value)    dict_bin = {}    class_bin = {}    len_dict_bin = {}    len_dict_bin_0 = {}    len_dict_bin_1 = {}    woe_bin = {}    iv_bin = {}    if sep_len == 1:        dict_bin[0] = data.loc[data[feature] <= float(sep_value[0]), :]        dict_bin[1] = data.loc[data[feature] > float(sep_value[0]), :]        dict_bin[2] = sum(data[feature].isnull())        len_dict_bin[0] = len(dict_bin[0])        len_dict_bin[1] = len(dict_bin[1])        len_dict_bin[2] = len(dict_bin[2])        class_bin[0] = "(0," + sep_value[0] + "]"        class_bin[1] = "(" + sep_value[0] + "...)"        class_bin[2] = "NA"    else:        for index, item in enumerate(sep_value):####区间            if index == 0:                dict_bin[0] = data.loc[data[feature] <= float(item), :]                len_dict_bin[0] = len(dict_bin[0])                class_bin[0] = "(0," + str(float(item)) + "]"            else:                dict_bin[index] = (                    data.loc[(data[feature] >= float(sep_value[index - 1])) & (data[feature] < float(item)),                    :])                len_dict_bin[index] = len(dict_bin[index])                class_bin[index] = "(" + str(sep_value[index - 1]) + "," + str(sep_value[index]) + "]"        dict_bin[index + 1] = data.loc[data[feature] > float(item), :]        dict_bin[index + 2] = data.loc[data[feature].isnull()]        len_dict_bin[index + 1] = len(dict_bin[index + 1])        len_dict_bin[index + 2] = len(dict_bin[index + 2])        class_bin[index + 1] = "(" + str(sep_value[index]) + "...)"        class_bin[index + 2] = "NA"    #print(class_bin)    for index, item in enumerate(dict_bin):        len_dict_bin_0[index] = len(dict_bin[index][dict_bin[index][target] == 0])        len_dict_bin_1[index] = len(dict_bin[index][dict_bin[index][target] == 1])    len_data_0 = len(data[data[target] == 0])    len_data_1 = len(data[data[target] == 1])    for index, item in enumerate(dict_bin):        try:            woe_bin[index] = math.log(math.e, (float(len_dict_bin_1[index]) / float(len_data_1)) / (                float(len_dict_bin_0[index]) / float(len_data_0)))            iv_bin[index] = ((float(len_dict_bin_1[index]) / float(len_data_1)) - (                float(len_dict_bin_0[index]) / float(len_data_0))) * math.log(math.e, (                float(len_dict_bin_1[index]) / float(len_data_1)) / (float(len_dict_bin_0[index]) / float(len_data_0)))        except Exception as e:            iv_bin[index] = 0    iv_sum=0.0    #print(iv_bin)    for key in iv_bin:        try:            iv_sum=iv_sum+float(iv_bin[key])        except Exception as e:            print (e)    #print(iv_sum)    #return iv_sum                            dict_result = {}    len_dict_bin_0[" "] = len_data_0    len_dict_bin_1[" "] = len_data_1    woe_bin[" "] = ""    iv_bin[" "]=sum(iv_bin.values())    class_bin[" "] = ""    len_dict_bin[" "] = len(data)    dict_result["bad"] = len_dict_bin_0    dict_result["good"] = len_dict_bin_1    dict_result["all"] = len_dict_bin    dict_result["woe"] = woe_bin    dict_result["iv"] = iv_bin    dict_result["class"] = class_bin    df = pd.DataFrame(dict_result)    dict_result["%good"] = (df['good'] / df['all']).map('{:.2%}'.format);    dict_result["%bad"] = (df['bad'] / df['all']).map('{:.2%}'.format);    df["%good"] = dict_result["%good"]    df["%bad"] = dict_result["%bad"]    # 调整列的顺序    df = df.loc[:, ['class', 'good', 'bad', '%good', '%bad', 'all', 'woe', 'iv']]    #print(iv_sum)    #print(df)    return iv_sum,df
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值