大数据预处理学习笔记03——信用卡数据集

《大数据预处理:基于Python的应用》学习笔记,信用卡数据集和书中用的数据集是一样的,可以在Kaggle获取。本数据集显示了2013年9月的某两天在欧洲的持卡人通过信用卡进行的交易。

数据集概览

导入需要的包

import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt
import time
import copy
from pandas.api.types import is_float_dtype
from sklearn.cluster import KMeans
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import mean_squared_error,roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from scipy import stats
from scipy.stats import pearsonr,spearmanr,f_oneway
from sklearn.linear_model import Lasso

导入数据包 

credit=pd.read_csv('/Users/lin/Desktop/dataset/creditcard.csv',header=0,encoding="utf8")
credit.head()

msno.matrix(credit)

可以看到这个数据集的数据质量很好,没有缺失值。

异常值

异常值有两种情况:一是数据出现了错误,二是数据是准确的,但是却是异常的大或小。异常值的处理对第一种情况应当是对错误的数据进行改正或删除,对第二种情况,要视研究的目的而定,如果研究的目标是数据的统计规律,可以考虑删除异常值,如果研究的目的是聚焦于这些异常情况,则需要对异常值进行标注,并进一步研究。

异常值的检测与处理

#识别与观察异常值
credit.V4.plot.box()
plt.show()#对信用卡数据V4画箱形图,可以看到存在若干异常值
v4_mean=credit.V4.mean()#计算V4的平均值
v4_std=credit.V4.std()#计算V4的标准差
print("均值为:%f" % v4_mean)
print("标准差为:%f" % v4_std)
#这里使用 credit.V4.between 方法来判断 V4 列中的数值是否在均值加减 3 倍标准差的范围内,~ 表示取反,sum() 统计不在这个范围内的样本数量。
print("超过3倍标准差样本量:%d" % (~credit.V4.between(
    v4_mean-3*v4_std,
    v4_mean+3*v4_std)).sum())#使用3倍标准差识别异常值
print("超过5倍标准差样本量:%d" % (~credit.V4.between(
    v4_mean-5*v4_std,
    v4_mean+5*v4_std)).sum())#使用5倍标准差识别异常值

从结果来看,V4超过3倍标准差的样本量有3094个,异常值较多。下一步对异常值进行标注,建立新的变量V4_outlier_3 和V4_outlier_5 。令不属于异常值的样本在新变量的对应值为0,大于均值的异常值样本在新变量的对应值为1,小于均值的异常值样本在新变量的对应值为-1.

#对异常值标记
V4_s=(credit.V4-v4_mean)/v4_std#对 V4 列进行标准化处理,将每个元素减去平均值,然后除以标准差,得到标准差单位化的结果。
V4_outlier_3=0*V4_s#创建一个和标准化后的 V4 列相同大小的全零 Series,用于存储 3 倍标准差范围内的异常值的标记。
V4_outlier_3[V4_s.gt(3)]=1#将标准化后的 V4 列中超过 3 倍标准差的值标记为 1。
V4_outlier_3[V4_s.lt(-3)]=-1#将标准化后的 V4 列中低于 -3 倍标准差的值标记为 -1。
V4_outlier_5=0*V4_s#创建一个和标准化后的 V4 列相同大小的全零 Series,用于存储 5 倍标准差范围内的异常值的标记。
V4_outlier_5[V4_s.gt(5)]=1#将标准化后的 V4 列中超过 5 倍标准差的值标记为 1。
V4_outlier_5[V4_s.lt(-5)]=-1#将标准化后的 V4 列中低于 -5 倍标准差的值标记为 -1。
#统计每个标记的数量,输出异常值分类计数的结果。标记 1 表示超过阈值,-1 表示低于阈值,0 表示在阈值范围内
print("3倍标准差异常值分类计数:\n%s" % V4_outlier_3.value_counts())
print("5倍标准差异常值分类计数:\n%s" % V4_outlier_5.value_counts())

异常值的处理 

截断处理:用异常值边界replace异常值

比如在本例中:用大于V4均值➕5倍标准差的异常值赋值为V4均值➕5倍标准差

用小于V4均值-5倍标准差的异常值赋值为V4均值-5倍标准差

