Using tidy data with Bayesian models

Matthew Kay

2022-12-15

イントロダクション

この vignette は、Rのベイズモデルでtidyデータ (1行に1つの観測値) の使用を容易にする tidybayes パッケージを紹介する。この vignette は、JAGSやStanなどの汎用モデリング関数でtidyデータを使用することに向けられたものである。brmsrstanarm のような高レベルのモデリング関数での tidybayes の使用に関する同様の紹介は、vignette("tidy-brms")vignette("tidy-rstanarm") を参照。 また、このビネットは、以下のような使用方法を説明している。 ggdist ( の姉妹パッケージ) を使ってモデル出力を可視化することができる。 tidybayes JAGS や Stan などの一般的なモデリング関数のデフォルトの出力 (場合によっては入力) データ形式は、 tidy data の理想に全く合致していないことがよくある。例えば、入力形式は、データフレームの代わりにリストを期待し、すべての変数が数値としてエンコードされることを期待する (因子を数値に変換し、因子ごとのレベルの数またはデータフレーム内の観察数を格納するためにインデックス変数を作成することが必要) 。出力形式は多くの場合、行列形式であり (ggplotのようなライブラリで使用するために変換が必要) 、数値インデックスを使用する (意味のあるラベルを付けたプロットや表を作成したい場合、因子レベルの名前に戻す変換が必要) 。tidybayes はこの種のタスクをすべて自動化している。

フィロソフィー

tidybayes APIには、使い勝手を良くするための核となる考え方がいくつかある。

  1. 整頓されたデータとは、必ずしもすべてのパラメータ名を値とすることではない__。ggmcmc ライブラリ (モデルの結果を Parametervalue の列を持つデータフレームに変換する) とは対照的に、tidybayesspread_draws 関数は、列の名前がパラメータと (場合によっては) それらのパラメータのインデックスであるデータフレームを、できるだけ自動的に、モデルの言語でそれらの変数を参照する方法とできるだけ同じ構文で作成する。ggmcmc のアプローチと同様の関数が gather_draws でも提供されている。のようなインデックスを持つパラメータを、データフレームをどのように見せるか、という面倒な作業を tidybayes が行ってくれることが目的である。 "b [1,2] " などのインデックスを持つパラメータを整頓されたデータに変換することも含めて、データフレームを必要な形にする面倒な作業を行うことである。

  2. Fit into the tidyverse. tidybayes メソッドは tidyverse ( dplyr , tidyr , ggplot2 , etc) のユーザーに馴染みのあるワークフローにフィットする。つまり、パイプ ( %>% ) のワークフローにフィットし、グループ化されたデータフレームを使用し尊重し (したがって spread_drawsgather_draws はすでに変数インデックスでグループ化された結果を返し、median_qi などのメソッドは変数とグループの点要約と間隔を同時に計算する) 、もし既存の tidyverse パッケージが提供する関数によってすでに容易になっているのなら (それが共通のイディオムにずっと明確なコードになっていなければ) 車輪をあまり再発明しないようにすることである。他のパッケージの列名 (例えば broom::tidy ) との互換性のために、tidybayesto_broom_names のような変換関数を提供し、データ変換パイプラインに直接ドロップすることができる。

  3. 一枚岩のプロットや操作ではなく、組み合わせ可能な操作やプロットプリミティブに重点を置く__。他のいくつかのパッケージ(特に bayesplotggmcmc )は、ベイズの結果をプロットするための優れた様々な既成のメソッドを既に提供している。tidybayes は、この関数を複製することは避けている。その代わりに、ベイズサンプルの生成と操作を整然としたデータ形式で行うための複合操作と、カスタムプロットを簡単に作成できる ggplot 用のグラフィカルプリミティブを提供することに焦点を合わせている。最も簡単には、bayesplotggmcmc が完全な ggplot オブジェクトを返す多くのオプションを持つ関数を持つ傾向があるのに対し、tidybayes は独自のカスタムプロットに合成し組み合わせることができるプリミティブを提供する傾向がある ( geom のような)。私はどちらのアプローチにもその場所があると信じている: 既製の関数は、(多くの診断プロットのように) カスタマイズを必要としない一般的で迅速な操作に特に有用であり、一方 composable 操作は ( my opinion で) より複雑なカスタムプロットにとって有用である傾向がある。

  4. しかし、オプション (と、そもそもデータが整頓されていること) を使えば、必要なときに簡単に独自の道を進むことができる。

  5. モデル内の変数名は、暗号ではなく、説明的であるべきです__。この原則は、暗号のような (短い) 添え字を避け、より長い (しかし説明的な) 添え字を使用することを意味する。これは、モデルの読みやすさとアクセスしやすさの問題である。例えば、Stanユーザーの間で (そしてStanのマニュアルでも) よくあるパターンは、グループ内の要素の数 (例えば、参加者の数) を参照するために J のような変数を使い、そのグループ内の特定の要素を参照するために j のような対応するインデックスを使うことである。私は、n_participant のようなパターンがグループのサイズ、participant (または p のようなニーモニックな短縮形) が特定の要素に適していると思っている。名前が自動生成される関数 ( compose_data など) では、tidybayes は (デフォルトで) あなたがこのようなより説明的な名前を望んでいると仮定する。しかし、あなたはいつでもデフォルトの命名方式をオーバーライドすることができる。

対応モデル

tidybayes は、統一されたインターフェースで様々なモデルをサポートすることを目的としている。現在、対応している機種は以下の通りである。
rstancmdstanrbrmsrstanarmrunjagsrjagsjagsUIcoda::mcmc and coda::mcmc.listposterior::drawsMCMCglmm
と、独自の as.mcmc.list の実装を持つものである。 tidybayes.rethinking パッケージをインストールすると、 rethinking パッケージのモデルもサポートされる。

最新の対応モデル一覧は、?"tidybayes-models" を参照。

セットアップ

このvignetteを実行するには、以下のライブラリが必要である。

library(magrittr)
library(dplyr)
library(forcats)
library(modelr)
library(ggdist)
library(tidybayes)
library(ggplot2)
library(cowplot)
library(emmeans)
library(broom)
library(rstan)
library(rstanarm)
library(brms)
library(bayesplot)
library(MCMCglmm)
library(RColorBrewer)

theme_set(theme_tidybayes() + panel_border())

これらのオプションは、Stanの動作を高速化するためのものである。

rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())

サンプルデータセット

tidybayes を実証するために、5つの条件からそれぞれ10個のオブザベーションを持つ単純なデータセットを使用することにする。

set.seed(5)
n = 10
n_condition = 5
ABC =
  tibble(
    condition = rep(c("A","B","C","D","E"), n),
    response = rnorm(n * 5, c(0,1,2,1,-1), 0.5)
  )

データのスナップショットはこのような感じである。

head(ABC, 10)
condition response
A -0.4204277
B 1.6921797
C 1.3722541
D 1.0350714
E -0.1442796
A -0.3014540
B 0.7639168
C 1.6823143
D 0.8571132
E -0.9309459

これは典型的な整頓されたフォーマットのデータフレームです: 1行に1つの観測値である。グラフィカルに

ABC %>%
  ggplot(aes(x = response, y = fct_rev(condition))) +
  geom_point(alpha = 0.5) +
  ylab("condition")

compose_data を使ってモデル用のデータフレームを準備する

データフレームから JAGS や Stan などのサンプラーで利用可能な形式にデータを移行する場合、演算回数や因子のレベル数 を格納するインデックス変数の生成など、一連の面倒な操作が必要になる。compose_data は、これらの操作を自動化するものである。

