回归模型中多重共线性问题——逐步回归法、方差膨胀因子(VIF)、因子分析【含代码与解释】

特征之间的多重共线性,是指在回归模型中,自变量之间存在高度的线性相关性,导致回归系数的估计不准确,不稳定,甚至不可信的现象。多重共线性的存在会影响模型的解释能力和预测能力,增加模型的复杂度和不确定性,降低模型的泛化能力。

举一个实际的例子,假设我们想用线性回归模型来预测房价,我们选择了以下几个自变量:房屋面积,房屋卧室数,房屋卫生间数,房屋所在地区,房屋建造年份等。这些自变量中,可能存在一些多重共线性的问题,例如:

  • 房屋面积和房屋卧室数,房屋卫生间数之间可能存在正相关,即面积越大,卧室数和卫生间数也越多。
  • 房屋所在地区和房屋建造年份之间可能存在负相关,即地区越发达,房屋越新。
  • 房屋卧室数和房屋卫生间数之间可能存在一定的比例关系,即卧室数和卫生间数的比例在一定范围内变化。

这些多重共线性的问题会导致我们的回归模型出现以下一些问题:

  • 回归系数的符号和大小可能与我们的常识或者理论不一致,例如,我们可能发现房屋面积对房价的影响是负的,或者房屋卧室数对房价的影响是正的,但是房屋卫生间数对房价的影响是负的。
  • 回归系数的置信区间可能非常宽,说明我们对回归系数的估计非常不确定,或者回归系数的显著性检验可能不通过,说明我们不能拒绝回归系数为零的假设,即该特征对因变量没有影响。
  • 回归模型的拟合优度可能很高,说明模型在训练数据上表现很好,但是在测试数据上表现很差,说明模型过拟合了,没有泛化能力。

因此,我们需要对多重共线性进行检测和处理,以提高模型的可靠性和有效性。一些常用的检测和处理多重共线性的方法有:

  • 计算变量的方差膨胀因子(VIF),如果VIF大于10,说明存在严重的多重共线性,需要剔除一些特征或者采用降维的方法。
# 添加常数项,因为VIF的计算需要截距项
df_with_const = add_constant(df)

# 计算VIF
vif = pd.DataFrame()
vif["Variable"] = df_with_const.columns
vif["VIF"] = [variance_inflation_factor(df_with_const.values, i) for i in range(df_with_const.shape[1])]

print(vif)
  • 采用逐步回归法,根据一定的准则,逐渐增加或者减少特征,直到找到一个最优的特征子集,使得模型的拟合优度最高,同时特征的数量最少。

逐步回归法是一种特征选择的方法,它的思想是通过逐渐增加或者减少特征,来找到一个最优的特征子集,使得模型的拟合优度最高,同时特征的数量最少。逐步回归法有三种方式:前向选择法,后向剔除法,和双向混合法。前向选择法是从一个空模型开始,每次选择一个对目标变量影响最大的特征加入模型,直到没有显著的特征可以加入为止。后向剔除法是从一个包含所有特征的模型开始,每次删除一个对目标变量影响最小的特征,直到没有不显著的特征可以删除为止。双向混合法是结合了前向选择法和后向剔除法,每次既考虑增加一个特征,又考虑删除一个特征,直到达到最优的模型为止。

下面我给出一个用Python实现逐步回归法的代码,以及一个示例数据集。我使用的是基于AIC(赤池信息量)的准则,即每次选择或者删除特征时,使得AIC值最小。AIC值是一种衡量模型复杂度和拟合度的指标,它考虑了模型的参数个数和残差平方和,AIC值越小,说明模型越好。

# 导入所需的库
import numpy as np
import pandas as pd
import statsmodels.api as sm

