Continuous-time longitudinal IRT model of PREDIALA study

This vignette illustrates how to estimate a longitudinal IRT following the methodology described in Proust-Lima et al. (2021 - https://arxiv.org/abs/2109.13064). The model combines a graded response measurement model to link a series of ordinal items measured repeatedly over time with their underlying latent process. Simultaneously, a linear mixed model describes the trajectory of the underlying latent process over time.

Importation

The longitudinal IRT model is estimated via multlcmm function of lcmm R package (Proust-Lima et al. 2017). The following libraries are used in this vignette:

library(lcmm)
library(ggplot2)
library(ggpubr)
library(splines)
library(gridExtra)
library(dplyr)

Dataset

The illustration is on a simulated dataset that mimics the PREDIALA study described and analyzed in Proust-Lima et al. (2021). The dataset is called simdataIRT. It contains the following information:

str(simdataHADS)
#> 'data.frame':    1140 obs. of  13 variables:
#>  $ grp       : int  1 0 0 0 0 0 1 0 0 0 ...
#>  $ sex       : int  1 0 0 0 0 0 1 1 1 1 ...
#>  $ age       : num  59.5 58.2 58.2 58.2 58.2 ...
#>  $ hads_2    : int  0 3 1 1 1 2 2 3 3 2 ...
#>  $ hads_4    : int  0 0 0 1 0 1 3 2 2 2 ...
#>  $ hads_6    : int  0 0 1 1 0 1 1 1 2 2 ...
#>  $ hads_8    : int  1 0 1 0 1 1 1 2 1 2 ...
#>  $ hads_10   : int  0 0 2 0 0 1 1 3 1 2 ...
#>  $ hads_12   : int  0 0 0 0 0 1 2 3 3 2 ...
#>  $ hads_14   : int  0 0 0 0 0 0 0 0 0 1 ...
#>  $ ID        : int  1 2 2 2 2 2 3 4 4 4 ...
#>  $ time      : num  7.74 3.18 8.95 15.08 19.74 ...
#>  $ time_entry: num  7.74 3.18 3.18 3.18 3.18 ...

Description of the sample

Timescale of interest: time on the waiting list

The timescale of interest is the time in months spent on the waiting list: time. This timescale poses two problems:

Distribution of the time at entry and time at follow_ups

Quantiles of the distribution of measurement times

Continuous-time longitudinal IRT model estimation

We consider here a longitudinal IRT model with natural cubic splines on time on the waiting list to account for a possible nonlinear trajectory over time, and we adjust the trajectory for the group. We consider 2 internal knots placed at 7 and 15, and shift the right boundary at 60 due to the long tail of the distribution. In the main analysis, we estimate a model with no differential item functioning (DIF) on the group and no response shift (RS) on time.

Estimation of the model with no DIF/RS

To reduce the computation time, we first estimate a model with only 1 random-effect, a random intercept. And then, we use the estimates as initial values for the estimation of the model that also includes random-effects on the time functions. Estimation involves a numerical integration approximated by quasi Monte-Carlo (QMC) with 1000 points. This induces very intensive and long computations but was shown to give accurate results in simulations.

The summary of the estimation:

summary(modIRT)
#> General latent class mixed model 
#>      fitted by maximum likelihood method 
#>  
#> multlcmm(fixed = hads_2 + hads_4 + hads_6 + hads_8 + hads_10 + 
#>     hads_12 + hads_14 ~ ns(time, knots = c(7, 15), Boundary.knots = c(0, 
#>     60)) * grp, random = ~1 + ns(time, knots = c(7, 15), Boundary.knots = c(0, 
#>     60)), subject = "ID", link = "thresholds", data = simdataHADS, 
#>     methInteg = "QMC", nMC = 1000)
#>  
#> Statistical Model: 
#>      Dataset: simdataHADS 
#>      Number of subjects: 561 
#>      Number of observations: 7980 
#>      Number of latent classes: 1 
#>      Number of parameters: 44  
#>      Link functions: Thresholds for hads_2 
#>                      Thresholds for hads_4 
#>                      Thresholds for hads_6 
#>                      Thresholds for hads_8 
#>                      Thresholds for hads_10 
#>                      Thresholds for hads_12 
#>                      Thresholds for hads_14 
#>  
#> Iteration process: 
#>      Convergence criteria satisfied 
#>      Number of iterations:  30 
#>      Convergence criteria: parameters= 2e-08 
#>                          : likelihood= 3.2e-06 
#>                          : second derivatives= 1.8e-06 
#>  
#> Goodness-of-fit statistics: 
#>      maximum log-likelihood: -7218.96  
#>      AIC: 14525.91  
#>      BIC: 14716.42  
#>  
#> Maximum Likelihood Estimates: 
#>  
#> Fixed effects in the longitudinal model:
#> 
#>                                                                coef      Se
#> intercept (not estimated)                                   0.00000        
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1     -0.13037 0.14971
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2      0.20745 0.35059
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3      0.55413 0.20665
#> grp                                                        -0.60195 0.19524
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp  0.23105 0.20314
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp  0.46499 0.44974
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp -0.06219 0.32068
#>                                                              Wald p-value
#> intercept (not estimated)                                                
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1     -0.871 0.38387
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2      0.592 0.55404
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3      2.681 0.00733
#> grp                                                        -3.083 0.00205
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp  1.137 0.25537
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp  1.034 0.30118
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp -0.194 0.84622
#> 
#> 
#> Variance-covariance matrix of the random-effects:
#> (the variance of the first random effect is not estimated)
#>                                                        intercept
#> intercept                                                1.00000
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1  -0.34385
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2  -1.00748
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3  -0.09015
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                0.65616
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                0.79507
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.40139
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                2.13117
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.30796
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                 0.2747
#> 
#>                            hads_2   hads_4   hads_6   hads_8  hads_10  hads_12
#> Residual standard error:  0.78685  0.63468  1.03439  1.01073  1.05546  0.67910
#>                           hads_14
#> Residual standard error:  1.63853
#> 
#> Parameters of the link functions:
#> 
#>                     coef      Se   Wald p-value
#> hads_2-Thresh1  -0.54296 0.16481 -3.294 0.00099
#> hads_2-Thresh2   1.12581 0.05349 21.046 0.00000
#> hads_2-Thresh3   0.88949 0.05032 17.678 0.00000
#> hads_4-Thresh1  -0.23896 0.15916 -1.501 0.13325
#> hads_4-Thresh2   1.01287 0.04844 20.909 0.00000
#> hads_4-Thresh3   1.04562 0.05856 17.856 0.00000
#> hads_6-Thresh1  -0.38855 0.16408 -2.368 0.01788
#> hads_6-Thresh2   1.35285 0.06836 19.790 0.00000
#> hads_6-Thresh3   1.32439 0.09443 14.025 0.00000
#> hads_8-Thresh1  -1.39105 0.20071 -6.931 0.00000
#> hads_8-Thresh2   1.34308 0.06446 20.836 0.00000
#> hads_8-Thresh3   1.10197 0.05825 18.918 0.00000
#> hads_10-Thresh1 -0.02481 0.16367 -0.152 0.87949
#> hads_10-Thresh2  1.00550 0.05464 18.403 0.00000
#> hads_10-Thresh3  1.10645 0.06561 16.863 0.00000
#> hads_12-Thresh1 -0.20523 0.15916 -1.289 0.19724
#> hads_12-Thresh2  0.99398 0.04808 20.675 0.00000
#> hads_12-Thresh3  1.02543 0.05776 17.753 0.00000
#> hads_14-Thresh1  0.91742 0.20988  4.371 0.00001
#> hads_14-Thresh2  1.49255 0.09869 15.124 0.00000
#> hads_14-Thresh3  0.87180 0.10143  8.595 0.00000

Predicted underlying depressive symptomatolgy trajectory

Location and discrimination of the items (with SE by Delta-Method)

The location and discrimination parameters are transformations of the estimated parameters. They are retrieved with the following code:

The 3 thresholds and discrimination estimates of each item are:

item category probability curve

The curve of each item category probability according to the underlying level of depressive symptomatology can be obtain usinf the ItemInfo function.

Item characteristic curves

The following code computes the expectation of each item according to the underlying level of depressive symptomatology. This is achieved with predictYcond function with two plot possibilities: direct plot function or ggplot

Item Information Function

The level of information brought by each item category (information share) and brought in total by each item is also computed by the ItemInfo function. The curves can be again plotted directly with options which=“LevelInfo” and which=“ItemInfo” respectively.

Predicted item trajectory according to time

Item predicted trajectories according to a specific profile of covariates can be computed using predictY function:

Assessment of DIF and RS

Estimation of the IRT model with a DIF on group

DIF is programmed using contrasts (item-specific departure around the mean effect on the underlying latent process)

sumDIF <- summary(modIRT_DIFg)
#> General latent class mixed model 
#>      fitted by maximum likelihood method 
#>  
#> multlcmm(fixed = hads_2 + hads_4 + hads_6 + hads_8 + hads_10 + 
#>     hads_12 + hads_14 ~ ns(time, knots = c(7, 15), Boundary.knots = c(0, 
#>     60)) * (grp) + contrast(grp), random = ~1 + ns(time, knots = c(7, 
#>     15), Boundary.knots = c(0, 60)), subject = "ID", link = "thresholds", 
#>     data = simdataHADS, methInteg = "QMC", nMC = 1000)
#>  
#> Statistical Model: 
#>      Dataset: simdataHADS 
#>      Number of subjects: 561 
#>      Number of observations: 7980 
#>      Number of latent classes: 1 
#>      Number of parameters: 50  
#>      Link functions: Thresholds for hads_2 
#>                      Thresholds for hads_4 
#>                      Thresholds for hads_6 
#>                      Thresholds for hads_8 
#>                      Thresholds for hads_10 
#>                      Thresholds for hads_12 
#>                      Thresholds for hads_14 
#>  
#> Iteration process: 
#>      Convergence criteria satisfied 
#>      Number of iterations:  18 
#>      Convergence criteria: parameters= 5.5e-10 
#>                          : likelihood= 7.2e-07 
#>                          : second derivatives= 3.5e-06 
#>  
#> Goodness-of-fit statistics: 
#>      maximum log-likelihood: -7215.26  
#>      AIC: 14530.53  
#>      BIC: 14747.01  
#>  
#> Maximum Likelihood Estimates: 
#>  
#> Fixed effects in the longitudinal model:
#> 
#>                                                                coef      Se
#> intercept (not estimated)                                   0.00000        
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1     -0.13297 0.14024
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2      0.20664 0.29380
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3      0.55506 0.22402
#> grp                                                        -0.57337 0.17193
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp  0.23311 0.20130
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp  0.47086 0.39851
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp -0.06025 0.33328
#> Contrasts on grp (p=0.29649)                                               
#> hads_2                                                     -0.02615 0.05552
#> hads_4                                                     -0.02251 0.05210
#> hads_6                                                     -0.00976 0.07969
#> hads_8                                                      0.03271 0.06527
#> hads_10                                                    -0.09166 0.07983
#> hads_12                                                    -0.10547 0.05426
#> hads_14**                                                   0.22284 0.10823
#>                                                              Wald p-value
#> intercept (not estimated)                                                
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1     -0.948 0.34306
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2      0.703 0.48184
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3      2.478 0.01322
#> grp                                                        -3.335 0.00085
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp  1.158 0.24685
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp  1.182 0.23738
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp -0.181 0.85654
#> Contrasts on grp (p=0.29649)                                             
#> hads_2                                                     -0.471 0.63768
#> hads_4                                                     -0.432 0.66569
#> hads_6                                                     -0.123 0.90249
#> hads_8                                                      0.501 0.61624
#> hads_10                                                    -1.148 0.25087
#> hads_12                                                    -1.944 0.05191
#> hads_14**                                                   2.059 0.03951
#> 
#> 
#> Variance-covariance matrix of the random-effects:
#> (the variance of the first random effect is not estimated)
#>                                                        intercept
#> intercept                                                1.00000
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1  -0.34300
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2  -1.00695
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3  -0.09042
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                0.65939
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                0.79736
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.40205
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                2.13395
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.30725
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                 0.2742
#> 
#>                            hads_2   hads_4   hads_6   hads_8  hads_10  hads_12
#> Residual standard error:  0.78759  0.63370  1.03198  1.00051  1.06841  0.68928
#>                           hads_14
#> Residual standard error:  1.56386
#> 
#> Parameters of the link functions:
#> 
#>                     coef      Se   Wald p-value
#> hads_2-Thresh1  -0.54084 0.14279 -3.788 0.00015
#> hads_2-Thresh2   1.12629 0.05507 20.453 0.00000
#> hads_2-Thresh3   0.88989 0.05133 17.335 0.00000
#> hads_4-Thresh1  -0.23444 0.13672 -1.715 0.08638
#> hads_4-Thresh2   1.01263 0.04931 20.536 0.00000
#> hads_4-Thresh3   1.04536 0.05931 17.624 0.00000
#> hads_6-Thresh1  -0.37640 0.15232 -2.471 0.01347
#> hads_6-Thresh2   1.35142 0.07135 18.942 0.00000
#> hads_6-Thresh3   1.32355 0.09637 13.734 0.00000
#> hads_8-Thresh1  -1.34484 0.18696 -7.193 0.00000
#> hads_8-Thresh2   1.33742 0.06746 19.826 0.00000
#> hads_8-Thresh3   1.09662 0.06048 18.133 0.00000
#> hads_10-Thresh1 -0.05778 0.14266 -0.405 0.68549
#> hads_10-Thresh2  1.01150 0.05570 18.161 0.00000
#> hads_10-Thresh3  1.11286 0.06683 16.653 0.00000
#> hads_12-Thresh1 -0.24864 0.13642 -1.823 0.06837
#> hads_12-Thresh2  1.00115 0.05013 19.970 0.00000
#> hads_12-Thresh3  1.03304 0.05981 17.271 0.00000
#> hads_14-Thresh1  1.01531 0.20581  4.933 0.00000
#> hads_14-Thresh2  1.46240 0.09729 15.032 0.00000
#> hads_14-Thresh3  0.85364 0.09947  8.582 0.00000
#> 
#>  ** coefficient not estimated but obtained from the others as minus the sum of them 
#> 
sumDIF[,2]
#>                                  intercept (not estimated) 
#>                                                         NA 
#>     ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1 
#>                                                    0.14024 
#>     ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2 
#>                                                    0.29380 
#>     ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3 
#>                                                    0.22402 
#>                                                        grp 
#>                                                    0.17193 
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp 
#>                                                    0.20130 
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp 
#>                                                    0.39851 
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp 
#>                                                    0.33328 
#>                               Contrasts on grp (p=0.29649) 
#>                                                         NA 
#>                                                     hads_2 
#>                                                    0.05552 
#>                                                     hads_4 
#>                                                    0.05210 
#>                                                     hads_6 
#>                                                    0.07969 
#>                                                     hads_8 
#>                                                    0.06527 
#>                                                    hads_10 
#>                                                    0.07983 
#>                                                    hads_12 
#>                                                    0.05426 
#>                                                  hads_14** 
#>                                                    0.10823

To be done again …. L’item 2 est le seul item qui semble être différent entre les groupes (p=0.0071) avec un niveau plus faible chez les preemptive par rapport aux autres items. Au global, la différence de groupe entre les 7 items ne semble pas significative (p=0.2665 au global (Chi2 à 6 degrés de liberté)).

Global test for contrasts

95% confidence interval of the difference between groups for item 2:

sum <- summary(modIRT_DIFg)[10,]
#> General latent class mixed model 
#>      fitted by maximum likelihood method 
#>  
#> multlcmm(fixed = hads_2 + hads_4 + hads_6 + hads_8 + hads_10 + 
#>     hads_12 + hads_14 ~ ns(time, knots = c(7, 15), Boundary.knots = c(0, 
#>     60)) * (grp) + contrast(grp), random = ~1 + ns(time, knots = c(7, 
#>     15), Boundary.knots = c(0, 60)), subject = "ID", link = "thresholds", 
#>     data = simdataHADS, methInteg = "QMC", nMC = 1000)
#>  
#> Statistical Model: 
#>      Dataset: simdataHADS 
#>      Number of subjects: 561 
#>      Number of observations: 7980 
#>      Number of latent classes: 1 
#>      Number of parameters: 50  
#>      Link functions: Thresholds for hads_2 
#>                      Thresholds for hads_4 
#>                      Thresholds for hads_6 
#>                      Thresholds for hads_8 
#>                      Thresholds for hads_10 
#>                      Thresholds for hads_12 
#>                      Thresholds for hads_14 
#>  
#> Iteration process: 
#>      Convergence criteria satisfied 
#>      Number of iterations:  18 
#>      Convergence criteria: parameters= 5.5e-10 
#>                          : likelihood= 7.2e-07 
#>                          : second derivatives= 3.5e-06 
#>  
#> Goodness-of-fit statistics: 
#>      maximum log-likelihood: -7215.26  
#>      AIC: 14530.53  
#>      BIC: 14747.01  
#>  
#> Maximum Likelihood Estimates: 
#>  
#> Fixed effects in the longitudinal model:
#> 
#>                                                                coef      Se
#> intercept (not estimated)                                   0.00000        
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1     -0.13297 0.14024
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2      0.20664 0.29380
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3      0.55506 0.22402
#> grp                                                        -0.57337 0.17193
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp  0.23311 0.20130
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp  0.47086 0.39851
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp -0.06025 0.33328
#> Contrasts on grp (p=0.29649)                                               
#> hads_2                                                     -0.02615 0.05552
#> hads_4                                                     -0.02251 0.05210
#> hads_6                                                     -0.00976 0.07969
#> hads_8                                                      0.03271 0.06527
#> hads_10                                                    -0.09166 0.07983
#> hads_12                                                    -0.10547 0.05426
#> hads_14**                                                   0.22284 0.10823
#>                                                              Wald p-value
#> intercept (not estimated)                                                
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1     -0.948 0.34306
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2      0.703 0.48184
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3      2.478 0.01322
#> grp                                                        -3.335 0.00085
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp  1.158 0.24685
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp  1.182 0.23738
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp -0.181 0.85654
#> Contrasts on grp (p=0.29649)                                             
#> hads_2                                                     -0.471 0.63768
#> hads_4                                                     -0.432 0.66569
#> hads_6                                                     -0.123 0.90249
#> hads_8                                                      0.501 0.61624
#> hads_10                                                    -1.148 0.25087
#> hads_12                                                    -1.944 0.05191
#> hads_14**                                                   2.059 0.03951
#> 
#> 
#> Variance-covariance matrix of the random-effects:
#> (the variance of the first random effect is not estimated)
#>                                                        intercept
#> intercept                                                1.00000
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1  -0.34300
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2  -1.00695
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3  -0.09042
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                0.65939
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                0.79736
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.40205
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                2.13395
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.30725
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                 0.2742
#> 
#>                            hads_2   hads_4   hads_6   hads_8  hads_10  hads_12
#> Residual standard error:  0.78759  0.63370  1.03198  1.00051  1.06841  0.68928
#>                           hads_14
#> Residual standard error:  1.56386
#> 
#> Parameters of the link functions:
#> 
#>                     coef      Se   Wald p-value
#> hads_2-Thresh1  -0.54084 0.14279 -3.788 0.00015
#> hads_2-Thresh2   1.12629 0.05507 20.453 0.00000
#> hads_2-Thresh3   0.88989 0.05133 17.335 0.00000
#> hads_4-Thresh1  -0.23444 0.13672 -1.715 0.08638
#> hads_4-Thresh2   1.01263 0.04931 20.536 0.00000
#> hads_4-Thresh3   1.04536 0.05931 17.624 0.00000
#> hads_6-Thresh1  -0.37640 0.15232 -2.471 0.01347
#> hads_6-Thresh2   1.35142 0.07135 18.942 0.00000
#> hads_6-Thresh3   1.32355 0.09637 13.734 0.00000
#> hads_8-Thresh1  -1.34484 0.18696 -7.193 0.00000
#> hads_8-Thresh2   1.33742 0.06746 19.826 0.00000
#> hads_8-Thresh3   1.09662 0.06048 18.133 0.00000
#> hads_10-Thresh1 -0.05778 0.14266 -0.405 0.68549
#> hads_10-Thresh2  1.01150 0.05570 18.161 0.00000
#> hads_10-Thresh3  1.11286 0.06683 16.653 0.00000
#> hads_12-Thresh1 -0.24864 0.13642 -1.823 0.06837
#> hads_12-Thresh2  1.00115 0.05013 19.970 0.00000
#> hads_12-Thresh3  1.03304 0.05981 17.271 0.00000
#> hads_14-Thresh1  1.01531 0.20581  4.933 0.00000
#> hads_14-Thresh2  1.46240 0.09729 15.032 0.00000
#> hads_14-Thresh3  0.85364 0.09947  8.582 0.00000
#> 
#>  ** coefficient not estimated but obtained from the others as minus the sum of them 
#> 
c(sum[1],sum[1]- qnorm(0.975)*sum[2],sum[1]+ qnorm(0.975)*sum[2])
#>       coef       coef       coef 
#> -0.0261500 -0.1349672  0.0826672

C. Estimation of the IRT model with a Response Shift over time

Response Shift is modelled by adding contrasts on the functions of time

summary(modIRT_DIFt)
#> General latent class mixed model 
#>      fitted by maximum likelihood method 
#>  
#> multlcmm(fixed = hads_2 + hads_4 + hads_6 + hads_8 + hads_10 + 
#>     hads_12 + hads_14 ~ ns(time, knots = c(7, 15), Boundary.knots = c(0, 
#>     60)) * (grp) + contrast(ns(time, knots = c(7, 15), Boundary.knots = c(0, 
#>     60))), random = ~1 + ns(time, knots = c(7, 15), Boundary.knots = c(0, 
#>     60)), subject = "ID", link = "thresholds", data = simdataHADS, 
#>     methInteg = "QMC", nMC = 1000)
#>  
#> Statistical Model: 
#>      Dataset: simdataHADS 
#>      Number of subjects: 561 
#>      Number of observations: 7980 
#>      Number of latent classes: 1 
#>      Number of parameters: 62  
#>      Link functions: Thresholds for hads_2 
#>                      Thresholds for hads_4 
#>                      Thresholds for hads_6 
#>                      Thresholds for hads_8 
#>                      Thresholds for hads_10 
#>                      Thresholds for hads_12 
#>                      Thresholds for hads_14 
#>  
#> Iteration process: 
#>      Convergence criteria satisfied 
#>      Number of iterations:  38 
#>      Convergence criteria: parameters= 1.7e-06 
#>                          : likelihood= 1.6e-05 
#>                          : second derivatives= 2.1e-05 
#>  
#> Goodness-of-fit statistics: 
#>      maximum log-likelihood: -7206.7  
#>      AIC: 14537.4  
#>      BIC: 14805.84  
#>  
#> Maximum Likelihood Estimates: 
#>  
#> Fixed effects in the longitudinal model:
#> 
#>                                                                                     coef
#> intercept (not estimated)                                                        0.00000
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                          -0.15670
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                           0.23260
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                           0.54188
#> grp                                                                             -0.60304
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp                       0.23158
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp                       0.47346
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp                      -0.05625
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1 (p=0.20081)         
#> hads_2                                                                           0.08729
#> hads_4                                                                           0.11625
#> hads_6                                                                          -0.44754
#> hads_8                                                                           0.07570
#> hads_10                                                                          0.02299
#> hads_12                                                                          0.07366
#> hads_14**                                                                        0.07165
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2 (p=0.29467)         
#> hads_2                                                                           0.04846
#> hads_4                                                                           0.31419
#> hads_6                                                                          -0.12537
#> hads_8                                                                          -0.16013
#> hads_10                                                                         -0.49717
#> hads_12                                                                         -0.22330
#> hads_14**                                                                        0.64332
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3 (p=0.25871)         
#> hads_2                                                                           0.09527
#> hads_4                                                                           0.26310
#> hads_6                                                                           0.07205
#> hads_8                                                                          -0.10425
#> hads_10                                                                         -0.22343
#> hads_12                                                                         -0.21563
#> hads_14**                                                                        0.11289
#>                                                                                      Se
#> intercept (not estimated)                                                              
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                          0.17358
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                          0.82370
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                          0.17632
#> grp                                                                             0.59386
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp                      0.23305
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp                      1.10893
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp                      0.42697
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1 (p=0.20081)        
#> hads_2                                                                          0.12368
#> hads_4                                                                          0.11111
#> hads_6                                                                          0.16139
#> hads_8                                                                          0.14550
#> hads_10                                                                         0.15418
#> hads_12                                                                         0.11400
#> hads_14**                                                                       0.25382
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2 (p=0.29467)        
#> hads_2                                                                          0.22799
#> hads_4                                                                          0.21434
#> hads_6                                                                          0.35246
#> hads_8                                                                          0.26551
#> hads_10                                                                         0.27058
#> hads_12                                                                         0.47554
#> hads_14**                                                                       0.48952
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3 (p=0.25871)        
#> hads_2                                                                          0.14528
#> hads_4                                                                          0.13281
#> hads_6                                                                          0.20025
#> hads_8                                                                          0.16574
#> hads_10                                                                         0.17771
#> hads_12                                                                         0.13640
#> hads_14**                                                                       0.28994
#>                                                                                   Wald
#> intercept (not estimated)                                                             
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                          -0.903
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                           0.282
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                           3.073
#> grp                                                                             -1.015
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp                       0.994
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp                       0.427
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp                      -0.132
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1 (p=0.20081)       
#> hads_2                                                                           0.706
#> hads_4                                                                           1.046
#> hads_6                                                                          -2.773
#> hads_8                                                                           0.520
#> hads_10                                                                          0.149
#> hads_12                                                                          0.646
#> hads_14**                                                                        0.282
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2 (p=0.29467)       
#> hads_2                                                                           0.213
#> hads_4                                                                           1.466
#> hads_6                                                                          -0.356
#> hads_8                                                                          -0.603
#> hads_10                                                                         -1.837
#> hads_12                                                                         -0.470
#> hads_14**                                                                        1.314
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3 (p=0.25871)       
#> hads_2                                                                           0.656
#> hads_4                                                                           1.981
#> hads_6                                                                           0.360
#> hads_8                                                                          -0.629
#> hads_10                                                                         -1.257
#> hads_12                                                                         -1.581
#> hads_14**                                                                        0.389
#>                                                                                 p-value
#> intercept (not estimated)                                                              
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                          0.36667
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                          0.77764
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                          0.00212
#> grp                                                                             0.30989
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1:grp                      0.32037
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2:grp                      0.66941
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3:grp                      0.89519
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1 (p=0.20081)        
#> hads_2                                                                          0.48034
#> hads_4                                                                          0.29546
#> hads_6                                                                          0.00555
#> hads_8                                                                          0.60290
#> hads_10                                                                         0.88145
#> hads_12                                                                         0.51819
#> hads_14**                                                                       0.77772
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2 (p=0.29467)        
#> hads_2                                                                          0.83167
#> hads_4                                                                          0.14270
#> hads_6                                                                          0.72205
#> hads_8                                                                          0.54644
#> hads_10                                                                         0.06615
#> hads_12                                                                         0.63865
#> hads_14**                                                                       0.18878
#> Contrasts on ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3 (p=0.25871)        
#> hads_2                                                                          0.51199
#> hads_4                                                                          0.04759
#> hads_6                                                                          0.71900
#> hads_8                                                                          0.52933
#> hads_10                                                                         0.20866
#> hads_12                                                                         0.11392
#> hads_14**                                                                       0.69701
#> 
#> 
#> Variance-covariance matrix of the random-effects:
#> (the variance of the first random effect is not estimated)
#>                                                        intercept
#> intercept                                                1.00000
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1  -0.34985
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2  -1.01606
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3  -0.09265
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                0.64899
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                0.79384
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.39651
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                2.12919
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.30894
#>                                                        ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3
#> intercept                                                                                                    
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))1                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))2                                                       
#> ns(time, knots = c(7, 15), Boundary.knots = c(0, 60))3                                                0.27109
#> 
#>                            hads_2   hads_4   hads_6   hads_8  hads_10  hads_12
#> Residual standard error:  0.78529  0.63525  1.01983  0.99728  1.03501  0.66202
#>                           hads_14
#> Residual standard error:  1.62216
#> 
#> Parameters of the link functions:
#> 
#>                     coef      Se   Wald p-value
#> hads_2-Thresh1  -0.51941 0.53286 -0.975 0.32968
#> hads_2-Thresh2   1.12436 0.07485 15.022 0.00000
#> hads_2-Thresh3   0.88847 0.06515 13.638 0.00000
#> hads_4-Thresh1  -0.11811 0.49181 -0.240 0.81021
#> hads_4-Thresh2   1.01475 0.06821 14.877 0.00000
#> hads_4-Thresh3   1.04881 0.07780 13.480 0.00000
#> hads_6-Thresh1  -0.45610 0.52238 -0.873 0.38260
#> hads_6-Thresh2   1.34774 0.09060 14.876 0.00000
#> hads_6-Thresh3   1.32073 0.11019 11.985 0.00000
#> hads_8-Thresh1  -1.41857 0.57951 -2.448 0.01437
#> hads_8-Thresh2   1.33452 0.08270 16.138 0.00000
#> hads_8-Thresh3   1.09520 0.07350 14.900 0.00000
#> hads_10-Thresh1 -0.21289 0.51793 -0.411 0.68105
#> hads_10-Thresh2  0.99702 0.06882 14.487 0.00000
#> hads_10-Thresh3  1.09847 0.08025 13.689 0.00000
#> hads_12-Thresh1 -0.25845 0.74848 -0.345 0.72987
#> hads_12-Thresh2  0.98380 0.07480 13.152 0.00000
#> hads_12-Thresh3  1.01692 0.07751 13.121 0.00000
#> hads_14-Thresh1  1.21337 0.46343  2.618 0.00884
#> hads_14-Thresh2  1.48581 0.11544 12.871 0.00000
#> hads_14-Thresh3  0.86655 0.10687  8.109 0.00000
#> 
#>  ** coefficient not estimated but obtained from the others as minus the sum of them 
#> 

There does not seem to be any difference in item trajectories over time (see global p-values for each function of time in the summary). We can seek whether there are some difference item by item using Wald Test. This can be done with the WaldMult function of lcmm except for the last item since this parameter is a combination of the others. Next code details how to obtain this item-specific test of invariance over time.

Interpretation.