この例のデータを階層モデルにすると、条件全体の平均値( overall_mean )、条件平均値の標準偏差( condition_mean_sd )、各条件内の平均値( condition_mean [condition] )、条件内平均( response_sd )、条件内平均が与えられたときの回答の標準偏差( )が当てはまる。

data {
  int<lower=1> n;
  int<lower=1> n_condition;
  int<lower=1, upper=n_condition> condition[n];
  real response[n];
}
parameters {
  real overall_mean;
  vector[n_condition] condition_zoffset;
  real<lower=0> response_sd;
  real<lower=0> condition_mean_sd;
}
transformed parameters {
  vector[n_condition] condition_mean;
  condition_mean = overall_mean + condition_zoffset * condition_mean_sd;
}
model {
  response_sd ~ cauchy(0, 1);         // => half-cauchy(0, 1)
  condition_mean_sd ~ cauchy(0, 1);   // => half-cauchy(0, 1)
  overall_mean ~ normal(0, 5);
  condition_zoffset ~ normal(0, 1);   // => condition_mean ~ normal(overall_mean, condition_mean_sd)
  for (i in 1:n) {
    response[i] ~ normal(condition_mean[condition[i]], response_sd);
  }
}

このモデルをコンパイルして、変数 ABC_stan にロードしている。

このモデルは、これらの変数を入力として想定している。

私たちのデータフレーム ( ABC ) には responsecondition しかなく、condition は間違った形式である (数値ではなく係数)。しかし、compose_data は、上記の変数を正しい形式で含むリストを自動的に生成することができる。それは、condition が因子であることを認識し、それを数値に変換し、condition のレベルの数を含む n_condition 変数を自動的に追加し、観測数 (データフレーム内の行数) を含む n 列を追加する。

compose_data(ABC)
## $condition
##  [1] 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5
## 
## $n_condition
## [1] 5
## 
## $response
##  [1] -0.42042774  1.69217967  1.37225407  1.03507138 -0.14427956 -0.30145399  0.76391681  1.68231434  0.85711318
## [10] -0.93094589  0.61381517  0.59911027  1.45980370  0.92123282 -1.53588002 -0.06949307  0.70134345  0.90801662
## [19]  1.12040863 -1.12967770  0.45025597  1.47093470  2.73398095  1.35338054 -0.59049553 -0.14674092  1.70929454
## [28]  2.74938691  0.67145895 -1.42639772  0.15795752  1.55484708  3.10773029  1.60855182 -0.26038911  0.47578692
## [37]  0.49523368  0.99976363  0.11890706 -1.07130406  0.77503018  0.59878841  1.96271054  1.94783398 -1.22828447
## [46]  0.28111168  0.55649574  1.76987771  0.63783576 -1.03460558
## 
## $n
## [1] 50

このため、自分でデータを加工することなく、すぐにモデルの実行に移れる。

m = sampling(ABC_stan, data = compose_data(ABC), control = list(adapt_delta = 0.99))

結果はこのようになる。

m
## Inference for Stan model: 150269bbfe815db848542eb814742386.
## 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
## overall_mean          0.61    0.03 0.68 -0.69  0.28  0.62  0.97  1.81   508 1.01
## condition_zoffset[1] -0.39    0.02 0.50 -1.41 -0.72 -0.39 -0.06  0.57   777 1.00
## condition_zoffset[2]  0.34    0.02 0.49 -0.59  0.02  0.35  0.66  1.33   888 1.01
## condition_zoffset[3]  1.11    0.02 0.58  0.01  0.70  1.10  1.48  2.26   865 1.01
## condition_zoffset[4]  0.36    0.02 0.49 -0.61  0.04  0.36  0.69  1.32   872 1.01
## condition_zoffset[5] -1.39    0.03 0.68 -2.78 -1.84 -1.35 -0.93 -0.12   681 1.01
## response_sd           0.56    0.00 0.06  0.46  0.52  0.56  0.60  0.70  1606 1.00
## condition_mean_sd     1.25    0.02 0.55  0.61  0.89  1.12  1.43  2.71   546 1.01
## condition_mean[1]     0.19    0.00 0.18 -0.16  0.07  0.19  0.31  0.55  4294 1.00
## condition_mean[2]     1.00    0.00 0.18  0.65  0.89  1.00  1.12  1.34  4700 1.00
## condition_mean[3]     1.84    0.00 0.18  1.48  1.71  1.84  1.96  2.19  4735 1.00
## condition_mean[4]     1.02    0.00 0.17  0.66  0.90  1.02  1.13  1.35  4398 1.00
## condition_mean[5]    -0.89    0.00 0.18 -1.24 -1.01 -0.89 -0.77 -0.54  4297 1.00
## lp__                  0.28    0.09 2.45 -5.51 -1.14  0.65  2.06  3.97   752 1.00
## 
## Samples were drawn using NUTS(diag_e) at Thu Dec 15 15:49:45 2022.
## 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).

spread_draws を使ってフィットからドローをtidyフォーマットで抽出する

モデル変数のインデックスを整頓されたフォーマットのデータフレーム内の別の列に抽出する

さて、結果が出たところで、お楽しみは、ドローを整頓されたフォーマットで取り出すことである!モデルからドローを抽出するためのStanのデフォルトのメソッドは、ネストされたフォーマットでそれを行う。

str(rstan::extract(m))
## List of 6
##  $ overall_mean     : num [1:4000(1d)] 1.057 2.369 -1.134 -0.295 0.106 ...
##   ..- attr(*, "dimnames")=List of 1
##   .. ..$ iterations: NULL
##  $ condition_zoffset: num [1:4000, 1:5] -0.5102 -1.0016 0.7883 0.2327 0.0647 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ iterations: NULL
##   .. ..$           : NULL
##  $ response_sd      : num [1:4000(1d)] 0.566 0.518 0.478 0.57 0.535 ...
##   ..- attr(*, "dimnames")=List of 1
##   .. ..$ iterations: NULL
##  $ condition_mean_sd: num [1:4000(1d)] 1.66 2.46 1.5 1.62 2.38 ...
##   ..- attr(*, "dimnames")=List of 1
##   .. ..$ iterations: NULL
##  $ condition_mean   : num [1:4000, 1:5] 0.2085 -0.0992 0.0492 0.082 0.2603 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ iterations: NULL
##   .. ..$           : NULL
##  $ lp__             : num [1:4000(1d)] 1.16 -2.464 -0.129 2.318 3.953 ...
##   ..- attr(*, "dimnames")=List of 1
##   .. ..$ iterations: NULL

また、Stanではドローをマトリクスやデータフレームとして抽出する方法がある (JAGSやMCMCglmmなど他のモデルタイプでは、独自のフォーマットがある) 。

spread_draws メソッドは、tidybayes がサポートするすべてのモデルタイプに共通の形式をもたらする。.chain.iteration の列が各行のチェーンとイテレーションを格納し (利用可能な場合) 、.draw の列が各ドローにユニークなインデックスを付け、残りの列がモデル変数または変数インデックスに対応する。spread_draws メソッドは、変数の名前と変数インデックスの名前を含むことができる、任意の数の列の指定を受け入れる。例えば、condition_mean 変数を整然としたデータフレームとして抽出し、その最初の (そして唯一の) インデックスの値を condition 列に入れることができる。この構文は、モデル自体で condition_mean 変数のインデックスを指定する方法と直接エコーするものである。

m %>%
  spread_draws(condition_mean[condition]) %>%
  head(10)