# 定义一个逐步回归的函数,输入为数据集,目标变量,初始特征集,候选特征集,方向(前向,后向,双向),输出为最优的特征集和AIC值
def stepwise_regression(data, target, initial_features, candidate_features, direction):
    # 初始化最优的特征集和AIC值
    best_features = initial_features.copy()
    best_aic = sm.OLS(target, sm.add_constant(data[best_features])).fit().aic
    # 根据方向进行不同的操作
    if direction == "forward":
        # 前向选择法
        while True:
            # 初始化一个临时的AIC值和特征
            tmp_aic = np.inf
            tmp_feature = None
            # 遍历候选特征集中的每个特征
            for feature in candidate_features:
                # 将该特征加入到当前的特征集中
                features = best_features + [feature]
                # 计算加入该特征后的AIC值
                aic = sm.OLS(target, sm.add_constant(data[features])).fit().aic
                # 如果AIC值小于临时的AIC值,更新临时的AIC值和特征
                if aic < tmp_aic:
                    tmp_aic = aic
                    tmp_feature = feature
            # 如果临时的AIC值小于最优的AIC值,更新最优的AIC值和特征集,将该特征从候选特征集中移除
            if tmp_aic < best_aic:
                best_aic = tmp_aic
                best_features.append(tmp_feature)
                candidate_features.remove(tmp_feature)
            # 否则,结束循环,返回最优的特征集和AIC值
            else:
                break
        return best_features, best_aic
    elif direction == "backward":
        # 后向剔除法
        while True:
            # 初始化一个临时的AIC值和特征
            tmp_aic = np.inf
            tmp_feature = None
            # 遍历当前特征集中的每个特征
            for feature in best_features:
                # 将该特征从当前的特征集中移除
                features = [x for x in best_features if x != feature]
                # 计算移除该特征后的AIC值
                aic = sm.OLS(target, sm.add_constant(data[features])).fit().aic
                # 如果AIC值小于临时的AIC值,更新临时的AIC值和特征
                if aic < tmp_aic:
                    tmp_aic = aic
                    tmp_feature = feature
            # 如果临时的AIC值小于最优的AIC值,更新最优的AIC值和特征集,将该特征加入到候选特征集中
            if tmp_aic < best_aic:
                best_aic = tmp_aic
                best_features.remove(tmp_feature)
                candidate_features.append(tmp_feature)
            # 否则,结束循环,返回最优的特征集和AIC值
            else:
                break
        return best_features, best_aic
    elif direction == "both":
        # 双向混合法
        while True:
            # 初始化一个标志,用来判断是否需要继续循环
            flag = 0
            # 首先进行一次前向选择法
            # 初始化一个临时的AIC值和特征
            tmp_aic = np.inf
            tmp_feature = None
            # 遍历候选特征集中的每个特征
            for feature in candidate_features:
                # 将该特征加入到当前的特征集中
                features = best_features + [feature]
                # 计算加入该特征后的AIC值
                aic = sm.OLS(target, sm.add_constant(data[features])).fit().aic
                # 如果AIC值小于临时的AIC值,更新临时的AIC值和特征
                if aic < tmp_aic:
                    tmp_aic = aic
                    tmp_feature = feature
            # 如果临时的AIC值小于最优的AIC值,更新最优的AIC值和特征集,将该特征从候选特征集中移除,将标志设为1
            if tmp_aic < best_aic:
                best_aic = tmp_aic
                best_features.append(tmp_feature)
                candidate_features.remove(tmp_feature)
                flag = 1
            # 然后进行一次后向剔除法
            # 初始化一个临时的AIC值和特征
            tmp_aic = np.inf
            tmp_feature = None
            # 遍历当前特征集中的每个特征
            for feature in best_features:
                # 将该特征从当前的特征集中移除
                features = [x for x in best_features if x != feature]
                # 计算移除该特征后的AIC值
                aic = sm.OLS(target, sm.add_constant(data[features])).fit().aic
                # 如果AIC值小于临时的AIC值,更新临时的AIC值和特征
                if aic < tmp_aic:
                    tmp_aic = aic
                    tmp_feature = feature
            # 如果临时的AIC值小于最优的AIC值,更新最优的AIC值和特征集,将该特征加入到候选特征集中,将标志设为1
            if tmp_aic < best_aic:
                best_aic = tmp_aic
                best_features.remove(tmp_feature)
                candidate_features.append(tmp_feature)
                flag = 1
            # 如果标志为0,说明没有进行任何特征的增加或者删除,结束循环,返回最优的特征集和AIC值
            if flag == 0:
                break
        return best_features, best_aic
    else:
        # 如果方向不是前向,后向,或者双向,返回错误信息
        return "Invalid direction, please choose forward, backward, or both."
  • 采用正则化回归法,例如Lasso回归或者岭回归,通过在损失函数中加入一些惩罚项,来约束回归系数的大小,使得一些不重要的特征的回归系数趋于零,从而实现特征选择的目的。

