Skip to content

Add condition classes to all posterior warnings - #470

Open
florence-bockting wants to merge 10 commits into
masterfrom
309-classed-warnings
Open

florence-bockting wants to merge 10 commits into
masterfrom
309-classed-warnings

Conversation

@florence-bockting

@florence-bockting florence-bockting commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #309

Summary

Packages such as loo call posterior functions many times. Each call can signal the same warning, and the caller cannot catch one warning type selectively. warning_no_call() now signals with rlang::warn() and gives each warning two classes: the shared class posterior_warning and a class for the cause, for example posterior_warning_ess_capped.
All 17 call sites pass a cause class (see comment below). The message text and the missing call stay the same.

A caller can now count and muffle one warning type:

withCallingHandlers(
  apply(x, 3, posterior::ess_mean),
  posterior_warning_ess_capped = function(w) invokeRestart("muffleWarning")
)

Side effect:
The base class changes from simpleWarning to rlang_warning. A handler for simpleWarning no longer catches posterior warnings. A handler for warning still catches them.

Tests:
The new tests in test-convergence.R check both classes on the ESS warning. They also check that a handler counts and muffles the warning. The project uses testthat edition 2, which ignores expect_warning(class = ), so
the tests use tryCatch() with expect_s3_class().

Documentation:
The user can look up the warning classes through ?posterior or ?posterior_warning.

AI assistance:
AI assistance was used to draft the PR. I reviewed every change and can explain it.

Copyright and Licensing

By submitting this pull request, the copyright holder is agreeing to
license the submitted work under the following licenses:

@florence-bockting

Copy link
Copy Markdown
Collaborator Author

Class names for the 17 warning_no_call() call sites

Rule: posterior_warning_<cause>. One class for one cause. Two sites with the same cause get the same class.

# Site Message (short) Class
1 R/convergence.R:788 The ESS has been capped posterior_warning_ess_capped
2 R/nested_rhat.R:64 Length of superchain_ids not equal to number of chains posterior_warning_superchain_ids_length
3 R/nested_rhat.R:74 Number of chains per superchain is not the same posterior_warning_superchain_unequal
4 R/as_draws.R:119 <to> does not support non-numeric variables posterior_warning_non_numeric
5 R/merge_chains.R:87 Chains were dropped posterior_warning_chains_dropped
6 R/summarise_draws.R:175 No variables with unreserved names posterior_warning_no_variables
7 R/split_chains.R:28 Number of iterations is not even posterior_warning_odd_iterations
8 R/pit.R:116 Some PIT values larger than 1 posterior_warning_pit_above_one
9 R/pit.R:453 All draws have zero weight posterior_warning_zero_weight
10 R/gpd.R:29 NaNs produced posterior_warning_nans_produced
11 R/as_draws_df.R:238 Dropping 'draws_df' class posterior_warning_draws_df_dropped
12 R/pareto_smooth.R:282 Input contains infinite or NA values ... posterior_warning_pareto_invalid_input
13 R/pareto_smooth.R:304 Input contains infinite or NA values ... (same text as 12) posterior_warning_pareto_invalid_input
14 R/pareto_smooth.R:309 Number of tail draws cannot be less than 5. Changing to 5 posterior_warning_pareto_tail_too_short
15 R/pareto_smooth.R:315 Tail draws not strictly less than total draws posterior_warning_pareto_tail_exceeds_draws
16 R/pareto_smooth.R:322 Number of tail draws cannot be more than half posterior_warning_pareto_tail_too_long
17 R/pareto_smooth.R:489 Can't fit GPD because ndraws_tail is less than 5 posterior_warning_pareto_tail_not_fitted

@florence-bockting
florence-bockting marked this pull request as ready for review October 21, 2026 10:05
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

This is how benchmark results would change (along with a 95% confidence interval in relative change) if 2da45f4 is merged into master:

  • ✔️as_draws_array: 125ms -> 124ms [-3.92%, +1.94%]
  • ✔️as_draws_df: 65ms -> 64.3ms [-5.1%, +2.91%]
  • ✔️as_draws_list: 116ms -> 116ms [-1.43%, +2.11%]
  • ✔️as_draws_matrix: 17.5ms -> 17.4ms [-1.83%, +0.63%]
  • ✔️as_draws_rvars: 92.8ms -> 93.6ms [-0.74%, +2.44%]
  • ✔️summarise_draws_100_variables: 464ms -> 465ms [-0.89%, +1.5%]
  • ✔️summarise_draws_10_variables: 51.2ms -> 50.9ms [-3.73%, +2.56%]
    Further explanation regarding interpretation and methodology can be found in the documentation.

