逻辑回归statsmodels的概率预测置信区间

16

我正在尝试重新创建An Introduction to Statistical Learning中的一个图表,但我无法确定如何计算概率预测的置信区间。具体来说,我正试图重新创建此图的右侧面板(figure 7.1),该面板基于年龄的4次多项式预测了工资高于250的概率,并给出了相应的95%置信区间。如果有人感兴趣,这里是工资数据(链接)

使用以下代码,我可以很好地预测和绘制预测概率:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from sklearn.preprocessing import PolynomialFeatures

wage = pd.read_csv('../../data/Wage.csv', index_col=0)
wage['wage250'] = 0
wage.loc[wage['wage'] > 250, 'wage250'] = 1

poly = Polynomialfeatures(degree=4)
age = poly.fit_transform(wage['age'].values.reshape(-1, 1))

logit = sm.Logit(wage['wage250'], age).fit()

age_range_poly = poly.fit_transform(np.arange(18, 81).reshape(-1, 1))

y_proba = logit.predict(age_range_poly)

plt.plot(age_range_poly[:, 1], y_proba)

但是我不知道如何计算预测概率的置信区间。 我多次考虑过对数据进行自助法重抽样,以获得每个年龄段概率的分布,但我知道还有一种更简单的方法,只是我无法理解。

我有估计的系数协方差矩阵和与每个估计系数相关的标准误差。 在拥有这些信息的情况下,如何计算右侧面板中所示的置信区间?

谢谢!

2个回答

31
你可以使用增量法来找到预测概率的近似方差。也就是说,
var(proba) = np.dot(np.dot(gradient.T, cov), gradient)

其中gradient是模型系数预测概率的导数向量,cov是系数协方差矩阵。

Delta方法已被证明在所有最大似然估计中渐近地有效。但是,如果您的训练样本很小,则渐近方法可能效果不佳,您应该考虑引入自助法。

以下是将Delta方法应用于逻辑回归的示例:

import numpy as np
import statsmodels.api as sm
import matplotlib.pyplot as plt

# generate data
np.random.seed(1)
x = np.arange(100)
y = (x * 0.5 + np.random.normal(size=100,scale=10)>30)
# estimate the model
X = sm.add_constant(x)
model = sm.Logit(y, X).fit()
proba = model.predict(X) # predicted probability

# estimate confidence interval for predicted probabilities
cov = model.cov_params()
gradient = (proba * (1 - proba) * X.T).T # matrix of gradients for each observation
std_errors = np.array([np.sqrt(np.dot(np.dot(g, cov), g)) for g in gradient])
c = 1.96 # multiplier for confidence interval
upper = np.maximum(0, np.minimum(1, proba + std_errors * c))
lower = np.maximum(0, np.minimum(1, proba - std_errors * c))

plt.plot(x, proba)
plt.plot(x, lower, color='g')
plt.plot(x, upper, color='g')
plt.show()

它绘制了以下漂亮的图片:enter image description here

对于您的示例,代码将是:

proba = logit.predict(age_range_poly)
cov = logit.cov_params()
gradient = (proba * (1 - proba) * age_range_poly.T).T 
std_errors = np.array([np.sqrt(np.dot(np.dot(g, cov), g)) for g in gradient])
c = 1.96 
upper = np.maximum(0, np.minimum(1, proba + std_errors * c))
lower = np.maximum(0, np.minimum(1, proba - std_errors * c))

plt.plot(age_range_poly[:, 1], proba)
plt.plot(age_range_poly[:, 1], lower, color='g')
plt.plot(age_range_poly[:, 1], upper, color='g')
plt.show()

然后它将呈现以下图片

enter image description here

看起来很像一条囊括了一只大象的巨蟒。

您可以将其与引导估计进行比较:

preds = []
for i in range(1000):
    boot_idx = np.random.choice(len(age), replace=True, size=len(age))
    model = sm.Logit(wage['wage250'].iloc[boot_idx], age[boot_idx]).fit(disp=0)
    preds.append(model.predict(age_range_poly))
p = np.array(preds)
plt.plot(age_range_poly[:, 1], np.percentile(p, 97.5, axis=0))
plt.plot(age_range_poly[:, 1], np.percentile(p, 2.5, axis=0))
plt.show()

enter image description here

差分法和 bootstrap 的结果看起来非常相似。

然而,这本书的作者采用了第三种方法。他们利用了以下事实:

proba = np.exp(np.dot(x, params)) / (1 + np.exp(np.dot(x, params)))

并计算线性部分的置信区间,然后再使用 logit 函数进行转换。

xb = np.dot(age_range_poly, logit.params)
std_errors = np.array([np.sqrt(np.dot(np.dot(g, cov), g)) for g in age_range_poly])
upper_xb = xb + c * std_errors
lower_xb = xb - c * std_errors
upper = np.exp(upper_xb) / (1 + np.exp(upper_xb))
lower = np.exp(lower_xb) / (1 + np.exp(lower_xb))
plt.plot(age_range_poly[:, 1], upper)
plt.plot(age_range_poly[:, 1], lower)
plt.show()

所以他们得到了分歧区间:

enter image description here

这些方法产生如此不同的结果,是因为它们假定分布正常的“不同事物”(预测的概率和对数几率)。也就是说,Delta方法假定预测的概率是正常的,在书中,对数几率是正常的。实际上,它们在有限样本中都不是正态的,它们都在无限样本中收敛于正态,但它们的方差同时收敛于零。最大似然估计对重新参数化不敏感,但它们的估计分布是敏感的,这就是问题所在。

非常好的回答,David,谢谢!那些分歧的置信区间真的让我困扰不已。 - Taylor
@DavidDale 很好的回答,但如果您澄清哪种方法假设预测概率服从正态分布(Delta方法),哪种方法假设对数几率服从正态分布(“转换”方法,即您展示的最后一个图),那将更好。 - DeltaIV
嗨,David,非常好的答案 - 我正在尝试使用Sklearn.LogisticRegression重现您的结果,但是predict_proba的结果不同 - 您认为这是为什么? - Xavier Bourret Sicotte
嗨,大卫,你使用置信区间计算的线性部分会给我们预测响应的预测区间吗?还是均值响应的置信区间?如果它提供了置信区间,那么我们如何计算预测区间? - pasternak
我计算均值响应的置信区间。这是二元分类,因此预测区间始终为{0}、{1}或[0,1]。我认为这样的区间意义不大。 - David Dale

1
这是一种教育性和高效的方法,用于计算与statsmodels Logit().fit()对象('fit')相关的拟合('mean_se')和单个观测值('obs_se')的标准误差('se'),与书籍ISLR中的方法相同,并与David Dale的最后一种方法相同。
fit_mean = fit.model.exog.dot(fit.params)
fit_mean_se = ((fit.model.exog*fit.model.exog.dot(fit.cov_params())).sum(axis=1))**0.5
fit_obs_se = ( ((fit.model.endog-fit_mean).std(ddof=fit.params.shape[0]))**2 + \
                fit_mean_se**2 )**0.5

与《ISLR》中的类似图形

阴影区域表示拟合和单个观测值的95%置信区间。

非常欢迎改进意见。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接