#异常值的截断处理
#将变量V4与均值距离大于5倍标准差的赋值为5倍的标准差
V4_1=copy.deepcopy(credit.V4)
v4_mean1=V4_1.mean()#计算V4的均值
v4_std1=V4_1.std()#计算V4标准差
v4_max=v4_mean1+5*v4_std1#计算截断的上界,即均值加上 5 倍标准差。
v4_min=v4_mean1-5*v4_std1
V4_1[V4_1.gt(v4_mean1+5*v4_std1)]=v4_max#将 V4 列中大于上界的值截断为上界。
V4_1[V4_1.lt(v4_mean1-5*v4_std1)]=v4_min
V4_1.plot.box()
plt.show()#对信用卡数据V4画箱形图,可以看到若干离群点
print("截断后均值为:%f" % V4_1.mean())
print("截断后标准差为:%f" % V4_1.std())
print("超过3倍标准差样本量:%d" % (~V4_1.between(
   v4_mean1-3*v4_std1,
   v4_mean1+3*v4_std1)).sum())
print("超过5倍标准差样本量:%d" % (~V4_1.between(
   v4_mean1-5*v4_std1,
   v4_mean1+5*v4_std1)).sum())

可以从结果看到超过五倍标准差的样本量为0了。 

异常值对数据分析的影响 

分别用未做截断处理V4异常值的数据,和做了截断处理V4异常值的数据做Logistic回归模型,然后比较模型的AUC。

#未处理异常值时
#首先切分训练集和测试集
train,test=train_test_split(credit,test_size=0.3,random_state=1,stratify=credit["Class"])
train.V4.plot.box()#绘制训练集的箱形图
plt.show()
test.V4.plot.box()
plt.show()
train_mean=train.V4.mean()
train_std=train.V4.std()
test_mean=test.V4.mean()
test_std=test.V4.std()
print("训练集中超过3倍标准差样本量%d" % (~train.V4.between(
    v4_mean1-3*v4_std1,
    v4_mean1+3*v4_std1)).sum())
print("训练集中超过5倍标准差样本量%d" % (~train.V4.between(
    v4_mean1-5*v4_std1,
    v4_mean1+5*v4_std1)).sum())
print("测试集中超过3倍标准差样本量%d" % (~test.V4.between(
    v4_mean1-3*v4_std1,
    v4_mean1+3*v4_std1)).sum())
print("测试集中超过5倍标准差样本量%d" % (~test.V4.between(
    v4_mean1-5*v4_std1,
    v4_mean1+5*v4_std1)).sum())
model_4=LogisticRegression()
#使用未处理异常值的数据建立Logistic回归模型
model_4.fit(X=train.drop("Class",axis=1),y=train["Class"])
#这里使用测试数据(test)来评估模型性能。predict_proba 方法用于获取模型的概率预测,[:, 1] 表示取第二列,即预测为正例的概率。
#AUC 是 ROC 曲线下的面积,用于评估二分类模型的性能,其值越接近1表示性能越好。
print("未处理异常值时模型的AUC=%f" % roc_auc_score(y_true=test["Class"],y_score=model_4.predict_proba(test.drop("Class",axis=1))[:,1]))


#使用截断法处理异常值时
credit_1=copy.deepcopy(credit)
credit_1.V4=V4_1#将V4执行截断处理
train1,test1=train_test_split(credit_1,test_size=0.3,random_state=1,stratify=credit_1["Class"])
train1.V4.plot.box()
plt.show()
test1.V4.plot.box()
plt.show()
train1_mean=train1.V4.mean()
train1_std=train1.V4.std()
test1_mean=test1.V4.mean()
test1_std=test1.V4.std()
print("训练集中超过3倍标准差样本量%d" % (~train1.V4.between(
    v4_mean1-3*v4_std1,
    v4_mean1+3*v4_std1)).sum())
print("训练集中超过5倍标准差样本量%d" % (~train1.V4.between(
    v4_mean1-5*v4_std1,
    v4_mean1+5*v4_std1)).sum())
print("测试集中超过3倍标准差样本量%d" % (~test1.V4.between(
    v4_mean1-3*v4_std1,
    v4_mean1+3*v4_std1)).sum())
print("测试集中超过5倍标准差样本量%d" % (~test1.V4.between(
    v4_mean1-5*v4_std1,
    v4_mean1+5*v4_std1)).sum())
model_5=LogisticRegression()
model_5.fit(X=train1.drop("Class",axis=1),y=train1["Class"])
print("处理异常值后模型的AUC=%f" % roc_auc_score(y_true=test["Class"],y_score=model_5.predict_proba(test.drop("Class",axis=1))[:,1]))

