累積ロジットモデル(cumulative logit model)#
比例オッズモデル(proportional odds model) や 累積ロジットモデル(cumulative logit model) と呼ばれる
順序ロジットモデル(ordered logit model) とも呼ばれる様子(Ordered logit - Wikipedia)
潜在連続変数モデル(Latent Variable Formulation)#
順序ロジットでは、\(y \in \{1,2,\dots,K\}\) という順序カテゴリは、背後に潜在連続変数
が存在し、それを閾値(cutpoint)\(\tau_k\)で区切った結果であると考える:
比例オッズモデル(proportional odds model)#
この仮定のもとで、累積確率は次のように表される:
各カテゴリに属する確率\(P(y = k \mid \mathbf{x})\)は、累積確率の差で表現する。
つまり
と表される。
端のカテゴリの扱い#
両端の閾値は\(-\infty, \infty\)と仮定する。
よって、両端の確率は\(0, 1\)と仮定する
こうすることで、
最小カテゴリ: \(P(y=1)=P(y \le 1)\)
最大カテゴリ: \(P(y=K)=1 - P(y \le K-1)\)
と書ける。
なお、累積確率の両辺のロジットをとると
と表すこともできる
比例オッズ仮定(Proportional Odds Assumption)#
比例オッズモデル(proportional odds model)と累積ロジットモデル(cumulative logit model)を区別しない文献もあるが、区別する文献においては、比例オッズモデルは
切片(\(\tau_k\))は \(k\) ごとに異なる
傾き(\(\beta\))はすべて共通
という制約(parallel slopes constraint)を課すモデルであるとされる。
そして累積ロジットモデルではカテゴリごとに異なる傾き\(\beta_k\)を持つとされる…が、共通の傾きを持つように書く文献のほうが多い
実装#
サンプルデータ#
gpa:GPA。\([0,4]\)のfloatpared:少なくとも片方の親は大学院に進学している。二値変数。public:学生が現在在籍している大学がpublicかprivateかapply: unlikely < somewhat likely < very likely の順序変数
import pandas as pd
url = "https://stats.idre.ucla.edu/stat/data/ologit.dta"
df = pd.read_stata(url)
y = df['apply']
X = df[['pared', 'public', 'gpa']]
df.head(5)
| apply | pared | public | gpa | |
|---|---|---|---|---|
| 0 | very likely | 0 | 0 | 3.26 |
| 1 | somewhat likely | 1 | 0 | 3.21 |
| 2 | unlikely | 1 | 1 | 3.94 |
| 3 | somewhat likely | 0 | 0 | 2.81 |
| 4 | somewhat likely | 0 | 0 | 2.53 |
statsmodelsによる実装(最尤推定)#
import numpy as np
import pandas as pd
import scipy.stats as stats
from statsmodels.miscmodels.ordinal_model import OrderedModel
model = OrderedModel(y, X, distr='logit')
result = model.fit(method='bfgs')
result.summary()
Optimization terminated successfully.
Current function value: 0.896281
Iterations: 22
Function evaluations: 24
Gradient evaluations: 24
| Dep. Variable: | apply | Log-Likelihood: | -358.51 |
|---|---|---|---|
| Model: | OrderedModel | AIC: | 727.0 |
| Method: | Maximum Likelihood | BIC: | 747.0 |
| Date: | Sat, 14 Feb 2026 | ||
| Time: | 09:40:06 | ||
| No. Observations: | 400 | ||
| Df Residuals: | 395 | ||
| Df Model: | 3 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| pared | 1.0476 | 0.266 | 3.942 | 0.000 | 0.527 | 1.569 |
| public | -0.0586 | 0.298 | -0.197 | 0.844 | -0.642 | 0.525 |
| gpa | 0.6158 | 0.261 | 2.363 | 0.018 | 0.105 | 1.127 |
| unlikely/somewhat likely | 2.2035 | 0.780 | 2.827 | 0.005 | 0.676 | 3.731 |
| somewhat likely/very likely | 0.7398 | 0.080 | 9.236 | 0.000 | 0.583 | 0.897 |
PyMCによる実装(MCMC)#
PyMCのOrderedLogisticでは予測子(predictor)をeta \(\eta = x^\top \beta\)、閾値\(\tau\)をcutpointsで表す。
参考:
import numpy as np
import pymc as pm
import pytensor.tensor as pt
from statsmodels.miscmodels.ordinal_model import OrderedModel
N, M = X.shape
y_num = y.cat.codes
with pm.Model() as model:
beta = pm.Normal("beta", mu=0, sigma=1, shape=M)
eta = X.to_numpy() @ beta
cutpoints = pm.Normal("cutpoints", mu=[-1,1], sigma=10, shape=2,
transform=pm.distributions.transforms.ordered)
y_ = pm.OrderedLogistic("y", cutpoints=cutpoints, eta=eta, observed=y_num)
idata = pm.sample()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, cutpoints]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
# EAP(事後平均)推定量
beta_EAP = idata.posterior["beta"].mean(dim=("chain", "draw"))
pd.DataFrame({
"exog": X.columns,
"EAP": beta_EAP.to_numpy().round(4),
})
| exog | EAP | |
|---|---|---|
| 0 | pared | 0.9995 |
| 1 | public | -0.0572 |
| 2 | gpa | 0.5796 |