Python特征工程之数据预处理【附代码】

目录

1、非数值类型数据处理

(1)Get_dummies哑变量处理

1、简单示例:“男”和“女”的数值转换

2、稍复杂点的案例:房屋朝向的数值转换

3、Label Encoding编号处理

4、pandas库中的replace()函数

2、重复值、缺失值及异常值处理

3、数据标准化

(1)min-max标准化

(2)Z-score标准化

4、数据分箱

5、特征筛选:WOE值与IV值

(1)WOE值的定义

(2)IV值的定义与演示

(3)WOE值与IV值的代码实现

1、数据分箱

2、统计各个分箱样本总数、坏样本数和好样本数

3、统计各分箱中坏样本比率和好样本比率

4、计算WOE值

5、计算IV值

(4)案例:客户流失预警模型的IV值计算


1、非数值类型数据处理

       机器学习建模时处理的都是数值类型的数据,然而实际工作中我们获取的数据往往或有非数值类型的数据,其中最常见的就是文本类型的数据,例如性别中的“男”和“女”,这个可以在Excel中利用数字“1”来代表男生,“0”代表女生来进行处理,但如果类别多了之后我们该如何在Python中进行处理呢?本节就主要介绍两种常见的非数值类型的数据处理:Get_dummies哑变量处理以及Label Encoding编号处理。

(1)Get_dummies哑变量处理

       哑变量也叫虚拟变量,构造取值为01的变量,上面提到的将性别中的“男”和“女”换成数字“1”和“0”就是哑变量最经典的应用,而在Python中我们通常利用get_dummies()函数来进行哑变量处理,它不仅可以处理“男”和“女”这种简单的只有两个分类的问题,还可以解决含有多个分类问题

1、简单示例:“男”和“女”的数值转换
利用pandas 创建 DataFrame 相关知识点创建数据,代码如下
import pandas as pd
df = pd.DataFrame({'客户编号': [1, 2, 3], '性别': ['男', '女', '男']})
df

       此时获得的表格如下所示,其中性别栏中为“男”和“女”两个文字类型的数据

df = pd.get_dummies(df, columns=['性别'])
df

        此时我们便可以通过get_dummies()函数来对文本类型的数据进行处理,代码如下,其中get_dummies()函数中的第一个参数为表格名称,第二个参数为需要处理的列的名称。

df = df.drop(columns='性别_女') 

       此时获得的新的df表格如下所示,可以看到原来的“性别”列变成了两列:“性别_女”和“性别_男”,这两列中的数字“1”表示的是符合列名,数字“0”表示的是不符合列名,例如用户2为女性,所以在“性别_女”这一列中的数字就是1,在“性别_男”这一列中的数字就是0

       此时我们已经将“男”和“女”这两个文本类型的数据转换成了数字了,不过此时我们还需要再做一个工作:删去其中一列,这是因为“性别_女”和“性别_男”这两列存在多重共线性,即知道其中一列,就能知道另一列的内容,可以通过如下的数学表达式来表达:

       这样会导致多重共线性带来的一系列问题,因此通过drop()函数删去其中一列并通过rename()函数更换列名,代码如下:

df = df.drop(columns='性别_女') 
df = df.rename(columns={'性别_男':'性别'})
2、复杂点的案例:房屋朝向的数值转换

       上面演示是只有两个类别的文本数据,这里我们再演示一个稍复杂点的案例

