順序回帰

[1]:
import numpy as np
import pandas as pd
import scipy.stats as stats

from statsmodels.miscmodels.ordinal_model import OrderedModel

UCLA のウェブサイトから Stata データファイルを読み込んでいます。このノートブックは、UCLA の R ノートブックである https://stats.idre.ucla.edu/r/dae/ordinal-logistic-regression/ からヒントを得ています。

[2]:
url = "https://stats.idre.ucla.edu/stat/data/ologit.dta"
data_student = pd.read_stata(url)
[3]:
data_student.head(5)
[3]:
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
[4]:
data_student.dtypes
[4]:
apply     category
pared         int8
public        int8
gpa        float32
dtype: object
[5]:
data_student['apply'].dtype
[5]:
CategoricalDtype(categories=['unlikely', 'somewhat likely', 'very likely'], ordered=True)

このデータセットは、三つの外生変数を基に学部生が大学院に出願する確率に関するものです:

  • ``gpa``: 成績平均点を示す値( 0 から 4 の範囲の浮動小数点数)。

  • ``pared``: 少なくとも 1 人の親が大学院に進学したかどうかを示すバイナリ変数。

  • ``public``: 学生が現在在籍している学部が公立か私立かを示すバイナリ変数。

``apply``(目的変数)は、順序付きカテゴリ変数で、以下のカテゴリがあります:
unlikely < somewhat likely < very likely
この変数は、カテゴリカル型のpd.Seriesとして定義されています。これは、NumPy 配列よりも推奨される形式です。
このモデルは、観測することはできないが外生変数を用いて計算可能な数値的潜在変数 \(y_{latent}\) に基づいています。
さらに、この \(y_{latent}\) を使用して、観測可能な \(y\) を定義することができます。

詳細については、OrderedModelのドキュメント、UCLAのウェブページ またはこの 書籍 を参照してください。

プロビット順序回帰:

[6]:
mod_prob = OrderedModel(data_student['apply'],
                        data_student[['pared', 'public', 'gpa']],
                        distr='probit')

res_prob = mod_prob.fit(method='bfgs')
res_prob.summary()
Optimization terminated successfully.
         Current function value: 0.896869
         Iterations: 17
         Function evaluations: 21
         Gradient evaluations: 21
[6]:
OrderedModel Results
Dep. Variable: apply Log-Likelihood: -358.75
Model: OrderedModel AIC: 727.5
Method: Maximum Likelihood BIC: 747.5
Date: Thu, 29 Aug 2024
Time: 10:03:21
No. Observations: 400
Df Residuals: 395
Df Model: 3
coef std err z P>|z| [0.025 0.975]
pared 0.5981 0.158 3.789 0.000 0.289 0.908
public 0.0102 0.173 0.059 0.953 -0.329 0.349
gpa 0.3582 0.157 2.285 0.022 0.051 0.665
unlikely/somewhat likely 1.2968 0.468 2.774 0.006 0.381 2.213
somewhat likely/very likely 0.1873 0.074 2.530 0.011 0.042 0.332

私たちのモデルでは、三つの外生変数(文書の表記に従うと\(\beta\)s)があるため、推定する必要がある3つの係数があります。

これらの三つの推定値とその標準誤差は、サマリーテーブルで取得できます。

ターゲット変数には三つのカテゴリ(「unlikely」、「somewhat likely」、「very likely」)があるため、二つの閾値を推定する必要があります。
OrderedModel.transform_threshold_paramsメソッドのドキュメントで説明されているように、最初に推定される閾値は実際の値であり、他のすべての閾値は累積的な指数増分の形で表されます。実際の閾値は次のように計算できます:
[7]:
num_of_thresholds = 2
mod_prob.transform_threshold_params(res_prob.params[-num_of_thresholds:])
[7]:
array([      -inf, 1.29684541, 2.50285885,        inf])

ロジット順序回帰:

[8]:
mod_log = OrderedModel(data_student['apply'],
                        data_student[['pared', 'public', 'gpa']],
                        distr='logit')