在本例结果中没有看到AUC在处理后升高,但在书中的例子中有一些提高,代表处理了异常值后提高了模型的效果。

不平衡数据 

因变量为二分类变量的数据分析中,常会出现因变量类别间样本量差异较大的情况,称为不平衡数据。

#观察数据不平衡的情况
print("原始数据分类计数\n%s" % credit["Class"].value_counts())
print("正样本占比%f%%" % (credit["Class"].mean()*100))

可以看到在总共284807笔交易中,有492笔交易被标记为欺诈交易,属于典型的不平衡数据集。

不平衡数据集的配平

对于不平衡数据,处理的思路有两个:一是改变数据分布,从数据层面使分布更加平衡,二是改变分类算法,通过加权等手段时分类算法更加重视少数类提供的信息。这种方法在没有增加模型的复杂程度的情况下,仅仅是通过改变数据分布就解决了数据不平衡问题。

第一种思路有向下抽样,向上抽样,和混合抽样三种常用方法。

 向下抽样

向下抽样又称为欠采样,从多数类样本中随机抽取一部分与少数类样本共同构成训练集,使训练集中多数类与少数类的样本容量相当。向下抽样的缺陷是,为了照顾少数类的样本容量,需要放弃大量多数类的样本。

#建立RandomUnderSampler模型
rus=RandomUnderSampler(sampling_strategy=1,random_state=0)#创建一个 RandomUnderSampler 的实例。sampling_strategy=1 表示对两个类别(正类别和负类别)使用相同的抽样策略,random_state=0 设置了随机种子,以确保结果的可重复性。
#使用 fit_resample 方法对数据进行向下抽样。X 参数是特征数据(不包含目标变量 "Class"),y 参数是目标变量 "Class"。抽样后的结果存储在 x 和 y 中。
x,y=rus.fit_resample(X=credit.drop("Class",axis=1),y=credit["Class"])
#使用RandomUnderSampler向下抽样
#将抽样后的特征数据 x 和目标变量 y 合并成一个新的数据框,并设置列名为原始数据框 credit 的列名。
credit_u_s =pd.DataFrame(np.column_stack((x,y)),columns=credit.columns).astype(credit.dtypes)#将合并后的数据框的数据类型设置为与原始数据框 credit 相同的数据类型
print("向下抽样分类计数\n%s" % credit_u_s["Class"].value_counts())

 作者也自己编写了一个向下抽样的函数:

#定义向下抽样函数
#data: 要进行抽样的数据框。
#target_col: 目标变量的列名。
#balance_rate: 抽样比例,默认为1,表示不进行抽样。
#random_state: 随机种子,用于确保结果的可重复性。
def under_sampling(data:pd.DataFrame,target_col:str,balance_rate:(int,float)=1,random_state:int=None):
    major,minor=data[target_col].value_counts(sort=True,ascending=False).index# # 获取目标变量的主要和次要类别
    line_no=pd.Series(data[target_col].values,index=range(data.shape[0]))#创建一个 Series,其中包含每个样本的目标变量值,索引为样本编号
    minor_ln=line_no[line_no.eq(minor)].index# 获取次要类别的样本编号
    # 获取主要类别的样本编号,并根据抽样比例进行随机抽样
    major_ln=line_no[line_no.eq(major)]
    major_ln=major_ln.sample(n=int(minor_ln.size*balance_rate),random_state=random_state).index
    return data.iloc[minor_ln.append(major_ln),:]# 返回抽样后的数据框
#使用自定义函数对数据进行向下抽样:
credit_u_s=under_sampling(credit,target_col="Class",balance_rate=1)
print("自定义函数结果\n%s" % credit_u_s["Class"].value_counts())

 从执行结果来看,向下抽样后,1类和0类的样本量一样了,但放弃了多数类的大部分样本,总样本量大大减少。

向上抽样

向上采样又被称为过采样,其思想是提升少数类的样本容量,并与多数类样本共同组成训练集,最原始的向上抽样方法是随机过采样,即将随机选中的少数类中的样本直接复制以增加容量。这种方法容易造成过拟合。目前常用的向上抽样的方法是SMOTE算法:

(1)少数类中的每个样本以欧式距离为标准计算出自己到其他少数类样本的距离,从而得到k个近邻。

(2)每个样本从K个近邻中随机选择n个样本

(3)在每个样本和其被选中的近邻的连线上随机选择一个点作为新的样本点。

