Stanで回帰分析を実行する

データを作って

# データの読み込み
bs <- read_csv("baseball.csv")
## Parsed with column specification:
## cols(
##   .default = col_integer(),
##   Name = col_character(),
##   team = col_character(),
##   salary = col_double(),
##   position = col_character(),
##   bloodType = col_character(),
##   throw.by = col_character(),
##   batting.by = col_character(),
##   birth.place = col_character(),
##   birth.day = col_date(format = ""),
##   背番号 = col_character(),
##   打率 = col_double(),
##   出塁率 = col_double(),
##   長打率 = col_double(),
##   OPS = col_double(),
##   RC27 = col_double(),
##   XR27 = col_double(),
##   防御率 = col_double(),
##   勝率 = col_double(),
##   投球回 = col_double(),
##   WHIP = col_double()
##   # ... with 2 more columns
## )
## See spec(...) for full column specifications.
# 身長と体重のデータだけ読み込む
df <- bs %>% dplyr::select("weight", "height") %>% na.omit

いわゆる普通の回帰分析

# 最尤推定
summary(lm(height ~ weight, data = df))
## 
## Call:
## lm(formula = height ~ weight, data = df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -13.9016  -2.8402  -0.0261   2.6653  16.4306 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 148.57283    1.33809  111.03   <2e-16 ***
## weight        0.38267    0.01586   24.13   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 4.37 on 916 degrees of freedom
## Multiple R-squared:  0.3886, Adjusted R-squared:  0.388 
## F-statistic: 582.3 on 1 and 916 DF,  p-value: < 2.2e-16

ベイジアン回帰分析

# データはリスト型で渡す
dataset <- list(L = NROW(df), X = df$weight, Y = df$height)
model.reg <- stan_model("regression.stan")
## data{
##   int<lower=0> L;
##   real X[L];
##   real Y[L];
## }
## 
## parameters{
##   real beta_0;
##   real beta_1;
##   real<lower=0> sigma;
## }
## 
## model{
##   for(l in 1:L){
##     Y[l] ~ normal(beta_0 + beta_1 * X[l], sigma);
##   }
## }
## 
## generated quantities{
##   real log_lik[L];
##   real pred[L];
##   for(l in 1:L){
##     log_lik[l] = normal_lpdf(Y[l]|beta_0 + beta_1 * X[l], sigma);
##     pred[l] = normal_rng(beta_0 + beta_1 * X[l], sigma);
##   }
## }
fit.reg <- sampling(model.reg, dataset)
# 係数の出力
print(fit.reg, pars = c("beta_0", "beta_1", "sigma"))
## Inference for Stan model: regression.
## 4 chains, each with iter=2000; warmup=1000; thin=1; 
## post-warmup draws per chain=1000, total post-warmup draws=4000.
## 
##          mean se_mean   sd   2.5%    25%    50%    75%  97.5% n_eff Rhat
## beta_0 148.50    0.04 1.29 145.88 147.65 148.50 149.35 151.02  1345    1
## beta_1   0.38    0.00 0.02   0.35   0.37   0.38   0.39   0.41  1350    1
## sigma    4.37    0.00 0.10   4.17   4.30   4.37   4.44   4.58  1507    1
## 
## Samples were drawn using NUTS(diag_e) at Mon Sep  3 19:33:19 2018.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at 
## convergence, Rhat=1).
# プロット
plot(fit.reg,pars=c("sigma"),show_density=T)
## ci_level: 0.8 (80% intervals)
## outer_level: 0.95 (95% intervals)

bayesplot::mcmc_dens(as.array(fit.reg), pars = c("beta_0", "beta_1", "sigma"))

事後予測分布

g1 <- rstan::extract(fit.reg, pars = "pred") %>% data.frame %>% 
  tidyr::gather(key,val) %>% 
  ggplot(aes(x=val))+geom_histogram(binwidth=1)+xlim(140,220)

g2 <- ggplot(df,aes(x=height))+geom_histogram(binwidth = 1)+xlim(140,220)
# gridExtraパッケージは複数のggplotオブジェクトを並べて表示してくれます
gridExtra::grid.arrange(g1, g2, ncol = 1)

R2や適合度も計算してみましょう

