Skip to content

Correct joint tests of ordinal response categories (emmeans backend) - #697

Open
jmgirard wants to merge 9 commits into
easystats:mainfrom
jmgirard:ordinal-joint-tests
Open

jmgirard wants to merge 9 commits into
easystats:mainfrom
jmgirard:ordinal-joint-tests

Conversation

@jmgirard

Copy link
Copy Markdown
Contributor

With backend = "emmeans", joint tests (comparison = "joint") of contrasts between the response categories of ordinal models gave wrong F and Chi-squared statistics. emmeans computes joint tests from the linear functions of its grid. For the "prob" mode of ordinal models, these did not match the contrasts that were shown. After emmeans::regrid(), the joint tests equal the Wald statistic of the contrasts.

Example, with m <- MASS::polr(Sat ~ Infl + Type, weights = Freq, data = MASS::housing, Hess = TRUE). All calls use backend = "emmeans":

Call Before Now
estimate_contrasts(m, "Sat", by = "Infl", comparison = "joint") Chisq 2323.25, 1216.66, 392.16 Chisq 45.10, 29.21, 90.34
estimate_contrasts(m, "Infl", by = "Type", comparison = "joint") warning "NaNs produced" no warning
print(estimate_contrasts(m, "Infl", comparison = "joint")) Chisq printed as 114.5880000 114.59
print(get_emcontrasts(m, "Infl", comparison = "joint")) error: non-numeric argument to mathematical function prints

The new values equal a delta-method Wald test of the Medium - Low and High - Low probability contrasts. They also equal marginaleffects::hypotheses(joint = TRUE) of the same contrasts (45.0955, 29.2141, 90.3444).

What changed:

  • For comparison = "joint", get_emcontrasts() (R/get_emcontrasts.R) calls emmeans::regrid() on the grid of ordinal models in the "prob" mode before emmeans::contrast(). Other modes are not regridded, because for "linear.predictor", regrid() back-transforms the grid to cumulative probabilities. Slope contrasts are not changed.
  • .get_jointtest.emmGrid() (R/get_jointtest.R) returns a plain data frame without the digits attribute. insight::format_table() used that attribute (7) for the Chisq column. If the attribute is missing, as.data.frame() of a summary_emm object sets it again.
  • NEWS entry and version bump.

Tests in test-ordinal.R:

  • The joint tests of "Sat" by "Infl" equal a delta-method Wald test and marginaleffects (polr and clm).
  • The joint tests of "Infl" equal the delta method and emmeans::joint_tests().
  • These calls give no warning, and Chisq prints with two decimals.
  • In the "latent", "linear.predictor", "cum.prob", "exc.prob" and "mean.class" modes, joint tests keep the values of emmeans::joint_tests().

Not fixed here, the same as on main:

  • Joint tests with two contrast variables, such as contrast = c("cyl", "am"), stop with an error, also for lm models. So do joint tests with a filter in contrast, such as "cyl=c('4','6')".
  • For ordinal::clm() fits, "Infl" joint tests still warn "NaNs produced" with predict = "cum.prob", and with "mean.class" and by = "Type".

@jmgirard
jmgirard requested a lite review from Copilot October 19, 2026 19:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

4 open issues
What changed in this PR

Fixes incorrect joint test (Wald/F/Chisq) statistics for ordinal-model response-category contrasts when using the emmeans backend, aligning joint tests with the displayed contrasts.

Changes:

  • Regrid ordinal "prob"-mode emmeans grids before computing joint tests to ensure correct joint-test statistics.
  • Return plain data.frame results for joint tests without a digits attribute to stabilize printed formatting.
  • Add/extend test coverage validating joint tests against delta-method, marginaleffects, and emmeans::joint_tests(), plus NEWS + version bump.
File Description
tests/​testthat/​test-ordinal.R Adds regression tests for ordinal joint tests (correct stats, no warnings, stable printing, mode-specific behavior).
R/​get_jointtest.R Forces joint-test output to be a plain data.frame without digits attribute to control downstream formatting.
R/​get_emcontrasts.R Applies emmeans::regrid() for ordinal "prob" mode when comparison = "joint" to correct joint-test calculations.
NEWS.md Documents the bugfix and related printing/warning changes.
DESCRIPTION Bumps package version.

Review effort: Lite

Comment thread tests/testthat/test-ordinal.R
Comment thread tests/testthat/test-ordinal.R
Comment thread tests/testthat/test-ordinal.R
Comment thread tests/testthat/test-ordinal.R
@jmgirard

Copy link
Copy Markdown
Contributor Author

#697 (review)

  • tests/testthat/test-ordinal.R:1387, missing marginaleffects skip: not changed, the file calls skip_if_not_installed("marginaleffects") at line 7.
  • tests/testthat/test-ordinal.R:1410, missing marginaleffects skip: not changed, same reason.
  • tests/testthat/test-ordinal.R:1420, missing marginaleffects skip: not changed, same reason.
  • tests/testthat/test-ordinal.R:1514, brittle print check: not changed, the pattern matches a table cell and still matches when the table is split at a console width of 30.

@strengejacke

Copy link
Copy Markdown
Member

Could you please resolve the merge conflicts? (I don't want to overwrite with the wrong state) Then this PR can be merged.

Keep the response-category filter from main and regrid after it. The joint
test with a contrast filter on the response now expects the regridded
statistics.

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.

3 participants