CS3.3 β Testing the Proportional Odds Assumption
0.1 What this page is about
The proportional odds (PO) model buys parsimony by assuming that the effect of each predictor is the same at every cumulative split of the outcome. This is a strong assumption. In practice it may hold for some predictors but not others β and using the model when it clearly fails can give misleading results.
Testing the PO assumption means asking: do the data support a single slope per predictor, or do the slopes differ across category boundaries? A visual check is always the first step: plot the empirical log-odds for each binary split of the outcome against the predictor. If the curves are roughly parallel, the PO assumption is plausible.
For a formal test, you can compare the PO model against an unconstrained multinomial model using a likelihood ratio test (LRT). The ordinal::clm package provides a convenient nominal_test() function that does this for each predictor individually. A significant result suggests that predictorβs effect varies across cut-points.
When the PO assumption is violated you have several options: fit a partial PO model (relax the assumption for specific predictors), use the full multinomial model, or consider an adjacent-category model. On this page you will learn all of these steps.
NoteAbbreviations used on this page
Abbreviation
Full term
PO
Proportional odds
LRT
Likelihood ratio test
OR
Odds ratio
df
Degrees of freedom
CLM
Cumulative link model
1 Concept: When Parallelism Fails
1.1 What the PO assumption states
The PO assumption says: for predictor \(x_j\) and any cut-point \(k\):
\[\text{logit}\,P(Y \leq k | x_j = a) - \text{logit}\,P(Y \leq k | x_j = b) = -\beta_j(a-b) \quad \forall k\]
Equivalently, the regression lines for different cut-points are parallel on the logit scale.
1.2 Visual check: empirical log-odds
Before formal testing, plot the empirical log-odds for each binary split against each predictor:
# Create binary splits and estimate log-ORs empiricallysplits <-c("well","mild","moderate")# For each split point: P(Y <= split) vs seslor_ses <-sapply(splits, function(k){ p1 <-mean(dat$impair <= k & dat$ses==1) /mean(dat$impair > k & dat$ses==1) p0 <-mean(dat$impair <= k & dat$ses==0) /mean(dat$impair > k & dat$ses==0)log(p1) -log(p0)})cat("Empirical log-OR for ses at each cut-point:\n")
Empirical log-OR for ses at each cut-point:
print(round(lor_ses, 3))
well mild moderate
0.506 0.619 0.641
cat("\nModel log-OR for ses (should be constant under PO):",round(-coef(fit_po)["ses"], 3), "\n")
Model log-OR for ses (should be constant under PO): 0.687
If the empirical log-ORs are approximately constant across cut-points, PO holds.
Empirical log-OR for ses at each cut-point:
well mild moderate
-1.23 -1.05 -0.88
Model log-OR for ses (should be constant under PO): -0.898
No β the values differ across cut-points (β1.23 vs β0.88), so PO is violated and a separate slope is needed for ses at each cut-point
Yes β the values are roughly constant across cut-points and close to the model estimate; the variation is within sampling noise for n = 200
No β the empirical ORs should all equal exactly the model log-OR (β0.898) if PO holds, but the variation here is too large to accept
Cannot tell without a formal test β visual checks of empirical log-ORs are never informative enough to assess the PO assumption
Under PO, the true log-OR is the same at every cut-point. The empirical values (β1.23, β1.05, β0.88) vary because they are estimated from data β sampling variation is expected. The range from β1.23 to β0.88 spans only about 0.35 log-OR units, and all values are on the same side and close to the model estimate (β0.898). This is consistent with PO. A systematic trend (e.g., values changing from β2 to β0.2) or a sign change would be more alarming.
The log-odds increase linearly with the cut-point index
The log-ORs for a predictor are approximately constant across all cut-points
The log-odds are the same for all predictors at each cut-point
The cumulative probabilities are symmetric around 0.5
Under PO, the log-OR for predictor x is βΞ² at every cut-point. So plotting the empirical log-OR at each cut-point should give roughly horizontal (constant) values. Systematic trends (e.g., increasing or decreasing log-ORs across cut-points) suggest PO violation. This visual check is informal β use it alongside formal tests.
1.3 Formal test: LRT against a non-proportional model
The non-proportional model allows separate coefficients for each cut-point equation. In base R, this requires fitting \(K-1\) binary logistic regressions:
Abandon ordinal regression entirely and switch to multinomial logistic regression for all predictors in the model
Remove the violating predictor from the model entirely β a significant LRT means its effect is too unstable to include
Fit a partial proportional odds model, allowing that predictor to have category-specific slopes while others remain proportional
Collapse adjacent categories until the PO test becomes non-significant, then refit the simplified ordinal model
A partial PO model allows some predictors to have separate slopes for each cut-point while others are constrained to be proportional. This is more flexible than full PO but more parsimonious than full multinomial regression. The R package `ordinal` (clm with nominal argument) supports this directly.
The model has severe convergence problems and the slope estimates are unreliable for all predictors in the model
The outcome should be treated as continuous rather than ordinal because the categories lack a natural ordering
At least one predictor's effect is not constant across cumulative splits β PO may be an oversimplification
The thresholds are too closely spaced to estimate reliably, leading to numerical instability during fitting
A significant Brant test suggests the parallel-regressions assumption is violated for at least one predictor. However, PO models are often robust to mild violations in large samples. Consider: (1) which predictor drives the violation, (2) whether a partial PO model is justified, (3) whether the substantive conclusions change under a relaxed model.
Y<=well Y<=mild Y<=moderate
ses 0.693 0.757 0.733
life -0.400 -0.310 -0.320
cat("\nPO model slopes (should match above if PO holds):\n")
PO model slopes (should match above if PO holds):
print(round(coef(fit_po), 3))
ses life
-0.687 0.334
# LRT: PO vs non-PO# Non-PO: fit multinomial and compare log-likelihoodslibrary(nnet)fit_multinom <-multinom(impair ~ ses + life, data=dat, trace=FALSE)ll_po <-logLik(fit_po)ll_nonpo <-logLik(fit_multinom)lrt_stat <-2*(ll_nonpo - ll_po)df_lrt_po <-length(coef(fit_multinom)) -length(c(coef(fit_po), fit_po$zeta))p_lrt <-pchisq(lrt_stat, df=df_lrt_po, lower.tail=FALSE)cat("LRT statistic:", round(lrt_stat, 4), "\n")
LRT statistic: 1.3972
cat("df: ", df_lrt_po, "\n")
df: 4
cat("p-value: ", round(p_lrt, 4), "\n")
p-value: 0.8447
cat("\nIf p > 0.05: PO assumption is consistent with the data\n")
If p > 0.05: PO assumption is consistent with the data
PO is rejected β the LRT statistic of 4.23 exceeds the critical value
We fail to reject PO (p = 0.376 > 0.05) β the proportional odds assumption is consistent with the data
The non-proportional model is better β it uses fewer degrees of freedom
The result is inconclusive β a df = 4 test is too underpowered to draw any conclusion
The LRT tests Hβ: PO holds (the proportional model is adequate) vs Hβ: PO is violated (slopes differ across cut-points). p = 0.376 > 0.05: we fail to reject Hβ. The extra flexibility of the non-proportional model (4 extra parameters) does not significantly improve fit. Proceed with the PO model. Note: failure to reject does not prove PO holds β the test may have low power, especially if n is small relative to the number of extra parameters being tested.
# Summary: PO model vs multinomialcat("PO model β df=", attr(ll_po,"df"), ", AIC=", round(AIC(fit_po),2), "\n")
The ordinal package provides nominal_test() which tests each predictor separately for PO violation. This is more targeted than the global LRT β it tells you which predictor(s) drive the violation.
library(ordinal)fit_po_clm <-clm(impair ~ ses + life, data=dat)# nominal_test: tests whether each predictor's effect is proportional across cut-points# Hβ for each predictor: the PO assumption holds for that predictornominal_test(fit_po_clm)
Tests of nominal effects
formula: impair ~ ses + life
Df logLik AIC LRT Pr(>Chi)
<none> -244.59 499.18
ses 2 -244.53 503.07 0.11315 0.9450
life 2 -243.71 501.42 1.76176 0.4144
NoteReading nominal_test output
Each row tests one predictor. A small p-value means that predictor has a significantly non-proportional effect. The degrees of freedom equal \((K-2) \times 1 = 2\) per predictor (for \(K=4\) categories): one set of slopes is already in the PO model, and the non-PO model adds \((K-2)\) extra slopes per predictor.
2.0.2 Using VGAM with parallel=FALSE
The VGAM package fits the fully non-proportional model directly. This is the most flexible approach: each predictor gets its own slope per cumulative equation.
# VGAM: fit cumulative logit model without PO assumption# Note: VGAM is not available in the browser (WebR) β this block runs at build timelibrary(VGAM)# PO model (parallel=TRUE): same Ξ² across all cut-pointsfit_vgam_po <-vglm(impair ~ ses + life,cumulative(parallel =TRUE, reverse =FALSE), data = dat)# Non-PO model (parallel=FALSE): separate Ξ² per cut-pointfit_vgam_npo <-vglm(impair ~ ses + life,cumulative(parallel =FALSE, reverse =FALSE), data = dat)# LRT comparing the twolrtest(fit_vgam_po, fit_vgam_npo)
Likelihood ratio test
Model 1: impair ~ ses + life
Model 2: impair ~ ses + life
#Df LogLik Df Chisq Pr(>Chisq)
1 595 -244.59
2 591 -243.64 -4 1.8994 0.7543
NoteVGAM and partial PO
VGAM also allows a partial PO model, where PO is relaxed for only one predictor:
# Allow life to have non-proportional effects, constrain ses to be proportionalfit_partial <-vglm(impair ~ ses + life,cumulative(parallel =FALSE~1+ life), data=dat)lrtest(fit_vgam_po, fit_partial)
A non-significant test here means the partial relaxation for life does not improve fit β life satisfies PO even if another predictor does not.
3 Guided Practice
R is loading in the background β the exercises below will be interactive once the status bar disappears. You can read the concept sections above immediately.
TipHints
For Task 2: dat[dat$x1 > 0,] vs dat[dat$x1 <= 0,] for each binary split
For Task 3: multinom(y ~ x1+x2, data=d7, trace=FALSE)
Minimum \(n \approx 160\) for a reliable LRT. With small samples, the LRT has low power β a non-significant result does not guarantee PO holds.
5 Why-Questions
CautionQ: βThe PO test is non-significant. Does this prove the PO assumption holds?β
No β it means we fail to detect a violation. With limited data, the test may have low power. Absence of evidence is not evidence of absence.
Practical guidance: if the empirical log-ORs at different cut-points are close (within sampling variation), and the test is non-significant, it is reasonable to proceed with the PO model. Report the test result and note that the assumption was checked.
CautionQ: βThe PO test is significant. Must I abandon the ordinal model entirely?β
Not necessarily. Options in order of complexity:
Partial proportional odds: constrain only the βwell-behavedβ predictors to have proportional odds; allow the violating predictor to have separate slopes per equation.
Multinomial logistic regression (CS2): fully flexible, no ordering assumed.
Adjacent-category model: models log-odds of adjacent categories; intermediate flexibility.
Accept the violation if small: if the practical impact on predicted probabilities is minor (compare PO vs multinomial predictions), the PO model may still be the most useful summary.
The choice depends on sample size, scientific goals, and the magnitude of the violation.
6 Spaced Review
NoteFurther reading
Agresti, A. (2010). Analysis of Ordinal Categorical Data (2nd ed.). Wiley. β Chapter 3.