Repository navigation
Remove the offset at a tied Pareto tail cutoff - #468
Merged
Merged
Conversation
This was referenced Oct 13, 2026
|
This is how benchmark results would change (along with a 95% confidence interval in relative change) if 557ce0f is merged into master:
|
florence-bockting
approved these changes
Oct 15, 2026
florence-bockting
left a comment
Collaborator
There was a problem hiding this comment.
Thank you. Looks good. I just added a bullet point to NEWS.md.
Member
Author
|
@florence-bockting your NEWS commit caused a conflict, can you resolve it? |
Collaborator
Done |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was assisted by Claude
Summary
Offset at a tied Pareto tail cutoff (added in d9f7627) is my mistake.
When the largest non-tail value ties with the smallest tail values,
ps_tail()andpareto_pit()subtract.Machine$double.epsfrom the cutoff. This offset causes three problems:pareto_smooth()can return values below the cutoff. For non-negative weights with many exact zeros, the result contains negative weights of about-2e-16, andresample_draws()then fails withAssertion on 'weights' failed: Element <i> is not >= 0.pareto_pit()puts an observation at a tied cutoff, or less thanepsbelow it, in the GPD tail. For example, with 350 of 400 draws at 0,pareto_pit()gives 0.85 fory = -1e-300, which is below all draws.|cutoff| >= 2, and it changesk-hatwhen the tail excesses are small. For natural-scale weights that span many orders of magnitude,k-hatdiffers from the fit to the same weights on the log scale.This PR removes the offset. Tail values equal to the cutoff then give zero excesses, which the GPD fit and the GPD quantile and distribution functions handle correctly.
Why zero excesses are valid
muand includes it: the density atmuis1 / sigma, finite and positive. A sample value atmuhas a finite log-likelihood.gpdfit()uses the excesses inlog1p(-theta * x), which is 0 forx = 0, and in1 / x[N]and1 / xstar.x[N] > 0because the tail is not constant.xstar == x[1](at least a quarter of the excesses equal the minimum).gpdfit()already returnsk = NAfor it (be503f2). The offset did not prevent this case: all tied values got the same excesseps.qgeneralized_pareto()returnsmuplus a value>= 0, so smoothed values are never below the cutoff. No clamp is necessary.|cutoff| >= 2, becausecutoff - eps == cutoffthere.How the negative weights occur in practice
cmdstanr
process_init_approx()smoothsexp(lp__ - lp_approx__ - max(...))for Pathfinder inits. When most Pathfinder paths are poor, most of these values underflow to exactly 0, and the cutoff and some tail values are both 0. The negative weights then go toresample_draws(), and$sample(init = pathfinder_fit)fails. This was observed as a deterministic failure in a cluster run with 10 paths of 40 draws.Minimal example (no Stan):
pareto_pit()example:Scale dependence on master,
x = s * c(rep(0, 350), exp(rnorm(50, 0, 2)))withset.seed(3)and 60 tail draws:k-hatis 1.6771 fors = 1ands = 1e-8, 1.6694 fors = 1e-12, 1.3330 fors = 1e-14, and 0.1066 fors = 1e-16. With this PR,k-hatis 1.6771 for all theses.Change
ps_tail()(R/pareto_smooth.R): remove the offset. Update the comment on the cutoff.pareto_pit.draws_matrix()(R/pit.R): remove the offset in the right and the left tail.Tests
tests/testthat/test-pareto_smooth.R:ps_tail does not smooth tail values below the cutoff: right tail and left tail, ties at 0. Checks the sign and that the order of the draws does not change.pareto_smooth does not return negative weights for ties at 0: checks the sign and thatresample_draws()accepts the weights.tests/testthat/test-pareto_pit.R:pareto_pit uses the raw PIT at a tail cutoff that ties with tail draws: right and left tail. Checks that draws tied at 0 and draws tied at 5 give the same PIT for an observation at the tie, and that an observation outside all draws gets the smallest or largest PIT.On unpatched master, the new tests give 9 failures: 5 in
test-pareto_smooth.R(one of them theresample_draws()assertion error) and 4 intest-pareto_pit.R. With this PR, all tests pass.Effect on other input
ps_tail()on master and with this PR on 1200 random inputs (100, 400 and 1000 draws):-Inf, 200 runsk-hat. With this PR, the natural-scale result equals the log-scale result for the same weights: in 200 cases, the smoothed values agree to1.9e-14,k-hatagrees to1.6e-13, andk-hatisNAin the same 57 cases.N(0, 20), 200 seeds), master gavek-hat = NAin 90 seeds; this PR gaveNAin none. The offset made tail values below about2e-16equal to the tied zeros, which triggered thexstar == x[1]case.Tested versions
master6dd1e12383.devtools::test(): 2232 tests, 0 failures, 0 errors, 4 skips (gbmis not installed).Copyright and Licensing
By submitting this pull request, the copyright holder is agreeing to
license the submitted work under the following licenses: