Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
89d5e9a
refactor: update loo_compare to work with loo_pred_measure
Jul 7, 2026
7ce2d50
refactor: update pred_measure API for integration with loo_compare()
Jul 7, 2026
7394ba3
tests: new tests for loo_compare with loo_pred_measure
Jul 7, 2026
494d24f
docs: update documentation and glossary
Jul 7, 2026
9611656
docs: update documentation
Jul 7, 2026
a3019e3
chore: update developer-notes
Jul 7, 2026
3e1bf93
Merge branch 'pred_measure' into integrate-loo_compare
florence-bockting Jul 8, 2026
06d6281
refactor: clean-up refactoring and adjust for renamed arg
Jul 8, 2026
b46ff58
fix: update missing argument and corresponding docs
Jul 8, 2026
f1fcaf0
chore: update NEWS.md
Jul 8, 2026
e17e46d
Merge branch 'pred_measure' into integrate-loo_compare
florence-bockting Jul 16, 2026
abc74b5
refactor: update computation of SE for loo_compare with pred_measure_loo
Aug 12, 2026
faa27c5
refactor: create model_compare and workout design
Aug 25, 2026
3e078aa
update model_compare() functionality
Aug 27, 2026
8897e1f
Merge branch 'pred_measure' into integrate-loo_compare
florence-bockting Aug 28, 2026
1eb66de
refactor: split model_compare.R and drop duplicated loo_compare inter…
Aug 28, 2026
166dab6
fix: restore the simplify argument to print.compare.loo
Aug 28, 2026
c0c1fc0
review: update the model_compare function
Aug 28, 2026
c594818
update: deprecate loo_compare but maintain backwards comptability
Aug 28, 2026
aa30e54
review: code refactoring review
Aug 28, 2026
b1f0b32
docs: update function documentation to roxygen2 8.0.0
Aug 29, 2026
63c16a8
docs: update glossary to match loo_compare and model_compare
Aug 29, 2026
d874044
fix: throw deprecation warning for loo_compare only once per session.
Aug 29, 2026
9e930e3
fix: remove old 'estimates_only' label
Aug 29, 2026
4fe4b0d
refactor: avoid duplicate computation
Aug 29, 2026
88890b6
docs: add loo_compare and model_compare to seealso statement
Aug 29, 2026
51b3c9c
refactor: add warning for disagreeing measure name in attribute and list
Aug 29, 2026
81ae8eb
review: minor style corrections and doc adjustment
Aug 29, 2026
fed3433
review: removed dead code
Aug 29, 2026
585b77c
vignette: update tutorial on model comparison
Aug 29, 2026
3f63357
chore: update warning messages
Aug 29, 2026
5307f23
review: update function docs and inline small helper
Aug 29, 2026
795cd4e
Merge branch 'pred_measure' into integrate-loo_compare
Aug 31, 2026
69346f3
tests: shrink the model-comparison fixture
Aug 31, 2026
e52ab8e
print: accept simplify on the pred_measure path
Aug 31, 2026
d55d9bf
model_compare: accept named models in dots
Aug 31, 2026
0f830e3
internal: share the k-fold K-mismatch warning
Aug 31, 2026
769b37a
internal: drop model_compare_checks.psis_loo_ss_list()
Aug 31, 2026
b80c946
internal: drop model_compare_matrix.psis_loo_ss_list()
Aug 31, 2026
ca8da41
docs: correct the Sivula et al. year in the glossary
Aug 31, 2026
47af38e
pred_measure: keep a renamed measure's own orientation
Aug 31, 2026
99d71a2
docs: remove draft scaffolding from the comparison article
Aug 31, 2026
0e07f63
docs: let the comparison article fit its own models
Aug 31, 2026
4317ce7
docs: point the custom-measure chunks at the new fits
Aug 31, 2026
0c1de0c
docs: rename the comparison article to model-comparison
Aug 31, 2026
c66e1e2
docs: correct the article path in the model_compare links
Aug 31, 2026
5f6bcb9
internal: drop the vignette fits from the data generator
Aug 31, 2026
6dbd05d
docs: give the two remaining articles their real vignette title
Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ release-prep.R

