--- title: "Checking Convergence and Fit" author: "Noah Greifer" date: "`r Sys.Date()`" output: html_vignette: toc: yes vignette: > %\VignetteIndexEntry{Checking Convergence and Fit} %\VignetteEngine{knitr::rmarkdown_notangle} %\VignetteEncoding{UTF-8} bibliography: references.bib link-citations: true --- ```{=html} ``` ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 6.5, fig.height = 3.0, dpi = 110, fig.align = "center", message = FALSE, warning = FALSE) options(digits = 3) has_post <- rlang::is_installed("posterior") # marginaleffects summarizes posterior draws with collapse, so both are needed. has_me <- rlang::is_installed(c("marginaleffects", "collapse")) has_bp <- rlang::is_installed("bayesplot") has_loo <- rlang::is_installed("loo") && rlang::is_installed("rstantools") has_rst <- rlang::is_installed("rstantools") run <- TRUE # The fits below take minutes, so their output is saved ahead of time and # replayed where the code is not run, as on CRAN; see saved-results.R. source("saved-results.R", local = knitr::knit_global()) knitr::opts_chunk$set(saved = TRUE) ``` ## Introduction Checking a fit involves questions that often get asked together and are worth separating. Has the sampler converged, meaning has it explored the posterior properly? And does the model fit, meaning does it describe the data? The first is about the algorithm and the second is about the model; a fit can converge beautifully on a badly chosen family, and a well chosen family can be fitted by a chain that has not run long enough. In this guide, we will take those questions in that order. First we'll fit a model with four chains, hand it to `diagnose()`, and then unpack what that summary reports and why, including the one row (the additive predictor `eta`) whose conventional threshold does not apply to a forest and the one quantity that is deliberately left out of the table. Next we'll ask whether the model describes the data, using posterior predictive checks, a calibration plot for the binary case in which those checks are weak, residuals, and a Bayesian $R^2$. A short checklist at the end collects what we recommend running before reporting anything from a fit. ```{r libs, eval = run} library(bartisan) # For parallelization; optional if (rlang::is_installed("future")) { future::plan(future::multisession) } data("rhc") set.seed(2026) fit <- bartisan(death ~ rhc + age + sex + race + edu + aps + meanbp + resp + hema + pafi + paco2 + crea + surv2m + card, data = rhc, family = binomial(), chains = 4) ``` ## Convergence ### Every Statistic in One Call (`diagnose()`) `diagnose()` computes every convergence and mixing statistic in this section, says which of them fall short, and says what to do about each. Everything it reports comes from the stored draws, so it needs no other package. ```{r diagnose, eval = run} diagnose(fit) ``` The rest of this section explains what it reports and why, which is worth reading once; when the summary itself is enough, `vignette("bartisan")` is the shorter tour of the whole workflow. ### Running More Than One Chain (`chains`) Setting `chains = 4` above is doing the work. The default is one chain, which produces estimates but no way to check most of what matters, because R-hat compares chains to each other; with one chain it is computed by splitting that chain, which catches drift but cannot catch two chains settling in different places. Running four costs four times as much sampling, which for most fits is a few seconds, and less than that under a *future* plan. More chains is not, however, a way to improve `rhat`, and it is worth knowing which way it cuts before reaching for it. Effective sample size depends on the total number of draws and not on how they are divided, so twice as many chains and twice as long a chain buy the same amount of it. `rhat` is not symmetric that way: because it compares chains against each other, its value under a sampler with nothing wrong is about `1 + chains / ess`, so at 400 effective draws four chains sit at 1.010 and sixteen chains at 1.040. Adding chains raises the bar it has to clear. When a fit fails on `rhat`, longer chains are the fix; when it fails only on effective sample size, either will do. The table is in `diagnose(fit)$table` when it is wanted as a data frame, and the checks and the advice are in `$checks` and `$advice`. `rhat` compares the variance between chains to the variance within them [@vehtari2021]; if the chains have found the same posterior the two agree and the ratio is near 1. `ess_bulk` and `ess_tail` are effective sample sizes, meaning how many independent draws the correlated ones are worth, in the middle of the distribution (i.e., the bulk) and in the tails respectively. The conventional thresholds are `rhat` below 1.01 and effective sample sizes above about 400 for a quantity we intend to report precisely. Those come from the general MCMC literature and are a reasonable default here, with one exception described below. `diagnose()` applies them, and takes both as arguments so they can be moved. Some of the columns are worth naming separately. `rhat_late` is the same statistic computed on the second half of the retained draws alone, which tells a warmup that ended too early (i.e., too small a `num_burn`) from chains that have each settled somewhere different: throwing away the early draws has the same effect as a larger `num_burn`, so if that fixes `rhat`, warmup was the problem. The `splits.*` row is the total number of splitting rules in the forest at each draw, the one quantity here that is about the trees rather than about the fitted values, and which no general-purpose MCMC diagnostic would think to look at. ### The Rows of the Table `loglik` is the log likelihood of the whole dataset at each draw. It is a useful scalar summary of the fit, and usually the slowest-mixing row in the table, since it moves with every observation's fitted value at once. `aux.*` are the nuisance parameters of the family (i.e., the parameters that are not part of the additive predictor), and are usually the best behaved rows in the table. The binomial likelihood has none of its own, so none appear here, though a `vc()` coding contributes its coefficients to `aux`, which is where the `aux.b.*` rows in the `bcf()` table below come from; a Gaussian fit would show `aux.sigma`, and a survival fit its scale or baseline hazard. `eta.*` is the additive predictor, and it gets two rows. One summarizes the worst 5% of observations rather than the single worst, because the worst of a thousand values is extreme even when every chain has converged; the other is the average over observations. Reading them against each other is the point of having both, because they routinely differ by orders of magnitude: a forest settles the level of the fitted function within a sweep or two and takes far longer to settle which observation gets which share of it. A prediction for one observation is governed by the worst-5% row. Neither row settles an effect, and the average row in particular should not be read as though it did. An effect is a *contrast*, and a contrast can mix badly where the function it is a contrast of mixes well, so what `estimate_effect()` or `avg_comparisons()` reports needs diagnosing on its own draws rather than inferring from this table. The section below does that. One quantity is deliberately not in the table. The leaf prior scale, in `fit$sigma_mu`, mixes badly in every BART implementation: on one dataset its counterparts in *dbarts* and in *stochtree* both come out above `rhat` 1.1 with effective sample sizes in the tens out of 3000 draws, and *stochtree* draws it exactly from its full conditional, so no sampler can do better. It is a hyperparameter whose disagreement between chains does not reach the fitted function, which on those same fits has `rhat` 1.00 and thousands of effective draws, so reporting it beside `eta` only produces an alarming row with nothing to act on. It is out of this table and only out of this table: the draws are in `fit$sigma_mu` and reach `as_draws()`, so it can still be diagnosed by anyone who wants to. ### The Exception for `eta` The `eta` row is a high percentile over every observation, so it is conservative by construction, and forests mix slowly on their fitted values. Two chains can visit quite different collections of trees, and a forest settles the level of the fitted function in a sweep or two while taking far longer to settle which observation gets which share of it, which is why the per-observation R-hats run high without meaning the two chains disagree about anything we would report, so whether two chains found the same trees is not a question the `eta` rows can answer; `splits.*` is the row that asks it. On harder problems this shows up clearly. Below we fit the Friedman function, first with a chain far too short and then at the defaults. ```{r friedmanbad} set.seed(3) fr <- as.data.frame(matrix(runif(400 * 10), 400, 10)) names(fr) <- paste0("x", 1:10) fr$y <- 10 * sin(pi * fr$x1 * fr$x2) + 20 * (fr$x3 - 0.5)^2 + 10 * fr$x4 + 5 * fr$x5 + rnorm(400) too_short <- bartisan(y ~ ., fr, family = gaussian(), chains = 4, control = bartisan_control(num_trees = 20, num_burn = 50, num_draws = 50)) diagnose(too_short)$table ``` Almost everything is bad. Every `rhat` but the average over observations is far above the 1.01 threshold, and the effective sample sizes are a fraction of the 200 draws kept (4 chains x 50 kept draws per chain) on every row but that one. `rhat_late` is no better than `rhat` on any row but the residual standard deviation, which is the informative part: discarding the early draws does not fix it, so this is not a warmup that ended too soon but chains that have each settled somewhere different. Nothing from this fit should be used. ```{r friedmangood} long_enough <- bartisan(y ~ ., fr, family = gaussian(), chains = 4) diagnose(long_enough)$table ``` Everything has improved by a large factor, and the residual standard deviation is nearly at the threshold. But the log likelihood and `eta` are both still above 1.01, and `eta`'s effective sample size is still in the tens. That residual is the phenomenon described above rather than a chain that merely needs to be longer, and adding draws does not reliably remove it. When the `eta` row is elevated, the distribution behind it is more informative than the maximum: ```{r perobs, eval = run && has_post} per_observation_rhat <- function(fit) { draws <- fit$eta$eta per <- nrow(draws) / fit$chains index <- matrix(seq_len(per * fit$chains), per, fit$chains) apply(draws, 2, function(column) { posterior::rhat(matrix(column[index], per, fit$chains)) }) } round(quantile(per_observation_rhat(fit), c(0.5, 0.9, 0.99, 1)), 3) ``` The median is a little above 1 and so is most of the distribution, so the worst-5% row in the table is not one badly behaved observation: the fitted function as a whole mixes slowly here. That is the phenomenon described above rather than a broken chain, and the two are told apart by what happens to the quantities we report. If the log likelihood has converged, along with any nuisance parameters the family has, and an `avg_comparisons()` estimate is stable across chains and across a longer run, the fit is usable. If instead the log likelihood were badly elevated too, or the estimate moved when the chain was lengthened, that would be a chain that has not converged, and the answer is more draws or a simpler model. ### Looking at the Chains (`as_draws()`) `as_draws()` hands the fit to *posterior* and *bayesplot*. It carries the scalar parameters and a representative set of `eta` columns. ```{r summarise, eval = run && has_post} library(posterior) as_draws(fit) |> summarise_draws() ``` ```{r trace, eval = run && has_bp, fig.height = 2.6} library(bayesplot) as_draws(fit, eta = 1) |> mcmc_trace(pars = c("loglik", "eta[1]")) ``` What we want to see is four chains overlapping, wandering around the same level, with no drift and no long excursions. Setting `eta = 1` selects the first observation, `eta = TRUE` a spread of ten, and `eta = FALSE` none. ### Diagnosing the Quantity Being Reported Everything above is about the parameters the sampler draws. What gets reported is usually not one of them: it is an effect, a contrast, a predicted probability at some covariate value, a difference between two subgroups. Those need their own diagnostics, because a contrast can mix badly where the function it is a contrast of mixes well, and nothing in the table above will say so. When estimating a treatment effect, the quantity of interest is produced by `estimate_effect()`, the output of which is accepted by `diagnose()` directly. Below we fit a Bayesian causal forest model to estimate the ATE: ```{r diagnose_effect, eval = run} bcf_fit <- bcf(death ~ age + sex + race + edu + aps + meanbp + resp + hema + pafi + paco2 + crea + surv2m + card, treat = ~rhc, data = rhc, family = binomial("probit"), chains = 4) estimate_effect(bcf_fit, estimand = "ATE") |> diagnose() ``` The output has a row for the contrast and one for each of the two average potential outcomes it is a contrast of. Here the contrast clears the thresholds and one of the averages, `Y[1]`, falls just short on bulk effective sample size. It does not always come out that way round: a difference can mix worse than either of its parts, so all three rows are worth reading rather than the contrast alone. Now compare what the fit's own table says about the same sampler: ```{r diagnose_bcf_fit, eval = run} diagnose(bcf_fit)$table ``` The two forests are far worse than the estimand. Their R-hats run from 1.2 to 1.6 with effective sample sizes in the tens, where the effect clears 400. That is not a contradiction: a varying coefficient model is $f_0(x) + z\,f_1(x)$, and on the treated units the two forests are only jointly identified, so the sampler can move level between them while their sum, and much of what is built from it, stays where it was. The effect inherits part of that wandering and not all of it. So the estimand's diagnosis cannot be deduced from the fit's in either direction. Read only the `eta` rows here and the fit looks unusable; read only the effect's own table and the sampler looks close to adequate; neither reading gives the other. Both are worth running, and it is the second that decides whether the number being reported can be reported. ### Diagnosing Anything Else (`summarise_draws()`) For a quantity this package has no method for, *posterior* is the general tool: anything that can be arranged as draws by chains can be handed to `summarise_draws()`, which computes the same statistics `diagnose()` reports. The arranging is the only step with a decision in it. The sampler stacks its chains, so the draws of any quantity are one long vector in chain order, and folding it into an iterations-by-chains matrix recovers the structure R-hat needs. Below, the quantity is an average comparison computed by *marginaleffects*, whose draws come out of `posterior_draws()`: ```{r me_draws, eval = run && has_me && has_post} library(marginaleffects) drawn <- avg_comparisons(fit, variables = "rhc") |> posterior_draws() chains <- fit$chains per_chain <- nrow(drawn) / chains folded <- array(drawn$draw, dim = c(per_chain, chains, 1L), dimnames = list(NULL, NULL, "ATE")) folded |> as_draws_array() |> summarise_draws("mean", "rhat", "ess_bulk", "ess_tail") ``` The same statistics `diagnose()` reports, computed by a different route on a quantity it has no method for, which is the point: any posterior quantity can be diagnosed this way, including ones neither package knows about. Draw it, fold it, summarize it. The fold has to match the order the draws are stored in, which here is all of chain one, then all of chain two, and so on; specifying `dim = c(per_chain, chains, 1)` fills the array in that order. And the quantity has to be the one being reported: diagnosing a prediction is not diagnosing the contrast of two predictions, for the reason the section above gives. ### Remedies for Poor Mixing There are a few remedies worth trying, given here in the order in which they usually help. **More draws**, from increasing `num_burn` and `num_draws`, fix most cases. **A smaller forest** helps next, since reducing `num_trees` leaves fewer ways to represent the same function, so the sampler mixes faster. **A different family** can be needed last, because a likelihood that fits the data badly can produce a posterior that is hard to explore, so the family is worth checking when more draws and a smaller forest have not helped; `vignette("families")` covers the alternatives. Note these latter two options change the model itself, so only use them when required, not as a routine fix. The first step is to rule out a false alarm. `rhat` above 1.01 on a quantity carrying only a handful of effective draws is not yet evidence that the chains disagree, since that is roughly where `rhat` sits when nothing is wrong, and `diagnose()` says so rather than reporting a disagreement it cannot support. In that case the effective sample size needs fixing, and more draws is the whole of the remedy. ### A Worked Example: More Draws, Same Model The first remedy is the only one that leaves the model alone, so it is worth seeing how far it goes on its own. Below, a fit that fails and then the same model run longer; the formula, the family, the tree count and every prior setting are identical between the two, and only `num_burn` and `num_draws` move. ```{r mixmodel, eval = run} model <- death ~ rhc + age + aps + meanbp + surv2m set.seed(2026) short <- bartisan(model, data = rhc, family = binomial(), chains = 4) diagnose(short) ``` Several things are flagged. `rhat` is above the threshold, and the check under it says that the R-hat in question rests on too few effective draws for the threshold to mean anything, which is the case the previous section says to rule out first: the chains are not demonstrably disagreeing, they have not run long enough for the statistic to be readable. Both effective sample sizes are short of 400 as well. Everything here points the same way, and none of it points at the model. So we run it longer, increasing the burn-in to 2000 and the number of draws per chain to 8000. Nothing else changes, so we can call `update()` on the previous model with our new arguments supplied: ```{r mixfixed, eval = run} set.seed(2026) longer <- update(short, num_burn = 2000, num_draws = 8000) diagnose(longer) ``` Most of it is cleared. The split-count row and both `eta` rows are well past the thresholds, and every tail effective sample size is above 900. What is left sits on the log likelihood alone: its bulk effective sample size rose from 85 to 208 rather than tenfold, and its R-hat of 1.02 is again the kind the check calls unreadable, since four chains average 1.019 at that count even when they agree. The log likelihood is usually the slowest-mixing row in a forest, because it moves with every fitted value at once, and it is not a quantity we report; the fitted function and its contrasts are, and those are fine here. This fit can be reported from, with the log-likelihood row noted rather than chased. The arithmetic of that fix carries over to other fits, with one qualification. Effective sample size grows roughly in proportion to the draws, so the shortfall tells you the factor you need: a fit an order of magnitude short of 400 needs about an order of magnitude more draws, not a little more. The `eta` rows and the split count grew five- to ninefold here on ten times the draws. The qualification is that the estimate of effective sample size is itself noisy, and can fall as the chain lengthens: a short chain cannot see autocorrelation at long lags, so it reports an efficiency the chain does not have. The log likelihood above did exactly that: its efficiency fell from 2.6 to 0.6 effective draws per hundred kept once the longer chain could see its slow component. Reading a single ESS figure as exact invites chasing it; the factor should be read instead. Increasing draws does not always suffice. It did most of the work here because the flagged R-hat was the unreadable kind, resting on too few effective draws. Where R-hat instead stays above the threshold however many draws are added, as it does for the Friedman fit above, the chains genuinely disagree and more of them will not help. That is when the remedies that change the model come up, and the order in the list above is the order to try them in. ## Fit Convergence says the sampler did its job; it says nothing at all about whether the model is right. The checks below assess the fit of the model to the data. ### Reading the Prior Back (`prior_summary()`) Everything else in this section compares the model to the data it was fitted to. Some checks are worth running before any of that information has been spent, and the first is simply to read the prior back. ```{r priorsummary, eval = run && has_rst} rstantools::prior_summary(fit) ``` This is the prior the engine was given rather than the one we wrote, and the two are not the same object: we named no priors at all, and the defaults are a function of the response and of the number of trees. Note in particular that the leaf scale is not a number anyone typed. It is `3 * s / (k * sqrt(num_trees))`, so halving the number of trees widens each leaf's prior by a factor of $\sqrt 2$ and leaves the prior on their sum alone, which is why `num_trees` is not the tuning knob it looks like. ### Before the Data: The Prior Predictive (`prior_only`) Reading the prior back is not the same as knowing what it implies. The settings in `bartisan_control()` are statements about trees and leaves, and nobody has intuition for what `k = 2` implies about a patient's chance of dying. A prior predictive check puts the prior on the outcome's own scale, where it can be judged. Setting `prior_only = TRUE` fits the same model to no data. Every observation is given a weight of zero, and since the weight multiplies that observation's contribution to the likelihood, the likelihood goes flat and the sampler draws from the prior. ```{r prioronly, eval = run} set.seed(2026) prior <- bartisan(death ~ rhc + age + sex + race + edu + aps + meanbp + resp + hema + pafi + paco2 + crea + surv2m + card, data = rhc, family = binomial(), prior_only = TRUE) ``` What comes back is an ordinary fit whose draws are prior draws, so everything that reads a fit reads this one. The question to put to it is whether the risks the prior considers plausible are ones a critically ill patient could actually have. ```{r priorquant, eval = run} p <- c(.01, .1, .5, .9, .99) quantile(predict(prior, type = "response", draws = TRUE), p) quantile(predict(fit, type = "response", draws = TRUE), p) ``` The prior runs from about .01 to about .99, as we would want of a prior on a probability: it rules almost nothing out and asserts almost nothing. The fitted model runs from about .18 to about .95 over the same quantiles, so the data have brought it a long way in from where it started. A prior that had come back concentrated near zero and one, or confined to a narrow band in the middle, would be worth changing before going any further. There is one thing not to read into this. The median sits close to the observed death rate because the additive predictor is anchored at an intercept-only fit, which is how a BART prior is specified and not a leak. The replicates take their location and scale from the response and everything else from the prior, so read them for shape and spread rather than for level. Every family supports this except `ordinal()` and `ordbeta()`, whose cutpoints are drawn from the likelihood alone and so have nothing to fall back on when the likelihood goes flat; `?bartisan` has the details. ### Posterior Predictive Checks (`pp_check()`) A posterior predictive check simulates outcomes from the fitted model and compares their distribution to the observed one. ```{r ppc, eval = run && has_bp, fig.height = 3.0} pp_check(fit) ``` For a continuous outcome this is the workhorse check, and we look for systematic differences: replicates that are too narrow, that miss a second mode, or that put mass where the outcome cannot go. Simulating negative values for an outcome that cannot be negative says the family is wrong, and `vignette("families")` covers the alternatives. For a binary outcome it is a weak check, which is worth knowing before reading too much into it. There are only two values the replicates can take, so they will match the observed proportion unless the model has gone badly wrong; passing this check tells us almost nothing. The calibration check below should be read instead. #### Checks Worth Reaching For The `type` argument names any of *bayesplot*'s checks without its `ppc_` prefix, and four of them earn their place for a forest. The default compares whole distributions, which is a coarse question. A test statistic is a sharper one: setting `type = "stat"` compares any summary of the replicates with the same summary of the outcome, so the spread, a tail quantile, or the share of zeros can each be asked about on its own. The share of zeros finds an unmodeled point mass, the case for which `vignette("families")` recommends `zi_poisson()` and `tweedie()`. ```{r ppcstat, eval = run && has_bp, fig.height = 2.6} pp_check(fit, type = "stat", stat = "sd") ``` **Calibration of the whole predictive distribution** is checked by setting `type = "loo_pit_ecdf"`. Each observation is transformed through its own leave-one-out predictive distribution, which should leave a uniform if the model is calibrated, and the plot compares the result with one. A curve that leaves the band says the predictive spread is wrong even where the mean is right, which the default check cannot see. ```{r ppcloo, eval = run && has_bp && has_loo, fig.height = 2.8} pp_check(fit, type = "loo_pit_ecdf") ``` **The location of a residual**, rather than its size, is shown by setting `type = "error_scatter_avg_vs_x"`: the average residual against a predictor, which is where a missing interaction appears as structure. And setting `type = "intervals"` draws a predictive interval per observation, so systematic under-coverage is visible as a run of points outside their intervals. The rest apply where the response allows, and say so when it does not: a rootogram wants counts, a bar plot wants discrete values, a calibration plot wants a binary outcome. The one written for censored data is `type = "km_overlay"`, which overlays the replicate survival curves on the observed Kaplan-Meier curve; it takes the censoring indicator from the fit's own response, and needs the *ggfortify* package installed alongside *bayesplot*. ### Calibration The useful question for a binary outcome is whether the predicted probabilities mean what they say: among the patients the model gave a 30% chance of dying, did about 30% die? ```{r calibration, eval = run && has_bp && has_loo, fig.height = 3.4} pp_check(fit, type = "loo_calibration") ``` A line on the diagonal means the probabilities are calibrated, and this one sits close to it across the whole range. The dots along the bottom show where the predictions fall, so a departure over a stretch holding few patients can be discounted. A line above the diagonal at the left and below it at the right means the predictions are too extreme, which is the usual failure of an overfitted model; the opposite pattern means they are too timid, which heavy shrinkage produces. Each patient is judged here against a probability estimated without them, which separates this from the in-sample version, `type = "calibration"`. That one reads optimistically, by however much the model has fitted the individual patients rather than the pattern. `fitted()` returns the probabilities themselves, for binning them some other way. ### Residuals For a continuous outcome, residuals plotted against fitted values read the way they would for a linear model, with one difference. A forest shrinks its predictions toward the overall mean, so a mild positive trend is expected even when the model is correct: the highest fitted values are pulled down and the lowest pulled up. A strong slope is not expected, and a fan shape means the spread of the outcome depends on the predictors, which `gaussian_ls()` models directly. For a binary outcome, the residuals take two values for any given fitted probability and the plot is not informative; the calibration check above should be used instead. ### The Bayesian R-Squared (`r2()`) ```{r r2, eval = run && rlang::is_installed("performance")} performance::r2(fit) ``` This is the Bayesian $R^2$ of @gelman2019, computed from the posterior rather than from a single fit, so it comes with an interval and cannot exceed 1. For a binary outcome it is bounded well below 1 by the outcome's own randomness, so it is best read as a relative measure rather than against any absolute standard. ## A Checklist Before fitting in earnest we read the prior back with `prior_summary()` and run the model once with `prior_only = TRUE`, checking that the replicates fall where the outcome can actually go, since a prior worth changing is cheapest to change before any of the data's information has been spent. Then, before reporting anything from a fit, we run it with `chains = 4` and pass it to `diagnose()`, reading what that says to change: it applies the thresholds, names the rows that fall short, says when an R-hat rests on too few effective draws to be read at all, and separates a warmup that ended too early from chains that have each genuinely settled somewhere different. We then run `pp_check()` and look for systematic differences, checking in particular that the replicates respect any bound the outcome has (e.g., a count that cannot go below zero), and for a binary outcome we read a calibration plot instead, since the predictive check has little to say there. ## Further Reading `vignette("comparison")` covers choosing between models once each of them fits, and `vignette("families")` covers what to do when the posterior predictive check says the family is wrong. ## References