#建立SMOTE模型
smote=SMOTE(sampling_strategy=0.05,random_state=0)
x,y=smote.fit_resample(X=credit.drop("Class",axis=1),y=credit["Class"])
credit_o_s =pd.DataFrame(np.column_stack((x,y)),columns=credit.columns).astype(credit.dtypes)
print("向上抽样分类计数\n%s" % credit_o_s["Class"].value_counts())

混合抽样

向上抽样会产生大量人工样本,向下抽样会损失大量多数类样本,可以将两种方法混合起来使用。首先使用SMOTE算法进行向上抽样,得到数据集credit_o_s,然后对得到的数据集进行向下抽样,得到数据集credit_m_s,该数据集中两类样本容量相同。 

smote=SMOTE(sampling_strategy=0.05,random_state=0)
x,y=smote.fit_resample(X=credit.drop("Class",axis=1),y=credit["Class"])
credit_o_s =pd.DataFrame(np.column_stack((x,y)),columns=credit.columns).astype(credit.dtypes)
credit_m_s=under_sampling(credit_o_s,target_col="Class",balance_rate=1)
print("混合抽样分类计数\n%s" % credit_m_s["Class"].value_counts())

可以看到现在两类样本都是14215个。

不平衡数据集的影响:

书中使用了GBDT模型观察不平衡数据配平对建模的影响。

从三个方面观察影响:

(1)从模型训练时间个预测模型的AUC

(2)基于真实数据及与模型预测结果的偏离程度,对比使用平衡数据与不平衡数据模型训练效果的差异,并介绍校正偏离的方法。

(3)对不平衡数据进行随机多次配平,考察数据配平对与模型预测结果稳定性的影响。 

不平衡数据配平的效果
#分训练集和测试集
train,test=train_test_split(credit,test_size=0.3,random_state=0,stratify=credit["Class"])
#将训练集向下抽样,正负样本1:1
train_u_s=under_sampling(train,target_col="Class",balance_rate=1)
#使用全部不平衡的训练集训练GBDT模型
start=time.time()
model_ub=GradientBoostingClassifier()
model_ub.fit(X=train.drop("Class",axis=1),y=train["Class"])
print("不平衡模型耗时%s秒" % (time.time()-start))
#使用向下抽样的平衡训练集训练GBDT模型
start=time.time()
model_b=GradientBoostingClassifier()
model_b.fit(X=train_u_s.drop("Class",axis=1),y=train_u_s["Class"])
print("平衡模型耗时%s秒" % (time.time()-start))
#使用不平衡数据模型为测试集打分
model_ub_p=model_ub.predict_proba(test.drop("Class",axis=1))[:,1]
model_b_p=model_b.predict_proba(test.drop("Class",axis=1))[:,1]
print("不平衡模型AUC=%f" % roc_auc_score(y_true=test["Class"],y_score=model_ub_p))
print("不平衡模型AUC=%f" % roc_auc_score(y_true=test["Class"],y_score=model_b_p))

纠正:最后一行应该为平衡模型AUC 

模型预测结果的偏度及其校正方法
print("原始数据因变量平均值%f" % credit.Class.mean())
print("不平衡模型预测结果平均值%f" % model_ub_p.mean())
print("平衡模型预测结果平均值%f" % model_b_p.mean())

可以看到平衡模型的平均值是远高于原始和不平衡的,这表明数据配平改变了因变量比例,从而导致平衡模型的预测结果发生了漂移。预测结果的漂移意味着预测结果分布与实际结果分布存在较大差异。所以可以对配平结果进行校正,作者提出了一个校正方法:

rectify_model=LogisticRegression()#建立LogisticsRegression()函数,命名为rectify_model,用于对预测结果重新建立单调映射
#使用平衡模型model_b 对原始数据(不平衡数据)进行预测,得到的结果作为rectify_model的解释变量X
rectify_model.fit(X=model_b.predict_proba(train.drop("Class",axis=1))[:,[1]],y=train["Class"])
#model_b.predict_proba(train.drop("Class", axis=1))[:, [1]] 获取平衡模型对训练数据的预测概率中的第二列(即预测为正例的概率)作为解释变量 X。
#train["Class"] 是训练数据的目标变量。
#rectify_model.fit 用于拟合 Logistic 回归模型,以建立校正映射。
model_b_p_rectify=rectify_model.predict_proba(model_b_p.reshape(-1,1))[:,1]
#model_b_p.reshape(-1, 1) 将平衡模型的预测结果转换为二维数组。
#rectify_model.predict_proba 用于获取校正后的平衡模型对测试数据的预测概率,其中取第二列作为预测为正例的概率
print("校正后平衡模型预测结果平均值%f" % model_ub_p.mean())
print("平衡模型AUC=%f" % roc_auc_score(y_true=test["Class"],y_score=model_b_p_rectify))

 可以看到校正后的平均值为0.001484 和不平衡模型的平均值0.001484一样。且对AUC没有影响。