# 予測値を抜き出します
rstan::extract(fit.reg, pars = "pred") %>% data.frame %>% 
  # データを縦長にし,変数ごとにグループ化
  tidyr::gather(key,val,factor_key=TRUE) %>% group_by(key) %>% 
  # 平均を推定値とします
  summarize(EAP = mean(val)) -> pred
# 実測値と予測値の相関係数の二乗
(cor(df$height, pred$EAP))^2
## [1] 0.389353
# 事後対数尤度(これだけでは比較できませんが)
rstan::extract(fit.reg, pars = "log_lik")$log_lik %>% loo()
## Warning: Relative effective sample sizes ('r_eff' argument) not specified.
## For models fit with MCMC, the reported PSIS effective sample sizes and 
## MCSE estimates will be over-optimistic.
## 
## Computed from 4000 by 918 log-likelihood matrix
## 
##          Estimate   SE
## elpd_loo  -2658.8 24.6
## p_loo         3.6  0.4
## looic      5317.5 49.1
## ------
## Monte Carlo SE of elpd_loo is 0.0.
## 
## All Pareto k estimates are good (k < 0.5).
## See help('pareto-k-diagnostic') for details.

ロジスティック回帰分析

セ・パ両リーグの違いをデータから予測してみます。従属変数が0/1のデータになるので,ロジスティック回帰分析を使います。

# 投手ならば1,野手ならば0という変数を作成
bs %>% mutate(pos2 = ifelse(position=="投手",1,0)) %>% 
  # 新変数と身長のデータだけ取り出す
  dplyr::select(pos2,height) %>% na.omit ->df2
# 身長とポジション(二値)の関係
ggplot(df2,aes(x=height,y=pos2))+geom_point()

# 最尤推定
glm(pos2 ~ height, data=df2, family=binomial(link="logit")) %>% summary
## 
## Call:
## glm(formula = pos2 ~ height, family = binomial(link = "logit"), 
##     data = df2)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.0454  -1.1364   0.6643   1.1006   1.7613  
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -16.83525    2.39691  -7.024 2.16e-12 ***
## height        0.09351    0.01327   7.045 1.86e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1272.0  on 917  degrees of freedom
## Residual deviance: 1216.6  on 916  degrees of freedom
## AIC: 1220.6
## 
## Number of Fisher Scoring iterations: 4

これをベイジアンモデリングで実行するには次のようにします。

model.logistic <- stan_model("logistic.stan")
## data{
##   int<lower=0> N; // sample size
##   real X[N];      // predictor
##   int<lower=0,upper=1> Y[N];      // predicted value(should be 0,1)
## }
## 
## parameters{
##   real beta0;     // baseline
##   real beta1;     // coefficient
## }
## 
## model{
##   real prob[N];  // transformed parameter
##   //likelihood
##   for(n in 1:N){
##     prob[n] = inv_logit(beta0 + beta1 * X[n]);
##     Y[n] ~ bernoulli(prob[n]);
##   }
##   //prior
##   beta0 ~ normal(0,100);
##   beta1 ~ normal(0,100);
## }

実行します。

# データはリスト型で渡す
dataset <- list(N = NROW(df2), X = df2$height, Y = df2$pos2)
# MCMC!
fit.logistic <- sampling(model.logistic,dataset)
# 結果の出力
fit.logistic
## Inference for Stan model: logistic.
## 4 chains, each with iter=2000; warmup=1000; thin=1; 
## post-warmup draws per chain=1000, total post-warmup draws=4000.
## 
##          mean se_mean   sd    2.5%     25%     50%     75%   97.5% n_eff
## beta0  -16.89    0.11 2.32  -21.74  -18.37  -16.88  -15.31  -12.31   436
## beta1    0.09    0.00 0.01    0.07    0.09    0.09    0.10    0.12   437
## lp__  -609.25    0.04 0.95 -611.80 -609.63 -608.96 -608.57 -608.32   738
##       Rhat
## beta0 1.00
## beta1 1.00
## lp__  1.01
## 
## Samples were drawn using NUTS(diag_e) at Mon Sep  3 19:34:21 2018.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at 
## convergence, Rhat=1).

