二项回归#

本笔记本涵盖了二项回归背后的逻辑,它是广义线性建模的一个特定实例。示例非常简单,只有一个预测变量。

为了理解何时适用二项回归,回顾逻辑回归会有所帮助。当你的结果变量是一系列成功或失败,即一系列 01 观测值时,逻辑回归非常有用。这种结果变量的一个例子是“你今天去跑步了吗?”。当你有一定数量的成功次数(在 \(n\) 次试验中)时,二项回归(也称为聚合二项回归)非常有用。因此,示例将是“过去 7 天你跑了多少天?”

观测数据是一组在 \(n\) 次总试验中成功的计数。 许多人可能倾向于将此数据简化为比例,但这不一定是好主意。 例如,比例不是直接测量的,它们通常最好被视为要估计的潜在变量。 此外,比例会丢失信息:0.5 的比例可能对应于 2 天中跑 1 次,或者过去 4 周中跑 4 次,或者许多其他情况,但你已经通过仅关注比例而丢失了该信息。

二项回归的适当似然函数是二项分布

\[ y_i \sim \text{Binomial}(n, p_i) \]

其中 \(y_i\)\(n\) 次试验中成功次数的计数,而 \(p_i\) 是(潜在的)成功概率。 二项回归想要实现的目标是使用线性模型来准确估计 \(p_i\)(即 \(p_i = \beta_0 + \beta_1 \cdot x_i\))。 因此,我们可以尝试使用如下似然项来做到这一点

\[ y_i \sim \text{Binomial}(n, \beta_0 + \beta_1 \cdot x_i) \]

如果我们这样做,当线性模型生成 \(p\) 值超出 \(0-1\) 范围时,我们将很快遇到问题。 这就是链接函数发挥作用的地方

\[ g(p_i) = \beta_0 + \beta_1 \cdot x_i \]

其中 \(g()\) 是链接函数。 这可以被认为是将 \((0, 1)\) 范围内的比例映射到 \((-\infty, +\infty)\) 域的转换。 有许多潜在的函数可以使用,但常用的函数是 Logit 函数

虽然我们实际想要做的是为 \(p_i\) 重新排列此方程,以便我们可以将其输入到似然函数中。 这导致

\[ p_i= g^{-1}(\beta_0 + \beta_1 \cdot x_i) \]

其中 \(g^{-1}()\) 是链接函数的逆函数,在本例中是 Logit 函数的逆函数(即 logistic sigmoid 函数,也称为 expit 函数)。 因此,如果我们将其输入到我们的似然函数中,我们最终得到

\[ y_i \sim \text{Binomial}(n, \text{InverseLogit}(\beta_0 + \beta_1 \cdot x_i)) \]

这定义了我们的似然函数。 现在,要完成一些贝叶斯二项回归,你只需要 \(\beta\) 参数的先验。 观测数据是 \(y_i\)\(n\)\(x_i\)

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pymc as pm

from scipy.special import expit
%config InlineBackend.figure_format = 'retina'
az.style.use("arviz-darkgrid")
rng = np.random.default_rng(1234)

生成数据#

# true params
beta0_true = 0.7
beta1_true = 0.4
# number of yes/no questions
n = 20

sample_size = 30
x = np.linspace(-10, 20, sample_size)
# Linear model
mu_true = beta0_true + beta1_true * x
# transformation (inverse logit function = expit)
p_true = expit(mu_true)
# Generate data
y = rng.binomial(n, p_true)
# bundle data into dataframe
data = pd.DataFrame({"x": x, "y": y})

我们可以看到,底层数据 \(y\) 是计数数据,在 \(n\) 次总试验中。

data.head()
x y
0 -10.000000 3
1 -8.965517 1
2 -7.931034 3
3 -6.896552 1
4 -5.862069 2
隐藏代码单元格源
# Plot underlying linear model
fig, ax = plt.subplots(2, 1, figsize=(9, 6), sharex=True)
ax[0].plot(x, mu_true, label=r"$β_0 + β_1 \cdot x_i$")
ax[0].set(xlabel="$x$", ylabel=r"$β_0 + β_1 \cdot x_i$", title="Underlying linear model")
ax[0].legend()

# Plot GLM
freq = ax[1].twinx()  # instantiate a second axes that shares the same x-axis
freq.set_ylabel("number of successes")
freq.scatter(x, y, color="k")
# plot proportion related stuff on ax[1]
ax[1].plot(x, p_true, label=r"$g^{-1}(β_0 + β_1 \cdot x_i)$")
ax[1].set_ylabel("proportion successes", color="b")
ax[1].tick_params(axis="y", labelcolor="b")
ax[1].set(xlabel="$x$", title="Binomial regression")
ax[1].legend()
# get y-axes to line up
y_buffer = 1
freq.set(ylim=[-y_buffer, n + y_buffer])
ax[1].set(ylim=[-(y_buffer / n), 1 + (y_buffer / n)])
freq.grid(None)
../_images/47b2df508c92dc27eed40a33b0d251d3b893b2509296aaf89312352c4cbf31c5.png

顶部面板显示(未转换的)线性模型。 我们可以看到,线性模型正在生成超出 \(0-1\) 范围的值,这清楚地表明需要逆链接函数 \(g^{-1}()\),它从 \((-\infty, +\infty) \rightarrow (0, 1)\) 域进行转换。 正如我们所见,这是通过逆逻辑函数(又名逻辑 sigmoid 函数)完成的。