向下抽样对预测稳定性的影响

向下抽样会导致样本数量减少,损失样本的代表性。解决这一问题的思路是采用集成算法的思想,对数据集进行多次有放回的向下抽样,得到多个相互独立的平衡数据训练集,使用这些训练集训练出多个模型,并根据这些模型的预测结果得到最终的预测结果。

auc=pd.Series(index=range(100))#创建一个名为 auc 的 Pandas Series,用于存储每次训练迭代的 AUC 值,索引为训练迭代的次数(0 到 99)。
model_predicts=pd.DataFrame(index=test.index)#创建一个名为 model_predicts 的空数据框,用于存储每次模型的预测结果。
for i in range(100):
    train_u_s=under_sampling(train,target_col="Class",balance_rate=1,random_state=1)
    model_balance=GradientBoostingClassifier()
    model_balance.fit(X=train_u_s.drop("Class",axis=1),y=train_u_s["Class"])
    predict=model_balance.predict_proba(test.drop("Class",axis=1))[:,1]
    auc[i]=roc_auc_score(y_true=test["Class"],y_score=predict)
    model_predicts["model_%d" % i]=predict
#使用一个循环进行 100 次迭代:
#a. 使用 under_sampling 函数对训练数据进行向下抽样,保持类别平衡。
#b. 创建一个 GradientBoostingClassifier 模型,并使用向下抽样后的训练数据进行训练。
#c. 对测试数据进行预测,得到每个样本为正例的概率。
#d. 计算当前迭代的 AUC 值,并将其存储到 auc Series 中的相应索引位置。
#e. 将当前迭代的预测结果存储到 model_predicts 数据框中,列名为 model_i,其中 i 为当前迭代的编号

通过上述步骤,经过100次独立的向下抽样得到100个独立的平衡训练集,进而训练100个GBDT模型,使用这些模型对同一个测试集test进行预测,得到100 个模型的AUC。 

绘制100个模型的AUC变化图

auc.plot()
plt.xlabel("random seed")
plt.ylabel("auc")
plt.show()
print("AUC最大值=%f,AUC最小值=%f" % (auc.max(),auc.min()))
ensemble_predict=model_predicts.mean(axis=1)
print("ensemble模型AUC=%f" % roc_auc_score(y_true=test["Class"],y_score=ensemble_predict))

结果可以看出100个模型的AUC波动很大,在0.980041和0.980432之间波动,用计算均值的方式进行整合,即相当于100个模型共同给出最终结果,这样可以在一定程度上减少误差,整合后的结果为0.980211。 

数据归约(data reduction)

数据归约是指在尽量保持数据集原貌的前提下减少数据规模,从而提高运算效率。数据归约有两种形式:

一是纬度归约,减少数据的列(类似于机器学习的特征选择,特征工程)

二是数量归约,减少数据的行

为了更好的测试,重新导入了一下数据,做了向下抽样和数据集划分

#数据导入
credit=pd.read_csv('/Users/lin/Desktop/dataset/creditcard.csv',header=0,encoding="utf8")
#划分训练集和测试集
train,test=train_test_split(credit,test_size=0.3,random_state=0,stratify=credit["Class"])
#对训练集平衡抽样,采用向下抽样的方法
random_u_s=RandomUnderSampler(sampling_strategy=0.5,random_state=0)
x,y=random_u_s.fit_resample(X=train.drop("Class",axis=1),y=train["Class"])
train_b =pd.DataFrame(np.column_stack((x,y)),columns=train.columns).astype(train.dtypes)
#划分自变量和因变量
train_x,train_y=train_b.drop("Class",axis=1),train_b["Class"]
test_x,test_y=test.drop("Class",axis=1),test["Class"]

首先使用未进行数据归约的数据集建立了GBDT模型,这个模型将作为基准。

模型的对比主要包括三个方面:

