[banner]

Summary and Analysis of Extension Program Evaluation in R

Salvatore S. Mangiafico

One-way Repeated Ordinal Regression with CLMM

 

Appropriate data

•  Two-way data with one treatment variable and one blocking variable

•  Dependent variable is ordered factor

•  Treatment variable is a factor with at least two levels or groups

•  Blocking variable is a factor with at least two levels

•  Observations can be paired or repeated measures within blocks

 

Interpretation

A significant result can be interpreted as, “There was a significant difference among groups.”  Or, “There was a significant effect of Independent Variable.”

 

A significant post-hoc analysis indicates e.g. “There was a significant difference between Group A and Group B”, and so on.

 

Packages used in this chapter

 

The packages used in this chapter include:

•  psych

•  FSA

•  lattice

•  ordinal

•  car

•  RVAideMemoire

•  multcomp

•  emmeans

 

The following commands will install these packages if they are not already installed:

 

if(!require(psych)){install.packages("psych")}
if(!require(FSA)){install.packages("FSA")}

if(!require(lattice)){install.packages("lattice")}
if(!require(ordinal)){install.packages("ordinal")}
if(!require(car)){install.packages("car")}
if(!require(RVAideMemoire)){install.packages("RVAideMemoire")}
if(!require(multcomp)){install.packages("multcomp")}
if(!require(emmeans)){install.packages("emmeans")}


One-way repeated ordinal regression example

 

The following example revisits the Belcher family data from the Friedman Test chapter.

 

In this example, we want to determine if there is a difference in scores among five instructors from the Belcher family.  Because we know which rater gave each score, we will use the Rater as a blocking variable, with the suspicion that one rater might rate consistently higher than another one.  If we hadn’t recorded this information about raters, we could use one-way ordinal regression.