# personal maintainer scratch (not shared)
internal-notes/
notes/loo_se.pdf
notes/loo-compare-se-diff.md
CRAN-SUBMISSION
1 change: 0 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,3 @@ LazyData: TRUE
Roxygen: list(markdown = TRUE)
SystemRequirements: pandoc (>= 1.12.3), pandoc-citeproc
Config/roxygen2/version: 8.0.0
RoxygenNote: 7.3.3
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ S3method(loo_moment_match,default)
S3method(loo_predictive_metric,matrix)
S3method(loo_scrps,matrix)
S3method(loo_subsample,"function")
S3method(model_compare,default)
S3method(model_compare,psis_loo_ss_list)
S3method(nobs,psis_loo_ss)
S3method(plot,loo)
S3method(plot,psis)
Expand Down Expand Up @@ -158,6 +160,7 @@ export(measure_r2)
export(measure_rmse)
export(measure_rps)
export(measure_srps)
export(model_compare)
export(nlist)
export(obs_idx)
export(pareto_k_ids)
Expand Down
107 changes: 105 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,111 @@
* New predictive performance API: `insample_pred_measure()`, `loo_pred_measure()`,
`kfold_pred_measure()`, `test_pred_measure()`, and `pred_measure()` with
built-in measures via `measure_*()` and [supported_measures_list()].
* Update user messages in `print()` by @ishaan-arora-1, @florence-bockting in
#328.
* `loo_compare()` is now called `model_compare()`, and `loo_compare()` is
deprecated. It still works and is still a generic, so `loo_compare` methods
registered by other packages (e.g. `loo_compare.brmsfit` in **brms**,
`loo_compare.stanreg` in **rstanarm**) keep dispatching, but it now warns
(once per session) and is frozen at its previous behaviour: `"loo"`, `"waic"`, and `"kfold"` objects
compared on ELPD only, returning exactly what `model_compare()` returns for
those inputs. Comparing `pred_measure` results, or using `rank_by` or
`custom_se_fn`, requires `model_compare()`.
* `model_compare()` supports every `pred_measure` result --- from
`loo_pred_measure()`, `kfold_pred_measure()`, `test_pred_measure()`, and
`insample_pred_measure()` --- with paired differences for all measures common
to the compared models, optional `rank_by` ranking, utility-scale sign
conversion for loss measures, and `print(compare, measures = ...)` for
multi-measure tables by @florence-bockting in #380.
Measures are matched on bare names, so the source suffix (`_loo`, `_kfold`,
`_test`, or none for in-sample) is handled transparently. All models in one
call must share an evaluation source; mixing them is an error. `diag_elpd` is
reported only for `loo_pred_measure()` comparisons, k-fold comparisons warn on
differing `K`, and in-sample comparisons warn that they are optimistically
biased.
* In `model_compare()`, when `rank_by` is not supplied each measure is now
compared against *its own* best model, so e.g. `mse_diff` can be relative to a
different model than `elpd_diff`. Rows are still ordered by `"elpd"`, and each
`{measure}_diff` column has exactly one `0` entry, at that measure's best
model. Supplying `rank_by` keeps the previous behaviour of pinning the
top-ranked model as the single reference for every measure. The reference used
per measure is recorded in the `compare_reference` attribute and shown by
`print(x, measures = "all")`. Each printed measure table is now sorted by its
own difference, so the best model on that measure is always the first row and
the differences run in decreasing order.
* `print()` on a `pred_measure` comparison now always names the reference model
each set of differences was computed against, including the default case where
every measure keeps its own best model. The line is written to standard output
rather than emitted as a message, so it survives `suppressMessages()` and
knitr chunks with `message = FALSE`. For `loo_pred_measure()` comparisons the
PSIS-LOO Pareto k diagnostic moved out of the per-measure difference tables
into a block above them: it describes a model's LOO approximation, not any one
measure or pair of models, so it is now reported once per model. The
`diag_elpd` column on the returned object is unchanged, and `print()` for
plain `"loo"` comparisons is unchanged. Printed sentences are wrapped so that
no line of output exceeds 80 characters.
* Bug fix: the `diag_diff` column tested `elpd_diff > -4` rather than
`|elpd_diff| < 4`, so every positive difference was flagged as a small one.
This was only reachable with `rank_by` set to a model that is not the best
model, where differences against the reference can be positive.
* In `model_compare()`, `rank_by` now also accepts a **model name** (as shown in
the `model` column). The named model then becomes the single reference model
for every measure, whether or not it is the best one, while rows stay ordered
by `"elpd"`. This form also works for plain `"loo"` comparisons, where
`elpd_diff` is then relative to the named model. The pinned model is recorded
in the `rank_by` attribute; a name matching both a measure and a model is
treated as the measure, with a warning.
* The `diff_method` value `"pairwise"` is now called `"measure_specific"`. Every
model comparison is pairwise, and the `"sum"`/`"mean"` methods are the ones
built from paired pointwise differences, so the old name described the one
category that is *not* a paired pointwise standard deviation. `diff_method` is
read-only metadata in `measure_info`, so no user code that passes
arguments is affected; `custom_se_fn` still accepts only `"sum"` and `"mean"`.
* Bug fix: in `model_compare()`, `mlpd` was registered with
`diff_method = "sum"` although its estimate is the *mean* of the pointwise log
predictive densities. `mlpd_diff` was therefore reported as the sum of the
pointwise differences (a factor of `N` too large, and inconsistent with the
difference of the reported `mlpd` estimates), with a matching `mlpd_se_diff`.
`mlpd` now uses `diff_method = "mean"`.
* `model_compare()` gains a `custom_se_fn` argument controlling how the standard
error of a difference is computed for a **custom** measure. It accepts a
function called as `custom_se_fn(ref, cmp)`, the shorthands `"sum"` and
`"mean"` for the paired pointwise formulas, or `NULL` to report the difference
with an `NA` standard error; with two or more custom measures, pass a list
named by measure. It is required whenever a custom measure is compared ---
nothing is inferred from a measure's values any more, and custom measures now
carry `diff_method = "custom"` instead of `"auto"`. The previous
`attr(my_fun, "se_diff_fun")` route has been removed.
* A custom measure can declare that it is a loss with
`attr(my_fun, "measure_loss") <- TRUE`, alongside `attr(my_fun,
"measure_name")`. `model_compare()` then flips its differences onto the
utility scale and orders `rank_by` by the lowest loss, as it does for built-in
loss measures; without the declaration a custom measure is still treated as a
utility. The declaration is recorded as `loss` in the `measure_info`
attribute, and models that disagree on it cannot be compared.
* Measure values are always stored on the measure's own scale; `loss` is the
single setting describing a measure's orientation. Measures no longer take a
`higher_is_better` argument, `control` no longer accepts one, and results no
longer carry a `measure_higher_is_better` attribute. Comparisons were already
invariant to the setting --- `model_compare()` reports every measure on the
utility scale regardless --- so no comparison output changes. Negate the
values yourself if you want a loss stored as a utility.
* Bug fix: `rps` was registered as a utility and `srps` as a loss, but
`measure_rps()` returns the Gneiting & Raftery (2007) unscaled score, where
lower is better, and `measure_srps()` the Bolin & Wallin (2023) scaled score,
where higher is better. `model_compare()` therefore flipped `srps_diff` and
left `rps_diff` unflipped, and `rank_by = "rps"` ordered models backwards.
The registry now records `rps` as a loss and `srps` as a utility.
* The per-measure metadata attribute `measure_compare_meta` is now called
`measure_info`.
* `control` entries now work for custom measures instead of failing with
`object 'measure_<name>' of mode 'function' was not found`. A `control` entry
naming no measure being computed now warns instead of aborting.
* `print()` on a multi-measure comparison now labels each measure's standard
error column `{measure}_se_diff`, matching the column name on the returned
data frame, instead of relabelling it `se_diff`. ELPD tables are unchanged.
* `print()` on a `pred_measure` comparison now accepts `simplify`. With
`simplify = FALSE` each measure table also shows that measure's per-model estimate and standard error, and an ELPD table also shows `p` and `se_p`.
* `model_compare(A = m1, B = m2)` now works.
* Update user messages in `print()` by @ishaan-arora-1, @florence-bockting in #328.