stanには専用の関数があって,これを利用した方が高速に結果を出すことができます。

## data{
##   int<lower=0> N; // sample size
##   real X[N];      // predictor
##   int<lower=0,upper=1> Y[N];      // predicted value(should be 0,1)
## }
## 
## parameters{
##   real beta0;     // baseline
##   real beta1;     // coefficient
## }
## 
## model{
##   //likelihood
##   for(n in 1:N){
##     Y[n] ~ bernoulli_logit(beta0 + beta1 * X[n]);
##   }
##   //prior
##   beta0 ~ normal(0,100);
##   beta1 ~ normal(0,100);
## }

一般化線形モデル

GLMとLink関数

GLMとLink関数

ポアソン回帰のコード

## data{
##   int<lower=0> L;   // sample size
##   real X[L];       // predictor
##   int<lower=0> Y[L];      // predicted
## }
## 
## parameters{
##   real beta0;
##   real beta1;
## }
## 
## transformed parameters{
##   real<lower=0> theta[L];
##   for(l in 1:L)
##     theta[l] = exp(beta0 + beta1*X[l]);
## }
## 
## model{
##   for(l in 1:L)
##     Y[l] ~ poisson(theta[l]);
##     
##   //prior
##   beta0 ~ normal(0,100);
##   beta1 ~ normal(0,100);
## }

専用の関数バージョン

## data{
##   int<lower=0> L;   // sample size
##   real X[L];       // predictor
##   int<lower=0> Y[L];      // predicted
## }
## 
## parameters{
##   real beta0;
##   real beta1;
## }
## 
## 
## model{
##   for(l in 1:L)
##     Y[l] ~ poisson_log(beta0 + beta1*X[l]);
##     
##   //prior
##   beta0 ~ normal(0,100);
##   beta1 ~ normal(0,100);
## }

数値例

# 野球データから野手だけフィルター
df3 <- bs %>% filter(position!="投手") %>% 
  # 変数を選択,欠損値の削除
  dplyr::select("打席数","本塁打") %>% na.omit %>% 
  # 日本語変数を英語名に
  rename(AT.Bat="打席数",HR="本塁打") %>% 
  # 単位を整えておきます 
  mutate(AT.Bat = AT.Bat/10)

# ホームランのヒストグラム
ggplot(df3,aes(x=HR))+geom_histogram(binwidth =3)

# ホームランと打席の散布図
ggplot(df3,aes(x=AT.Bat,y=HR))+geom_point()

# 事後予測分布を作るモデルに書き換え
model.pois <- stan_model("poisson3.stan")
# データ作成
data.pois <- list(L=NROW(df3),X=df3$AT.Bat,Y=df3$HR)
# MCMC
fit.pois <- sampling(model.pois,data.pois)
## Warning in .local(object, ...): some chains had errors; consider specifying
## chains = 1 to debug
## here are whatever error messages were returned
## [[1]]
## Stan model 'poisson3' does not contain samples.
# 表示
print(fit.pois,pars=c("beta0","beta1"))
## Inference for Stan model: poisson3.
## 3 chains, each with iter=2000; warmup=1000; thin=1; 
## post-warmup draws per chain=1000, total post-warmup draws=3000.
## 
##        mean se_mean   sd  2.5%   25%   50%   75% 97.5% n_eff Rhat
## beta0 -0.22       0 0.07 -0.35 -0.26 -0.22 -0.17 -0.08   736    1
## beta1  0.06       0 0.00  0.06  0.06  0.06  0.07  0.07   805    1
## 
## Samples were drawn using NUTS(diag_e) at Mon Sep  3 19:34:26 2018.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at 
## convergence, Rhat=1).

階層線形モデル

過分散に対応する

先ほどの例,事後予測分布をつかって予測区間をみてみます。

rstan::extract(fit.pois, pars = "pred") %>% data.frame %>% 
  # 変数を縦長にし,ケースごとにグループ化
  tidyr::gather(key,val,factor_key=T) %>% group_by(key) %>% print %>% 
  # 統計量の計算
  summarise(EAP=mean(val),
            U95=quantile(val,prob=0.975),
            L95=quantile(val,prob=0.025)) %>% ungroup %>% 
  # 元データの予測値と合わせる
  mutate(AT=df3$AT.Bat,HR=df3$HR) %>% print %>% 
  # key列を除外
  dplyr::select(-key) %>% 
  # 作図用に改めて縦長に作り直し
  tidyr::gather(type,val,-AT,-HR,factor_key=T) %>% 
  # 作図
  ggplot(aes(x=AT,y=val,color=type)) + geom_point() +  
  geom_point(aes(x=AT,y=HR),color="Black")