res_log = mod_log.fit(method='bfgs', disp=False)
res_log.summary()
[8]:
OrderedModel Results
Dep. Variable: apply Log-Likelihood: -358.51
Model: OrderedModel AIC: 727.0
Method: Maximum Likelihood BIC: 747.0
Date: Thu, 29 Aug 2024
Time: 10:03:22
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
[9]:
predicted = res_log.model.predict(res_log.params, exog=data_student[['pared', 'public', 'gpa']])
predicted
[9]:
array([[0.54884071, 0.35932276, 0.09183653],
       [0.30558191, 0.47594216, 0.21847593],
       [0.22938356, 0.47819057, 0.29242587],
       ...,
       [0.69380357, 0.25470075, 0.05149568],
       [0.54884071, 0.35932276, 0.09183653],
       [0.50896793, 0.38494062, 0.10609145]])
[10]:
pred_choice = predicted.argmax(1)
print('Fraction of correct choice predictions')
print((np.asarray(data_student['apply'].values.codes) == pred_choice).mean())
Fraction of correct choice predictions
0.5775

カスタム累積 cLogLog 分布を使用した順序回帰:

logitおよびprobit回帰に加えて、SciPy.statsパッケージからの任意の連続分布をdistr引数に使用することができます。 また、独自の分布を定義する場合は、rv_continuousからサブクラスを作成し、いくつかのメソッドを実装することで簡単に行うことができます。

[11]:
# using a SciPy distribution
res_exp = OrderedModel(data_student['apply'],
                           data_student[['pared', 'public', 'gpa']],
                           distr=stats.expon).fit(method='bfgs', disp=False)
res_exp.summary()
[11]:
OrderedModel Results
Dep. Variable: apply Log-Likelihood: -360.84
Model: OrderedModel AIC: 731.7
Method: Maximum Likelihood BIC: 751.6
Date: Thu, 29 Aug 2024
Time: 10:03:22
No. Observations: 400
Df Residuals: 395
Df Model: 3
coef std err z P>|z| [0.025 0.975]
pared 0.4690 0.117 4.021 0.000 0.240 0.698
public -0.1308 0.149 -0.879 0.379 -0.422 0.161
gpa 0.2198 0.134 1.638 0.101 -0.043 0.483
unlikely/somewhat likely 1.5370 0.405 3.792 0.000 0.742 2.332
somewhat likely/very likely 0.4082 0.093 4.403 0.000 0.226 0.590
[12]:
# minimal definition of a custom scipy distribution.
class CLogLog(stats.rv_continuous):
    def _ppf(self, q):
        return np.log(-np.log(1 - q))

    def _cdf(self, x):
        return 1 - np.exp(-np.exp(x))


cloglog = CLogLog()

# definition of the model and fitting
res_cloglog = OrderedModel(data_student['apply'],
                           data_student[['pared', 'public', 'gpa']],
                           distr=cloglog).fit(method='bfgs', disp=False)
res_cloglog.summary()
[12]:
OrderedModel Results
Dep. Variable: apply Log-Likelihood: -359.75
Model: OrderedModel AIC: 729.5
Method: Maximum Likelihood BIC: 749.5
Date: Thu, 29 Aug 2024
Time: 10:03:22
No. Observations: 400
Df Residuals: 395
Df Model: 3
coef std err z P>|z| [0.025 0.975]
pared 0.5167 0.161 3.202 0.001 0.200 0.833
public 0.1081 0.168 0.643 0.520 -0.221 0.438
gpa 0.3344 0.154 2.168 0.030 0.032 0.637
unlikely/somewhat likely 0.8705 0.455 1.912 0.056 -0.022 1.763
somewhat likely/very likely 0.0989 0.071 1.384 0.167 -0.041 0.239

式の使用 – endog の処理

Pandas の順序カテゴリ値と数値は、式の従属変数としてサポートされています。他のタイプでは ValueError が発生します。

[13]:
modf_logit = OrderedModel.from_formula("apply ~ 0 + pared + public + gpa", data_student,
                                      distr='logit')
resf_logit = modf_logit.fit(method='bfgs')
resf_logit.summary()
Optimization terminated successfully.
         Current function value: 0.896281
         Iterations: 22
         Function evaluations: 24
         Gradient evaluations: 24
[13]:
OrderedModel Results
Dep. Variable: apply Log-Likelihood: -358.51
Model: OrderedModel AIC: 727.0
Method: Maximum Likelihood BIC: 747.0
Date: Thu, 29 Aug 2024
Time: 10:03:22
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

従属変数に数値コードを使用することはサポートされていますが、カテゴリ・レベルの名前は失われます。レベルと名前は、式を使用しない場合と同様に、英数字順にソートされた従属変数の一意の値に対応します。

