Use Pearson residuals for glmmTMB/MixMod homogeneity panel - #927
Use Pearson residuals for glmmTMB/MixMod homogeneity panel#927jbogomolovas2 wants to merge 3 commits into
Conversation
The glmmTMB/MixMod branch of .model_diagnostic_homogeneity() divided residuals by a single scalar, which is only correct when V(mu) does not depend on mu. For binomial and poisson that scalar is 1, so no standardization occurred and the panel bowed for correctly specified models. Delegate to residuals(type = "pearson"), falling back to the previous behaviour where glmmTMB cannot compute them.
|
Thanks for this PR! Will review manually later, and also start a co-pilot AI review (so don't worry that review is only done by AI, there'a always a human review of PR's as well).
That would be great! |
There was a problem hiding this comment.
Pull request overview
This PR addresses issue #926 by changing how the homogeneity-of-variance diagnostic computes residuals for glmmTMB/MixMod models so the panel uses per-observation variance scaling (Pearson residuals) rather than a single global scale factor.
Changes:
- Prefer
stats::residuals(model, type = "pearson")forglmmTMB/MixModhomogeneity diagnostics. - Retain the existing scalar-standardization approach as a fallback when Pearson residuals fail or are entirely
NA. - Add inline rationale explaining why scalar division is insufficient for many non-Gaussian families.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ## Pearson residuals are scaled by the family's variance function | ||
| ## V(mu_i), which varies across observations. The fallback below | ||
| ## divides by a single scalar, which is only correct when V() does | ||
| ## not depend on mu (e.g. gaussian); for binomial and poisson that | ||
| ## scalar is 1, i.e. no standardization at all. |
There was a problem hiding this comment.
@jbogomolovas2 This is "nitpicking", but you can apply this suggestion, if you like.
| } else { | ||
| .sigma_glmmTMB_nonmixed(model, faminfo) | ||
| } | ||
| stats::residuals(model) / residual_sigma |
There was a problem hiding this comment.
@jbogomolovas2 Same here - I don't think defaults will change, but you may decide to be definitely safe and apply this suggestion.
| r_pearson <- .safe(stats::residuals(model, type = "pearson")) | ||
| if (is.null(r_pearson) || all(is.na(r_pearson))) { | ||
| residual_sigma <- if (faminfo$is_mixed) { | ||
| sqrt(insight::get_variance_residual(model)) | ||
| } else { | ||
| .sigma_glmmTMB_nonmixed(model, faminfo) | ||
| } | ||
| stats::residuals(model) / residual_sigma | ||
| } else { | ||
| .sigma_glmmTMB_nonmixed(model, faminfo) | ||
| r_pearson | ||
| } |
|
Thanks, looks good to me! Can you also please increase the version number? (4th digit increase by 1) and add a news item? After merging this PR, I can add some graphical snapshot tests. |
Done: Applied both suggestions, bumped to 0.17.1.3, NEWS item added." |
|
Great! One last thing, you may add yourself as contributor to the DESCRIPTION file, if you like (unless you want to stay "incognito" 😉) |
Closes issue #926.
The problem
.model_diagnostic_homogeneity()branches by class. Theglmbranch usesrstandard(type = "pearson"), which divides each residual bysqrt(V(mu_i) * phi * (1 - h_ii)). TheglmmTMB/MixModbranch instead divides by a single scalar:residuals.glmmTMB()defaults totype = "response", andresidual_sigmais one number for the whole model. That is only correct whenV()does not depend onmu. For non-mixed fits.sigma_glmmTMB_nonmixed()returns1outright forbinomial,poissonandtruncated_poisson, so no standardization happens at all and the panel plotssqrt(|raw response residuals|).Since binomial spread is proportional to
sqrt(p(1-p))and the panel plotssqrt(|r_i|), the smooth followsV(mu_i)^(1/4)and must bow near p = 0.5 however well specified the model is.This also differs from what
?check_modeldocuments under Residuals for (Generalized) Linear Models, which says these plots use standardized Pearson's residuals for generalized linear models.Scope: not just binomial
Implied per-observation SD (
residuals(type="response") / residuals(type="pearson")) across families, glmmTMB 1.1.15:No family errored, so the fallback added here is protection for future or exotic families rather than a workaround for current failures.
The change
Try
residuals(type = "pearson")first; keep the existing expression as a fallback when glmmTMB cannot compute them..safe()catches errors, and theall(is.na())guard avoids a silently blank panel.Delegating rather than computing
V(mu)here keeps per-family variance knowledge in one place.performancealready maintains.expected_variance()forcheck_overdispersion(), andseecarries a duplicate of this same homogeneity branch, so a per-family table here would be the third copy.Effect
Ratio of max to min of the panel's loess smooth (1 = flat), n = 600, size = 20, fitted probabilities spanning about 0.03 to 0.95:
The
glmcolumn is the control and does not move. Both glmmTMB columns flatten to the same value, and the flat level sits near 0.85, close to E sqrt|Z| = 0.822 for a standard normal, i.e. the residuals are now scaled to unit variance rather than merely looking flat.The third column is a Conway-Maxwell-Binomial family from a pending glmmTMB PR. It is included because it makes the delegation argument concretely: the panel comes out correct for a family
performancehas never heard of, with no changes here.Two things to flag
gaussian. This is the one family the current code gets right:
betadispis sigma and gaussian variance does not depend on mu, so a constant divisor is correct. glmmTMB deliberately returns Pearson residuals unscaled by sigma for fixed-dispersion and constant-dispformulacases, for base R compatibility. So after this change gaussian glmmTMB panels lose the sigma division. Panel shape is unaffected, but the y-scale no longer matches theglmbranch. Restoring parity would mean multiplying bysigma(model)in exactly the case glmmTMB omits it, which reintroduces family-specific logic. Happy to add it if you would rather have the parity.beta-binomial with non-constant
dispformula. glmmTMB documents that beta-binomial Pearson residuals are not scaled by the dispersion factor, because that factor needs the number of trials. With constant dispersion this is a constant multiplier and panel shape is fine; withdispformula = ~xthe factor varies and some curvature would remain. Upstream limitation, not introduced here.Related
see::plot.see_check_heteroscedasticity()contains a copy of this branch, soplot(check_heteroscedasticity(m))is affected the same way. Happy to open the matching PR there if useful.Separately, that copy reads
model$fit$par["betad"]while glmmTMB renamed the parameter tobetadisp(glmmTMB commit 472ccbe0).[does not partial-match, so the divisor isNAand the panel comes out all-NA with no error for thetryCatchto catch. That affects gaussian and Gamma glmmTMB fits. Filing separately.Notes