## Warning: `gather_()` was deprecated in tidyr 1.2.0.
## ℹ Please use `gather()` instead.
## ℹ The deprecated feature was likely used in the tidybayes package.
##   Please report the issue at <]8;;https://github.com/mjskay/tidybayes/issues/newhttps://github.com/mjskay/tidybayes/issues/new]8;;>.
condition condition_mean .chain .iteration .draw
1 0.0425315 1 1 1
1 0.4303988 1 2 2
1 0.0148958 1 3 3
1 0.5342393 1 4 4
1 0.2983696 1 5 5
1 0.0502674 1 6 6
1 0.1020039 1 7 7
1 0.1701838 1 8 8
1 0.0351719 1 9 9
1 0.2785180 1 10 10

列やインデックスを元のデータ型に自動変換して戻す

そのままでは、結果として得られる変数は、そのインデックスがどこから来たのかについて何も知らない。condition_mean 変数のインデックスはもともと ABC データフレーム内の condition 因子から派生したものである。しかし、Stanはこのことを知らない:Stanにとっては単なる数値インデックスなので、condition 列には、これらの数値が対応する因子レベル ( 1, 2, 3, 4, 5 ) ではなく、ただ数値 ( ) が含まれている。"A "," B "," C "," D "," E" ).

spread_draws を使う前に recover_types を通してモデルを渡すことで、この失われた型情報を回復することができる。recover_types はモデルのコピーに、渡されたデータフレーム (または他のオブジェクト) の型情報を格納する属性を追加して返すだけである。これだけでは何の役にも立ちないが、spread_draws のような関数はこの情報を使って、任意の列やインデックスを元のデータフレーム内の同じ名前を持つ列のデータ型に変換して戻する。この例では、spread_draws は、condition 列が5つのレベルを持つ因子であることを認識する ( "A "," B "," C "," D "," E" )であることを認識し、自動的に因子に変換する。

m %>%
  recover_types(ABC) %>%
  spread_draws(condition_mean[condition]) %>%
  head(10)
condition condition_mean .chain .iteration .draw
A 0.0425315 1 1 1
A 0.4303988 1 2 2
A 0.0148958 1 3 3
A 0.5342393 1 4 4
A 0.2983696 1 5 5
A 0.0502674 1 6 6
A 0.1020039 1 7 7
A 0.1701838 1 8 8
A 0.0351719 1 9 9
A 0.2785180 1 10 10

spread_draws を複数回に分けて呼び出したいことがよくあるので、元のモデルが適合された直後に recover_types を使って装飾すると、一回だけ呼び出せばよいので便利である。

m %<>% recover_types(ABC)

これで、以降の spread_draws の呼び出しの前に、recover_types の呼び出しを省略することができるようになった。

point_interval 関数で点の要約と間隔を指定する中央値|平均値|モード][qi|hdi]`. {#point-summaries-and-intervals-with-the-point_interval-functions-[median|mean|mode][qi|hdi]}

シンプルな変数で、ワイドフォーマット

tidybayes は、描画から点の要約や区間を整然としたフォーマットで生成するための関数群を提供する。これらの関数は、 [median|mean|mode] _ [qi|hdi] の命名規則に従っている。例えば、median_qi , mean_qi , mode_hdi , などである。最初の名前 ( _ の前) は点要約のタイプを示し、2番目の名前は区間のタイプを示す。qi は分位数区間 (別名:等値尾区間、中心区間、パーセンタイル区間) を、hdi は最高密度区間を出力する。カスタム点または区間関数は、point_interval 関数を使用して適用することもできる。

例えば、オブザベーションの全体的な平均と標準偏差に対応するドローを抽出することができる。

m %>%
  spread_draws(overall_mean, response_sd) %>%
  head(10)
.chain .iteration .draw overall_mean response_sd
1 1 1 0.4720119 0.5490346
1 2 2 0.7776089 0.5470606
1 3 3 -0.1013847 0.5573434
1 4 4 -0.0086601 0.6155352
1 5 5 0.3126983 0.6161216
1 6 6 0.3534224 0.5174635
1 7 7 -0.1594659 0.4984251
1 8 8 1.4268865 0.5383222
1 9 9 1.9375415 0.5635136
1 10 10 1.3272984 0.5369298

condition_mean [condition] と同様、整頓されたデータフレームが得られる。変数の中央値と95%分位間隔が欲しい場合は、median_qi を適用する。

m %>%
  spread_draws(overall_mean, response_sd) %>%
  median_qi(overall_mean, response_sd)
overall_mean overall_mean.lower overall_mean.upper response_sd response_sd.lower response_sd.upper .width .point .interval
0.6177286 -0.6883475 1.80586 0.5564333 0.4580972 0.702512 0.95 median qi

median_qi は、各入力列をその中央値を用いて要約する。要約する列が複数ある場合、 %区間の境界に対応する と 列 (各列 ) がそれぞれ作成される。列が1つしかない場合、 と という名前が区間境界に使用される。 .width x.lower x.upper x .lower .upper 上記のように、中央値と区間を取得したい列を指定することができるが、列のリストを省略すると、median_qi は、グループ化列や特殊列ではないすべての列 ( .chain , .iteration , または .draw のような) を使用する。したがって、上記の例では、overall_meanresponse_sd は、モデルから収集した唯一の列でもあるため、median_qi の冗長な引数となっている。から、前のコードを次のように単純化することができる。

m %>%
  spread_draws(overall_mean, response_sd) %>%
  median_qi()
overall_mean overall_mean.lower overall_mean.upper response_sd response_sd.lower response_sd.upper .width .point .interval
0.6177286 -0.6883475 1.80586 0.5564333 0.4580972 0.702512 0.95 median qi

インデックス付き変数の場合

condition_mean のように1つ以上のインデックスを持つ変数がある場合、前と同様に median_qi (または point_interval ファミリーの他の関数) を適用することができる。

m %>%
  spread_draws(condition_mean[condition]) %>%
  median_qi()
condition condition_mean .lower .upper .width .point .interval
A 0.1923963 -0.1587834 0.5495929 0.95 median qi
B 1.0007694 0.6510203 1.3431703 0.95 median qi
C 1.8382099 1.4805718 2.1913833 0.95 median qi
D 1.0180656 0.6615895 1.3503778 0.95 median qi
E -0.8904054 -1.2397288 -0.5440394 0.95 median qi

median_qi はどのようにして集約するものを知ったのだろうか? spread_draws によって返されたデータフレームは、あなたが渡したすべてのインデックス変数によって自動的にグループ化される。この場合、それは condition によってグループ化されることを意味する。median_qi はグループを尊重し、すべてのグループ内のポイントの要約と間隔を計算する。そして、median_qi に列が渡されなかったので、唯一の非特殊列 ( . -prefixed) であり、非グループ列である condition_mean に対して処理を行う。したがって、上記の短縮された構文は、より冗長なこの呼び出しと等価である。

m %>%
  spread_draws(condition_mean[condition]) %>%
  group_by(condition) %>%    # this line not necessary (done automatically by spread_draws)
  median_qi(condition_mean)
condition condition_mean .lower .upper .width .point .interval
A 0.1923963 -0.1587834 0.5495929 0.95 median qi
B 1.0007694 0.6510203 1.3431703 0.95 median qi
C 1.8382099 1.4805718 2.1913833 0.95 median qi
D 1.0180656 0.6615895 1.3503778 0.95 median qi
E -0.8904054 -1.2397288 -0.5440394 0.95 median qi

1列しか与えられない場合、median_qi は区間の下端と上端にそれぞれ .lower.upper という名前を使用する。

tidybayes は、 の実装も提供している。 posterior::summarise_draws() グループ化されたデータフレーム ( tidybayes::summaries_draws.grouped_df() ) を作成することができる。 を使用すると、コンバージェンス診断が迅速に行える。

m %>%
  spread_draws(condition_mean[condition]) %>%
  summarise_draws()
condition variable mean median sd mad q5 q95 rhat ess_bulk ess_tail
A condition_mean 0.1941861 0.1923963 0.1803730 0.1795563 -0.0997071 0.4963313 1.000495 4351.924 2942.437
B condition_mean 1.0000573 1.0007694 0.1758305 0.1706891 0.7145377 1.2846996 1.000052 4734.204 3041.213
C condition_mean 1.8369746 1.8382099 0.1810089 0.1802728 1.5394096 2.1299333 1.000210 4748.792 3486.504
D condition_mean 1.0168191 1.0180656 0.1735925 0.1730640 0.7296146 1.2970612 1.001107 4421.551 3420.011
E condition_mean -0.8899228 -0.8904054 0.1759922 0.1760503 -1.1813634 -0.5975538 1.000152 4330.823 3349.994

点と間隔のプロット

geom_pointinterval` を使用する