[14]:
data_student["apply_codes"] = data_student['apply'].cat.codes * 2 + 5
data_student["apply_codes"].head()
[14]:
0    9
1    7
2    5
3    7
4    7
Name: apply_codes, dtype: int8
[15]:
OrderedModel.from_formula("apply_codes ~ 0 + pared + public + gpa", data_student,
                          distr='logit').fit().summary()
Optimization terminated successfully.
         Current function value: 0.896281
         Iterations: 421
         Function evaluations: 663
[15]:
OrderedModel Results
Dep. Variable: apply_codes Log-Likelihood: -358.51
Model: OrderedModel AIC: 727.0
Method: Maximum Likelihood BIC: 747.0
Date: Thu, 29 Aug 2024
Time: 10:03:22
No. Observations: 400
Df Residuals: 395
Df Model: 3
coef std err z P>|z| [0.025 0.975]
pared 1.0477 0.266 3.942 0.000 0.527 1.569
public -0.0587 0.298 -0.197 0.844 -0.642 0.525
gpa 0.6157 0.261 2.362 0.018 0.105 1.127
5.0/7.0 2.2033 0.780 2.826 0.005 0.675 3.731
7.0/9.0 0.7398 0.080 9.236 0.000 0.583 0.897
[16]:
resf_logit.predict(data_student.iloc[:5])
[16]:
0 1 2
0 0.548841 0.359323 0.091837
1 0.305582 0.475942 0.218476
2 0.229384 0.478191 0.292426
3 0.616118 0.312690 0.071191
4 0.656003 0.283398 0.060599

文字列値を従属変数として直接使用すると、ValueError が発生します。

[17]:
data_student["apply_str"] = np.asarray(data_student["apply"])
data_student["apply_str"].head()
[17]:
0        very likely
1    somewhat likely
2           unlikely
3    somewhat likely
4    somewhat likely
Name: apply_str, dtype: object
[18]:
data_student.apply_str = pd.Categorical(data_student.apply_str, ordered=True)
data_student.public = data_student.public.astype(float)
data_student.pared = data_student.pared.astype(float)
[19]:
OrderedModel.from_formula("apply_str ~ 0 + pared + public + gpa", data_student,
                          distr='logit')
[19]:
<statsmodels.miscmodels.ordinal_model.OrderedModel at 0x1a2acf8a200>

数式の使用 - モデルに定数項がない場合

OrderedModelのパラメータ化では、モデルに定数項がないことが求められます。定数項は、すべての閾値をシフトさせることに相当し、したがって別々に識別されません。

Patsy の数式仕様では、説明変数にカテゴリカル変数(またはおそらくスプライン)が含まれている場合、明示的または暗黙的な定数項なしのデザイン行列は許可されていません。そのため、statsmodels は明示的な切片項を削除します。

その結果、切片項なしのデザイン行列を得るための有効なケースは2つあります。

  • モデルに数値変数のみが含まれている場合、明示的および暗黙的な切片項なしでモデルを指定する。

  • 明示的な切片項を指定し、statsmodels がそれを削除する。

暗黙的な切片項を持つモデルは過剰にパラメータ化され、パラメータ推定値が完全に識別されず、cov_paramsは逆行列を持たず、標準誤差に NaN が含まれる可能性があります。

以下では、追加のカテゴリカル変数を含む例を見ていきます。

[20]:
nobs = len(data_student)
data_student["dummy"] = (np.arange(nobs) < (nobs / 2)).astype(float)

明示的な切片 は削除されます:

「1 +」 は patsy のデフォルトであるため、ここでは冗長であることに注意してください。

[21]:
modfd_logit = OrderedModel.from_formula("apply ~ 1 + pared + public + gpa + C(dummy)", data_student,
                                      distr='logit')
resfd_logit = modfd_logit.fit(method='bfgs')
print(resfd_logit.summary())
Optimization terminated successfully.
         Current function value: 0.896247
         Iterations: 26
         Function evaluations: 28
         Gradient evaluations: 28
                             OrderedModel Results
==============================================================================
Dep. Variable:                  apply   Log-Likelihood:                -358.50
Model:                   OrderedModel   AIC:                             729.0
Method:            Maximum Likelihood   BIC:                             752.9
Date:                Thu, 29 Aug 2024
Time:                        10:03:23
No. Observations:                 400
Df Residuals:                     394
Df Model:                           4
===============================================================================================
                                  coef    std err          z      P>|z|      [0.025      0.975]
