一般化線形モデル入門 第4章 解答例
演習問題4.1
https://zenn.dev/honwakasan/articles/b9ae08fd8a694e の第4章の解答例です.
この章では, 数値データを用いた解析があるが, pythonを使って実装する.
表4.5を作成しておく.
df_45 = pd.DataFrame(
{
"year":[1984, 1985, 1986, 1987, 1988],
"四半期_1":[1, 27, 43, 88, 110],
"四半期_2":[6, 39, 51, 97, 113],
"四半期_3":[16, 31, 63, 91, 149],
"四半期_4":[23, 30, 70, 104, 159]
}
)
(a)
以下のコードで, 期間と患者数を散布図で描く.
array_45 = df_45.drop(["year"], axis = 1).values.reshape(-1)
plt.scatter(
list(range(1, len(array_45) + 1)),
array_45
)
plt.xlabel("期間") # x軸ラベル
plt.ylabel("患者数") # y軸ラベル
plt.xticks(range(1, len(array_45) + 1))
plt.show()
出力結果は以下であった.

(b)
期間および患者数を対数変換し散布図を描く.
plt.scatter(
np.log(list(range(1, len(array_45) + 1))),
np.log(array_45)
)
plt.xlabel("期間(対数表示)") # x軸ラベル
plt.ylabel("患者数(対数表示)") # y軸ラベル
plt.show()
出力結果は以下となった.

(c)
IRLSをする際に必要な
である.
\beta の初期値を設定
初期値
Wの値を求める
一般化線形モデルの式から
を得る. また,
となる. さらに,
よって,
で表せる.
z を求める
(4.24)より,
である[1]. よってベクトル表記すると,
である.
bの推定値を更新
(4.25)より
に対し, これまで計算してきた
Pythonによる実装
以上を踏まえ, 以下のコードでIRLSを実行した.
# 1列目は定数部分なので1を入れる.
# x_iはlog(i)とみなすので対数変換も行う.
X = np.stack(
(np.ones(len(array_45)),
np.log(list(range(1, len(array_45) + 1)))),
axis = 1
)
y = array_45.copy()
# パラメータの初期値
beta = np.ones(X.shape[1])
# 繰り返し回数
n_iters = 10
list_beta = []
for t in range(n_iters):
# etaの値を推定
eta_t = X @ beta
# 連結関数が対数関数であったことに注意
lambda_t = np.exp(eta_t)
W_t = np.diag(lambda_t)
# (4.24)より
z_t = eta_t + ( y - lambda_t ) / lambda_t
# (4.25)より以下でbetaを推定
beta = np.linalg.inv((X.T)@W_t@X)@(X.T)@W_t@z_t
list_beta.append(beta)
df_beta = pd.DataFrame(list_beta, columns = ["beta_0", "beta_1"])
なお, 出力結果は以下である. 数回程度で収束しているのがわかる.
| 回数 | beta_0 | beta_1 |
|---|---|---|
| 0 | 0.652354 | 1.65199 |
| 1 | 0.841857 | 1.42955 |
| 2 | 0.98454 | 1.33373 |
| 3 | 0.995952 | 1.32664 |
| 4 | 0.995998 | 1.32661 |
| 5 | 0.995998 | 1.32661 |
| 6 | 0.995998 | 1.32661 |
| 7 | 0.995998 | 1.32661 |
| 8 | 0.995998 | 1.32661 |
| 9 | 0.995998 | 1.32661 |
(d)
statsmodelsライブラリを用いて, 同様のポアソン回帰を実行し, (c)の結果を比較する.
import statsmodels.api as sm
import statsmodels.formula.api as smf
data = pd.DataFrame(
np.stack((X[: , 1], y),axis = 1),
columns = ["x", "y"]
)
model = smf.glm(formula='y ~ x', data=data, family=sm.families.Poisson())
result = model.fit()
print(pd.DataFrame(result.params.values.reshape(1, 2), columns = ["beta_0", "beta_1"]))
出力結果は以下となった. (c)の結果と一致していることが確認できた.
| beta_0 | beta_1 | |
|---|---|---|
| 0 | 0.995998 | 1.32661 |
演習問題4.2
問題文の表4.6のデータを以下のコードで作成する.
import pandas as pd
import numpy as np
import japanize_matplotlib
import matplotlib.pyplot as plt
import statsmodels.api as sm
import statsmodels.formula.api as smf
# データ
x = [3.36, 2.88, 3.63, 3.41, 3.78, 4.02, 4.00, 4.23, 3.73,
3.85, 3.97, 4.51, 4.54, 5.00, 5.00, 4.72, 5.00]
y = [65, 156, 100, 134, 16, 108, 121, 4, 39,
143, 56, 26, 22, 1, 1, 5, 65]
# DataFrame 作成
df = pd.DataFrame({
"x": x,
"y": y
})
(a)
散布図を以下のコードで作成する.
plt.title("xとyの散布図")
plt.scatter(df["x"], df["y"])
plt.xlabel("x(初期の白血球数)")
plt.ylabel("y(生存時間;週)")
plt.show()
出力結果は以下であった. 初期白血球数が多くなると, 生存時間が減少していくように見える.