中央値と区間のプロットは、ggdist::geom_pointinterval() または ggdist::stat_pointinterval() を使えば簡単である。これは ggplot2::geom_pointrange() と似ているが、複数の区間に対して賢明なデフォルトが設定されている。たとえば

m %>%
  spread_draws(condition_mean[condition]) %>%
  ggplot(aes(y = fct_rev(condition), x = condition_mean)) +
  stat_pointinterval()
## Warning: Using the `size` aesthietic with geom_segment was deprecated in ggplot2 3.4.0.
## ℹ Please use the `linewidth` aesthetic instead.

これらの関数はデフォルトで .width = c(.66, .95) を持つが (66%および95%区間を表示) 、これは ggdist::stat_pointinterval().width 引数を渡すことで変更可能である。

後方バイオリンによる区間統計的眼球

ggdist::stat_halfeye() geomは、「半眼プロット」 (区間と密度の組み合わせ) を生成するためのショートカットを提供する。この例は、区間確率を変更する方法も示している (ここでは、90%および50%区間に変更) 。

m %>%
  spread_draws(condition_mean[condition]) %>%
  ggplot(aes(y = fct_rev(condition), x = condition_mean)) +
  stat_halfeye(.width = c(.90, .5))

あるいは、密度の一部をカラーでアノテートしたいとする。fill の美観は、ggdist::stat_halfeye() を含む ggdist::geom_slabinterval() ファミリーのすべてのジオムおよび統計において、スラブ内で変化させることができる。例えば、ドメイン固有のROPE (region of practical equivalence) をアノテーションしたい場合、次のようなことが可能である。

m %>%
  spread_draws(condition_mean[condition]) %>%
  ggplot(aes(y = fct_rev(condition), x = condition_mean, fill = stat(abs(x) < .8))) +
  stat_halfeye() +
  geom_vline(xintercept = c(-.8, .8), linetype = "dashed") +
  scale_fill_manual(values = c("gray80", "skyblue"))
## Warning: `stat(abs(x) < 0.8)` was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(abs(x) < 0.8)` instead.

その他の分布の可視化 stat_slabinterval

分布を視覚化するための様々な追加統計が ggdist::geom_slabinterval() ファミリーの統計とジオムで提供されている。

The slabinterval family of geoms and stats

を参照。 vignette("slabinterval ", package =" ggdist") を参照。

複数の確率レベルを持つ区間: .width = 引数

プロットする前にデータを要約したい場合 (以下のような場合に便利である。 large samples)、median_qi() とその姉妹関数も生成することができる。
引数 .width = を設定することにより、任意の数の確率区間を設定することができる。

m %>%
  spread_draws(condition_mean[condition]) %>%
  median_qi(.width = c(.95, .8, .5))
condition condition_mean .lower .upper .width .point .interval
A 0.1923963 -0.1587834 0.5495929 0.95 median qi
B 1.0007694 0.6510203 1.3431703 0.95 median qi
C 1.8382099 1.4805718 2.1913833 0.95 median qi
D 1.0180656 0.6615895 1.3503778 0.95 median qi
E -0.8904054 -1.2397288 -0.5440394 0.95 median qi
A 0.1923963 -0.0329867 0.4267323 0.80 median qi
B 1.0007694 0.7753011 1.2225014 0.80 median qi
C 1.8382099 1.6050739 2.0721056 0.80 median qi
D 1.0180656 0.7954088 1.2386680 0.80 median qi
E -0.8904054 -1.1131385 -0.6652568 0.80 median qi
A 0.1923963 0.0736693 0.3141231 0.50 median qi
B 1.0007694 0.8863465 1.1165120 0.50 median qi
C 1.8382099 1.7139749 1.9584869 0.50 median qi
D 1.0180656 0.9012443 1.1341256 0.50 median qi
E -0.8904054 -1.0090758 -0.7716409 0.50 median qi

結果は整然とした形式で、インデックス( condition )と確率水準( .width )ごとに1行ずつ表示される。これはプロットを容易にする。たとえば、-.widthsize の美観に割り当てると、すべての区間が表示され、太い線がより小さい区間に対応するようになる。

m %>%
  spread_draws(condition_mean[condition]) %>%
  median_qi(.width = c(.95, .66)) %>%
  ggplot(aes(
    y = fct_rev(condition), x = condition_mean, xmin = .lower, xmax = .upper,
    # size = -.width means smaller probability interval => thicker line
    # this can be omitted, geom_pointinterval includes it automatically
    # if a .width column is in the input data.
    size = -.width
  )) +  
  geom_pointinterval()

ggdist::geom_pointinterval() が含まれている。
size = -.width をデフォルトの美的マッピングとして使用することで、まさにこの使い方を容易にすることができる。

後景を分位点描画でプロットする

アルファレベルがたまたまあなたが行おうとしている決定と一致する場合、区間は良いが、事後的な形状を得ることはより良いことである (したがって、上記の目のプロット) 。一方、密度プロットから推論するのは不正確である (ある形状の面積を別の形状の割合で推定するのは難しい知覚的作業である) 。頻度形式での確率の推論はより簡単で、 quantile dotplots ( Kay et al. 2016, Fernandes et al. 2018)の動機となった。これは任意の間隔 (プロットのドット解像度まで、下の例では100) を正確に推定することも可能である。

tidybayes のジオムの slabinterval ファミリーには dotsdotsinterval ファミリーがあり、ドットプロットの適切なビンサイズを自動的に決定し、サンプルから分位を計算して分位ドットプロットを構築できる。ggdist::stat_dots() はサンプルに使用するために設計されたバリアントである。

m %>%
  spread_draws(condition_mean[condition]) %>%
  ggplot(aes(x = condition_mean, y = fct_rev(condition))) +
  stat_dotsinterval(quantiles = 100)

このアイデアは、事後を1つの標準的な点または区間として考えるのではなく、 (例えば) 100のほぼ等しい可能性のある点として表現することである。

中央値、平均値、最頻値、qi、hdi、hdci

point_interval() 関数ファミリーは、以下の命名規則に従っている。 [median|mean|mode] _ [qi|hdi|hdci] median_qi() これらは、一連の名前 (または列で計算された式) を取り、対応する点要約関数 (中央値、平均値、最頻値) と区間 (qi、hdi、またはhdci) でこれらの列を要約する。 qi は分位数区間 (別名:等値尾区間、中心区間、パーセンタイル区間) を、hdi は1つ以上の最高 (事後) 密度区間を、hdci は単一の (おそらく) 最高密度連続区間をそれぞれ出力する。これらは、任意の組み合わせで使用することができる。

*_hdi 関数には、さらに違いがある。多峰性分布の場合、各確率レベルに対して複数の区間を返すかもしれない。以下は、多峰性正規混合分布からのいくつかの抽選である。

set.seed(123)
multimodal_draws = tibble(
  x = c(rnorm(5000, 0, 1), rnorm(2500, 4, 1))
)