二项回归模型#

从技术上讲,我们不需要提供 coords,但提供此项(观测值列表)有助于稍后重塑数据数组。 coords 中的信息由模型中的 dims kwarg 使用。

coords = {"observation": data.index.values}

with pm.Model(coords=coords) as binomial_regression_model:
    x = pm.ConstantData("x", data["x"], dims="observation")
    # priors
    beta0 = pm.Normal("beta0", mu=0, sigma=1)
    beta1 = pm.Normal("beta1", mu=0, sigma=1)
    # linear model
    mu = beta0 + beta1 * x
    p = pm.Deterministic("p", pm.math.invlogit(mu), dims="observation")
    # likelihood
    pm.Binomial("y", n=n, p=p, observed=data["y"], dims="observation")
pm.model_to_graphviz(binomial_regression_model)
../_images/416e190e3cba27b9495347b6b596b1c634994cf1b232c9b02da86c99b9e3d03c.svg
with binomial_regression_model:
    idata = pm.sample(1000, tune=2000)
100.00% [12000/12000 00:00<00:00 采样 4 条链, 0 个发散]
Sampling 4 chains for 2_000 tune and 1_000 draw iterations (8_000 + 4_000 draws total) took 1 seconds.

通过目视检查链来确认没有推断问题。 我们没有收到关于发散、\(\hat{R}\) 或有效样本量的警告。 一切看起来都很好。

az.plot_trace(idata, var_names=["beta0", "beta1"]);
../_images/a70f2638224ceaa8f1ab9ecd4f28e75ffd0b32b3c0c0601688f2810a4e204540.png

检查结果#

下面的代码在数据空间中绘制模型预测,并在参数空间中绘制我们的后验信念。

隐藏代码单元格源
fig, ax = plt.subplots(1, 2, figsize=(9, 4), gridspec_kw={"width_ratios": [2, 1]})

# Data space plot ========================================================
az.plot_hdi(
    data["x"],
    idata.posterior.p,
    hdi_prob=0.95,
    fill_kwargs={"alpha": 0.25, "linewidth": 0},
    ax=ax[0],
    color="C1",
)
# posterior mean
post_mean = idata.posterior.p.mean(("chain", "draw"))
ax[0].plot(data["x"], post_mean, label="posterior mean", color="C1")
# plot truth
ax[0].plot(data["x"], p_true, "--", label="true", color="C2")
# formatting
ax[0].set(xlabel="x", title="Data space")
ax[0].set_ylabel("proportion successes", color="C1")
ax[0].tick_params(axis="y", labelcolor="C1")
ax[0].legend()
# instantiate a second axes that shares the same x-axis
freq = ax[0].twinx()
freq.set_ylabel("number of successes")
freq.scatter(data["x"], data["y"], color="k", label="data")
# get y-axes to line up
y_buffer = 1
freq.set(ylim=[-y_buffer, n + y_buffer])
ax[0].set(ylim=[-(y_buffer / n), 1 + (y_buffer / n)])
freq.grid(None)
# set both y-axis to have 5 ticks
ax[0].set(yticks=np.linspace(0, 20, 5) / n)
freq.set(yticks=np.linspace(0, 20, 5))

# Parameter space plot ===================================================
az.plot_kde(
    az.extract(idata, var_names="beta0"),
    az.extract(idata, var_names="beta1"),
    contourf_kwargs={"cmap": "Blues"},
    ax=ax[1],
)
ax[1].plot(beta0_true, beta1_true, "C2o", label="true")
ax[1].set(xlabel=r"$\beta_0$", ylabel=r"$\beta_1$", title="Parameter space")
ax[1].legend(facecolor="white", frameon=True);
../_images/2af54aa3c6d0dcc893750608896f94dc41d623fee75756f05e3e97b629e9718a.png

左侧面板显示后验均值(实线)和 95% 可信区间(阴影区域)。 因为我们正在使用模拟数据,所以我们知道真实模型是什么,因此我们可以看到后验均值与真实数据生成模型相比表现良好。

参数空间(右侧面板)上的后验分布也显示了这一点,当与真实数据生成参数进行比较时,它表现良好。

在实际数据分析情况下使用二项回归可能涉及更多的预测变量,以及相应更多的模型参数,但希望这个例子已经演示了二项回归背后的逻辑。

Roback 和 Legler [2021] 提供了一个关于广义线性模型的很好的介绍,该介绍有精装本和免费在线版本

作者#

参考文献#

[1]

Roback, P. 和 J. Legler. 超越多元线性回归:R 中的应用广义线性模型和多层模型. CRC Press, 2021. URL: https://bookdown.org/roback/bookdown-BeyondMLR/.

水印#

%load_ext watermark
%watermark -n -u -v -iv -w -p pytensor,aeppl
Last updated: Sun Feb 05 2023

Python implementation: CPython
Python version       : 3.10.9
IPython version      : 8.9.0

pytensor: 2.8.11
aeppl   : not installed

matplotlib: 3.6.3
numpy     : 1.24.1
arviz     : 0.14.0
pandas    : 1.5.3
pymc      : 5.0.1

Watermark: 2.3.1