1. 房屋 朝向的数值转换:在给房屋定价时,一个很重要的指标是房屋的朝向,因此我们选择该指标构造哑变量
2. 使用 Pandas 库中的 get_dummies () 方法构造哑 变量。
3. 因为存在多重共线性(即通过三个朝向,我们就能判断第四个朝向的数值为 0 和还是 1 ,我们 需要将新构造出来的这 4 个哑变量删去一个,假设我们 删去 朝向 _ 西 列。
import pandas as pd
df = pd.DataFrame({'房屋编号': [1, 2, 3, 4, 5], '朝向': ['东', '南', '西', '北', '南']})
df = pd.get_dummies(df, columns=['朝向'])
df = df.drop(columns='朝向_西') 
3、Label Encoding编号处理

       下面用Python简单演示如何对DataFrame中城市的这列分类变量进行转化,演示代码如下:

import pandas as pd
df = pd.DataFrame({'编号': [1, 2, 3, 4, 5], '城市': ['北京', '上海', '广州', '深圳', '北京']})
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
label = le.fit_transform(df['城市'])
df['城市'] = label

       "北京"被转化成数值1"上海"转化成数值0"广州"转化成数值2"深圳"转化成数值3, df显示结果如下:

4、pandas库中的replace()函数

       Label Encoding生成的数字是随机的,如果想按特定内容进行替换,可以采用replace()函数,其实这个对于建模效果不会有太大的影响,这里作为一个补充知识点给感兴趣的读者讲解一下,数据还是用之前演示的数据。

       通过value_counts()函数查看该列有哪些内容需要替换,代码如下

df['城市'].value_counts()

       value_counts()函数可以统计非重复项出现的次数,因此也可以方便的看到非重复内容,几个如下:

       需要替换的就是“北京”、“上海”、“深圳”、“广州”这4个词,通过replace()函数进行替换,这里我们按“北上广深”的顺序来进行数字编码,代码如下:

df['城市'] = df['城市'].replace({'北京': 0, '上海': 1, '广州': 2, '深圳':3})
df

2、重复值、缺失值及异常值处理

       这里首先创建一个含有重复值的DataFrame,代码如下

data = pd.DataFrame([[1, 2, 3], [1, 2, 3], [4, 5, 6]], columns=['c1', 'c2', 'c3'])

       此时的data二维列表如下所示,可以看到第一行和第二行是重复的。

       如果数据量较大,我们可以通过duplicated()函数来查询重复的内容,代码如下

data[data.duplicated()]

       将其打印输出,结果如下,可以看到它已经将重复的第二行筛选出来了。

       想统计重复行的数量,可以通过sum()函数进行查看,代码如下,本案例结果为1

data.duplicated().sum()

       通过drop_duplicates()函数删除重复行,代码如下

data = data.drop_duplicates()

       如果想按列进行去重,比如说如果c1列出现相同的内容,就把那行代码删掉,可以采用如下代码。这样的筛选条件则不如之前要全部一样才删除严格。

data = data.drop_duplicates('c1')

      注意:drop_duplicates()函数并不改变原表格结构,所以需要进行重新赋值,或者在其中设置inplace参数为True。运行结果如下,此时已经将重复的那行删去了。

       这里先构造一个含有缺失值的DataFrame,代码如下

import numpy as np
data = pd.DataFrame([[1, np.nan, 3], [np.nan, 2, np.nan], [1, np.nan, 0]], columns=['c1', 'c2', 'c3'])

       这里引用Numpy库来构造缺失值或者说空值,其中np.nan就是代表空值,运行效果如下:

       可以用isnull()函数或isna()函数(两者作用类似)来查看空值,代码如下

data.isnull()

       打印输出如下,isnull()其实就是询问是否是空值,是空值就被赋予成True,否则为False

       单列查看缺失值情况,代码如下

data[data['c1'].isnull()]

       输出结果如下,可以看到对于c1列,第二行的内容是空值。

0    False
1     True
2    False
Name: c1, dtype: bool

       对于空值有两种常见的处理方式:删除空值填补空值。通过dropna()函数可以删除空值,代码如下

data[data['c1'].isnull()]

       这种写法是只要含有空值,该行就会被删除。运行结果如下,因为每行都有空值,所以都被删除了。

       如果觉得该删除方法过于激进,可以设置thresh参数,比如将其设置为n,那么其含义是如果该行的非空值少于n个则删除该行,演示代码如下

a = data.dropna(thresh=2)

       运行结果如下:

       通过fillna()函数可以填补空值,这里采用的是均值填充法,通过每一列的均值对该列的空值进行填充,也可以把其中的data.mean()换成data.meian()则变为中位数填充。

b = data.fillna(data.mean())

       运行结果如下:

       此处method='pad'代表用缺失值所在列的前一个值填充,如果前一个值不存在或也缺失,则结果不变。运行结果如下:

c = data.fillna(method='pad')

       运行结果如下:

       fillna()函数还可以设置参数limit来限制每列能替换的缺失值个数。若使用limit=1,用法如下:

d = data.fillna(method='backfill')
e = data.fillna(method='bfill')

       这里先构造一个含有异常值的数据集

data = pd.DataFrame({'c1': [3, 10, 5, 7, 1, 9, 69], 'c2': [15, 16, 14, 100, 19, 11, 8], 'c3': [20, 15, 18, 21, 120, 27, 29]}, columns=['c1', 'c2', 'c3'])
data

       运行效果如下:

       以下的数据框为例,可以看到第一列的数字69,第二列的数字100,第三列的数字120为比较明显的异常值,那么该如何利用Python来进行异常值的检测呢?下面我们主要通过两种方法来进行检测:利用箱体图观察和利用标准差检测

       箱型图是一种用作显示一组数据分散情况资料的统计图,可以通过设定标准将大于或小于箱型图上下界的数值识别为异常值。

将数据的下四分位数记作 Q1 ,即样本中仅有 25% 的数据小于 Q1
数据的上四分位数记作 Q3 ,即样本中仅有 25% 的数据大于 Q3
上四分位数和下四分位数的差值记作 IQR ,即 IQR=Q3-Q1 ;令箱型图上界为 Q3+1.5*IQR ,下界为 Q1-1.5*IQR

       在Python中我们可以通过DataFrameboxplot()方法绘制箱型图,代码如下:

data.boxplot() 

       当数据服从正态分布时,99%的数值应该位于距离均值3个标准差之内的距离,95%的数值应该位于距离均值2个标准差之内的距离。因为3个标准差过于严格,此处我们将阈值设定为2即可,即认为当数值与均值距离超出2个标准差,则可以认为它是异常值。

       根据标准差检测异常值的代码如下

a = pd.DataFrame()
for i in data.columns:
    z = (data[i] - data[i].mean()) / data[i].std()
    a[i] = abs(z) > 2

       第4行代码进行逻辑判断,如果Z-score后的数值大于标准正态分布的标准差12倍,那么改数据为异常值,返回布尔值True,否则返回布尔值False

3、数据标准化

(1)min-max标准化

       min-max标准化(Min-Max Normalization)也称离差标准化,它利用原始数据的最大最小值把原始数据转换到[0,1]区间内,转换函数如下

       min-max标准化代码:

import pandas as pd
X = pd.DataFrame({'酒精含量(%)': [50, 60, 40, 80, 90], '苹果酸含量(%)': [2, 1, 1, 3, 2]})
y = [0, 0, 0, 1, 1]
from sklearn.preprocessing import MinMaxScaler
X_new = MinMaxScaler().fit_transform(X)

(2)Z-score标准化

       Z-score标准化(mean normaliztion)也称均值归一化,通过原始数据的均值(mean)和标准差(standard deviation)对数据进行归一化。归一化后的数据符合标准正态分布,即均值为0,标准差为1。转化函数为: 

       Z-score标准化标准化代码:

from sklearn.preprocessing import StandardScaler
X_new = StandardScaler().fit_transform(X)

4、数据分箱

       实际应用中等宽分箱应用相对较多,下面便讲解如何在Python中根据年龄进行等宽分箱

import pandas as pd
data = pd.DataFrame([[22,1],[25,1],[20,0],[35,0],[32,1],[38,0],[50,0],[46,1]], columns=['年龄', '是否违约'])
data_cut = pd.cut(data['年龄'], 3)
print(data_cut)

       此时的data_cut如下所示

1    (19.97, 30.0]
2    (19.97, 30.0]
3     (30.0, 40.0]
4     (30.0, 40.0]
5     (30.0, 40.0]
6     (40.0, 50.0]
7     (40.0, 50.0]
Name: 年龄, dtype: category
Categories (3, interval[float64]): [(19.97, 30.0] < (30.0, 40.0] < (40.0, 50.0]]

       通过groupby()函数进行分组,count()函数进行计数可以获取每个分箱中的样本数目,代码如下

data['年龄'].groupby(data_cut).count()

       其打印结果如下所示:

年龄
(19.97, 30.0]    3
(30.0, 40.0]     3
(40.0, 50.0]     2
Name: 年龄, dtype: int64

5、特征筛选:WOE值与IV

(1)WOE值的定义

       WOE的全称是“Weight of Evidence”,即证据权重,其反映了某一特征的特征区分度,要计算一个变量的WOE值,需要首先把这个变量进行11.4节提到的分箱处理。分箱后,对于第i组分箱内的数据,该分箱中的WOE值的计算公式如下

       演示数据如下:

       对上面的数据进行分箱,并分别计算每个分箱中对应的WOEi值,最终整理表格如下表所示:

       这里简单说明下第一个分箱中的计算过程,计算过程如下图所示,对于年龄在20-30之间的人来说:

(2)IV值的定义与演示

       在进行特征筛选的时候,IV值能较好的反应特征变量的预测能力,特征变量对于预测结果做出的贡献越大,它的价值就越大,相对应的IV值越大,因此根据IV值的大小,我们便能筛选出所需要的特征变量。在计算特征变量的IV值前,需要首先计算各个分箱的IV值,各个分箱的IV值的计算公式如下所示

       使用上一节用到的年龄和违约的相关数据,我们先来计算各个分箱中的IV值,如下所示:

       各个分箱的IV值后,我们就可以“年龄”这一特征变量的IV值,如下所示

(3)WOE值与IV值的代码实现

1、数据分箱
import pandas as pd
data = pd.DataFrame([[22,1],[25,1],[20,0],[35,0],[32,1],[38,0],[50,0],[46,1]], columns=['年龄', '是否违约'])
data_cut = pd.cut(data['年龄'], 3)
data_cut
2、统计各个分箱样本总数、坏样本数和好样本数

       过如下代码可以统计分箱后各个分箱中的人数情况:

# 统计总客户数
cut_group_all = data['是否违约'].groupby(data_cut).count()
# 统计违约客户
cut_y = data['是否违约'].groupby(data_cut).sum()
# 统计未违约客户
cut_n = cut_group_all - cut_y
df = pd.DataFrame()  # 创建一个空DataFrame用来汇总数据
df['总数'] = cut_group_all
df['坏样本'] = cut_y
df['好样本'] = cut_n
df

       此时获取的df如下图所示:

3、统计各分箱中坏样本比率和好样本比率

       计算各分箱中坏样本比率和好样本比率:

df['坏样本%'] = df['坏样本'] / df['坏样本'].sum()
df['好样本%'] = df['好样本'] / df['好样本'].sum()
df

4、计算WOE

       算WOE值了,代码如下

import numpy as np
df['WOE'] = np.log(df['坏样本%'] / df['好样本%'])
df

       这时我们加了第三行是应为我们不希望WOE值出现无穷大解决办法是当WOE值为无穷大时,将它替换为0

5、计算IV

       算IV值,代码如下

df['IV'] = df['WOE'] * (df['坏样本%'] - df['好样本%'])

       获得的df如下所示:

       我们就可以通过公式计算“年龄”这一特征变量的IV值,代码如下

iv = df['IV'].sum()
(4)案例:客户流失预警模型的IV计算

       为了更具有通用性,我们将上一节的代码稍作改变,写成如下函数的形式,该函数共有4个参数:data(原始数据集)、cut_num(数据分箱步骤中,需要分箱的个数)、feature(需要计算IV值的特征变量名称)、target(目标变量名称),有了这个函数之后,任意一个数据集,我们都能够方便地计算各个数据集的特征变量的IV值了。

import pandas as pd
import numpy as np

def cal_iv(data, cut_num, feature, target):
    # 1.数据分箱
    data_cut = pd.cut(data[feature], cut_num)

    # 2.统计各个分箱样本总数、坏样本数和好样本数
    cut_group_all = data[target].groupby(data_cut).count()  # 总客户数
    cut_y = data[target].groupby(data_cut).sum()  # 坏样本数
    cut_n = cut_group_all - cut_y  # 好样本数
    # 汇总基础数据
    df = pd.DataFrame()  # 创建一个空DataFrame用来汇总数据
    df['总数'] = cut_group_all
    df['坏样本'] = cut_y
    df['好样本'] = cut_n

    # 3.统计坏样本%和好样本%
    df['坏样本%'] = df['坏样本'] / df['坏样本'].sum()
    df['好样本%'] = df['好样本'] / df['好样本'].sum()

    # 4.计算WOE值
    df['WOE'] = np.log(df['坏样本%'] / df['好样本%'])
    df = df.replace({'WOE': {np.inf: 0, -np.inf: 0}}) 

    # 5.计算各个分箱的IV值
    df['IV'] = df['WOE'] * (df['坏样本%'] - df['好样本%'])

    # 6.汇总各个分箱的IV值,获得特征变量的IV值
    iv = df['IV'].sum()
    
    print(iv)

       有了上面的自动计算IV值的函数后,通过如下代码来读取客户流失预警模型中的相关数据

data = pd.read_excel('股票客户流失.xlsx')

       此时的data如下图所示,其中“是否流失”列为目标变量,其余列则为特征变量:

       通过for循环,我们可以快速获得所有特征变量的IV值,代码如下:

for i in data.columns[:-1]:
    print(i + '的IV值为:')
    cal_iv(data, 4, i, '是否流失')

       最终打印结果如下所示:

账户资金(元)的IV值为:
0.15205722409339645
最后一次交易距今时间(天)的IV值为:
0.2508468300174099
上月交易佣金(元)的IV值为:
0.30811632146662304
本券商使用时长(年)的IV值为:
0.6144219248359752

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值