(1)自变量个数:反映了数据归约的直接影响

(2)训练耗时:反映数据归约对于建模效率的影响

(3)模型的AUC,反映数据归约对于模型预测能力的影响 

start=time.time()
model_all=GradientBoostingClassifier()
model_all.fit(X=train_x,y=train_y)
duration=time.time()-start
auc_all=roc_auc_score(y_true=test_y,y_score=model_all.predict_proba(test_x)[:,1])
print("model_all自变量个数:%d\nmodel_all训练耗时:%f秒\nmodel_all的 AUC:%f" % (train_x.shape[1],duration,auc_all))

这个结果将作为比较的基准

变量选择 

使用统计量选择变量

使用相关系数选择变量(Pearson & Spearman)
#计算训练集每一列与因变量的Pearson和Spearman相关系数
pearson=pd.Series(name="pearson correlation")#创建两个空的 Pandas Series,用于存储 Pearson 和 Spearman 相关系数
spearman=pd.Series(name="spearman correlation")
for i in train_x:
    pearson[i] =pearsonr(train_y,train_x[i])[0]
    spearman[i]=spearmanr(train_y,train_x[i])[0]
#对每个自变量进行计算:
#pearson[i] = pearsonr(train_y, train_x[i])[0]:计算当前自变量 i 与因变量 train_y 之间的 Pearson 相关系数,并将结果存储到 pearson Series 中。
#spearman[i] = spearmanr(train_y, train_x[i])[0]:计算当前自变量 i 与因变量 train_y 之间的 Spearman 相关系数,并将结果存储到 spearman Series 中。
#查找两个相关系数的绝对值同时大于0.5的变量
var_cor=(pearson.abs()>0.5)&(spearman.abs()>0.5)
#创建一个布尔 Series,表示在 Pearson 和 Spearman 相关系数的绝对值均大于 0.5 的自变量。这里使用了 abs() 方法来取绝对值。
var_cor=var_cor[var_cor].index#提取变量名
print(pd.DataFrame((pearson,spearman)).T)
print("\n与因变量相关性较强的自变量为:\n%s"% var_cor.values)

使用相关系数筛选出的变量训练GBDT模型 

start=time.time()
model_cor=GradientBoostingClassifier(random_state=0)
model_cor.fit(X=train_x[var_cor],y=train_y)
duration=time.time()-start
auc_cor=roc_auc_score(y_true=test_y,y_score=model_cor.predict_proba(test_x[var_cor])[:,1])
print("model_cor自变量个数:%d\nmodel_cor训练耗时:%f秒\nmodel_cor的 AUC:%f" % (var_cor.size,duration,auc_cor))

可以看到与基准模型相比,这个模型的自变量只有11 个,训练耗时少了很多,并且AUC也有所提高,因为剔除了无关变量后,降低了模型的过拟合,因此在测试集上有更优的表现。

使用方差分析的F检验结果选择变量 

ANOVA是用于检验两组或多组数据间样本均值差异是否显著的方法,其检验形式是F检验

书中以P值0.01 为选择标准

anova=pd.Series(name="P-value")
anova_sig=pd.Series(name="P<0.01")#创建两个空的 Pandas Series,用于存储 ANOVA 的 p-value 和是否小于 0.01。
for i in train_x:
    group_0 = train_x[i][train_y.eq(0)]
    group_1 = train_x[i][train_y.eq(1)]
    anova[i]=f_oneway(group_0,group_1)[1]
#使用一个循环,对每个自变量进行计算:
#group_0 = train_x[i][train_y.eq(0)]:提取目标变量为 0 的样本在当前自变量上的取值。
#group_1 = train_x[i][train_y.eq(1)]:提取目标变量为 1 的样本在当前自变量上的取值。
#anova[i] = f_oneway(group_0, group_1)[1]:进行一元方差分析,计算 p-value,并将结果存储到 anova Series 中。
anova = anova.to_frame()#将 anova 转换为数据框,并添加一个列 anova_sig 表示是否小于 0.01。
anova = anova.join(anova_sig)
anova["P<0.01"]= anova["P-value"]<0.01
var_anova = anova["P-value"][anova["P<0.01"]].index #var_anova = anova["P-value"][anova["P<0.01"]].index:提取 p-value 小于 0.01 的自变量的索引。
print(anova)
print("\nF检验P<0.01的自变量为:\n%s"% var_anova.values)     

