完整代码
# 二元Logistic回归算法
# 数据准备
# 1 载入分析所需要的模块和函数
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from warnings import simplefilter
simplefilter(action='ignore', category=FutureWarning)
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.metrics import cohen_kappa_score
# 2 数据读取及观察
data = pd.read_csv(r'G:\重庆移通学院工作\01教学工作\00各科各学期课程资料\2023-2024学年(2)学期-机器学习与深度学习+python程序设计竞赛\机器学习与深度学习\Python机器学习原理与算法实现之PPT与源代码\正文源代码及数据文件\第五章 二元Logistic回归算法\数据5.1.csv')
data.info()
len(data.columns)
data.columns
data.shape
data.dtypes
data.isnull().values.any()
data.isnull().sum()
data.head()
# 3 描述性分析
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
print(data.describe())
data.groupby('V1').describe().unstack() # 按照V1变量的取值分组对其他变量开展描述性分析
print(data.groupby('V1').describe())
pd.crosstab(data.V3, data.V1)
pd.crosstab(data.V3, data.V1, normalize='index')
# 4 数据处理
# 4.1 区分分类特征和连续特征并进行处理
def data_encoding(data):
data = data[["V1", 'V2', "V3", "V4", "V5", "V6", "V7", "V8", "V9"]]
Discretefeature = ["V3"]
Continuousfeature = ['V2', "V4", "V5", "V6", "V7", "V8", "V9"]
df = pd.get_dummies(data, columns=Discretefeature)
df[Continuousfeature] = (df[Continuousfeature] - df[Continuousfeature].mean()) / (df[Continuousfeature].std())
df["V1"] = data[["V1"]]
return df
data = data_encoding(data)
# 4.2 将样本示例全集分割为训练样本和测试样本
X = data.drop(['V1', 'V3_5'], axis=1) # 设置特征变量,即除V1、V3_5之外的全部变量
X.shape
X['intercept'] = [1] * X.shape[0] # 为X增加1列,设置模型中的常数项。
y = data['V1'] # 设置响应变量,即V1
print(data["V1"].value_counts())
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, random_state=100)
X_train.head()
y_train.head()
# 5 使用sklearn建立二元Logistic回归算法模型
model = LogisticRegression(C=1e10, fit_intercept=True)
model.fit(X_train, y_train)
print("训练样本预测准确率: {:.3f}".format(model.score(X_train, y_train))) # 训练样本预测对的个数 / 总个数
print("测试样本预测准确率: {:.3f}".format(model.score(X_test, y_test))) # 测试样本预测对的个数 / 总个数
model.coef_
predict_target = model.predict(X_test)
predict_target
predict_target_prob = model.predict_proba(X_test) # 生成样本响应变量的预测概率
predict_target_prob
predict_target_prob_lr = predict_target_prob[:, 1]
predict_target_prob_lr
df = pd.DataFrame({'prob': predict_target_prob_lr, 'target': predict_target, 'labels': list(y_test)})
df.head()
print('预测正确总数:')
print(sum(predict_target == y_test))
print('训练样本:')
predict_Target = model.predict(X_train)
print(metrics.classification_report(y_train, predict_Target))
print(metrics.confusion_matrix(y_train, predict_Target))
print('测试样本:')
print(metrics.classification_report(y_test, predict_target))
print(metrics.confusion_matrix(y_test, predict_target))
# 6 特征变量重要性水平分析
lr1 = [i for item in model.coef_ for i in item] # 生成列表lr1,表中元素为每个特征变量的回归系数
lr1 = np.array(lr1) # 将lr1转换成np模块中的数组格式
lr1 # 查看lr1,运行结果为:
model.coef_
feature = list(X.columns)
feature
dic = {}
for i in range(len(feature)):
dic.update({feature[i]: lr1[i]})
dic
df = pd.DataFrame.from_dict(dic, orient='index', columns=['权重'])
df
df = df.reset_index().rename(columns={'index': '特征'})
df
df = df.sort_values(by='权重', ascending=False)
df
data_hight = df['权重'].values.tolist()
data_hight
data_x = df['特征'].values.tolist()
data_x
font = {'family': 'Times New Roman', 'size': 7, }
sns.set(font_scale=1.2)
plt.rc('font', family='Times New Roman')
plt.figure(figsize=(6, 6))
plt.barh(range(len(data_x)), data_hight, color='#6699CC')
plt.yticks(range(len(data_x)), data_x, fontsize=12)
plt.tick_params(labelsize=12)
plt.xlabel('Feature importance', fontsize=14)
plt.title("LR feature importance analysis", fontsize=14)
plt.show()
# 7 计算科恩kappa得分
print(cohen_kappa_score(y_test, predict_target))
#8 绘制ROC曲线,计算AUC值
from sklearn.metrics import RocCurveDisplay
# 使用 RocCurveDisplay.from_estimator 绘制 ROC 曲线
RocCurveDisplay.from_estimator(model,X_test, y_test)
plt.plot([0, 1], [0, 1], linestyle='--', color='gray')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.show()
前面讲述的线性回归算法要求因变量是连续变量,但很多情况下因变量是离散而非连续的。例如,预测下雨的概率,是下雨还是不下雨;预测一笔贷款业务的资产质量,包括正常、关注、次级、可疑、损失等。Logistic回归算法可以有效地解决这一问题,它包括二元Logistic回归算法、多元Logistic回归算法等。
当因变量只有两种取值,比如下雨、不下雨时,则使用二元Logistic回归算法来解决问题;当因变量有多种取值,比如贷款业务的资产质量包括正常、关注、次级、可疑、损失等,则使用多元Logistic回归算法来解决问题。
我们讲解二元Logistic回归算法的基本原理,并结合具体实例讲解该算法在Python中的实现与应用。
一、 二元Logistic回归算法的基本原理
二元Logistic回归算法的基本原理在线性回归算法中,我们假定因变量为连续定量变量,但在很多情况下,因变量只能取二值(0,1),比如是否满足某一特征等。因为一般回归分析要求因变量呈现正态分布,并且各组中具有相同的方差——协方差矩阵,所以直接用它来为二值因变量进行回归估计是不恰当的。这时候就可以用到本节介绍的二元Logistic回归算法。二元Logistic回归算法的基本原理是考虑因变量(0,1)发生的概率,用发生概率除以没有发生概率再取对数。通过这一变换改变了“回归方程左侧因变量估计值取值范围为0~1,而右侧取值范围是无穷大或者无穷小”这一取值区间的矛盾,也使得因变量和自变量之间呈线性关系。当然,正是由于这一变换,使得Logistic回归自变量系数不同于一般回归分析自变量系数,而是模型中每个自变量概率比的概念。Logistic回归系数的估计通常采用最大似然法。最大似然法的基本思想是先建立似然函数与对数似然函数,再通过使对数似然函数最大来求解相应的系数值,所得到的估计值称为系数的最大似然估计值。
其中,p为发生的概率,为模型的截距项,
为待估计系数,X=
为自变量,
为误差项。通过公式也可以看出,Logistic模型实质上是建立了因变量发生的概率和自变量之间的关系,回归系数是模型中每个自变量概率比的概念。
假设你想预测一个人是否会购买某一款手机。这里 “购买” 就是因变量的一种结果,可以用 1 表示;“不购买” 就是另一种结果,可以用 0 表示。
二元 Logistic 回归算法就是去分析一些可能影响这个人是否购买手机的因素,这些因素就是自变量,比如手机价格、手机性能、品牌知名度等等。
它的特别之处在于考虑这个人购买手机的概率。比如说,一开始我们不知道这个概率具体是多少,但我们可以通过一些数据去推测。我们把这个人购买手机的概率除以不购买手机的概率,然后再取对数。这样做的好处是可以把本来可能很复杂的概率关系变得简单一些,让因变量(是否购买手机)和自变量(手机价格等因素)之间有了一种类似线性的关系。
就好像我们有一个魔法盒子,把各种影响因素放进去,经过这个魔法盒子的处理,就能比较准确地预测出这个人会不会购买手机。
比如我们收集了一些人的数据,包括他们最终是否购买了某品牌的高端手机,以及他们的月收入、对该品牌的喜爱程度等信息。
我们发现月收入高的人更有可能购买这款高端手机,对品牌喜爱程度高的人也更有可能购买。
通过二元 Logistic 回归算法分析这些数据后,我们可能得到一个这样的结果:购买手机的概率 = 某个公式,这个公式里包含了月收入、品牌喜爱程度等自变量。比如公式可能是:购买概率 = 1 /(1 + e^(-(0.5× 月收入 + 0.3× 品牌喜爱程度 - 10)))。
这里面的系数 0.5 和 0.3 就反映了月收入和品牌喜爱程度对购买手机概率的影响程度。如果一个人的月收入很高,对品牌也很喜爱,那么代入这个公式后,计算出来的购买概率就会比较高,我们就可以预测这个人很有可能会购买这款手机。
二、案例
1数据文件请私信获取
2 数据准备
用于分析的“数据5.1”文件中的内容是XX银行XX省分行的700个对公授信客户的信息数据,如图所示。这700个对公授信客户是以前曾获得贷款的客户,包括存量授信客户和已结清贷款客户。在数据文件中共有9个变量,V1~V9分别代表“征信违约记录”“资产负债率”“行业分类”“实际控制人从业年限”“企业经营年限”“主营业务收入”“利息保障倍数”“银行负债”“其他渠道负债”。由于客户信息数据既涉及客户隐私,也涉及商业机密,因此进行了适当的脱密处理,对于其中的部分数据也进行了必要的调整。针对V1(征信违约记录),分别用0、1来表示未违约、违约。针对V3(行业分类),分别用1、2、3、4、5来表示“制造业”“批发零售业”“建筑业、房地产与基础设施”“科教文卫”“农林牧渔业”。
要研究的是对公授信客户违约的影响因素,或者说哪些特征可以影响对公客户的信用状况,进而提出针对性的风险防控策略,所以把响应变量设置为V1(征信违约记录),将其他变量作为特征变量。本例为讲解方便,采用了“V2资产负债率”等8个特征变量,实务中大家需根据实际业务情况及数据的可获得性、便利程度等因素灵活选取特征变量。
模型构建的基本思路商业银行对公客户违约问题本质上还是一种对客户的分类问题。基本逻辑是把客户是否守约作为响应变量,这一响应变量在测量方式上属于二分类变量,把客户分为“违约”和“守约”两类;把客户特征作为特征变量,客户特征包括客户的经营能力、盈利能力、偿债能力、发展潜力、现有负债及担保情况等,这些特征变量既可以是生产经营指标、财务指标等连续变量,也可以是是否对外担保、是否存在历史违约记录等分类变量。
1 导入分析所需要的模块和函数
# 1 载入分析所需要的模块和函数
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from warnings import simplefilter
simplefilter(action='ignore', category=FutureWarning)
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.metrics import cohen_kappa_score
2 数据读取及观察
#2 数据读取及观察
data = pd.read_csv(r'\数据5.1.csv')#改成自己的路径
data.info()
len(data.columns)
data.columns
data.shape
data.dtypes
data.isnull().values.any()
data.isnull().sum()
data.head()
3 描述性分析
针对各变量开展描述性分析。针对连续变量,通常使用计算平均值、标准差、最大值、最小值、四分位数等统计指标的方式来进行描述性分析;针对分类变量,通常使用交叉表的方式开展分析。交叉表分析是描述统计的一种,分析特色是将数据按照行变量、列变量进行描述统计。比如我们要针对体检结果分析高血脂和高血压情况,则可以使用交叉表分析方法将高血脂作为行变量、高血压作为列变量(当然,行列变量也可以互换),对所有被体检者生成二维交叉表格描述统计分析。
# 3 描述性分析
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
print(data.describe())
data.groupby('V1').describe().unstack() # 按照V1变量的取值分组对其他变量开展描述性分析
print(data.groupby('V1').describe())
pd.crosstab(data.V3, data.V1)
pd.crosstab(data.V3, data.V1, normalize='index')
例中变量V1和变量V3均为分类离散变量,统计指标的意义不大,但是其他变量为连续变量,具有一定的参考价值。比如变量V2为资产负债率,参与分析的样本客户的资产负债率平均值为28.341543,最大值为45.530000,最小值为16.260000,标准差为6.501841,25%、50%、75%三个四分位数分别为23.580000、27.640000、32.520000。其中50%的四分位数也是中位数。
由于结果过多,仅展示一部分,比如V2,可以看到V2按照V1变量的取值分组展示了描述性分析指标。
对变量V3和变量V1进行交叉表分析,pd.crosstab()的第一个参数是 列,第二个参数是行。本例是以V1征信违约记录作为列变量,以V3行业分类作为行变量进行分析。参数 normalize='index是按行求百分比的概念,即每行之和等于1。本例是以V1征信违约记录作为列变量,以V3行业分类作为 行变量,求每个行业分类中未违约客户和违约客户各自的百分比。运行结果为:
即在第1类行业“制造业”中,未违约的客户占比为0.702020,违约的客户占比为0.297980。在第2类行业“批发零售业”中,未违约的客户占比为0.787634,违约的客户占比为0.212366。在第3类行业“建筑业、房地产与基础设施”中,未违约的客户占比为0.631579,违约的客户占比为0.368421。在第4类行业“科教文卫”中,未违约的客户占比为0.655172,违约的客户占比为0.344828。在第5类行业“农林牧渔业”中,未违约的客户占比为0.800000,违约的客户占比为0.200000。单纯从描述性分析的结果来看,“农林牧渔业”的客户违约占比最低,“建筑业、房地产与基础设施”的客户违约占比最高。
3 数据处理
1 区分分类特征和连续特征并进行处理首先定义一个函数data_encoding(),该函数的作用是区分分类特征和连续特征,并对分类特征设置虚拟变量,对连续特征进行标准化处理。在Spyder代码编辑区输入以下代码并运行,完成对函数data_encoding()的定义。
#1 区分分类特征和连续特征并进行处理
def data_encoding(data):
data = data[["V1", 'V2', "V3", "V4", "V5", "V6", "V7", "V8", "V9"]]
Discretefeature = ["V3"]
Continuousfeature = ['V2', "V4", "V5", "V6", "V7", "V8", "V9"]
df = pd.get_dummies(data, columns=Discretefeature)
df[Continuousfeature] = (df[Continuousfeature] - df[Continuousfeature].mean()) / (df[Continuousfeature].std())
df["V1"] = data[["V1"]]
return df
执行代码完毕后,即完成对特征变量的区分并进行处理。我们还可以在变量管理器窗口查看“data”文件,可以发现针对V3变量生成了“V3_1”~“V3_5”五个虚拟变量。
在进行虚拟变量编码时,通常会删除一个类别作为参考类别,为了避免多重共线性。设置特征变量。在整个data数据文件中,首先需要响应变量V1去掉,然后由于我们针对 V3 的每一个类别都生成了一个虚拟变量,因此需要删除一个虚拟变量,将被删除的类别作为参考类别,所以将最后一个类别V3_5删除。
X = data.drop(['V1','V3_5'],axis=1) #在这种情况下,其他V3类别的系数含义就是其他行业相对于“农林牧渔业”行业客户的违约对比情况
X['intercept'] = [1]*X.shape[0] # 为X增加1列,设置模型中的常数项
y = data['V1'] # 设置响应变量,即V1
print(data["V1"].value_counts()) # 输出V1变量的值
4 使用sklearn建立二元Logistic回归算法模型
# 使用sklearn建立二元Logistic回归算法模型
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, random_state=100)
X_train.head()
y_train.head()
model = LogisticRegression(C=1e10, fit_intercept=True)
model.fit(X_train, y_train)
print("训练样本预测准确率: {:.3f}".format(model.score(X_train, y_train))) # 训练样本预测对的个数 / 总个数
print("测试样本预测准确率: {:.3f}".format(model.score(X_test, y_test))) # 测试样本预测对的个数 / 总个数
model.coef_
predict_target = model.predict(X_test)
predict_target
predict_target_prob = model.predict_proba(X_test) # 生成样本响应变量的预测概率
predict_target_prob
predict_target_prob_lr = predict_target_prob[:, 1]
predict_target_prob_lr
df = pd.DataFrame({'prob': predict_target_prob_lr, 'target': predict_target, 'labels': list(y_test)})
df.head()
print('预测正确总数:')
print(sum(predict_target == y_test))
print('训练样本:')
predict_Target = model.predict(X_train)
print(metrics.classification_report(y_train, predict_Target))
print(metrics.confusion_matrix(y_train, predict_Target))
print('测试样本:')
print(metrics.classification_report(y_test, predict_target))
print(metrics.confusion_matrix(y_test, predict_target))
5 特征变量重要性水平分析
# 特征变量重要性水平分析
lr1 = [i for item in model.coef_ for i in item] # 生成列表lr1,表中元素为每个特征变量的回归系数
lr1 = np.array(lr1) # 将lr1转换成np模块中的数组格式
lr1 # 查看lr1,运行结果为:
model.coef_
feature = list(X.columns)
feature
dic = {}
for i in range(len(feature)):
dic.update({feature[i]: lr1[i]})
dic
df = pd.DataFrame.from_dict(dic, orient='index', columns=['权重'])
df
df = df.reset_index().rename(columns={'index': '特征'})
df
df = df.sort_values(by='权重', ascending=False)
df
data_hight = df['权重'].values.tolist()
data_hight
data_x = df['特征'].values.tolist()
data_x
font = {'family': 'Times New Roman', 'size': 7, }
sns.set(font_scale=1.2)
plt.rc('font', family='Times New Roman')
plt.figure(figsize=(6, 6))
plt.barh(range(len(data_x)), data_hight, color='#6699CC')
plt.yticks(range(len(data_x)), data_x, fontsize=12)
plt.tick_params(labelsize=12)
plt.xlabel('Feature importance', fontsize=14)
plt.title("LR feature importance analysis", fontsize=14)
plt.show()
6 计算科恩kappa得分
# 7 计算科恩kappa得分
print(cohen_kappa_score(y_test, predict_target))
7 绘制ROC曲线,计算AUC值
#8 绘制ROC曲线,计算AUC值
from sklearn.metrics import RocCurveDisplay
# 使用 RocCurveDisplay.from_estimator 绘制 ROC 曲线
RocCurveDisplay.from_estimator(model,X_test, y_test)
plt.plot([0, 1], [0, 1], linestyle='--', color='gray')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.show()
三、优缺点
一、优点
-
可解释性强:
- 系数易于解释。每个自变量的系数表示在其他变量保持不变的情况下,该变量对因变量取 “1”(通常表示某个特定结果发生)的对数优势比(log odds ratio)的影响。
- 例如,如果一个自变量的系数为正,说明该变量的增加会提高因变量取 “1” 的概率;反之,系数为负则降低概率。这种直观的解释使得人们能够理解各个因素对结果的影响方向和程度。
-
适用于二分类问题:
- 在很多实际场景中,问题往往可以归结为二分类问题,如疾病的发生与否、客户是否购买产品、贷款是否违约等。二元 Logistic 回归能够有效地处理这类问题,为决策提供依据。
- 相比一些复杂的多分类算法,二元 Logistic 回归在二分类问题上更加简洁高效,且具有较好的性能。
-
不需要严格的变量分布假设:
- 与一些传统的统计方法相比,二元 Logistic 回归对自变量的分布要求相对宽松。它不要求自变量服从特定的分布,如正态分布等。
- 这使得它在处理各种类型的数据时更加灵活,能够适应不同的数据特征。
-
可以处理连续和分类自变量:
- 二元 Logistic 回归能够同时纳入连续型变量(如年龄、收入等)和分类变量(如性别、地区等)。对于分类变量,可以通过虚拟变量编码的方式进行处理,方便地将不同类型的变量整合到一个模型中。
- 这种灵活性使得它在实际应用中能够充分利用各种类型的信息,提高模型的预测能力。
-
稳健性较好:
- 在面对一些异常值或噪声数据时,二元 Logistic 回归相对较为稳健。它不像一些基于距离或方差的方法那样容易受到极端值的影响,能够在一定程度上保持模型的稳定性。
- 此外,通过适当的正则化方法(如 L1 或 L2 正则化),可以进一步提高模型的稳健性,防止过拟合。
二、缺点
-
线性假设限制:
- 二元 Logistic 回归假设自变量和因变量之间存在线性关系。然而,在实际问题中,这种线性关系并不总是成立。
- 例如,某些自变量与因变量之间可能存在非线性关系,如二次关系、指数关系等。在这种情况下,直接使用二元 Logistic 回归可能会导致模型的拟合效果不佳,无法准确地捕捉到变量之间的真实关系。
-
独立性假设:
- 该算法通常假设各个观测值之间是相互独立的。但在实际数据中,观测值之间可能存在相关性,例如时间序列数据中的自相关、空间数据中的空间相关性等。
- 如果违反了独立性假设,可能会导致模型的参数估计不准确,影响模型的预测性能。
-
对数据不平衡敏感:
- 当数据中两类样本的比例严重不平衡时,二元 Logistic 回归可能会偏向于多数类,对少数类的分类效果较差。
- 例如,在疾病检测问题中,如果患病的样本数量远远少于未患病的样本数量,模型可能会更多地预测为未患病,而忽略了少数患病样本的特征。在这种情况下,需要采取一些特殊的方法来处理数据不平衡问题,如过采样、欠采样或使用集成学习方法等。
-
解释系数的局限性:
- 虽然二元 Logistic 回归的系数具有一定的解释性,但这种解释是基于对数优势比的。对于一些非专业人士来说,理解对数优势比可能比较困难。
- 此外,系数的解释也受到其他变量的影响,在多变量的情况下,很难孤立地解释一个变量的系数。而且,当变量之间存在交互作用时,系数的解释会变得更加复杂。