目录
1、非数值类型数据处理
机器学习建模时处理的都是数值类型的数据,然而实际工作中我们获取的数据往往或有非数值类型的数据,其中最常见的就是文本类型的数据,例如性别中的“男”和“女”,这个可以在Excel中利用数字“1”来代表男生,“0”代表女生来进行处理,但如果类别多了之后我们该如何在Python中进行处理呢?本节就主要介绍两种常见的非数值类型的数据处理:Get_dummies哑变量处理以及Label Encoding编号处理。
(1)Get_dummies哑变量处理
哑变量也叫虚拟变量,构造取值为0或1的变量,上面提到的将性别中的“男”和“女”换成数字“1”和“0”就是哑变量最经典的应用,而在Python中我们通常利用get_dummies()函数来进行哑变量处理,它不仅可以处理“男”和“女”这种简单的只有两个分类的问题,还可以解决含有多个分类问题。
1、简单示例:“男”和“女”的数值转换
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、稍复杂点的案例:房屋朝向的数值转换
上面演示是只有两个类别的文本数据,这里我们再演示一个稍复杂点的案例:
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来进行异常值的检测呢?下面我们主要通过两种方法来进行检测:利用箱体图观察和利用标准差检测。
箱型图是一种用作显示一组数据分散情况资料的统计图,可以通过设定标准将大于或小于箱型图上下界的数值识别为异常值。
在Python中我们可以通过DataFrame的boxplot()方法绘制箱型图,代码如下:
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后的数值大于标准正态分布的标准差1的2倍,那么改数据为异常值,返回布尔值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