start=time.time()
model_anova=GradientBoostingClassifier(random_state=0)
model_anova.fit(X=train_x[var_anova],y=train_y)
duration=time.time()-start
auc_anova=roc_auc_score(y_true=test_y,y_score=model_anova.predict_proba(test_x[var_anova])[:,1])
print("model_anova自变量个数:%d\nmodel_anova训练耗时:%f秒\nmodel_anova的 AUC:%f" % (var_anova.size,duration,auc_anova))

从结果来看,通过方差分析选择的模型的自变量有23个,训练耗时和AUC都比基准模型好。

使用决策树模型选择:

#使用全部变量建立GBDT模型,提取变量的重要度
feature_imp=pd.Series(model_all.feature_importances_,index=train_x.columns)
#取重要程度最大的8个变量
var_tree=feature_imp.sort_values(ascending=False).head(8).index
print("变量重要性排序:\n",feature_imp.sort_values(ascending=False))
print("\n重要度较高的自变量为:\n%s" % var_tree.values)

start=time.time()
model_tree=GradientBoostingClassifier(random_state=0)
model_tree.fit(X=train_x[var_tree],y=train_y)
duration=time.time()-start
auc_tree=roc_auc_score(y_true=test_y,y_score=model_tree.predict_proba(test_x[var_tree])[:,1])
print("model_tree自变量个数:%d\nmodel_tree训练耗时:%f秒\nmodel_tree的 AUC:%f" % (var_tree.size,duration,auc_tree))

使用Lasso回归模型选择变量

使用Lasso模型选择变量,首先使用所有的自变量进行Lasso模型训练,提取各个自变量的参数,提除系数为0的变量。

#使用 Lasso 回归对数据进行特征选择,找出对目标变量有显著影响的自变量。 Lasso 回归的特点是能够对系数进行稀疏化,将一些系数缩小为零,从而实现特征选择。
lasso=Lasso(alpha=0.03,random_state=0)#创建 Lasso 回归模型的实例,其中 alpha 是正则化项的强度。
lasso.fit(train_x,train_y)
coef=pd.Series(lasso.coef_,index=train_x.columns)#创建一个 Pandas Series,存储 Lasso 回归的系数,其中索引为训练数据的自变量名称。
var_lasso=coef[coef.ne(0)].index#提取系数不为零的自变量的索引。
print("Lasso模型回归系数:\n",coef)
print("\nLasso模型筛出的自变量为:\n%s"% var_lasso.values)

rt=time.time()
model_lasso=GradientBoostingClassifier(random_state=0)
model_lasso.fit(X=train_x[var_lasso],y=train_y)
duration=time.time()-start
auc_lasso=roc_auc_score(y_true=test_y,y_score=model_lasso.predict_proba(test_x[var_lasso])[:,1])
print("model_lasso自变量个数:%d\nmodel_lasso训练耗时:%f秒\nmodel_lasso的 AUC:%f" % (var_lasso.size,duration,auc_lasso))

可以看到选出了自变量有9个,训练耗时比基准模型低很多,但是AUC的提升很少。

样本归约 

对于数据建模来说,样本量总是多多益善的。但是样本量的增加并不会一直以相同的速度提高模型预测的精度,而是会在达到某一个样本量之后,模型预测的精度会呈现缓慢增长甚至停止增长。

result=pd.Series(index=range(50,train_b.shape[0],10))#创建一个 Pandas Series,用于存储 AUC 值,索引为每次抽样的样本数。
#使用一个循环,每次增加 10 个样本,从 train_b 中随机抽样并训练模型:
#sample = train_b.sample(n=i, random_state=0):从 train_b 中随机抽样 i 个样本。
#train_x, train_y = sample.drop("Class", axis=1), sample["Class"]:提取抽样后的自变量和目标变量。
#m = GradientBoostingClassifier(random_state=0):创建 Gradient Boosting 分类器的实例。
#m.fit(X=train_x, y=train_y):使用抽样后的数据进行训练。
#result[i] = roc_auc_score(y_true=test_y, y_score=m.predict_proba(test_x)[:, 1]):计算在测试数据上的 AUC,并将结果存储到 result Series 中。
for i in range(50,train_b.shape[0],10):
    sample=train_b.sample(n=i,random_state=0)
    train_x,train_y=sample.drop("Class",axis=1),sample["Class"]
    m=GradientBoostingClassifier(random_state=0)
    m.fit(X=train_x,y=train_y)
    result[i]=roc_auc_score(y_true=test_y,y_score=m.predict_proba(test_x)[:,1])