mode_hdi() を通すと、80%の確率レベルで複数の区間が得られる。

multimodal_draws %>%
  mode_hdi(x, .width = .80)
x .lower .upper .width .point .interval
-0.0152584 -1.523224 1.598902 0.8 mode hdi
-0.0152584 3.043151 4.997135 0.8 mode hdi

これは、プロットするとわかりやすいね。

multimodal_draws %>%
  ggplot(aes(x = x)) +
  stat_slab(aes(y = 0)) +
  stat_pointinterval(aes(y = -0.5), point_interval = median_qi, .width = c(.95, .80)) +
  annotate("text", label = "median, 80% and 95% quantile intervals", x = 6, y = -0.5, hjust = 0, vjust = 0.3) +
  stat_pointinterval(aes(y = -0.25), point_interval = mode_hdi, .width = c(.95, .80)) +
  annotate("text", label = "mode, 80% and 95% highest-density intervals", x = 6, y = -0.25, hjust = 0, vjust = 0.3) +
  xlim(-3.25, 18) +
  scale_y_continuous(breaks = NULL)

異なるインデックスを持つ変数を1つの整頓されたフォーマットのデータフレームに結合する

spread_draws() は、異なるインデックスを持つ変数の抽出をサポートしている。同じ名前のインデックスを自動的にマッチングし、必要に応じて値を複製して、すべてのインデックスのレベルの組み合わせごとに1つの行を生成する。例えば、各条件の平均と全体の平均の差を計算したい場合がある。そのためには、全体の平均とすべての条件の平均から描画を抽出することができる。

m %>% 
  spread_draws(overall_mean, condition_mean[condition]) %>%
  head(10)
.chain .iteration .draw overall_mean condition condition_mean
1 1 1 0.4720119 A 0.0425315
1 1 1 0.4720119 B 0.7921493
1 1 1 0.4720119 C 1.4227343
1 1 1 0.4720119 D 0.9238246
1 1 1 0.4720119 E -0.7925493
1 2 2 0.7776089 A 0.4303988
1 2 2 0.7776089 B 0.8152688
1 2 2 0.7776089 C 1.6948455
1 2 2 0.7776089 D 1.0371610
1 2 2 0.7776089 E -0.7596942

各抽選の中で、condition_mean の各インデックスに対応するように overall_mean が繰り返される。したがって、dplyr::mutate() 関数を用いてすべての行の差分をとり、median_qi() で要約することができる。

m %>%
  spread_draws(overall_mean, condition_mean[condition]) %>%
  mutate(condition_offset = condition_mean - overall_mean) %>%
  median_qi(condition_offset)
condition condition_offset .lower .upper .width .point .interval
A -0.4129033 -1.6453950 0.9101742 0.95 median qi
B 0.3879801 -0.7981041 1.6785521 0.95 median qi
C 1.2143535 0.0143735 2.5476898 0.95 median qi
D 0.4014766 -0.8057669 1.7446861 0.95 median qi
E -1.5085301 -2.7207445 -0.2364395 0.95 median qi

事後予測

差分指標を持つ変数の組み合わせを使って、モデルから予測値を生成することができる。この場合、条件平均と残差標準偏差を組み合わせて、モデルから予測分布を生成し、ggdist::stat_interval() を使って分布を表示し、データと比較することができる。

m %>%
  spread_draws(condition_mean[condition], response_sd) %>%
  mutate(y_rep = rnorm(n(), condition_mean, response_sd)) %>%
  median_qi(y_rep, .width = c(.95, .8, .5)) %>%
  ggplot(aes(y = fct_rev(condition), x = y_rep)) +
  geom_interval(aes(xmin = .lower, xmax = .upper)) + #auto-sets aes(color = fct_rev(ordered(.width)))
  geom_point(aes(x = response), data = ABC) +
  scale_color_brewer()

このモデルがうまくキャリブレーションされていれば、約95%のデータが外側の区間に、80%が次に小さい区間に、50%が最も小さい区間に収まるはずである。

平均の事後分布による事後予測値

合わせて、データ、事後予測、平均の事後分布。

draws = m %>%
  spread_draws(condition_mean[condition], response_sd)

reps = draws %>%
  mutate(y_rep = rnorm(n(), condition_mean, response_sd))

ABC %>%
  ggplot(aes(y = condition)) +
  stat_interval(aes(x = y_rep), .width = c(.95, .8, .5), data = reps) +
  stat_pointinterval(aes(x = condition_mean), .width = c(.95, .66), position = position_nudge(y = -0.3), data = draws) +
  geom_point(aes(x = response)) +
  scale_color_brewer()

因子の水準を比較する

compare_levels() は、ある変数の値をある因子のレベル間で比較することができる。デフォルトでは一対の差分をすべて計算するが、 引数を使用して変更することができる。 comparison =

m %>%
  spread_draws(condition_mean[condition]) %>%
  compare_levels(condition_mean, by = condition) %>%
  ggplot(aes(y = condition, x = condition_mean)) +
  stat_halfeye()

すべてのモデル変数名を1つの列にまとめる gather_drawsgather_variables である

また、すべてのモデル変数名が列名としてではなく、1つの列 (long-format) であることを好むかもしれない。tidybayes を使ってロングフォーマットのデータフレームを得る方法が2つある。これらの方法は、データ処理チェーンのどこで、どのようにロングフォーマットに変換したいかによる: gather_draws()gather_variables()。また、ワイド (またはセミワイド) フォーマットのデータフレームを得るには、spread_draws() (前述) と tidy_draws() の2つの方法がある。

gather_draws() は、すべての変数名を 列に、すべての描画を 列に置くことを除いて、 と対になるものである。 .variable .value spread_draws()

m %>%
  gather_draws(overall_mean, condition_mean[condition]) %>%
  median_qi()
.variable condition .value .lower .upper .width .point .interval
condition_mean A 0.1923963 -0.1587834 0.5495929 0.95 median qi
condition_mean B 1.0007694 0.6510203 1.3431703 0.95 median qi
condition_mean C 1.8382099 1.4805718 2.1913833 0.95 median qi
condition_mean D 1.0180656 0.6615895 1.3503778 0.95 median qi
condition_mean E -0.8904054 -1.2397288 -0.5440394 0.95 median qi
overall_mean NA 0.6177286 -0.6883475 1.8058598 0.95 median qi

なお、condition = NAoverall_mean の行に対して、gather_draws() に渡された仕様の中にその名前のインデックスがないためである。

複数の列を含む計算をする必要がない場合はこれでうまくいくが、spread_draws() が返す半角フォーマットは、上記の condition_offset の計算のように、複数の列名を含む計算には非常に有効である。spread_draws が返すフォーマットで中間計算を行い、その後*変数を1つの列に集めたい場合は、gather_variables() を使用すると、特別な列でないグループ化されていないすべての変数 ( .chain , .iteration , .draw のような) を集めることができる。

m %>%
  spread_draws(overall_mean, condition_mean[condition]) %>%
  mutate(condition_offset = condition_mean - overall_mean) %>%
  gather_variables() %>%
  median_qi()