-----------------------------------------------------------------------------------------------
C(dummy)[T.1.0]                 0.0326      0.198      0.164      0.869      -0.356       0.421
pared                           1.0489      0.266      3.945      0.000       0.528       1.570
public                         -0.0589      0.298     -0.198      0.843      -0.643       0.525
gpa                             0.6153      0.261      2.360      0.018       0.104       1.126
unlikely/somewhat likely        2.2183      0.785      2.826      0.005       0.680       3.757
somewhat likely/very likely     0.7398      0.080      9.237      0.000       0.583       0.897
===============================================================================================
[22]:
modfd_logit.k_vars
[22]:
4
[23]:
modfd_logit.k_constant
[23]:
0

暗黙の切片は過剰にパラメータ化されたモデルを作成します。

式に「0 +」を指定すると、明示的な切片が削除されます。しかし、カテゴリカルエンコーディングは暗黙の切片を含むように変更されます。この例では、作成されたダミー変数C(dummy)[0.0]C(dummy)[1.0]が1になるように合計されます。

OrderedModel.from_formula("apply ~ 0 + pared + public + gpa + C(dummy)", data_student, distr='logit')

過剰パラメータ化された場合に何が起こるかを確認するために、モデル内で定数項のチェックを回避することができます。これは、定数項が存在するかどうかを明示的に指定することで可能です。モデルには暗黙的な定数項があるにもかかわらず、hasconst=Falseを使用します。

2つのダミー変数列と最初の閾値のパラメータは別々に識別されません。これらのパラメータの推定値と標準誤差の有無は任意であり、環境によって異なる数値的な詳細に依存します。

ログ尤度値のような一部の要約統計量は、収束許容範囲と数値的な精度内では影響を受けません。予測も可能であるべきです。しかし、推論は利用できないか、無効です。

[24]:
modfd2_logit = OrderedModel.from_formula("apply ~ 0 + pared + public + gpa + C(dummy)", data_student,
                                         distr='logit', hasconst=False)
resfd2_logit = modfd2_logit.fit(method='bfgs')
print(resfd2_logit.summary())
Optimization terminated successfully.
         Current function value: 0.896247
         Iterations: 24
         Function evaluations: 26
         Gradient evaluations: 26
                             OrderedModel Results
==============================================================================
Dep. Variable:                  apply   Log-Likelihood:                -358.50
Model:                   OrderedModel   AIC:                             731.0
Method:            Maximum Likelihood   BIC:                             758.9
Date:                Thu, 29 Aug 2024
Time:                        10:03:23
No. Observations:                 400
Df Residuals:                     393
Df Model:                           5
===============================================================================================
                                  coef    std err          z      P>|z|      [0.025      0.975]
-----------------------------------------------------------------------------------------------
C(dummy)[0.0]                  -0.6834   1059.180     -0.001      0.999   -2076.638    2075.271
C(dummy)[1.0]                  -0.6508   1059.180     -0.001      1.000   -2076.605    2075.303
pared                           1.0489      0.266      3.944      0.000       0.528       1.570
public                         -0.0588      0.298     -0.197      0.844      -0.643       0.525
gpa                             0.6153      0.261      2.360      0.018       0.104       1.126
unlikely/somewhat likely        1.5349   1059.179      0.001      0.999   -2074.417    2077.487
somewhat likely/very likely     0.7398      0.080      9.237      0.000       0.583       0.897
===============================================================================================
[25]:
resfd2_logit.predict(data_student.iloc[:5])
[25]:
0 1 2
0 0.544858 0.361972 0.093170
1 0.301918 0.476667 0.221416
2 0.226434 0.477700 0.295867
3 0.612254 0.315481 0.072264
4 0.652280 0.286188 0.061532
[26]:
resf_logit.predict()
[26]:
array([[0.54884071, 0.35932276, 0.09183653],
       [0.30558191, 0.47594216, 0.21847593],
       [0.22938356, 0.47819057, 0.29242587],
       ...,
       [0.69380357, 0.25470075, 0.05149568],
       [0.54884071, 0.35932276, 0.09183653],
       [0.50896793, 0.38494062, 0.10609145]])

バイナリモデルとロジットモデルの比較