result.plot.line()
plt.xlabel("number of sample")
plt.ylabel("AUC")
plt.show() 

 

可以看到,随着样本量的增加,AUC也在增加,但当超过200之后基本趋于稳定。 

为了进一步观察样本归约对数据建模的影响,本书中又建立了两个模型。第一个模型单纯的进行了数据归约,对训练集进行了容量为700的抽样,并训练了GBDT模型,观察训练耗时 和AUC;第二个模型在样本归约之外,还基于之前树模型的变量选择进行了纬度归约。

#对训练集进行容量为700的随机抽样
sample=train_b.sample(n=700,random_state=0)#从 train_b 数据中随机抽样 700 个样本,构建训练集 train_x 和目标变量 train_y
train_x,train_y=sample.drop("Class",axis=1),sample["Class"]
start=time.time()
m=GradientBoostingClassifier(random_state=0)#使用所有的特征(变量)建立 Gradient Boosting 分类器模型,并计算模型的训练时间和在测试集上的 AUC:
m.fit(X=train_x,y=train_y)
duration=time.time()-start
auc_m=roc_auc_score(y_true=test_y,y_score=m.predict_proba(test_x)[:,1])
print("抽样后,使用所有变量建模:\n模型训练耗时:%f秒\n模型的 AUC:%f" % (duration,auc_m))
#同时对训练集进行容量为700的随机抽样和使用树模型对变量进行筛选
#使用树模型(var_tree 指定的特征)建立 Gradient Boosting 分类器模型,并计算模型的训练时间和在测试集上的 AUC
start=time.time()
m=GradientBoostingClassifier(random_state=0)
m.fit(X=train_x[var_tree],y=train_y)
duration=time.time()-start
auc_m=roc_auc_score(y_true=test_y,y_score=m.predict_proba(test_x[var_tree])[:,1])
print("\n抽样后,使用树模型建模:\n模型训练耗时:%f秒\n模型的 AUC:%f" % (duration,auc_m))

结果表明同时进行样本归约和纬度归约可以提高训练耗时和AUC。

伪自变量的识别与影响:

伪自变量是作者自己定义的,用来描述:一个变量,其本身是受因变量的影响的(它不但不是因变量的影响因素,反而因变量是它的影响因素),若这个变量被视作自变量添加到模型中,则会导致其他自变量不能进入模型。本书在信用卡数据集中构造了一个伪自变量“fraud_days”。

c=copy.deepcopy(credit)
c.insert(30,"fraud_days",c["Class"]*np.random.randint(low=1,high=100,size=c.shape[0]))
#创建数据集 c,并在其后插入名为 "fraud_days" 的列,该列包含了与 "Class" 列相乘的随机整数
#fraud_days对于Class=1情况在1-100间随机取整数值作为逾期天数,对于其他情况一律取0
train,test=train_test_split(c,test_size=0.3,random_state=0,stratify=c["Class"])
random_u_s=RandomUnderSampler(sampling_strategy=0.5,random_state=0)
#使用 RandomUnderSampler 进行向下抽样,以使类别 "1" 的样本数量为类别 "0" 的一半:
x,y=random_u_s.fit_resample(X=train.drop("Class",axis=1),y=train["Class"])
train_b =pd.DataFrame(np.column_stack((x,y)),columns=train.columns).astype(train.dtypes)
train_x,train_y=train_b.drop("Class",axis=1),train_b["Class"]
test_x,test_y=test.drop("Class",axis=1),test["Class"]
model_f=GradientBoostingClassifier(random_state=0)
model_f.fit(X=train_x,y=train_y)
auc_f=roc_auc_score(y_true=test_y,y_score=model_f.predict_proba(test_x)[:,1])
print("包含伪自变量的模型 AUC:%f" % auc_f)
#计算模型的变量重要性排序,并输出重要度大于 0 的变量名:
feature_imp=pd.Series(model_f.feature_importances_,index=train_x.columns)
print("\n变量重要性排序:\n",feature_imp.sort_values(ascending=False))
print("\n重要度大于0的变量为:\n%s" % train_x.columns[feature_imp>0].tolist())

结果表明,model_f 的AUC为1,看起来是完美预测。本例中的结果并不明显,在书中给出的例子中,最后重要性大于0的变量只有fraud_days.证明这个变量是伪自变量。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值