condition .variable .value .lower .upper .width .point .interval
A condition_mean 0.1923963 -0.1587834 0.5495929 0.95 median qi
A condition_offset -0.4129033 -1.6453950 0.9101742 0.95 median qi
A overall_mean 0.6177286 -0.6883475 1.8058598 0.95 median qi
B condition_mean 1.0007694 0.6510203 1.3431703 0.95 median qi
B condition_offset 0.3879801 -0.7981041 1.6785521 0.95 median qi
B overall_mean 0.6177286 -0.6883475 1.8058598 0.95 median qi
C condition_mean 1.8382099 1.4805718 2.1913833 0.95 median qi
C condition_offset 1.2143535 0.0143735 2.5476898 0.95 median qi
C overall_mean 0.6177286 -0.6883475 1.8058598 0.95 median qi
D condition_mean 1.0180656 0.6615895 1.3503778 0.95 median qi
D condition_offset 0.4014766 -0.8057669 1.7446861 0.95 median qi
D overall_mean 0.6177286 -0.6883475 1.8058598 0.95 median qi
E condition_mean -0.8904054 -1.2397288 -0.5440394 0.95 median qi
E condition_offset -1.5085301 -2.7207445 -0.2364395 0.95 median qi
E overall_mean 0.6177286 -0.6883475 1.8058598 0.95 median qi

ここで、overall_mean が各条件で繰り返されていることに注意。これは、モデル変数を列間で分散させた後に収集を行ったことがある。

最後に、インデックスをそれ自身の列名として分割するのではなく、生のモデル変数名を列名としたい場合、tidy_draws() を使用することができる。一般的には spread_draws()gather_draws() の方が tidy_draws() よりも便利であるが、多くの種類のベイジアンモデルからデータフレームを生成するための一般的な方法として提供されており、gather_draws()spread_draws() によって内部的に使用されている。

m %>%
  tidy_draws() %>%
  head(10)
.chain .iteration .draw overall_mean condition_zoffset[1] condition_zoffset[2] condition_zoffset[3] condition_zoffset[4] condition_zoffset[5] response_sd condition_mean_sd condition_mean[1] condition_mean[2] condition_mean[3] condition_mean[4] condition_mean[5] lp__ accept_stat__ stepsize__ treedepth__ n_leapfrog__ divergent__ energy__
1 1 1 0.4720119 -0.7228089 0.5387863 1.6000509 0.7603937 -2.1282368 0.5490346 0.5941826 0.0425315 0.7921493 1.422734 0.9238246 -0.7925493 -3.3916720 0.9812419 0.0544657 6 127 0 9.6097018
1 2 2 0.7776089 -0.4906112 0.0532138 1.2960640 0.3667496 -2.1722238 0.5470606 0.7077093 0.4303988 0.8152688 1.694846 1.0371610 -0.7596942 -0.1715153 0.9997818 0.0544657 5 63 0 6.2706423
1 3 3 -0.1013847 0.0753446 0.6906783 1.3778737 0.8135068 -0.5670191 0.5573434 1.5433165 0.0148958 0.9645506 2.025110 1.1541138 -0.9764747 3.0305862 0.9971638 0.0544657 6 95 0 3.3818547
1 4 4 -0.0086601 0.2916825 0.6530099 1.0097827 0.5041831 -0.4694991 0.6155352 1.8612685 0.5342393 1.2067666 1.870817 0.9297600 -0.8825239 1.4644291 0.9806065 0.0544657 6 95 0 1.0158377
1 5 5 0.3126983 -0.0121558 0.4270635 1.3580532 0.5114565 -1.0401146 0.6161216 1.1787490 0.2983696 0.8160989 1.913502 0.9155771 -0.9133358 2.3408543 0.9869015 0.0544657 6 127 0 1.9863168
1 6 6 0.3534224 -0.1667149 0.3922855 0.7505944 0.3816615 -0.5939700 0.5174635 1.8184037 0.0502674 1.0667559 1.718306 1.0474370 -0.7266549 3.6913915 0.9842025 0.0544657 4 31 0 5.4256122
1 7 7 -0.1594659 0.1910058 0.8607167 1.4978749 0.8421032 -0.7143273 0.4984251 1.3689105 0.1020039 1.0187783 1.890991 0.9932980 -1.1373160 2.8755970 0.9918319 0.0544657 6 127 0 -0.1272299
1 8 8 1.4268865 -1.1670336 -0.4316543 0.1962286 -0.5619779 -2.0107542 0.5383222 1.0768350 0.1701838 0.9620660 1.638192 0.8217290 -0.7383640 0.6703955 0.9668672 0.0544657 7 127 0 2.6377963
1 9 9 1.9375415 -1.5311064 -0.7487792 -0.1065111 -0.7260632 -2.3884682 0.5635136 1.2424804 0.0351719 1.0071980 1.805204 1.0354223 -1.0300833 0.6420246 0.9714637 0.0544657 5 47 0 4.8342776
1 10 10 1.3272984 -0.3881331 -0.1504101 0.2346359 -0.1610924 -0.8453990 0.5369298 2.7021155 0.2785180 0.9208728 1.961312 0.8920080 -0.9570674 4.3854093 0.9961164 0.0544657 6 127 0 2.2540402

tidy_draws()gather_variables() を組み合わせることで、必要であれば ggmcmc::ggs() と同様の出力を導き出すことも可能である。

m %>%
  tidy_draws() %>%
  gather_variables() %>%
  head(10)
.chain .iteration .draw .variable .value
1 1 1 overall_mean 0.4720119
1 2 2 overall_mean 0.7776089
1 3 3 overall_mean -0.1013847
1 4 4 overall_mean -0.0086601
1 5 5 overall_mean 0.3126983
1 6 6 overall_mean 0.3534224
1 7 7 overall_mean -0.1594659
1 8 8 overall_mean 1.4268865
1 9 9 overall_mean 1.9375415
1 10 10 overall_mean 1.3272984

しかし、繰り返しになるが、この方法では可変インデックスを自動的に処理してくれないので、可変インデックスを気にする必要がない場合以外は、一般的に spread_draws()gather_draws() を使用することが推奨される。

正規表現で変数を選択する

spread_draws()gather_draws() に渡す仕様の中で正規表現を使い、regex = TRUE を渡すことで複数の列にマッチさせることができる。この例のフィットでは、変数名 condition_mean [i]condition_zoffset [i] .この2つの変数を1つの正規表現で取り出すことができる。

m %>%
  spread_draws(`condition_.*`[condition], regex = TRUE) %>%
  head(10)
condition condition_mean condition_zoffset .chain .iteration .draw
A 0.0425315 -0.7228089 1 1 1
A 0.4303988 -0.4906112 1 2 2
A 0.0148958 0.0753446 1 3 3
A 0.5342393 0.2916825 1 4 4
A 0.2983696 -0.0121558 1 5 5
A 0.0502674 -0.1667149 1 6 6
A 0.1020039 0.1910058 1 7 7
A 0.1701838 -1.1670336 1 8 8
A 0.0351719 -1.5311064 1 9 9
A 0.2785180 -0.3881331 1 10 10

この結果は、この場合、次のものと同じである。 spread_draws(c(condition_mean, condition_zoffset) [condition] ) と同じであるが、各変数を明示的に列挙する必要はない—これは、例えば、係数に b_ [some name] のような命名法を持つモデルで有用である。

不確実性を考慮したフィットカーブの描画

不確実性を伴う適合曲線の描画を実証するために、mtcars データセットの一部に、brms::brm() を使って、少し素朴なモデルを適合させてみよう。

m_mpg = brm(
  mpg ~ hp * cyl, 
  data = mtcars, 
  
  file = "models/tidybayes_m_mpg.rds"  # cache model (can be removed)
)

add_epred_draws()ggdist::stat_lineribbon() を使って、確率帯のあるフィットカーブを描くことができる。

mtcars %>%
  group_by(cyl) %>%
  data_grid(hp = seq_range(hp, n = 51)) %>%
  add_epred_draws(m_mpg) %>%
  ggplot(aes(x = hp, y = mpg, color = ordered(cyl))) +
  stat_lineribbon(aes(y = .epred)) +
  geom_point(data = mtcars) +
  scale_fill_brewer(palette = "Greys") +
  scale_color_brewer(palette = "Set2")
