Skip to content

proof of concept for posterior_pit#1857

Open
avehtari wants to merge 63 commits into
paul-buerkner:masterfrom
avehtari:posterior_pit
Open

proof of concept for posterior_pit#1857
avehtari wants to merge 63 commits into
paul-buerkner:masterfrom
avehtari:posterior_pit

Conversation

@avehtari

@avehtari avehtari commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #1855

  • Extend posterior_predict() beyond random draws (PIT / CDF / density / quantile) for calibration and predictive checks.
  • More detailed description of this PR and its status can be found in notes/pr-summary.md

Current API

  • new argument output with values: "random", "probability", "pit", "density", "quantile" (default is "random")
  • additional support of q / p / lower.tail / log.p / log.
# example usage
pits <- posterior_predict(fit, output = "pit")
ppc_pit_ecdf(pit = colMeans(pits), method = "correlated")

What’s implemented now

Known limitations

  • Some families still "random" only (cox none; custom only if implemented)
  • Discrete truncated "random": rejection sampling

TODOs

  • validity checks
  • test code in different case-studies
  • remove "note" folder before merging
  • update vignettes and decide what we want to do with them
  • update News.md

Original PR text (click to expand)

This is a proof of concept branch and far from ready to be merged, but easier to show a possible way to solve issue #1855

To avoid duplication of code in brms, the idea is to use posterior_predict(), but add a new argument type with default value "r" referring to random draws. New type="p" would instead return CDFs / PITs.

PITs = posterior_predict(fit, type = "p")
PIT_post = colMeans(PITs)
PIT_loo = E_loo(PITs, loo(fit)$psis_object)$value

These PIT values are more useful than the ones based on ranks.

The proof of concept works with families "gaussian" and "student_t" without truncation. If the general idea is acceptable then support for the rest of the families and truncation can be added.

Adding here that natural other types are "d" for posterior predictive densities

ppd = posterior_predict(fit, type = "d")

and
"q" for quantiles that are more accurate than current rank based quantiles (used e.g. by predictive_interval())

ppqs = posterior_predict(fit, type = "q", probs = c(0.05, 0.95))
ppq = apply(ppqs, 1, mean)

@paul-buerkner paul-buerkner left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks! I like the idea. Please find some comments to code structure and naming in my review.

Comment thread R/posterior_predict.R Outdated
Comment thread R/posterior_predict.R Outdated
Comment thread R/posterior_predict.R Outdated
Comment thread R/posterior_predict.R Outdated
@paul-buerkner

paul-buerkner commented Feb 25, 2026 via email

Copy link
Copy Markdown
Owner

Comment thread R/posterior_predict.R Outdated
Comment thread tests/testthat/tests.posterior_predict.R Outdated
@florence-bockting

Copy link
Copy Markdown
Collaborator

I have now updated the proof of concept for 4 functions:

  • posterior_predict_gaussian()
  • posterior_predict_student()
  • posterior_predict_binomial()
  • posterior_predict_poisson()

They include two additional new arguments:

  • output (which can take currently the values random or probability) and
  • randomized (which can be NULL or a boolean).

The current implementation follows the idea that we can call:

  • rpred <- posterior_predict(fit_norm)
    • computes random draws from the normal predictive
    • output = "random" and randomized = NULL by default
  • ppred <- posterior_predict(fit_norm, output = "probability")
    • computes cdf of the normal predictive
    • randomized = NULL by default
  • rpred <- posterior_predict(fit_pois)
    • computes random draws from the poisson predictive
    • output = "random" and randomized = NULL by default
  • ppred <- posterior_predict(fit_pois, output = "probability")
    • computes randomized PITs from the poisson predictive
    • randomized = TRUE by default
  • ppred <- posterior_predict(fit_pois, output = "probability", randomized = FALSE)
    • computes the non-randomized PITs from the poisson predictive, which are currently defined as the mid points (F(x) +F(x-1)) * 0.5

@avehtari

Copy link
Copy Markdown
Contributor Author

computes the non-randomized PITs from the poisson predictive, which are currently defined as the mid points (F(x) +F(x-1)) * 0.5

I think these should be just F(x) to follow what ppois etc. do. For the same reason, I think that it might be more logical to have non-randomized as the default