従属変数が二つのレベルのみの順序カテゴリ変数である場合、モデルはロジットモデルとしても推定できます。

この場合、モデルは定数項のパラメータ化を除いて理論的には同一です。ロジットモデルは他の多くのモデルと同様に、一般に切片を必要とします。これは OrderedModel の閾値パラメータに対応しますが、符号は逆になります。

実装方法は異なり、同じ結果統計や推定後機能のすべてが利用できるわけではありません。推定されたパラメータやその他の結果統計は、主に最適化の収束許容範囲に基づいて異なります。

[27]:
from statsmodels.discrete.discrete_model import Logit
from statsmodels.tools.tools import add_constant

データから中間のカテゴリを削除し、2 つの極端なカテゴリを保持します。

[28]:
mask_drop = data_student['apply'] == "somewhat likely"
data2 = data_student.loc[~mask_drop, :].copy()
# we need to remove the category also from the Categorical Index
data2['apply'] = data2['apply'].cat.remove_categories("somewhat likely")
data2["apply"].head()
[28]:
0     very likely
2        unlikely
5        unlikely
8        unlikely
10       unlikely
Name: apply, dtype: category
Categories (2, object): ['unlikely' < 'very likely']
[29]:
mod_log = OrderedModel(data2['apply'],
                        data2[['pared', 'public', 'gpa']],
                        distr='logit')

res_log = mod_log.fit(method='bfgs', disp=False)
res_log.summary()
[29]:
OrderedModel Results
Dep. Variable: apply Log-Likelihood: -102.87
Model: OrderedModel AIC: 213.7
Method: Maximum Likelihood BIC: 228.0
Date: Thu, 29 Aug 2024
Time: 10:03:23
No. Observations: 260
Df Residuals: 256
Df Model: 3
coef std err z P>|z| [0.025 0.975]
pared 1.2861 0.438 2.934 0.003 0.427 2.145
public 0.4014 0.444 0.903 0.366 -0.470 1.272
gpa 0.7854 0.489 1.605 0.108 -0.174 1.744
unlikely/very likely 4.4147 1.485 2.974 0.003 1.505 7.324

ロジットモデルにはデフォルトでは定数がないので、説明変数に追加する必要があります。

結果は、主に推定における収束許容度から生じる数値精度まで、ロジットモデルと順序付きモデルの間で本質的に同一です。

唯一の違いは定数の符号です。ロジット と OrdereModel は定数の符号が反対です。これは、デザイン行列に定数列を含める代わりに、OrderedModel のカット ポイントに関してパラメータ化を行った結果です。

[30]:
ex = add_constant(data2[['pared', 'public', 'gpa']], prepend=False)
mod_logit = Logit(data2['apply'].cat.codes, ex)

res_logit = mod_logit.fit(method='bfgs', disp=False)
[31]:
res_logit.summary()
[31]:
Logit Regression Results
Dep. Variable: y No. Observations: 260
Model: Logit Df Residuals: 256
Method: MLE Df Model: 3
Date: Thu, 29 Aug 2024 Pseudo R-squ.: 0.07842
Time: 10:03:23 Log-Likelihood: -102.87
converged: True LL-Null: -111.62
Covariance Type: nonrobust LLR p-value: 0.0005560
coef std err z P>|z| [0.025 0.975]
pared 1.2861 0.438 2.934 0.003 0.427 2.145
public 0.4014 0.444 0.903 0.366 -0.470 1.272
gpa 0.7854 0.489 1.605 0.108 -0.174 1.744
const -4.4148 1.485 -2.974 0.003 -7.324 -1.505

ロバストな標準誤差は、discrete.Logit と同じように OrderedModel でも利用できます。例として、横断的データがあり、自己相関が適切でない場合でも、HAC 共分散タイプを指定します。

[32]:
res_logit_hac = mod_logit.fit(method='bfgs', disp=False, cov_type="hac", cov_kwds={"maxlags": 2})
res_log_hac = mod_log.fit(method='bfgs', disp=False, cov_type="hac", cov_kwds={"maxlags": 2})
[33]:
res_logit_hac.bse.values - res_log_hac.bse
[33]:
pared                   7.629474e-08
public                 -2.672022e-07
gpa                    -2.224600e-08
unlikely/very likely    2.898501e-08
dtype: float64
[33]:


最終更新日: 2025年01月28日