# loo 2.10.1

Expand Down
4 changes: 2 additions & 2 deletions R/compare.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#' Model comparison (deprecated, old version)
#'
#' **This function is deprecated**. Please use the new [loo_compare()] function
#' **This function is deprecated**. Please use the new [model_compare()] function
#' instead. See `vignette("migration-guide", package = "loo")` for details.
#' `compare()` and `R/compare.R` are scheduled for removal in a future release.
#'
Expand Down Expand Up @@ -60,7 +60,7 @@
#' }
#'
compare <- function(..., x = list()) {
.Deprecated("loo_compare")
.Deprecated("model_compare")
dots <- list(...)
if (length(dots)) {
if (length(x)) {
Expand Down
40 changes: 28 additions & 12 deletions R/helpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -182,18 +182,34 @@ loo_cores <- function(cores) {
return(cores)
}


# nocov start
# release reminders (for devtools)
release_questions <- function() {
c(
"Have you updated references?",
"Have you updated inst/CITATION?",
"Have you updated the vignettes?"
)
}
# nocov end

is_constant <- function(x, tol = .Machine$double.eps) {
abs(max(x) - min(x)) < tol
}

#' Issue a deprecation warning the first time it is triggered in a session
#'
#' Repeated calls with the same `id` are silent, so a script calling a
#' deprecated function in a loop is not flooded with warnings. `old` is passed
#' on explicitly so the message does not depend on which method called this.
#' Which `id`s have already warned is kept in `state`, an environment created
#' once when the package is built and private to this function.
#'
#' @noRd
#' @param id Identifier for the deprecation; one warning per `id` per session.
#' @param new,old Name of the replacement and deprecated function.
#' @return `TRUE` if a warning was issued, `FALSE` otherwise, invisibly.
#'
.deprecate_once <- local({
state <- new.env(parent = emptyenv())
function(id, new, old = id) {
if (isTRUE(state[[id]])) {
return(invisible(FALSE))
}
state[[id]] <- TRUE
warning(
"\n'", old, "' is deprecated. Use '", new, "' instead.\n",
call. = TRUE, immediate. = TRUE
)
invisible(TRUE)
}
})
4 changes: 2 additions & 2 deletions R/kfold-generic.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#'
#' The **Value** section below describes the objects that `kfold()`
#' methods should return in order to be compatible with
#' [loo_compare()] and the **loo** package print methods.
#' [model_compare()] and the **loo** package print methods.
#'
#'
#' @name kfold-generic
Expand All @@ -25,7 +25,7 @@
#'
#' It is important for the object to have at least these classes and
#' components so that it is compatible with other functions like
#' [loo_compare()] and `print()` methods.
#' [model_compare()] and `print()` methods.
#'
NULL

Expand Down
Loading
Loading