Skip to content

Commit 0140dc1

Browse files
author
Florence Bockting
committed
vignettes: update model-comparison tutorial with other pred_measure family types
1 parent 0944a95 commit 0140dc1

1 file changed

Lines changed: 111 additions & 19 deletions

File tree

vignettes/articles-online-only/model-comparison.Rmd

Lines changed: 111 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,18 @@ knitr::opts_chunk$set(
3636
::: {.callout .callout-warning}
3737
**Questions**
3838

39-
+ How detailed should the math for the computation be and how to attribute properly the work from the overleaf document? Are you considering to publish it? -> Answered: we target for a preprint that we can cite.
39+
+ ~How detailed should the math for the computation be and how to attribute properly the work from the overleaf document? Are you considering to publish it?~ -> Answered: we target for a preprint that we can cite.
4040
+ Currently, `elpd` is computed by default in `pred_measure`. This is helpful, when we want to have it by default in `model_compare`. Do we want this default behavior?
41-
+ Related question: The default print method of `model_compare` shows only the `elpd`, only when `print(..., measures = "all")` is passed, all measures are shown. Do we want this?
41+
+ Related question: The default print method of `model_compare` shows `elpd` output only. Using `print(..., measures = "all")` allows to see all measures. See below for example. Do we want this?
4242
+ For `elpd`, we provide in the `model_compare` output the additional columns `p_worse`, `diag_diff`, `diag_elpd`.
4343
+ Do we want to have `p_worse` and `diag_diff` for all measures?
44-
+ I moved the column `diag_elpd` out from the elpd-specific measure block and treat it as an "overall" information (see printed output below).
45-
+ Furthermore, I renamed `diag_elpd` to `diag_psis` as it seems not be specific to elpd or do I misunderstand this?
46-
+ General: Is the current design of `model_compare()` okay?
47-
+ Which warnings/messages do we want to provide and which should only be part of the function documentation?
44+
+ I moved the column `diag_elpd` out from the elpd-specific measure block and treat it as an "overall" (and not measure-specific) information (see printed output below).
45+
+ Currently, there are a lot of warnings/messages. Which ones do we want to provide in output and which ones in function documentation?
4846
:::
4947

5048
```{r, child="../children/LOAD-BRMS-GITHUB.txt", eval=params$EVAL}
5149
```
5250

53-
::: {.callout .callout-note}
54-
**Acknowledgements**
55-
56-
We thank Seth Axen, Frank Weber, and Aki Vehtari for deriving the formulas of
57-
the standard errors for LOO-CV estimators and differences.
58-
:::
59-
6051
## Introduction
6152
In the following, our goal is to compare performance between two or more
6253
models. To do this, we will use the function `loo::model_compare()`.
@@ -131,10 +122,6 @@ Note that `rps` is the only one of these that needs the posterior *predictive*
131122
draws `ypred`; the error measures are computed from the posterior expectation
132123
`mupred` (see the input requirements table in `?loo_pred_measure`).
133124

134-
<!--
135-
Provide a section where we use kfold_pred_measure, test_pred_measure, etc.
136-
-->
137-
138125
```{r load-libraries}
139126
library(loo)
140127
```
@@ -183,16 +170,22 @@ Passing these `loo` objects to the classical `loo_compare()` reproduces the
183170
familiar ELPD-only comparison, and warns once per session that `model_compare()`
184171
is now the preferred function:
185172

186-
```{r loo_compare, warning=TRUE}
173+
```{r loo_compare}
187174
loo_compare(loos)
188175
```
189176

190177
When we use `model_compare` instead, we get the same output for `loo` objects:
191178

192-
```{r model_compare_wloo, warning=TRUE}
179+
```{r model_compare_wloo}
193180
model_compare(loos)
194181
```
195182

183+
It also supports the `simplify` argument in the print method:
184+
185+
```{r model_compare_wloo_simplify}
186+
print(model_compare(loos), simplify = FALSE)
187+
```
188+
196189
## Preview: Glimpse into the model comparison results
197190

198191
We now use `loo_pred_measure()` to compute the predictive performance for
@@ -227,6 +220,13 @@ information to the `print` method:
227220
print(comp, measures = "r2")
228221
```
229222

223+
Or if you want to see all columns related to the measures specified in
224+
`measures` use `simplify = FALSE`:
225+
226+
```{r pred-measure-r2-simplify}
227+
print(comp, measures = "r2", simplify = FALSE)
228+
```
229+
230230
Or in order to see all measures we can use `measures = "all"`:
231231

232232
```{r pred-measure-all}
@@ -907,6 +907,98 @@ print(
907907
)
908908
```
909909

910+
## Comparing other predictive sources
911+
912+
`model_compare()` treats every `*_pred_measure()` result the same way. Only the
913+
constructor changes. All models in one call must share one source. A mixed call
914+
is an error.
915+
916+
### K-fold cross-validation
917+
918+
`brms::kfold()` refits each model `K` times, so this is the most expensive step
919+
in this article.
920+
921+
```{r kfold-compare, message=FALSE}
922+
kfold_measure <- function(fit, measure) {
923+
kf <- brms::kfold(fit, K = 5, save_fits = TRUE)
924+
kfold_pred_measure(
925+
y = fit$data$y,
926+
ypred = brms::kfold_predict(kf, method = "predict")$yrep,
927+
mupred = brms::kfold_predict(kf, method = "fitted")$yrep,
928+
kfold = kf,
929+
measure = measure
930+
)
931+
}
932+
933+
set.seed(SEED)
934+
k1 <- kfold_measure(fit_m1, measures)
935+
k2 <- kfold_measure(fit_m2, measures)
936+
k3 <- kfold_measure(fit_m3, measures)
937+
938+
model_compare(list(m1 = k1, m2 = k2, m3 = k3))
939+
```
940+
941+
The measure names now carry a `_kfold` suffix. `print()` names the source above
942+
the table. The PSIS-LOO block is gone, because Pareto $\hat{k}$ describes the
943+
LOO approximation only.
944+
945+
### Held-out test data
946+
947+
Here we hold out 60 buildings, refit each model on the rest, and score the
948+
held-out ones. `ylp_test` supplies `elpd_test`; `ylp` stays the training
949+
log-likelihood.
950+
951+
```{r test-compare}
952+
set.seed(SEED)
953+
test_id <- sample(nrow(roaches), 60)
954+
roaches_train <- roaches[-test_id, ]
955+
roaches_test <- roaches[test_id, ]
956+
957+
test_measure <- function(fit, measure) {
958+
fit_train <- update(fit, newdata = roaches_train, refresh = 0)
959+
test_pred_measure(
960+
y = roaches_test$y,
961+
ypred = brms::posterior_predict(fit_train, newdata = roaches_test),
962+
mupred = brms::posterior_epred(fit_train, newdata = roaches_test),
963+
ylp = brms::log_lik(fit_train),
964+
ylp_test = brms::log_lik(fit_train, newdata = roaches_test),
965+
measure = measure
966+
)
967+
}
968+
969+
t1 <- test_measure(fit_m1, measures)
970+
t2 <- test_measure(fit_m2, measures)
971+
t3 <- test_measure(fit_m3, measures)
972+
973+
model_compare(list(m1 = t1, m2 = t2, m3 = t3))
974+
```
975+
976+
### In-sample
977+
978+
In-sample scores need no refit. They are optimistically biased and favor the
979+
more complex model, so `model_compare()` warns:
980+
981+
```{r insample-compare}
982+
insample_measure <- function(fit, measure) {
983+
insample_pred_measure(
984+
y = fit$data$y,
985+
ypred = brms::posterior_predict(fit),
986+
mupred = brms::posterior_epred(fit),
987+
ylp = brms::log_lik(fit),
988+
measure = measure
989+
)
990+
}
991+
992+
i1 <- insample_measure(fit_m1, measures)
993+
i2 <- insample_measure(fit_m2, measures)
994+
i3 <- insample_measure(fit_m3, measures)
995+
996+
model_compare(list(m1 = i1, m2 = i2, m3 = i3))
997+
```
998+
999+
Everything else in this article, `rank_by`, `custom_se_fn`, the `print`
1000+
arguments `measures` and `simplify`, works unchanged for all four sources.
1001+
9101002
## Summary
9111003

9121004
```{r summary-table, echo=FALSE}

0 commit comments

Comments
 (0)