## # A tibble: 906,000 x 2
## # Groups:   key [302]
##    key      val
##    <fct>  <dbl>
##  1 pred.1     1
##  2 pred.1     2
##  3 pred.1     3
##  4 pred.1     0
##  5 pred.1     3
##  6 pred.1     1
##  7 pred.1     1
##  8 pred.1     1
##  9 pred.1     0
## 10 pred.1     0
## # ... with 905,990 more rows
## # A tibble: 302 x 6
##    key        EAP   U95   L95    AT    HR
##    <fct>    <dbl> <dbl> <dbl> <dbl> <int>
##  1 pred.1   0.871     3     0   1       0
##  2 pred.2  25.0      35    16  52.9     9
##  3 pred.3   0.826     3     0   0.4     0
##  4 pred.4   3.33      7     0  21.8     4
##  5 pred.5   1.13      4     0   5.1     0
##  6 pred.6  11.6      19     5  41      29
##  7 pred.7   0.859     3     0   0.8     0
##  8 pred.8   1.52      4     0   9.7     3
##  9 pred.9   5.02     10     1  28.3    12
## 10 pred.10  1.44      4     0   8.7     1
## # ... with 292 more rows

結構外れている点がありますね。 これは同じ打席数であっても,HRバッターかどうかによって変わる個人差が大きいようです。 そこで,「基本的には打席数はホームラン数を予測する」けど「部分的には個人差が生じる」をモデル化して行きたいと思います。

全体的には \[ Y_i \sim Poisson(\lambda_i)\] ただし \[ \log(\lambda_i) = \beta_0 + \beta_1 X_i\] あるいは \[ \lambda_i = exp(\beta_0 + \beta_1 X_i) \]

ですが,個人差\(\tau_i\)を付け加えて \[ \log(\lambda_i) = \beta_0 + \beta_1 X_i + \tau_i\] とします。ただしこの個人差は人によってプラスマイナスはありますが,平均はゼロの正規分布に従うとします。 \[ \tau_i \sim Normal(0,\sigma^2) \]

回帰分析のロジックと同じで,平均部分が0なのですから, \[ \log(\lambda_i) \sim Normal(\beta_0 + \beta_1 X_i,\sigma^2) \]

これで個人変動\(\sigma^2\)を想定したモデルになります。

実装
model.pois2 <- stan_model("poisson4.stan")
fit.pois2 <- sampling(model.pois2,data.pois)
# 係数の確認
print(fit.pois2,pars=c("beta0","beta1","sig"))
## Inference for Stan model: poisson4.
## 4 chains, each with iter=2000; warmup=1000; thin=1; 
## post-warmup draws per chain=1000, total post-warmup draws=4000.
## 
##        mean se_mean   sd  2.5%   25%   50%   75% 97.5% n_eff Rhat
## beta0 -0.99       0 0.14 -1.28 -1.08 -0.99 -0.90 -0.73   853    1
## beta1  0.08       0 0.00  0.07  0.08  0.08  0.08  0.09  1278    1
## sig    0.87       0 0.07  0.75  0.82  0.87  0.92  1.02   823    1
## 
## Samples were drawn using NUTS(diag_e) at Mon Sep  3 19:34:44 2018.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at 
## convergence, Rhat=1).
rstan::extract(fit.pois2, pars = "pred") %>% data.frame %>% 
  # 変数を縦長にし,ケースごとにグループ化
  tidyr::gather(key,val,factor_key=T) %>% group_by(key) %>% print %>% 
  # 統計量の計算
  summarise(EAP=mean(val),
            U95=quantile(val,prob=0.975),
            L95=quantile(val,prob=0.025)) %>% ungroup %>% 
  # 元データの予測値と合わせる
  mutate(AT=df3$AT.Bat,HR=df3$HR) %>% print %>% 
  # key列を除外
  dplyr::select(-key) %>% 
  # 作図用に改めて縦長に作り直し
  tidyr::gather(type,val,-AT,-HR,factor_key=T) %>% 
  # 作図
  ggplot(aes(x=AT,y=val,color=type)) + geom_point() +  
  geom_point(aes(x=AT,y=HR),color="Black")