## Warning: Using the `size` aesthietic with geom_ribbon was deprecated in ggplot2 3.4.0.
## ℹ Please use the `linewidth` aesthetic instead.
## Warning: Unknown or uninitialised column: `linewidth`.
## Warning: Using the `size` aesthietic with geom_line was deprecated in ggplot2 3.4.0.
## ℹ Please use the `linewidth` aesthetic instead.
## Warning: Unknown or uninitialised column: `linewidth`.
## Unknown or uninitialised column: `linewidth`.
## Unknown or uninitialised column: `linewidth`.
## Unknown or uninitialised column: `linewidth`.
## Unknown or uninitialised column: `linewidth`.
## Unknown or uninitialised column: `linewidth`.
## Unknown or uninitialised column: `linewidth`.
## Unknown or uninitialised column: `linewidth`.

あるいは、適当な数のフィットラインをサンプリングして (たとえば100本) 、それらをオーバープロットすることもできる。

mtcars %>%
  group_by(cyl) %>%
  data_grid(hp = seq_range(hp, n = 101)) %>%
  # NOTE: this shows the use of ndraws to subsample within add_epred_draws()
  # ONLY do this IF you are planning to make spaghetti plots, etc.
  # NEVER subsample to a small sample to plot intervals, densities, etc.
  add_epred_draws(m_mpg, ndraws = 100) %>%
  ggplot(aes(x = hp, y = mpg, color = ordered(cyl))) +
  geom_line(aes(y = .epred, group = paste(cyl, .draw)), alpha = .1) +
  geom_point(data = mtcars) +
  scale_color_brewer(palette = "Dark2")

フィットラインの不確かさの例については、vignette("tidy-brms") または vignette("tidy-rstanarm") の対応するセクションを参照。

他のパッケージとの互換性

point_intervalbroom::tidy の互換性モデル比較の例

to_broom_names()median_qi() (あるいはより一般的には point_interval() 関数群) を組み合わせると、broom::tidy() でサポートされているモデルに対して、簡単に結果を比較することができる。例えば、条件付き平均に対する私たちのモデルの適合を、通常の最小二乗 (OLS) 回帰と比較してみよう。

m_linear = lm(response ~ condition, data = ABC)

emmeans::emmeansbroom::tidy を組み合わせると、上記のモデルから条件付き平均の整頓された形式の要約を生成することができる。

linear_results = m_linear %>% 
  emmeans(~ condition) %>% 
  tidy(conf.int = TRUE) %>%
  mutate(model = "OLS")

linear_results
condition estimate std.error df conf.low conf.high statistic p.value model
A 0.1815842 0.173236 45 -0.1673310 0.5304993 1.048190 0.3001485 OLS
B 1.0142144 0.173236 45 0.6652993 1.3631296 5.854526 0.0000005 OLS
C 1.8745839 0.173236 45 1.5256687 2.2234990 10.820985 0.0000000 OLS
D 1.0271794 0.173236 45 0.6782642 1.3760946 5.929366 0.0000004 OLS
E -0.9352260 0.173236 45 -1.2841411 -0.5863108 -5.398567 0.0000024 OLS

私たちのモデルから対応する適合度を導き出すことができる。

bayes_results = m %>%
  spread_draws(condition_mean[condition]) %>%
  median_qi(estimate = condition_mean) %>%
  to_broom_names() %>%
  mutate(model = "Bayes")

bayes_results
condition estimate conf.low conf.high .width .point .interval model
A 0.1923963 -0.1587834 0.5495929 0.95 median qi Bayes
B 1.0007694 0.6510203 1.3431703 0.95 median qi Bayes
C 1.8382099 1.4805718 2.1913833 0.95 median qi Bayes
D 1.0180656 0.6615895 1.3503778 0.95 median qi Bayes
E -0.8904054 -1.2397288 -0.5440394 0.95 median qi Bayes

ここで、to_broom_names().lower.upperconf.lowconf.high に変換し、比較に必要な列の名前 ( condition , estimate , conf.low , conf.high ) をすべて簡単に並べられるようにする。これにより、2つの整頓されたデータフレームを bind_rows を使って結合し、プロットすることが簡単になる。

bind_rows(linear_results, bayes_results) %>%
  mutate(condition = fct_rev(condition)) %>%
  ggplot(aes(y = condition, x = estimate, xmin = conf.low, xmax = conf.high, color = model)) +
  geom_pointinterval(position = position_dodge(width = .3))

OLSモデルと比較して、ベイズモデルで全体の平均に向かって縮小していることを観察してみよう。

unspread_drawsungather_draws を使用した bayesplot との互換性

他のパッケージの関数は、変数を列、ドローを行とするデータフレームまたはマトリックスの形式でドローを期待するだろう。これは、tidy_draws() が返す形式であるが、gather_draws()spread_draws() は、変数のインデックスを列で分割している。

spread_draws()gather_draws() 関数を使って描画を何らかの方法で変換し、bayesplot のような他のパッケージの関数に渡すために、描画 \(\times\) の変数フォーマットに 戻す ようにするとよいだろう。unspread_draws()ungather_draws() 関数は spread_draws()gather_draws() を逆にして、インデックスを含む変数列名と行としての描画を持つデータフレームを返す。

例として、先ほどの compare_levels() の例をもう一度やってむが、ggdist::stat_eye() の代わりに bayesplot::mcmc_areas() を使って結果をプロットしてみよう。まず、compare_levels() の結果は次のようになる。

m %>%
  spread_draws(condition_mean[condition]) %>%
  compare_levels(condition_mean, by = condition) %>%
  head(10)
.chain .iteration .draw condition condition_mean
1 1 1 B - A 0.7496179
1 2 2 B - A 0.3848700
1 3 3 B - A 0.9496548
1 4 4 B - A 0.6725274
1 5 5 B - A 0.5177293
1 6 6 B - A 1.0164884
1 7 7 B - A 0.9167743
1 8 8 B - A 0.7918821
1 9 9 B - A 0.9720261
1 10 10 B - A 0.6423548

bayesplot::mcmc_areas() に渡せるバージョンを得るために必要なことは、最初に行った spread_draws() の呼び出しを反転させることである。

m %>%
  spread_draws(condition_mean[condition]) %>%
  compare_levels(condition_mean, by = condition) %>%
  unspread_draws(condition_mean[condition]) %>%
  head(10)
## Warning: `spread_()` was deprecated in tidyr 1.2.0.
## ℹ Please use `spread()` instead.
## ℹ The deprecated feature was likely used in the tidybayes package.
##   Please report the issue at <]8;;https://github.com/mjskay/tidybayes/issues/newhttps://github.com/mjskay/tidybayes/issues/new]8;;>.
.chain .iteration .draw condition_mean[B - A] condition_mean[C - A] condition_mean[C - B] condition_mean[D - A] condition_mean[D - B] condition_mean[D - C] condition_mean[E - A] condition_mean[E - B] condition_mean[E - C] condition_mean[E - D]
1 1 1 0.7496179 1.380203 0.6305850 0.8812931 0.1316753 -0.4989097 -0.8350808 -1.584699 -2.215284 -1.716374
1 2 2 0.3848700 1.264447 0.8795767 0.6067623 0.2218922 -0.6576845 -1.1900930 -1.574963 -2.454540 -1.796855
1 3 3 0.9496548 2.010215 1.0605599 1.1392180 0.1895632 -0.8709967 -0.9913705 -1.941025 -3.001585 -2.130588
1 4 4 0.6725274 1.336577 0.6640500 0.3955207 -0.2770067 -0.9410567 -1.4167632 -2.089291 -2.753341 -1.812284
1 5 5 0.5177293 1.615133 1.0974032 0.6172075 0.0994782 -0.9979250 -1.2117054 -1.729435 -2.826838 -1.828913
1 6 6 1.0164884 1.668039 0.6515501 0.9971696 -0.0193188 -0.6708690 -0.7769223 -1.793411 -2.444961 -1.774092
1 7 7 0.9167743 1.788987 0.8722125 0.8912941 -0.0254803 -0.8976927 -1.2393200 -2.156094 -3.028307 -2.130614
1 8 8 0.7918821 1.468009 0.6761263 0.6515452 -0.1403370 -0.8164633 -0.9085478 -1.700430 -2.376556 -1.560093
1 9 9 0.9720261 1.770032 0.7980056 1.0002504 0.0282243 -0.7697813 -1.0652552 -2.037281 -2.835287 -2.065506
1 10 10 0.6423548 1.682794 1.0404388 0.6134900 -0.0288648 -1.0693036 -1.2355854 -1.877940 -2.918379 -1.849075