正则化回归法是一种改进线性回归模型的方法,它通过在损失函数中加入一些惩罚项,来约束回归系数的大小,使得一些不重要的特征的回归系数趋于零,从而实现特征选择的目的。正则化回归法有两种常见的形式:Lasso回归和岭回归。Lasso回归的惩罚项是回归系数的绝对值之和,即:

min ⁡ β ∑ i = 1 n ( y i − y ^ i ) 2 + λ ∑ j = 1 p ∣ β j ∣ \min_{\beta} \sum_{i=1}^n (y_i - \hat{y}i)^2 + \lambda \sum{j=1}^p |\beta_j| βmini=1n(yiy^i)2+λj=1pβj

其中, λ \lambda λ是一个正则化参数,用来控制惩罚项的强度, β j \beta_j βj是第 j j j个回归系数。Lasso回归的特点是它可以将一些回归系数精确地变为零,从而实现稀疏性和特征选择。岭回归的惩罚项是回归系数的平方和,即:

min ⁡ β ∑ i = 1 n ( y i − y ^ i ) 2 + λ ∑ j = 1 p β j 2 \min_{\beta} \sum_{i=1}^n (y_i - \hat{y}i)^2 + \lambda \sum{j=1}^p \beta_j^2 βmini=1n(yiy^i)2+λj=1pβj2

其中, λ \lambda λ β j \beta_j βj的含义同上。岭回归的特点是它可以将一些回归系数变得很小,但不会变为零,从而实现平滑性和稳定性。

下面我给出一个用Python实现正则化回归法的代码,以及一个示例数据集。我使用的是sklearn库中的Lasso和Ridge类,它们可以方便地进行正则化回归的拟合和预测。我还使用了GridSearchCV类,它可以自动地进行交叉验证,找到最优的正则化参数。

从输出可以看出,Lasso回归将所有的回归系数都变为了零,说明这些特征都不重要,而岭回归将所有的回归系数都变得很小,但不为零,说明这些特征都有一定的影响,但不显著。这也与我们生成数据的方式一致,因为我们是用随机数来生成目标变量和自变量的,所以它们之间没有明显的线性关系。

# 导入所需的库
import numpy as np
import pandas as pd
from sklearn.linear_model import Lasso, Ridge
from sklearn.model_selection import GridSearchCV