Data = read.table(header=TRUE, stringsAsFactors=TRUE, text="

 Instructor        Rater  Likert
 'Bob Belcher'        a      4
 'Bob Belcher'        b      5
 'Bob Belcher'        c      4
 'Bob Belcher'        d      6
 'Bob Belcher'        e      6
 'Bob Belcher'        f      6
 'Bob Belcher'        g     10
 'Bob Belcher'        h      6
 'Linda Belcher'      a      8
 'Linda Belcher'      b      6
 'Linda Belcher'      c      8
 'Linda Belcher'      d      8
 'Linda Belcher'      e      8
 'Linda Belcher'      f      7
 'Linda Belcher'      g     10
 'Linda Belcher'      h      9
 'Tina Belcher'       a      7
 'Tina Belcher'       b      5
 'Tina Belcher'       c      7
 'Tina Belcher'       d      8
 'Tina Belcher'       e      8
 'Tina Belcher'       f      9
 'Tina Belcher'       g     10
 'Tina Belcher'       h      9
 'Gene Belcher'       a      6
 'Gene Belcher'       b      4
 'Gene Belcher'       c      5
 'Gene Belcher'       d      5
 'Gene Belcher'       e      6
 'Gene Belcher'       f      6
 'Gene Belcher'       g      5
 'Gene Belcher'       h      5
 'Louise Belcher'     a      8
 'Louise Belcher'     b      7
 'Louise Belcher'     c      8
 'Louise Belcher'     d      8
 'Louise Belcher'     e      9
 'Louise Belcher'     f      9
 'Louise Belcher'     g      8
 'Louise Belcher'     h     10            
")


### Order levels of the factor; otherwise R will alphabetize them


Data$Instructor = factor(Data$Instructor,
                         levels=unique(Data$Instructor))


### Create a new variable which is the likert scores as an ordered factor


Data$Likert.f = factor(Data$Likert,
                       ordered=TRUE)


###  Check the data frame


library(psych)

headTail(Data)

str(Data)

summary(Data)


Summarize data treating Likert scores as factors


XT = xtabs( ~ Instructor + Likert.f,
           data = Data)

XT


                Likert.f
Instructor       4 5 6 7 8 9 10
  Bob Belcher    2 1 4 0 0 0  1
  Linda Belcher  0 0 1 1 4 1  1
  Tina Belcher   0 1 0 2 2 2  1
  Gene Belcher   1 4 3 0 0 0  0
  Louise Belcher 0 0 0 1 4 2  1


prop.table(XT,
           margin = 1)


                Likert.f
Instructor           4     5     6     7     8     9    10
  Bob Belcher    0.250 0.125 0.500 0.000 0.000 0.000 0.125
  Linda Belcher  0.000 0.000 0.125 0.125 0.500 0.125 0.125
  Tina Belcher   0.000 0.125 0.000 0.250 0.250 0.250 0.125
  Gene Belcher   0.125 0.500 0.375 0.000 0.000 0.000 0.000
  Louise Belcher 0.000 0.000 0.000 0.125 0.500 0.250 0.125

### Proportion with each row


Bar plots by group

Note that the bar plots don’t show the effect of the blocking variable.


library(lattice)

histogram(~ Likert.f | Instructor,
          data=Data,
          layout=c(1,5)      #  columns and rows of individual plots
          )

image


Summarize data treating Likert scores as numeric


library(FSA)

Summarize(Likert ~ Instructor,
          data=Data,
          digits=3)


      Instructor n  mean    sd min   Q1 median   Q3 max percZero
1    Bob Belcher 8 5.875 1.885   4 4.75      6 6.00  10        0
2  Linda Belcher 8 8.000 1.195   6 7.75      8 8.25  10        0
3   Tina Belcher 8 7.875 1.553   5 7.00      8 9.00  10        0
4   Gene Belcher 8 5.250 0.707   4 5.00      5 6.00   6        0
5 Louise Belcher 8 8.375 0.916   7 8.00      8 9.00  10        0


One-way repeated ordinal regression

The model is specified using formula notation with the clmm function.  Likert.f is the dependent variable, and an ordered factor in R, and Instructor is the independent variable.  Rater is the blocking variable, identified as the random variable in the mixed model.  The data= option indicates the data frame that contains the variables.  For the meaning of other options, see ?clmm.

is determined using the Anova function in the packages RVAideMemoire and car.

 

Define model


library(ordinal)

model = clmm(Likert.f ~ Instructor + (1|Rater),
             data = Data)

 

Analysis of deviance


anova(model, type="II")


Error in eval(predvars, data, env) : object 'Likert.f' not found

   ### I don’t know why this error occurs


Comparison of models analysis


model.null = clmm(Likert.f ~ 1 + (1|Rater),
                 data=Data)

anova(model, model.null)


Likelihood ratio tests of cumulative link models:

           no.par    AIC logLik LR.stat df Pr(>Chisq)   
model.null      7 163.00 -74.50                         
model          11 134.94 -56.47  36.059  4  2.814e-07 ***

### The p-value is for the effect of Instructor


Alternate analysis of deviance analysis


library(car)

library(RVAideMemoire)

Anova.clmm(model, type = "II")


Analysis of Deviance Table (Type II tests)

           LR Chisq Df Pr(>Chisq)   
Instructor   36.059  4  2.814e-07 ***


Determine the effect of the random variable


model.fixed = clm(Likert.f ~ Instructor,
                  data = Data)

anova(model,
       null = model.fixed)


Likelihood ratio tests of cumulative link models:

            no.par    AIC  logLik LR.stat df Pr(>Chisq)  
model.fixed     10 140.74 -60.368                        
model           11 134.94 -56.470   7.795  1   0.005239 **

   ### p-value is effect of Rater


Post-hoc test with emmeans

Because the main effect of Instructor in the model was significant, we want to know which instructor has statistically different scores than which other instructors.  Comparing EM means for each instructor will allow us to do this.

 

For clmm model objects, the emmean, SE, LCL, and UCL values should be ignored, unless specific options in emmeans are selected.


library(emmeans)

marginal = emmeans(model,
                   ~ Instructor)

pairs(marginal,
      adjust="tukey")


contrast                       estimate    SE  df z.ratio p.value
 Bob Belcher - Linda Belcher      -4.641 1.326 Inf  -3.500  0.0042
 Bob Belcher - Tina Belcher       -4.381 1.284 Inf  -3.413  0.0058
 Bob Belcher - Gene Belcher        1.036 1.026 Inf   1.010  0.8511
 Bob Belcher - Louise Belcher     -5.376 1.398 Inf  -3.845  0.0011
 Linda Belcher - Tina Belcher      0.260 0.914 Inf   0.284  0.9986
 Linda Belcher - Gene Belcher      5.677 1.434 Inf   3.958  0.0007
 Linda Belcher - Louise Belcher   -0.735 0.908 Inf  -0.809  0.9279
 Tina Belcher - Gene Belcher       5.417 1.385 Inf   3.911  0.0009
 Tina Belcher - Louise Belcher    -0.994 0.919 Inf  -1.082  0.8160
 Gene Belcher - Louise Belcher    -6.412 1.514 Inf  -4.235  0.0002

P value adjustment: tukey method for comparing a family of 5 estimates


library(multcomp)

cld(marginal, Letters=letters)


Instructor     emmean    SE  df asymp.LCL asymp.UCL .group
 Gene Belcher    -3.59 1.104 Inf   -5.7539    -1.427  a   
 Bob Belcher     -2.55 1.038 Inf   -4.5878    -0.521  a   
 Tina Belcher     1.83 0.909 Inf    0.0456     3.608   b  
 Linda Belcher    2.09 0.937 Inf    0.2490     3.924   b  
 Louise Belcher   2.82 0.980 Inf    0.9011     4.742   b  

Confidence level used: 0.95
P value adjustment: tukey method for comparing a family of 5 estimates
significance level used: alpha = 0.05

NOTE: Compact letter displays can be misleading
      because they show NON-findings rather than findings.
      Consider using 'pairs()', 'pwpp()', or 'pwpm()' instead.

   ### Groups sharing a letter in .group are not significantly different

   ### Remember to ignore “emmean”, “SE”, “LCL”, and “UCL” for CLM


Check model assumptions

At the time of writing, the nominal_test and scale_test functions don’t work with clmm model objects, so we will define a similar model with clm and no random effects, and test the proportional odds assumption on this model.  In this example, neither the nominal_test nor scale_test functions produced a p-value, so the results of these tests are not conclusive.


model.clm = clm(Likert.f ~ Instructor + Rater,
            data = Data)

nominal_test(model.clm)


Tests of nominal effects

           Df  logLik    AIC LRT Pr(>Chi)
<none>        -45.907 125.81             
Instructor                              
Rater

   ### No p-value produced for this example.


scale_test(model.clm)


Tests of scale effects

           Df  logLik    AIC LRT Pr(>Chi)
<none>        -45.907 125.81            
Instructor                               
Rater

Warning messages:
1: (-2) Model failed to converge: degenerate Hessian with 1 negative eigenvalues
In addition: maximum number of consecutive Newton modifications reached
2: (-1) Model failed to converge with max|grad| = 6.85558e-06 (tol = 1e-06)
In addition: maximum number of consecutive Newton modifications reached

   ### No p-value produced for this example.
   ### Errors produced for this example.