@avehtari

Copy link
Copy Markdown
Contributor Author

Or we could have separately output options probability (not randomized F(x)) and pit randomized for discrete

@florence-bockting

Copy link
Copy Markdown
Collaborator

Or we could have separately output options probability (not randomized F(x)) and pit randomized for discrete

Okay, one option could be to have for outcome the values random, probability, density, quantile, and pit. Whereby pit is only a valid option for discrete cases. Cons are that there is a conceptual overlap between pit and probability and pit is only a valid choice for a subclass of inputs. Pro is that we have all alternatives in one function and no additional argument (like randomized).

An alternative is to outsource the pit case entirely and to have an additional function such as posterior_pit(fit_pois) that applies only to discrete cases and additionally, we have posterior_predict(fit_pois, outcome = "probability") supporting the default implementations like ppois(). In my opinion, this option is from a design point relatively clean, as we don't have the problem that there are "cases" which work/don't work. However, we would have to introduce an additional function.

@paul-buerkner

paul-buerkner commented Mar 16, 2026 via email

Copy link
Copy Markdown
Owner

@avehtari

Copy link
Copy Markdown
Contributor Author

Okay, one option could be to have for outcome the values random, probability, density, quantile, and pit. Whereby pit is only a valid option for discrete cases

In this case pit for continuous would be valid, but the result would be the same as with probability

  • PITs (with randomization for discrete) are needed fot PIT and LOO-PIT uniformity checks (pp_check(..., type = "pit_ecdf|loo_pit_ecdf"))
  • Probabilities (non-randomized) are needed for calibration plots (reliabilitydiag type plots), for which we have PR in bayesplot PPC Calibration plots stan-dev/bayesplot#352
  • Probabilities are useful in general for making predictions and modeling visualization
  • If the outcome options are random, probability, density, quantile, and pit, we don't need separate argument for randomization, and pit computes such PIT values which are useful for uniformity tests both for continuous and discrete case

@paul-buerkner

Copy link
Copy Markdown
Owner

I am fine with option random, probability, density, quantile, and pit (or whatever subset we go with first), as long all all is realized in the same function (posterior_predict). Otherwise code overhead is too high. If we want, we can still have lightweight wrappers around posterior_predict with output argument fixed.

@florence-bockting

Copy link
Copy Markdown
Collaborator

I am fine with option random, probability, density, quantile, and pit (or whatever subset we go with first), as long all all is realized in the same function (posterior_predict).

Alright. We definitely stick with one function - the idea of an extra function is out.
I will change the implementation to having posterior_predict with output = (random, probability, density, quantile, pit). Whereby pit is the randomized pit for discrete distributions and the "normal" cdf for continuous distributions.

@florence-bockting

florence-bockting commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Overview of current output support in R/posterior_predict.R (click to expand)

Legend: Y - yes,
column* - newly implemented