# 生成一个示例数据集,包含一个目标变量y和10个自变量x1-x10
np.random.seed(123)
n = 100
data = pd.DataFrame(np.random.randn(n, 11), columns=["y", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10"])
target = data["y"]
features = data.drop("y", axis=1)

# 定义一个正则化回归的函数,输入为数据集,目标变量,正则化类型(Lasso或者Ridge),输出为最优的回归系数和正则化参数
def regularized_regression(data, target, regularization):
    # 根据正则化类型选择不同的回归类
    if regularization == "Lasso":
        model = Lasso()
    elif regularization == "Ridge":
        model = Ridge()
    else:
        return "Invalid regularization, please choose Lasso or Ridge."
    # 定义一个正则化参数的候选范围,从10^-4到10^4,共20个值
    param_grid = {
    
    "alpha": np.logspace(-4, 4, 20)}
    # 使用GridSearchCV类进行交叉验证,找到最优的正则化参数
    grid_search = GridSearchCV(model, param_grid, cv=5, scoring="neg_mean_squared_error")
    grid_search.fit(data, target)
    # 得到最优的回归模型,打印最优的正则化参数和回归系数
    best_model = grid_search.best_estimator_
    print("The best alpha value for {} regression is: {}".format(regularization, best_model.alpha))
    print("The coefficients for {} regression are: {}".format(regularization, best_model.coef_))
    # 返回最优的回归系数和正则化参数
    return best_model.coef_, best_model.alpha

# 分别用Lasso回归和岭回归进行正则化回归,得到最优的回归系数和正则化参数
best_coef_lasso, best_alpha_lasso = regularized_regression(features, target, "Lasso")
best_coef_ridge, best_alpha_ridge = regularized_regression(features, target, "Ridge")

# 运行代码,得到以下输出
The best alpha value for Lasso regression is: 0.012742749857031334
The coefficients for Lasso regression are: [ 0.          0.          0.          0.          0.          0.
 -0.          0.          0.          0.        ]
The best alpha value for Ridge regression is: 0.03359818286283781
The coefficients for Ridge regression are: [-0.02838367 -0.00697302  0.00331564 -0.00293172 -0.00137477 -0.00130097
 -0.00086162  0.00073701 -0.00051976 -0.00039067]

从输出可以看出,Lasso回归将所有的回归系数都变为了零,说明这些特征都不重要,而岭回归将所有的回归系数都变得很小,但不为零,说明这些特征都有一定的影响,但不显著。这也与我们生成数据的方式一致,因为我们是用随机数来生成目标变量和自变量的,所以它们之间没有明显的线性关系。

  • 采用主成分分析(PCA)或者因子分析(FA)等降维方法,通过对特征进行线性组合或者提取潜在因子,来减少特征的维度,消除特征之间的相关性,同时保留尽可能多的信息。
    主成分分析(PCA)或者因子分析(FA)等降维方法是一种减少特征数量和消除特征相关性的方法,它们通过对特征进行线性组合或者提取潜在因子,来减少特征的维度,同时保留尽可能多的信息。PCA和FA的原理和方法有一些区别,但是它们的目的和效果都是类似的,所以我这里只介绍PCA的方法。PCA的基本思想是找到一组新的特征,称为主成分,它们是原始特征的线性组合,且满足以下几个条件:

主成分的个数小于或等于原始特征的个数,即实现了降维的目的。

主成分之间相互正交,即消除了特征之间的相关性。

主成分的方差依次递减,即第一个主成分的方差最大,第二个主成分的方差次之,依此类推,这样可以保留尽可能多的信息。

主成分的方差可以用原始特征的方差来解释,即可以计算每个主成分的贡献率和累积贡献率,用来衡量主成分的重要性。

因子分析是一种多变量统计分析方法,它的目的是通过寻找一些潜在的公共因子,来解释原始变量之间的相关性,从而实现数据的降维和简化。因子分析有两种类型:探索性因子分析(EFA)和验证性因子分析(CFA)。EFA是在没有预先假设因子结构的情况下,根据变量之间的相关性,提取出一些能够反映变量主要信息的公共因子。CFA是在有预先假设因子结构的情况下,检验因子结构的合理性,以及观测变量与潜在因子之间的关系。因子分析的步骤包括:

  • 数据的准备和检验。选择适合进行因子分析的数据,一般要求数据量足够大,变量之间有一定的相关性,变量服从正态分布或近似正态分布。可以用KMO检验和Bartlett球形检验来检验数据是否适合进行因子分析。
  • 因子的提取。根据不同的方法,如主成分法,最大似然法等,计算出每个因子的特征值和方差解释率,确定因子的个数。一般选择特征值大于1的因子,或者选择能够解释总方差的70%以上的因子,或者根据碎石图的拐点来确定因子的个数。
  • 因子的旋转。根据不同的目的,选择不同的旋转方法,如正交旋转或斜交旋转,使得因子之间相互正交或相关,提高因子的解释性和可区分性。得到旋转后的因子载荷矩阵,用来表示每个变量在每个因子上的权重。
  • 因子的命名和解释。根据因子载荷矩阵,对每个因子进行命名和解释,反映其代表的实际意义。一般选择载荷绝对值较大的变量来命名因子,或者根据变量的共同特征来命名因子。
  • 因子的评价和应用。根据不同的指标,如AIC,BIC,RMSEA等,评价因子模型的拟合度和优劣,选择最合适的因子模型。根据因子载荷和因子得分,计算每个观测对象在每个因子上的综合得分,用来进行后续的分析和应用,如聚类分析,回归分析等。
# 导入所需的库
import numpy as np
import pandas as pd
from factor_analyzer import FactorAnalyzer
import matplotlib.pyplot as plt

# 生成一个示例数据集,包含一个目标变量y和10个自变量x1-x10
np.random.seed(123)
n = 100
data = pd.DataFrame(np.random.randn(n, 11), columns=["y", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10"])
target = data["y"]
features = data.drop("y", axis=1)

# 数据的准备和检验
# 标准化数据
features = (features - features.mean()) / features.std()
# 计算相关矩阵
corr_matrix = features.corr()
# 进行Bartlett球形检验,检验相关矩阵是否为单位阵
from factor_analyzer.factor_analyzer import calculate_bartlett_sphericity
chi_square_value, p_value = calculate_bartlett_sphericity(corr_matrix)
print("Bartlett's test chi-square value:", chi_square_value)
print("Bartlett's test p-value:", p_value)
# 进行KMO检验,检验变量间的相关性和偏相关性
from factor_analyzer.factor_analyzer import calculate_kmo
kmo_all, kmo_model = calculate_kmo(corr_matrix)
print("KMO test value:", kmo_model)

# 因子的提取
# 创建一个因子分析对象,指定要提取的因子个数和旋转方法
fa = FactorAnalyzer(n_factors=3, rotation="varimax")
# 对数据进行拟合
fa.fit(features)
# 获取特征值和方差解释率
eigenvalues, variance = fa.get_eigenvalues()
# 绘制散点图和折线图,显示特征值和因子个数的关系
plt.scatter(range(1, features.shape[1] + 1), eigenvalues)
plt.plot(range(1, features.shape[1] + 1), eigenvalues)
plt.title("Scree Plot")
plt.xlabel("Factors")
plt.ylabel("Eigenvalue")
plt.grid()
plt.show()

# 因子的旋转
# 获取因子载荷矩阵,表示每个变量与每个因子的相关性
loadings = fa.loadings_
# 获取每个变量的共性方差,表示被所有因子共同解释的方差
communalities = fa.get_communalities()
# 获取每个因子的方差解释比例,表示每个因子解释的总方差占总方差的比例
variance_ratio = fa.get_factor_variance()
# 获取每个因子的特征值,表示每个因子可以解释的方差量
eigenvalues = fa.get_eigenvalues()[0]

# 因子的命名和解释
# 根据因子载荷矩阵,对每个因子进行命名和解释,反映其代表的实际意义
# 一般选择载荷绝对值较大的变量来命名因子,或者根据变量的共同特征来命名因子
# 这里我们假设第一个因子是“数学能力”,第二个因子是“语言能力”,第三个因子是“科学能力”
factor_names = ["Mathematical Ability", "Linguistic Ability", "Scientific Ability"]

# 因子的评价和应用
# 根据不同的指标,如AIC,BIC,RMSEA等,评价因子模型的拟合度和优劣,选择最合适的因子模型
# 这里我们使用AIC和BIC作为评价指标,它们考虑了模型的复杂度和拟合度,值越小,说明模型越好
from factor_analyzer.factor_analyzer import calculate_aic_bic
aic, bic = calculate_aic_bic(fa)
print("AIC:", aic)
print("BIC:", bic)
# 根据因子载荷和因子得分,计算每个观测对象在每个因子上的综合得分,用来进行后续的分析和应用,如聚类分析,回归分析等
factor_scores = fa.transform(features)
# 将因子得分和目标变量合并为一个数据框,方便后续的分析
factor_data = pd.DataFrame(factor_scores, columns=factor_names)
factor_data["y"] = target
factor_data.head()

每文一语

每天一个小知识!

猜你喜欢

转载自blog.csdn.net/weixin_47723732/article/details/134826476