本文代码及数据集来自《Python大数据分析与机器学习商业案例实战》
一、非数值类型数据处理
Get_dummies哑变量处理
哑变量也叫虚拟变量,通常取值为0或1,上面提到的将性别中的“男”和“女”分别转换成数字1和0就是哑变量最经典的应用。在Python中,通常利用pandas库中的get_dummies()函数进行哑变量处理,它不仅可以处理“男”和“女”这种只有两个分类的简单问题,还可以处理含有多个分类的问题。
# 非数值类型数据处理
# Get_dummies哑变量处理
import pandas as pd
df = pd.DataFrame({'房屋编号': [1, 2, 3, 4, 5], '朝向': ['东', '南', '西', '北', '南']})
print(df)
运行结果:
df = pd.get_dummies(df, columns=['朝向'])
print(df)
运行结果:
上表存在多重共线性(即根据3个朝向的数字就能判断第4个朝向的数字是0还是1),因此需要从新构造出来的4个哑变量中删去1个,假设删去“朝向_西”列,代码如下。
df = df.drop(columns='朝向_西')
print(df)
运行结果:
举例:转换三个分类变量并添加至dataframe中。
a = pd.get_dummies(df_heart['cp'], prefix = "cp")
b = pd.get_dummies(df_heart['thal'], prefix = "thal")
c = pd.get_dummies(df_heart['slope'], prefix = "slope")
frames = [df, a, b, c]
df = pd.concat(frames, axis = 1)
df = df.drop(columns = ['cp', 'thal', 'slope'])
df.head()
Label Encoding编号处理
# Label Encoding编号处理
import pandas as pd
df = pd.DataFrame({'编号': [1, 2, 3, 4, 5], '城市': ['北京', '上海', '广州', '深圳', '北京']})
print(df)
运行结果:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
label = le.fit_transform(df['城市'])
print(label)
运行结果:[1 0 2 3 1]
可以看到,“北京”被转换成数字1,“上海”被转换成数字0,“广州”被转换成数字2,“深圳”被转换成数字3。通过如下代码可以用转换结果替换原来的列内容。
df['城市'] = label
print(df)
运行结果:
LabelEncoder()函数生成的数字是随机的,如果想按特定内容进行替换,可以使用replace()函数。
df['城市'].value_counts()
在使用replace()函数之前,先利用value_counts()函数查看“城市”列有哪些内容需要替换(因为有时数据量很大,通过人眼判断可能会遗漏某些内容)。
运行结果:
从上述结果可知,需要替换的是“北京”“上海”“深圳”“广州”这4个词。这里用replace()函数按“北上广深”的顺序进行数字编号。
df['城市'] = df['城市'].replace({'北京': 0, '上海': 1, '广州': 2, '深圳':3})
print(df)
运行结果:
二、重复值、缺失值及异常值处理
重复值
# 重复值处理
import pandas as pd
data = pd.DataFrame([[1, 2, 3], [1, 2, 3], [4, 5, 6]], columns=['c1', 'c2', 'c3'])
print(data)
#用duplicated()函数来查询重复的内容
print(data[data.duplicated()])
#若要统计重复行的数量,可以用sum()函数,其结果为1
print(data.duplicated().sum())
#发现有重复行时,可以用drop_duplicates()函数删除重复行
data = data.drop_duplicates()
print(data)
注意,drop_duplicates()函数并不改变原表格结构,所以需要进行重新赋值,或者在其中设置inplace参数为True。运行结果如下,此时已删除重复行。
若要按列进行去重,例如,c1列出现重复的内容,就将重复内容所在的一整行删除,代码如下。
data = pd.DataFrame([[1, 2, 3], [1, 2, 3], [4, 5, 6]], columns=['c1', 'c2', 'c3'])
data = data.drop_duplicates('c1')
print(data)
缺失值
# 缺失值处理
import numpy as np
import pandas as pd
data = pd.DataFrame([[1, np.nan, 3], [np.nan, 2, np.nan], [1, np.nan, 0]], columns=['c1', 'c2', 'c3'])
print(data)
print(data.isnull())
isnull()函数的作用是判断是否是空值,若是空值就赋予True,否则赋予False。运行结果:
# 对单列查看空值
data['c1'].isnull()
# 如果数据量较大,可以通过如下代码筛选出某列中内容为空值的行
data[data['c1'].isnull()]
# 只要含有空值的行都会被删除
a = data.dropna()
print(a)
# 如果一行中的非空值少于2个则删除该行
a = data.dropna(thresh=2)
print(a)
# 均值填补法,data.median()中位数填补
b = data.fillna(data.mean())
print(b)
# 用空值上方的值替换空值,如果上方的值不存在或也为空值,则不替换
c = data.fillna(method='pad')
print(c)
# 用空值下方的值来替换空值,如果下方的值不存在或也为空值,则不替换
d = data.fillna(method='backfill')
# 或 e = data.fillna(method='bfill')
print(d)
异常值处理:
# 异常值处理
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'])
print(data)
# 1.利用箱型图观察
data.boxplot() # 画箱型图
运行结果:
# 2.利用标准差检测
a = pd.DataFrame()
for i in data.columns:
z = (data[i] - data[i].mean()) / data[i].std()
a[i] = abs(z) > 2
print(a)
用mean()函数(获取均值)和std()函数(获取标准差)将每列数据进行Z-score标准化,运行结果:
三、数据标准化
对于以特征距离为算法基础的机器学习算法(如K近邻算法),数据标准化尤为重要。数据标准化有两种方法——min-max标准化及Z-score标准化。
- min-max标准化(Min-Max Normalization)也称离差标准化,它利用原始数据的最大值和最小值把原始数据转换到[0,1]区间内;
- Z-score标准化(Mean Normaliztion)也称均值归一化,通过原始数据的均值(mean)和标准差(standard deviation)对数据进行标准化。标准化后的数据符合标准正态分布,即均值为0,标准差为1。
# 数据标准化
import pandas as pd
X = pd.DataFrame({'酒精含量(%)': [50, 60, 40, 80, 90], '苹果酸含量(%)': [2, 1, 1, 3, 2]})
y = [0, 0, 0, 1, 1]
print(X) # 查看X
# min-max标准化
from sklearn.preprocessing import MinMaxScaler
X_new = MinMaxScaler().fit_transform(X)
print(X_new) # 查看X_new
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_new, y, test_size=0.2, random_state=123)
# Z-score标准化
from sklearn.preprocessing import StandardScaler
X_new = StandardScaler().fit_transform(X)
print(X_new) # 查看X_new
对于树模型则无须做数据标准化处理,因为数值缩放不影响分裂点位置,对树模型的结构不造成影响。因此,决策树模型及基于决策树模型的随机森林模型、AdaBoost模型、GBDT模型、XGBoost模型、LightGBM模型通常都不需要进行数据标准化处理,因为它们不关心变量的值,而是关心变量的分布和变量之间的条件概率。
四、数据分箱与特征筛选
数据分箱就是将一个连续型变量离散化,可分为等宽分箱和等深分箱。
- 等宽分箱是指每个分箱的差值相等,以“年龄”这一连续型特征变量为例,其取值范围为0~100的连续数值,可以将“年龄”分为0~20、20~40、40~60、60~80、80~100共5个分箱,这5个分箱就可以当成离散的分类变量,每个分箱的年龄差相等(都相差20岁)。
- 等深分箱是指每个分箱中的样本数一致,同样按“年龄”这一特征变量进行分箱,例如,500个样本分成5箱,那么每个分箱中都是100人,此时对应的5个分箱可能就是0~20、20~25、25~30、30~50、50~100,确保每个分箱中的人数一致。
# 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=['年龄', '是否违约'])
print(data)
# 对“年龄”这一特征变量进行等宽分箱
data_cut = pd.cut(data['年龄'], 3)
print(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
print(cut_group_all)
df = pd.DataFrame() # 创建一个空DataFrame用来汇总数据
df['总数'] = cut_group_all
df['坏样本'] = cut_y
df['好样本'] = cut_n
print(df)
运行结果:
挑选入模变量需要考虑很多因素,其中最主要的衡量标准是变量的预测能力,对分类模型来说,即希望变量具有较好的特征区分度,可以较准确地将样本进行分类。对于决策树等树模型来说,可以通过基尼系数或信息增益来衡量变量的特征区分度,而对于逻辑回归等没有基尼系数等指标的模型而言,可以通过WOE值和IV值进行变量选择。
# 3.统计各分箱中坏样本比率和好样本比率
df['坏样本%'] = df['坏样本'] / df['坏样本'].sum()
df['好样本%'] = df['好样本'] / df['好样本'].sum()
print(df)
# 4.计算WOE值
import numpy as np
df['WOE'] = np.log(df['坏样本%'] / df['好样本%'])
print(df)
df = df.replace({'WOE': {np.inf: 0, -np.inf: 0}}) #将无穷大替换为0
# 5.计算IV值
df['IV'] = df['WOE'] * (df['坏样本%'] - df['好样本%'])
print(df)
iv = df['IV'].sum()
print(iv)
运行结果:
实战案例:
# 案例实战:客户流失预警模型的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)
data = pd.read_excel('股票客户流失.xlsx')
data.head()
cal_iv(data, 4, '账户资金(元)', '是否流失')
for i in data.columns[:-1]:
print(i + '的IV值为:')
cal_iv(data, 4, i, '是否流失') # 调用函数
第1个参数data就是刚刚读取的股票客户流失数据;设置第2个参数cut_num为4,即将数据分为4箱;设置第3个参数feature为’账户资金(元)’,即要计算IV值的特征变量;设置第4个参数target为’是否流失’,即原始表格中的目标变量。通过for循环可以快速计算出所有特征变量的IV值,运行结果:
可得出结论:“本券商使用时长(年)”的信息量最大,而“账户资金(元)”的信息量最小,预测能力最低。
五、多重共线性的分析与处理
对多元线性回归模型Y=k0+k1X1+k2X2+…+knXn而言,如果特征变量X1、X2、X3…之间存在高度线性相关关系,则称为多重共线性(multicollinearity)。例如,X1=1-X2,此时X1与X2存在高度的线性相关关系,则认为该模型存在多重共线性,需要删去X1和X2中的一个变量。
在实际应用中,多重共线性会带来如下不利影响:线性回归估计式变得不确定或不精确;线性回归估计式方差变得很大,标准误差增大;当多重共线性严重时,甚至可能使估计的回归系数符号相反,得出错误的结论;削弱特征变量的特征重要性。
这里主要讲解两种判别方法——相关系数判断和方差膨胀系数法(VIF检验)。
# 多重共线性的分析与处理
import pandas as pd
df = pd.read_excel('数据.xlsx')
df.head()
X = df.drop(columns='Y')
Y = df['Y']
# 1.相关系数判断
print(X.corr())
运行结果:
第1行第2列的相关系数0.99,表示的就是特征变量X1和特征变量X2的相关系数,可以看到它们的相关性还是非常强的,有理由相信它们会导致多重共线性,因此需要删去其中一个特征变量。
相关系数判断使用起来非常简单,结论也比较清晰,不过它有一个缺点:简单相关系数只是多重共线性的充分条件,不是必要条件。在有多个特征变量时,相关系数较小的特征变量间也可能存在较严重的多重共线性。为了更加严谨,实战中还经常用到下面要讲解的方差膨胀系数法(VIF检验)。
# 2.方差膨胀因子法(VIF检验)
from statsmodels.stats.outliers_influence import variance_inflation_factor
vif = [variance_inflation_factor(X.values, X.columns.get_loc(i)) for i in X.columns]
print(vif)
vif = []
for i in X.columns: # i对应的是每一列的列名
vif.append(variance_inflation_factor(X.values, X.columns.get_loc(i)))
print(vif)
运行结果都为[259.6430487184967, 257.6315718292196, 1.302330632715429]。从上述计算结果也可以看出,前2个VIF值均大于100,暗示多重共线性十分严重,应该删掉X1或X2。下面删掉X2再进行一次回归和VIF检验,看看结果的变化。
X = df[['X1', 'X3']]
Y = df['Y']
from statsmodels.stats.outliers_influence import variance_inflation_factor
vif = [variance_inflation_factor(X.values, X.columns.get_loc(i)) for i in X.columns]
print(vif)
运行结果为[1.289349054516766, 1.289349054516766],不存在多重共线性。
六、过采样和欠采样
建立模型时,可能会遇到正负样本比例极度不均衡的情况。例如,建立信用违约模型时,违约样本的比例远小于不违约样本的比例,此时模型会花更多精力去拟合不违约样本,但实际上找出违约样本更为重要。这会导致模型可能在训练集上表现良好,但测试时表现不佳。为了改善样本比例不均衡的问题,可以使用过采样和欠采样的方法。
过采样
过采样的方法有随机过采样和SMOTE法过采样。
-
随机过采样
随机过采样是从100个违约样本中随机抽取旧样本作为一个新样本,共反复抽取900次,然后和原来的100个旧样本组合成新的1000个违约样本,和1000个不违约样本一起构成新的训练集。因为随机过采样重复地选取了违约样本,所以有可能造成对违约样本的过拟合。 -
SMOTE法过采样
SMOTE法过采样即合成少数类过采样技术,它是一种针对随机过采样容易导致过拟合问题的改进方案。假设对少数类进行4倍过采样,通过下图来讲解SMOTE法的原理。
# 过采样
import pandas as pd
data = pd.read_excel("信用卡数据.xlsx")
data.head()
X = data.drop(columns='分类')
y = data['分类']
from collections import Counter
Counter(y) #对目标变量进行计数
# 不违约样本数为1000,远远大于违约样本数100
# (1)随机过采样
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=0)
X_oversampled, y_oversampled = ros.fit_resample(X, y)
print(Counter(y_oversampled))
# 违约样本数从100上升至1000,与不违约样本数相同,证明随机过采样有效。
# (2)SMOTE过采样
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=0)
X_smotesampled, y_smotesampled = smote.fit_resample(X, y)
print(Counter(y_smotesampled))
运行结果都为:Counter({0: 1000, 1: 1000})
欠采样
欠采样是从1000个不违约样本中随机选取100个样本,和100个违约样本一起构成新的训练集。欠采样抛弃了大部分不违约样本,在搭建模型时有可能产生欠拟合。
# 欠采样
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0)
X_undersampled, y_undersampled = rus.fit_resample(X, y)
print(Counter(y_undersampled))
运行结果:Counter({0: 100, 1: 100})
在实战中处理样本不均衡问题时,如果样本数据量不大,通常使用过采样,因为这样能更好地利用数据,不会像欠采样那样很多数据都没有使用到;如果数据量充足,则过采样和欠采样都可以考虑使用。