method random probability* density* pit* quantile* notes
gaussian Y Y Y Y Y
student Y Y Y Y Y
lognormal Y Y Y Y Y
shifted_lognormal Y Y Y Y Y
skew_normal Y Y Y Y Y
gaussian_mv Y no support yet
student_mv Y no support yet
gaussian_time Y no support yet
student_time Y no support yet
gaussian_lagsar Y no support yet
student_lagsar Y no support yet
gaussian_errorsar Y no support yet
student_errorsar Y no support yet
gaussian_fcor Y no support yet
student_fcor Y no support yet
binomial Y Y Y Y Y
beta_binomial Y Y Y Y Y
bernoulli Y Y Y Y Y
poisson Y Y Y Y Y
negbinomial Y Y Y Y Y
negbinomial2 Y Y Y Y Y
geometric Y Y Y Y Y
discrete_weibull Y Y Y Y Y
com_poisson Y Y Y Y Y
exponential Y Y Y Y Y
gamma Y Y Y Y Y
weibull Y Y Y Y Y
frechet Y Y Y Y Y
gen_extreme_value Y Y Y Y Y
inverse.gaussian Y Y Y Y Y
exgaussian Y Y Y Y Y
wiener Y
beta Y Y Y Y Y
xbeta Y Y Y Y Y
von_mises Y Y Y Y Y
asym_laplace Y Y Y Y Y
zero_inflated_asym_laplace Y Y Y Y Y
cox explicitly unsupported
hurdle_poisson Y Y Y Y Y no truncation for "random" implemented
hurdle_negbinomial Y Y Y Y Y no truncation for "random" implemented
hurdle_gamma Y Y Y Y Y no truncation for "random" implemented
hurdle_lognormal Y Y Y Y Y no truncation for "random" implemented
hurdle_cumulative Y Y Y Y Y
zero_inflated_beta Y Y Y Y Y
zero_one_inflated_beta Y Y Y Y Y
zero_inflated_poisson Y Y Y Y Y
zero_inflated_negbinomial Y Y Y Y Y
zero_inflated_binomial Y Y Y Y Y
zero_inflated_beta_binomial Y Y Y Y Y
categorical Y Y Y Y Y
multinomial Y no support yet
dirichlet_multinomial Y no support yet
dirichlet Y no support yet
dirichlet2 Y no support yet
logistic_normal Y no support yet
cumulative Y Y Y Y Y
sratio Y Y Y Y Y
cratio Y Y Y Y Y
acat Y Y Y Y Y
ordinal Y Y Y Y Y
custom delegated to custom family method
mixture depends depends depends depends depends delegated to component families

"no support yet" usually because of different output structure.

@florence-bockting

florence-bockting commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Overview of current distribution helpers (R/distributions.R; click to expand)

Legend: Y - yes, Y* - yes, newly implemented

Family ddist pdist qdist rdist notes
acat Y
asym_laplace Y Y Y Y
beta_binomial Y Y Y* Y
categorical Y Y Y* Y*
com_poisson Y Y Y* Y refactored rdist to reuse qdist code
cox Y Y
cratio Y
cumulative Y
dirichlet Y Y
dirichletmultinomial Y
discrete_weibull Y Y Y Y
exgaussian Y Y Y* Y check robustness of qdist implementation
frechet Y Y Y Y
gen_extreme_value Y Y Y Y
hurdle_gamma Y Y Y*
hurdle_lognormal Y Y Y*
hurdle_negbinomial Y Y Y*
hurdle_poisson Y Y Y*
inv_gaussian Y Y Y* Y check robustness of qdist implementation
invgamma Y
logistic_normal Y Y
multi_normal Y Y
multi_student_t Y Y
multinomial Y
ordinal Y* Y Y* Y*
shifted Y Y Y Y
shifted_lnorm Y Y Y Y
skew_normal Y Y Y Y
sratio Y
student_t Y Y Y Y
von_mises Y Y Y* Y
wiener Y Y
xbeta Y Y Y Y
zero_inflated_asym_laplace Y Y Y*
zero_inflated_beta Y Y Y*
zero_inflated_beta_binomial Y Y Y*
zero_inflated_binomial Y Y Y*
zero_inflated_negbinomial Y Y Y*
zero_inflated_poisson Y Y Y*

Florence Bockting added 23 commits April 28, 2026 09:00
… geometric, disc_weibull, exp, frechet, gen_extr_val, xbeta, asym_laplace
@florence-bockting

Copy link
Copy Markdown
Collaborator

Graphics from the knitted vignettes\brms_distribution_output_cheatsheet.Rmd:

output = "random" (click to expand)

image

output = "probability" (click to expand)

image

output = "pit" (click to expand)

image

output = "density" (click to expand)

image

output = "quantile" (click to expand)

image

@florence-bockting

Copy link
Copy Markdown
Collaborator

PR is currently failing because of some add_subsampling_vars_to_pointwise(pointwise = x$pointwise, idxs, elpd_loo_approx), which seems to be unrelated to my changes. Any idea @paul-buerkner where this comes from?

@paul-buerkner

paul-buerkner commented Jul 20, 2026 via email

Copy link
Copy Markdown
Owner

@florence-bockting

Copy link
Copy Markdown
Collaborator

@paul-buerkner When you have time, you can probably take a look at the PR.
I implemented not with the support of posterior_predict for most families.
See PR description for reference to relevant files.

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.

New posterior_pit and loo_pit functions

3 participants