## # A tibble: 1,208,000 x 2
## # Groups:   key [302]
##    key      val
##    <fct>  <dbl>
##  1 pred.1     0
##  2 pred.1     2
##  3 pred.1     0
##  4 pred.1     0
##  5 pred.1     1
##  6 pred.1     0
##  7 pred.1     0
##  8 pred.1     1
##  9 pred.1     2
## 10 pred.1     1
## # ... with 1,207,990 more rows
## # A tibble: 302 x 6
##    key        EAP   U95   L95    AT    HR
##    <fct>    <dbl> <dbl> <dbl> <dbl> <int>
##  1 pred.1   0.401     2     0   1       0
##  2 pred.2  10.4      20     3  52.9     9
##  3 pred.3   0.385     2     0   0.4     0
##  4 pred.4   3.6       9     0  21.8     4
##  5 pred.5   0.503     3     0   5.1     0
##  6 pred.6  27.7      43    15  41      29
##  7 pred.7   0.395     2     0   0.8     0
##  8 pred.8   2.02      6     0   9.7     3
##  9 pred.9  10.7      21     3  28.3    12
## 10 pred.10  0.944     4     0   8.7     1
## # ... with 292 more rows

今のはポアソン回帰のパラメータ\(\lambda\)に事前分布ならぬ別の分布が入っている(確率分布の入れ子)ので,階層線形モデルと呼ばれるモデルになっています。

入れ子になったデータ構造

個別の回帰分析

チームごとの階層性を考えてみましょう。次のグラフを見てください。

# 野手のデータだけフィルタリング
bs %>% filter(position != "投手") %>% 
  # 年俸と本塁打数,チーム情報変数をセレクト
  select("salary", "本塁打", "team") %>% 
  # 欠損値を除外
  na.omit() %>% 
  # 描画
  ggplot(aes(x = 本塁打, y = salary, color = team)) + 
  geom_point() + geom_smooth(method = "lm", se = F)

チームごとに傾きが異なりそうですので,チームごとの傾きが違うことをモデリングして行きたいと思います。

# データセットの作成。まずは野手のみフィルタリング
df <- bs %>% filter(position != "投手") %>%
  # 変数を選んで欠損地を除外
  select("salary","本塁打", "team") %>% na.omit() %>%
  # 縦軸の数字が桁違いに大きいので,桁を整えます
  mutate(salary = salary/1000)

チームによって傾きは違う,というのを考慮するモデルを書きます。

普通の回帰分析は \[ Y_i = \beta_0 + \beta_1 X_i\] ですが,チーム(添字j)をつけて \[ Y_{ij} = \beta_0 + \beta_{1j} X_{ij} \] とします。

切片(\(\beta_0\))には\(j\)がついていないので,各チーム共通です。

# データはリスト型で渡す
data.HLM <- list(L=NROW(df),X=df$本塁打, Y=df$salary,
                G=12, Idx=as.numeric(as.factor(df$team)))
