UPDATE: 2022-09-05 12:56:14
ここではポアソン回帰分析の理解を深めるために、ポアソン分布から初めてポアソン回帰分析のパラメタ推定、分析結果の解釈などをまとめていく。主に下記を参照にしている。この書籍はポアソン回帰について非常に詳しく書かれ、EXCELで計算過程まで細かくまとめられており、非常に勉強になる。ここではこの書籍を参照しながら、Rでポアソン回帰分析への理解を深める。
ポアソン分布は下記の通り定義される離散型確率分布である。
\[ f(Y=y) = \frac{e^{-\lambda} \lambda^{y}}{y!}, \lambda>0,x=0,1,2, \dots \]
ここでも書籍のp13のデータを利用して、最尤法でパラメタ\(\lambda\)を推定する。
## data:
## スネデカー・コクラン著,畑村・奥野・津村訳(1972) 「統計的方法第6版」の第8.14節
## Phleum praetense(イチゴツナギ)の98標本に含まれる有害雑草の種子数
library(tidyverse)
library(numDeriv)
library(gganimate)
<- tibble(
d x = rep(0:11, times = c(3, 17, 26, 16, 18, 9, 3, 5, 0, 1, 0, 0))
)
<- d %>% count(y = x)
df df
## # A tibble: 9 × 2
## y n
## <int> <int>
## 1 0 3
## 2 1 17
## 3 2 26
## 4 3 16
## 5 4 18
## 6 5 9
## 7 6 3
## 8 7 5
## 9 9 1
このサンプルデータのカウント数を可視化するとこのような分布になる。
ggplot(data = df) +
geom_bar(aes(y, n), stat = 'identity', fill = '#E86670', col = 'black') +
geom_text(aes(y, n + 1, label = n)) +
scale_x_continuous(breaks = seq(0, 11, 1)) +
theme_classic()
少し手間を加えて、ポアソン分布の確率を重ねて表示する。天下り的だが、この後に推定する最尤推定値をパラメタとして利用する。可視化した分布をみると、ポアソン分布がある程度フィットしている事がわかる。
<- 3.020402
LAM <- df %>%
df_ratio mutate(ratio = n/sum(n))
<- tibble(x = 0:9) %>%
df_pois mutate(prob = dpois(x, lambda = LAM))
ggplot() +
geom_bar(data = df_ratio, aes(y, ratio), stat = 'identity', fill = '#E86670', col = 'black') +
geom_text(data = df_ratio, aes(y, ratio + 0.01, label = round(ratio,3))) +
geom_line(data = df_pois, aes(x = x, y = prob), alpha = 0.5) +
scale_x_continuous(breaks = seq(0, 11, 1)) +
theme_classic()
ここからはこのサンプルデータが、ポアソン分布から生成されたと仮定して最尤法でパラメタを推定していく。
対数尤度の合計を地道に計算してみる。尤度は各\(y_{i}\)がポアソン分布で起こる確率を\(n\)個分掛け合わせた値である。つまり、\(\prod L\)である。\(y=0\)のとき、ポアソン分布は確率\(\frac{e^{-3.02}3.02^{0}}{0!}=0.048781604\)を返すので。\(y=0\)のとき、\(n=3\)より、\(0.048781604^3=0.0001160829\)となり\(log(0.0001160829)=-9.061206\)となる。これをその他の\(y\)で繰り返して足し合わせると対数尤度が計算できる。
<- tibble(i = 0:9) %>%
df2 left_join(df, by = c('i' = 'y')) %>%
rename(y = i) %>%
mutate(
probability = dpois(y, lambda = LAM),
likelihood = probability^n,
log_likelihood = log(likelihood)
) df2
## # A tibble: 10 × 5
## y n probability likelihood log_likelihood
## <int> <int> <dbl> <dbl> <dbl>
## 1 0 3 0.0488 1.16e- 4 -9.06
## 2 1 17 0.147 7.27e-15 -32.6
## 3 2 26 0.223 1.07e-17 -39.1
## 4 3 16 0.224 4.03e-11 -23.9
## 5 4 18 0.169 1.29e-14 -32.0
## 6 5 9 0.102 1.22e- 9 -20.5
## 7 6 3 0.0514 1.36e- 4 -8.90
## 8 7 5 0.0222 5.39e- 9 -19.0
## 9 8 NA 0.00838 NA NA
## 10 9 1 0.00281 2.81e- 3 -5.87
\(\lambda=3.02\)のとき、対数尤度は下記の通りとなる。
%>%
df2 summarise(sum_log_likelihood = sum(log_likelihood, na.rm = TRUE))
## # A tibble: 1 × 1
## sum_log_likelihood
## <dbl>
## 1 -191.
最尤法は、このような形でポアソン分布のパラメタ\(\lambda\)をずらしていき、対数尤度が最大となる点を求める方法である。\(n\)個のサンプルデータがある場合、対数尤度関数\(logL\)は下記のように書ける。
\[ log L(\lambda | \boldsymbol{y}) = \left( \sum y_{i} \right) log \lambda - \sum log(y_{i} !) - n\lambda \]
\(\lambda\)を少しづつずらしていき、対数尤度関数が最大となるイメージを可視化する。
# 対数尤度関数
# 下記の個別のyをsumしても対数尤度の合計は計算可能
# sum(dpois(y, lambda = λ,log=TRUE))でもよい
# log(exp(-λ)*λ^x/factorial(x))
# x*log(λ) - λ - log(factorial(x))
# lfactorial(y) = log(factorial(y))
<- function(lam, y) sum(y)*log(lam) - sum(lfactorial(y)) - length(y)*lam
loglikelihood
<- seq(0.01, 10, 0.01)
lam <- length(lam)
lam_n <- vector(mode = 'numeric', length = lam_n)
ll for (i in seq_along(1:lam_n)) {
<- loglikelihood(lam[[i]], d$x)
ll[[i]]
}
<-
loglikelihood_df tibble(
lambda = lam,
loglikelihood = ll
)
<- loglikelihood_df[which.max(loglikelihood_df$loglikelihood), 'lambda'][[1]]
xx <- max(loglikelihood_df$loglikelihood)
yy
ggplot() +
geom_line(data = loglikelihood_df, aes(lambda, loglikelihood)) +
geom_point(aes(x = xx, y = yy), col = 'red', size = 2) +
geom_text(aes(x = xx, y = yy + 50, label = xx)) +
scale_x_continuous(breaks = seq(0, 10, 0.5)) +
theme_classic()
optimize
関数を使えば、最大となる点を簡単に計算できる。
<- optimize(loglikelihood, interval = c(0, 5), y = d$x, maximum = TRUE)
opt_res opt_res
## $maximum
## [1] 3.020408
##
## $objective
## [1] -190.9517
対数尤度関数が最大となる点、つまり微分して傾きが0になる点が、対数尤度が最大となる点なので、イメージを掴むために、勾配上昇法で探索してみる。実際、ポアソン分布の最尤推定量は平均になるため、ここまでしてきたように、\(\lambda\)をずらして、最大となる点を探すことや勾配法を使う必要はなく、平均を計算すれば良い。
mean(d$x)
## [1] 3.020408
勾配上昇法で探索するにあたり、スクリプトが簡潔にするために、対数尤度関数に手を加えておく。探索したい\(\lambda\)を\(x\)に変更し、予め\(y\)をd$x
で固定しておく。
# 対数尤度関数
<- function(x, y = d$x) sum(y)*log(x) - sum(lfactorial(y)) - length(y)*x
f
#f(opt_res$maximum)と同じ
f(mean(d$x))
## [1] -190.9517
勾配上昇法で計算し、可視化用のデータフレームを作成する。
<- 40
iter <- 0.01
eta <- 10
x_1
<- x_1
x_1_values <- f(x_1)
y_1_values <- NULL
gradient_values <- NULL
intercept_values
for(i in 1:iter){
# Steepest ascent:
<- grad(func = f, x = c(x_1))
grad
<- -grad*x_1 + f(x_1)
intercept_value # Keeping track
<- c(gradient_values, grad)
gradient_values <- c(intercept_values, intercept_value)
intercept_values
# Updating the value
<- x_1 + eta * grad
x_1 <- f(x_1)
y_1
# Keeping track
<- c(x_1_values, x_1)
x_1_values <- c(y_1_values, y_1)
y_1_values
}
<- tibble(
df_plot iter = 1:iter,
xx = seq(0, 10, length.out = iter),
x_1 = x_1_values[-length(x_1_values)],
y = f(x_1),
gradient = gradient_values,
intercept = intercept_values
) head(df_plot)
## # A tibble: 6 × 6
## iter xx x_1 y gradient intercept
## <int> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 0 10 -521. -68.4 163.
## 2 2 0 9.32 -475. -66.2 142.
## 3 3 0 8.65 -431. -63.8 121.
## 4 4 0 8.02 -392. -61.1 98.0
## 5 5 0 7.41 -355. -58.0 74.5
## 6 6 0 6.82 -322. -54.6 50.3
アニメーションさせると、傾きが0になる点で収束していることがわかる。
ggplot(df_plot) +
stat_function(fun = f, aes(xx)) +
geom_point(aes(x = x_1, y = y), colour = "red") +
geom_abline(aes(slope = gradient, intercept = intercept), colour = "blue") +
geom_text(aes(x = x_1, y = y + 10, label = gradient)) +
scale_x_continuous(breaks = seq(0, 10, 0.5)) +
theme_bw() +
transition_time(iter) +
labs(title = "Iteration: {frame_time}", x = 'x') +
ease_aes('elastic-in')