(b)
として表されるとき, 連結関数として
と表せるので, 対数関数が適切である.
(c)
となるので
また, statsmodelsでモデルを当てはめる.
import statsmodels.api as sm
import statsmodels.formula.api as smf
model_exp = smf.glm(
formula="y ~ x",
data=df,
family=sm.families.Gamma(link=sm.families.links.log())
)
results_exp = model_exp.fit()
結果は以下であった.
| item | coef | std err | z | p-value | 下側2.5%点 | 上側2.5%点 |
|---|---|---|---|---|---|---|
| Intercept | 8.4775 | 1.603 | 5.287 | 0.000 | 5.335 | 11.620 |
| x | -1.1093 | 0.387 | -2.865 | 0.004 | -1.868 | -0.350 |
(c) スクラッチの場合
である. また, 情報行列は
であるから, 更新式
により更新すれば良い. 更新は以下のPythonコードで実行する.
import numpy as np
import pandas as pd
from tqdm import tqdm
from scipy.stats import norm
# 情報行列
J = np.array(
[
[len(df), df["x"].sum()],
[df["x"].sum(), (df["x"]**2).sum()]
]
)
beta = np.zeros(2)
tol = 1e-8
max_iter = 1000
for j in tqdm(range(max_iter)):
mu_i = np.exp(beta[0] + beta[1] * df["x"])
U = np.array(
[
((df["y"] - mu_i) / mu_i).sum(),
(((df["y"] - mu_i) / mu_i) * df["x"]).sum()
]
)
delta = np.linalg.inv(J) @ U
beta_new = beta + delta
# 収束判定
if np.linalg.norm(beta_new - beta, ord=2) < tol:
beta = beta_new
print(f"Converged at iteration {j+1}")
break
beta = beta_new.copy()
得られた結果は以下である. なお, 標準誤差は
| item | coef | std err |
|---|---|---|
| 8.47749765 | 1.65480771 | |
| -1.10929792 | 0.39965447 |
(d)
以下のコードで標準化残差を算出する.
# 当てはめ値
df["pred"] = np.exp(results_exp.params["x"] * df["x"] + results_exp.params["Intercept"])
# 標準化残差の算出
df["std_resid"] = (df["y"] - df["pred"]) / df["pred"]
標準化残差として以下を得る. 最もズレが大きいのは, 16行目のx = 65, y = 5.00のとき, r = 2.467310 となるレコードである.
| x | y | pred | r |
|---|---|---|---|
| 3.36 | 65 | 115.613954 | -0.437784 |
| 2.88 | 156 | 196.904661 | -0.207738 |
| 3.63 | 100 | 85.690865 | 0.166986 |
| 3.41 | 134 | 109.376030 | 0.225131 |
| 3.78 | 16 | 72.555482 | -0.779479 |
| 4.02 | 108 | 55.596488 | 0.942569 |
| 4.00 | 121 | 56.843734 | 1.128643 |
| 4.23 | 4 | 44.043046 | -0.909180 |
| 3.73 | 39 | 76.693459 | -0.491482 |
| 3.85 | 143 | 67.134675 | 1.130047 |
| 3.97 | 56 | 58.767262 | -0.047088 |
| 4.51 | 26 | 32.283752 | -0.194641 |
| 4.54 | 22 | 31.227063 | -0.295483 |
| 5.00 | 1 | 18.746519 | -0.946657 |
| 5.00 | 1 | 18.746519 | -0.946657 |
| 4.72 | 5 | 25.574902 | -0.804496 |
| 5.00 | 65 | 18.746519 | 2.467310 |
残差をプロットすると以下のようになっており, 大多数は小さい値であることがわかる.

演習問題4.3
最尤推定量の導出
であり
となる.
(4.18)式のチェック
この問題設定において, 本文(4.18)
\begin{aligned} U_j = \sum_{i = 1}^N \frac{y_i - \mu_i}{Var(Y_i)}x_{ij} \left(\frac{\partial \mu_i}{\partial \eta_i} \right) \end{aligned}
が成り立つことを確認する.
より, これらを(4.18)式の右辺に代入することで,
(4.25)式のチェック
最尤推定量
\begin{aligned} X^T W X b^{(m)} = X^T W z \end{aligned}
を満たしているか確認する.[2]
となる.[3] 一方, (4.25)式の右辺は
となる. ただし, 右辺第2式において,
-
説明変数を表す行列
の1列目は, 線形モデルの定数項に相当するので,X のように記載されていても, 値は1である. なお, 定数項を除外したモデルの場合はその限りではない. ↩︎x_{ik} -
の最尤推定量\beta が(4.25)式を満たすかどうかをチェックしろ、というのが著者の意図だと理解したのでこのような証明を行っている. 本文の演習問題には, 「・・・また, この場合について式(4.18)と(4.25)が成り立つことを確かめよ.」と記載されており, "この場合" が指している事象がやや曖昧なのだが上記ように解釈した. ↩︎\beta_{MLE} -
本文では, パラメータ数がp個ある場合を想定しているため,
やx_{ij} のように記載されているが, 今回はパラメータはx_{ik} の1個だけなので, このように記載している. 本当は,\beta のように記載した方が見通しやすいかもしれない. ↩︎x_i
Discussion