model.HLM.0 <- stan_model('HLM0.stan')
## data{
##   int<lower=0> L; // data length
##   int<lower=0> G; //number of maximim groups
##   real X[L];
##   real Y[L];
##   int Idx[L]; // index of team
## }
## 
## parameters{
##   real beta_0;
##   real beta_1[G];
##   real<lower=0> sigma[G];
## }
## 
## model{
##   for(l in 1:L){
##     Y[l] ~ normal(beta_0 + beta_1[Idx[l]] * X[l], sigma[Idx[l]]);
##   }
##   beta_0 ~ normal(0,100);
##   beta_1 ~ normal(0,100);
##   sigma ~ cauchy(0,5);
## }
## 
## generated quantities{
##   real pred[L];
##   for(l in 1:L){
##     pred[l] = normal_rng(beta_0 + beta_1[Idx[l]]* X[l], sigma[Idx[l]]);
##   }
## }
fit.HLM.0 <- sampling(model.HLM.0,data.HLM)
print(fit.HLM.0,pars=c("beta_0","beta_1"))
## Inference for Stan model: HLM0.
## 4 chains, each with iter=2000; warmup=1000; thin=1; 
## post-warmup draws per chain=1000, total post-warmup draws=4000.
## 
##            mean se_mean   sd  2.5%  25%  50%  75% 97.5% n_eff Rhat
## beta_0     2.47    0.01 0.37  1.74 2.22 2.47 2.72  3.17  4000    1
## beta_1[1]  0.46    0.00 0.10  0.26 0.40 0.46 0.53  0.67  4000    1
## beta_1[2]  0.63    0.00 0.11  0.41 0.56 0.63 0.71  0.86  4000    1
## beta_1[3]  0.65    0.00 0.14  0.38 0.56 0.65 0.75  0.94  4000    1
## beta_1[4]  0.63    0.00 0.16  0.30 0.52 0.63 0.73  0.94  4000    1
## beta_1[5]  0.77    0.00 0.12  0.53 0.69 0.77 0.84  0.99  4000    1
## beta_1[6]  1.13    0.00 0.26  0.64 0.95 1.12 1.30  1.64  4000    1
## beta_1[7]  0.56    0.00 0.21  0.15 0.43 0.56 0.70  0.99  4000    1
## beta_1[8]  0.22    0.00 0.15 -0.08 0.12 0.21 0.32  0.52  4000    1
## beta_1[9]  0.71    0.00 0.25  0.23 0.55 0.71 0.87  1.21  4000    1
## beta_1[10] 1.35    0.00 0.17  1.02 1.24 1.35 1.47  1.68  4000    1
## beta_1[11] 0.96    0.00 0.13  0.69 0.88 0.96 1.05  1.22  4000    1
## beta_1[12] 2.11    0.01 0.39  1.36 1.85 2.12 2.37  2.88  4000    1
## 
## Samples were drawn using NUTS(diag_e) at Mon Sep  3 19:34:55 2018.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at 
## convergence, Rhat=1).
可視化
# MCMCオブジェクトから予測値を取り出してデータフレームへ
rstan::extract(fit.HLM.0,pars='pred') %>% data.frame() %>%
  # データを縦型に
  tidyr::gather(key,val,factor_key=T) %>% 
  # 変数ごとにグルーピング
  group_by(key) %>% 
  # 平均値を推定値とします
  summarize(EAP=mean(val)) %>% ungroup %>% 
  # 元データの情報をくっつけて
  mutate(salary=df$salary,team=df$team,HR=df$本塁打) %>% 
  # 必要な変数だけにして
  select("EAP","HR","salary","team") %>% 
  # 描画
  ggplot(aes(x=HR,y=EAP,color=team))+geom_point()+
  geom_point(aes(x=HR,y=salary),color="Black")+
  facet_wrap(~team)

階層化ー個別の推定を超えて

回帰係数が12もあるのでわかりにくいかもしれません。そこで,回帰係数\(\beta_{1j}\)が分布することを考えます。すなわち, \[\beta_{1j} \sim Normal(\beta_{1m},\tau^2)\] のように,一箇所に情報を集約してやるのです。これもパラメータ\(\beta_{1j}\)に更に上位階層のパラメータ(\(\beta_{1m},\tau\))がついた階層モデルになります。