@jgabry jgabry left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @florence-bockting, I think this is good. I made a few small comments. One question: should we list the class names somewhere users can find them, for example a short section in ?posterior-package or a separate doc file? I'm not sure. Right now a maintainer of a package that depends on posterior would have to grep R/ to find e.g. posterior_warning_ess_capped. What do you think?

Comment thread R/misc.R
warning_no_call <- function(...) {
warning(..., call. = FALSE)
warning_no_call <- function(..., class = NULL) {
rlang::warn(paste0(...), class = c(class, "posterior_warning"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ok, but there's something we should keep in mind for when we add .frequency = "once", since it interacts with the handlers that are enabled in this PR. I checked with Claude and then check myself and once a warning has been shown, rlang doesn't signal it again at all, so a handler like the one in the PR description only sees the first one. Also, a muffled warning still counts as shown. If loo muffles the first ESS-capped warning, the user won't see it again that session, even when calling posterior directly.

So maybe we'll want .frequency only on the noisy warnings (ESS capped, the Pareto ones) rather than inside warning_no_call() for every call site.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I actually didn't thought about using .frequency = "once". I rather thought to use a corresponding caller at the sites where a posterior function is called multiple times (e.g., loo package) . This caller then collects the warning by class and reports a summary, for example "ESS capped in 12 of 100 calls". Then loo's handlers see every warning. Or do I misunderstand something?

@jgabry jgabry Oct 28, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, and I think your approach is better for the loo case. loo can collect warnings by class and report one summary, and it sees every warning. .frequency = "once" would conflict with that because after the first warning rlang stops signaling them and loo wouldn't be able to count them.

Users calling posterior directly might want once per session warnings, but we could add that in the future if we want it for certain specific warnings only. If we do that then loo could use rlib_warning_verbosity = "verbose" in those cases.

But for now fine to go with your approach.

Comment thread R/pit.R
Comment thread tests/testthat/test-convergence.R
Comment thread tests/testthat/test-convergence.R
@florence-bockting

Copy link
Copy Markdown
Collaborator Author

Thanks @florence-bockting, I think this is good. I made a few small comments. One question: should we list the class names somewhere users can find them, for example a short section in ?posterior-package or a separate doc file? I'm not sure. Right now a maintainer of a package that depends on posterior would have to grep R/ to find e.g. posterior_warning_ess_capped. What do you think?

Thank you. ?posterior-package has a new section "Warning and error classes". I also added the alias posterior_warning, so ?posterior_warning opens that page.

@jgabry jgabry left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few tiny suggestions on the new documentation, otherwise looks good!

Comment thread R/posterior-package.R Outdated
#' Every warning signaled by \pkg{posterior} has the class
#' `posterior_warning` and one class for its cause. Use these classes to
#' handle or muffle a specific warning, for example with
#' [withCallingHandlers()] and [invokeRestart()]`("muffleWarning")`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This renders strangely for me in the help page. For the second one I think just skip the link and make it invokeRestart("muffleWarning") instead. [withCallingHandlers()] already correctly links to the same page anyway.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread R/posterior-package.R Outdated
#' `superchain_ids` does not equal the number of chains.
#' * `posterior_warning_superchain_unequal`: The superchains do not have the
#' same number of chains.
#' * `posterior_warning_non_numeric`: A conversion dropped non-numeric

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the variables are actually dropped, just converted right? The warning itself just says "Converting non-numeric variables to numeric". So maybe: "Non-numeric variables (e.g., factors) were converted to numeric."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread R/posterior-package.R Outdated
Comment on lines +92 to +93
#' * `posterior_warning_pareto_invalid_input`: The input to Pareto smoothing
#' contains infinite or `NA` values.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this warning also happens when the input is constant or has a constant tail. So maybe: "The input to Pareto smoothing contains infinite or NA values, is constant, or has a constant tail."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@florence-bockting

Copy link
Copy Markdown
Collaborator Author

A few tiny suggestions on the new documentation, otherwise looks good!

Great, thank you for review. I updated the docs corresponding to your suggestions.
Once the CI passes I would merge.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Warning levels

2 participants