それを直接 bayesplot::mcmc_areas() に渡すことができる。unspread_draws() の引数 drop_indices = TRUE は、.chain , .iteration , および .draw が出力に含まれるべきではないことを示す。

m %>%
  spread_draws(condition_mean[condition]) %>%
  compare_levels(condition_mean, by = condition) %>%
  unspread_draws(condition_mean[condition], drop_indices = TRUE) %>%
  bayesplot::mcmc_areas()

gather_draws()gather_variables() で生成された整頓された描画を扱う場合、ungather_draws() 関数はそれらの描画を draw \(\times\) という変数形式に変換する。これは unspread_draws() と同じ構文を持っている。

emmeans (旧 lsmeans) との互換性

emmeans::emmeans() 関数は、多数の種類の対比を含むモデルからマージナル推定値を生成するための便利な構文を提供する。また、MCMCglmm , rstanarm , brms のようないくつかのベイズモデリングパッケージをサポートしている。 しかし、整頓されたフォーマットでの描画は提供されていない。gather_emmeans_draws() 関数は emmeans の出力を整頓された形式に変換し、emmeans の参照グリッドを維持し、ロングフォーマットのドローを持つ .value の列を追加する。

(別のアプローチとして、emmeans コントラスト法を使用し、emmeans_comparison() で emmeans コントラストを変換する。 メソッドを、tidybayes::compare_levels() で使用できる比較関数に変換する。のドキュメントを参照。 emmeans_comparison() を参照) 。

rstanarm または brms を使用する

rstanarmbrms は、emmeans::emmeans() と一緒に使っても同じような動作になる。以下の例では、rstanarm を使用しているが、brms でも同様に動作するはずである。

この rstanarm のモデルから考えると。

m_rst = stan_glm(response ~ condition, data = ABC)

emmeans::emmeans() を使って、不確実性を伴う条件付き平均を求めることができる。

m_rst %>%
  emmeans( ~ condition) %>%
  gather_emmeans_draws() %>%
  median_qi()
condition .value .lower .upper .width .point .interval
A 0.1839216 -0.1737448 0.5399800 0.95 median qi
B 1.0138490 0.6394168 1.3551706 0.95 median qi
C 1.8682159 1.5290403 2.2075733 0.95 median qi
D 1.0265241 0.6727074 1.3780107 0.95 median qi
E -0.9316006 -1.2598607 -0.5845776 0.95 median qi

または、emmeans::emmeans() emmeans::contrast()、すべてのペアワイズ比較を行うことができる。

m_rst %>%
  emmeans( ~ condition) %>%
  contrast(method = "pairwise") %>%
  gather_emmeans_draws() %>%
  median_qi()
contrast .value .lower .upper .width .point .interval
A - B -0.8241267 -1.3265320 -0.3296754 0.95 median qi
A - C -1.6818203 -2.1729825 -1.1927322 0.95 median qi
A - D -0.8484698 -1.3617588 -0.3429223 0.95 median qi
A - E 1.1102204 0.6142124 1.6199968 0.95 median qi
B - C -0.8592072 -1.3348505 -0.3867405 0.95 median qi
B - D -0.0188042 -0.5064541 0.4846524 0.95 median qi
B - E 1.9457872 1.4444502 2.4137282 0.95 median qi
C - D 0.8398055 0.3274180 1.3475858 0.95 median qi
C - E 2.8057222 2.3211588 3.2873458 0.95 median qi
D - E 1.9617316 1.4741161 2.4367297 0.95 median qi

emmeans::emmeans() がサポートする多数のコントラストタイプの一覧は、emmeans::pairwise.emmc() のドキュメントを参照。

前回と同様に、表を使う代わりに結果をプロットすることができる。

m_rst %>%
  emmeans( ~ condition) %>%
  contrast(method = "pairwise") %>%
  gather_emmeans_draws() %>%
  ggplot(aes(x = .value, y = contrast)) +
  stat_halfeye()

gather_emmeans_draws() は、異なる参照グリッドからの推定値を含む オブジェクトもサポートしている (参照グリッドの詳細については を参照)。出力の各行の参照グリッドを示すために、 というデフォルト名を持つ列が追加される。 emm_list emmeans::ref_grid() .grid

m_rst %>%
  emmeans(pairwise ~ condition) %>%
  gather_emmeans_draws() %>%
  median_qi()
condition contrast .grid .value .lower .upper .width .point .interval
A NA emmeans 0.1839216 -0.1737448 0.5399800 0.95 median qi
B NA emmeans 1.0138490 0.6394168 1.3551706 0.95 median qi
C NA emmeans 1.8682159 1.5290403 2.2075733 0.95 median qi
D NA emmeans 1.0265241 0.6727074 1.3780107 0.95 median qi
E NA emmeans -0.9316006 -1.2598607 -0.5845776 0.95 median qi
NA A - B contrasts -0.8241267 -1.3265320 -0.3296754 0.95 median qi
NA A - C contrasts -1.6818203 -2.1729825 -1.1927322 0.95 median qi
NA A - D contrasts -0.8484698 -1.3617588 -0.3429223 0.95 median qi
NA A - E contrasts 1.1102204 0.6142124 1.6199968 0.95 median qi
NA B - C contrasts -0.8592072 -1.3348505 -0.3867405 0.95 median qi
NA B - D contrasts -0.0188042 -0.5064541 0.4846524 0.95 median qi
NA B - E contrasts 1.9457872 1.4444502 2.4137282 0.95 median qi
NA C - D contrasts 0.8398055 0.3274180 1.3475858 0.95 median qi
NA C - E contrasts 2.8057222 2.3211588 3.2873458 0.95 median qi
NA D - E contrasts 1.9617316 1.4741161 2.4367297 0.95 median qi

MCMCglmm の使用

上と同じ例をもう一度やってみよう。今度は rstanarm の代わりに MCMCglmm::MCMCglmm() を使う。MCMCglmm() を使うときに唯一違うのは、MCMCglmm()emmeans() とともに使うには、モデルの適合に使われた元のデータも emmeans() の呼び出しに渡さなければならないことである(詳細は vignette("models ", package =" emmeans")) を参照)。

まず、モデルのフィッティングを行う。

# MCMCglmm does not support tibbles directly,
# so we convert ABC to a data.frame on the way in
m_glmm = MCMCglmm(response ~ condition, data = as.data.frame(ABC))

これで、emmeans()gather_emmeans_draws()rstanarm と全く同じように使えるが、emmeans() の呼び出しの中に data の引数を含める必要がある。

m_glmm %>%
  emmeans( ~ condition, data = ABC) %>%
  contrast(method = "pairwise") %>%
  gather_emmeans_draws() %>%
  ggplot(aes(x = .value, y = contrast)) +
  stat_halfeye()