model.HLM <- stan_model('HLM.stan')
## data{
##   int<lower=0> L;
##   int<lower=0> G; //グループ数
##   real X[L];
##   real Y[L];
##   int Idx[L];
## }
## 
## parameters{
##   real beta_0;
##   real beta_1[G];
##   real mu_beta1;
##   real<lower=0> sigma[G];
##   real<lower=0> tau;
## }
## 
## model{
##   for(l in 1:L){
##     Y[l] ~ normal(beta_0 + beta_1[Idx[l]] * X[l], sigma[Idx[l]]);
##   }
##   
##   beta_1 ~ normal(mu_beta1,tau);
##   beta_0 ~ normal(0,100);
##   sigma ~ cauchy(0,5);  
##   mu_beta1 ~ normal(0,100);
##   tau ~ cauchy(0,5);
## }
## 
## generated quantities{
##   real pred[L];
##   for(l in 1:L){
##     pred[l] = normal_rng(beta_0 + beta_1[Idx[l]]* X[l], sigma[Idx[l]]);
##   }
## }
fit.HLM <- sampling(model.HLM,data.HLM)
print(fit.HLM,pars=c("beta_0","mu_beta1","tau"))
## Inference for Stan model: HLM.
## 4 chains, each with iter=2000; warmup=1000; thin=1; 
## post-warmup draws per chain=1000, total post-warmup draws=4000.
## 
##          mean se_mean   sd 2.5%  25%  50%  75% 97.5% n_eff Rhat
## beta_0   2.47    0.01 0.37 1.72 2.22 2.48 2.72  3.19  4000    1
## mu_beta1 0.79    0.00 0.14 0.54 0.70 0.78 0.87  1.09  2741    1
## tau      0.39    0.00 0.14 0.18 0.29 0.37 0.46  0.71  2251    1
## 
## Samples were drawn using NUTS(diag_e) at Mon Sep  3 19:35:04 2018.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at 
## convergence, Rhat=1).

このようにすることで得られるメリットは三つあります。

  • 係数の情報の簡素化。これが100チームあったらその利便性は明らかでしょう。
  • 個々のケースについての過剰な適合(オーバーフィティング)からの脱却
  • サンプルサイズが小さな群があっても適切な推定ができる

一方留意すべき点として,次の二点があります。

  • 係数が同じ上位階層の分布から来ている=似通っていることを前提にしており,極端な係数が出にくくなるような縛りをかけられているとも言える。係数が小さくなることを縮小shrinkageという。

上位階層のパラメータであったとしても,ベイジアンモデリングの文脈では事後分布という同時確率空間の中にあることを思い出してください。

  • 予測に関する適合度に注意が必要である。すなわち,あるチームに新しい選手が入ることを考えるのか(下のレベルでの予測),次のチームができることを考えるのか(上位レベルでの予測)によって,予測の意味が変わってくる。

二点めのことについては,渡邊先生のサイトに詳しいので,参照してください。

個人と刺激でネストされたデータ;IRTを例に

個人iが刺激(項目)jに反応したデータ\(Y_{ij}\)は,正答を1,誤答を0としたダミーデータが入っているとします。この時,個人の正答率\(p_i\)も検証したいですが,問題が難問なのか簡単だったのか,という難易度にも夜と思われます。そこで難易度を\(q_j\)とし,

\[ Y_{ij} \sim Bernoulli(\theta_{ij})\] \[ \theta_{ij} = p_i \times q_j \] を考えたモデルを書くことができます。

model.irt0 <- stan_model("IRT0.stan")
## data{
##   int<lower=0> N; //number of persons
##   int<lower=0> M; //number of questions
##   int K[N,M]; 
## }
## 
## parameters{
##   real<lower=0,upper=1> p[N];
##   real<lower=0,upper=1> q[M];
## }
## 
## transformed parameters{
##   real<lower=0,upper=1> theta[N,M];
##   for(n in 1:N){
##     for(m in 1:M){
##       theta[n,m] = p[n] * q[m];
##     }
##   }
## }
## 
## model{
##   for(n in 1:N){
##     for(m in 1:M){
##       K[n,m] ~ bernoulli(theta[n,m]);
##     }
##   }
##   p ~ beta(1,1);
##   q ~ beta(1,1);
## }

このように,刺激の特性と個人の特性を区別して推定できるようになります。実験刺激の個々の特性から独立した個人の性質を測定したい場合などに応用できるでしょう。

この考え方は項目反応理論(Item Response Theory)とよばれるテスト理論の一部を,ベイズモデルとして書いたものです(よく用いられるラッシュモデルは異なる定式化をしますが)。多段階の反応モデルも提案されており,リッカーと尺度の分析などにも応用できるでしょう。

ベイズモデルを使うと,このように「個人でネストしている」「変数でネストしている」というような状況も表現できますし,それぞれの上位階層を想定することも可能です。まさに,ベイジアンモデリングの限界は研究者の想像力だけなのです。