Skip to contents

Lagrange (i.e., score) test to test whether parameters should be freed from a more constrained baseline model.

Usage

lagrange(mod, parnum, SE.type = "Oakes", type = "Richardson", ...)

Arguments

mod

an estimated model

parnum

a vector, or list of vectors, containing one or more parameter locations/sets of locations to be tested. See objects returned from mod2values for the locations

SE.type

type of information matrix estimator to use. See mirt for further details

type

type of numerical algorithm passed to numerical_deriv to obtain the gradient terms

...

additional arguments to pass to mirt

References

Chalmers, R. P. (2012). mirt: A Multidimensional Item Response Theory Package for the R Environment. Journal of Statistical Software, 48(6), 1-29. doi:10.18637/jss.v048.i06

See also

Author

Phil Chalmers rphilip.chalmers@gmail.com

Examples


# \donttest{
dat <- expand.table(LSAT7)
mod <- mirt(dat, 1, 'Rasch')
(values <- mod2values(mod))
#>    group   item     class   name parnum value lbound ubound   est const nconst
#> 1    all Item.1      dich     a1      1 1.000   -Inf    Inf FALSE  none   none
#> 2    all Item.1      dich      d      2 1.868   -Inf    Inf  TRUE  none   none
#> 3    all Item.1      dich      g      3 0.000      0      1 FALSE  none   none
#> 4    all Item.1      dich      u      4 1.000      0      1 FALSE  none   none
#> 5    all Item.2      dich     a1      5 1.000   -Inf    Inf FALSE  none   none
#> 6    all Item.2      dich      d      6 0.791   -Inf    Inf  TRUE  none   none
#> 7    all Item.2      dich      g      7 0.000      0      1 FALSE  none   none
#> 8    all Item.2      dich      u      8 1.000      0      1 FALSE  none   none
#> 9    all Item.3      dich     a1      9 1.000   -Inf    Inf FALSE  none   none
#> 10   all Item.3      dich      d     10 1.461   -Inf    Inf  TRUE  none   none
#> 11   all Item.3      dich      g     11 0.000      0      1 FALSE  none   none
#> 12   all Item.3      dich      u     12 1.000      0      1 FALSE  none   none
#> 13   all Item.4      dich     a1     13 1.000   -Inf    Inf FALSE  none   none
#> 14   all Item.4      dich      d     14 0.521   -Inf    Inf  TRUE  none   none
#> 15   all Item.4      dich      g     15 0.000      0      1 FALSE  none   none
#> 16   all Item.4      dich      u     16 1.000      0      1 FALSE  none   none
#> 17   all Item.5      dich     a1     17 1.000   -Inf    Inf FALSE  none   none
#> 18   all Item.5      dich      d     18 1.993   -Inf    Inf  TRUE  none   none
#> 19   all Item.5      dich      g     19 0.000      0      1 FALSE  none   none
#> 20   all Item.5      dich      u     20 1.000      0      1 FALSE  none   none
#> 21   all  GROUP GroupPars MEAN_1     21 0.000   -Inf    Inf FALSE  none   none
#> 22   all  GROUP GroupPars COV_11     22 1.022      0    Inf  TRUE  none   none
#>    prior.type prior_1 prior_2
#> 1        none     NaN     NaN
#> 2        none     NaN     NaN
#> 3        none     NaN     NaN
#> 4        none     NaN     NaN
#> 5        none     NaN     NaN
#> 6        none     NaN     NaN
#> 7        none     NaN     NaN
#> 8        none     NaN     NaN
#> 9        none     NaN     NaN
#> 10       none     NaN     NaN
#> 11       none     NaN     NaN
#> 12       none     NaN     NaN
#> 13       none     NaN     NaN
#> 14       none     NaN     NaN
#> 15       none     NaN     NaN
#> 16       none     NaN     NaN
#> 17       none     NaN     NaN
#> 18       none     NaN     NaN
#> 19       none     NaN     NaN
#> 20       none     NaN     NaN
#> 21       none     NaN     NaN
#> 22       none     NaN     NaN

# test all fixed slopes individually
parnum <- values$parnum[values$name == 'a1']
lagrange(mod, parnum)
#>       X2 df     p
#> 1  0.367  1 0.545
#> 5  0.048  1 0.827
#> 9  4.697  1  0.03
#> 13  1.45  1 0.229
#> 17 2.554  1  0.11

# compare to LR test for first two slopes
mod2 <- mirt(dat, 'F = 1-5
                   FREE = (1, a1)', 'Rasch')
coef(mod2, simplify=TRUE)$items
#>              a1         d g u
#> Item.1 1.157956 1.9393358 0 1
#> Item.2 1.000000 0.7850452 0 1
#> Item.3 1.000000 1.4501952 0 1
#> Item.4 1.000000 0.5175858 0 1
#> Item.5 1.000000 1.9787464 0 1
anova(mod, mod2)
#>           AIC    SABIC       HQ      BIC    logLik    X2 df     p
#> mod  5341.802 5352.192 5352.994 5371.248 -2664.901               
#> mod2 5343.264 5355.386 5356.321 5377.618 -2664.632 0.538  1 0.463

mod2 <- mirt(dat, 'F = 1-5
                   FREE = (2, a1)', 'Rasch')
coef(mod2, simplify=TRUE)$items
#>               a1         d g u
#> Item.1 1.0000000 1.8746626 0 1
#> Item.2 0.9464702 0.7810629 0 1
#> Item.3 1.0000000 1.4661198 0 1
#> Item.4 1.0000000 0.5234068 0 1
#> Item.5 1.0000000 1.9997347 0 1
anova(mod, mod2)
#>           AIC    SABIC       HQ      BIC    logLik    X2 df     p
#> mod  5341.802 5352.192 5352.994 5371.248 -2664.901               
#> mod2 5343.720 5355.842 5356.777 5378.075 -2664.860 0.081  1 0.775

mod2 <- mirt(dat, 'F = 1-5
                   FREE = (3, a1)', 'Rasch')
coef(mod2, simplify=TRUE)$items
#>              a1         d g u
#> Item.1 1.000000 1.8101732 0 1
#> Item.2 1.000000 0.7649559 0 1
#> Item.3 1.848252 1.7774916 0 1
#> Item.4 1.000000 0.5042538 0 1
#> Item.5 1.000000 1.9316244 0 1
anova(mod, mod2)
#>           AIC    SABIC       HQ      BIC    logLik    X2 df     p
#> mod  5341.802 5352.192 5352.994 5371.248 -2664.901               
#> mod2 5335.130 5347.252 5348.187 5369.485 -2660.565 8.671  1 0.003

# test slopes first two slopes and last three slopes jointly
lagrange(mod, list(parnum[1:2], parnum[3:5]))
#>            X2 df     p
#> 1.5     0.459  2 0.795
#> 9.13.17 9.153  3 0.027

# test all 5 slopes and first + last jointly
lagrange(mod, list(parnum[1:5], parnum[c(1, 5)]))
#>                X2 df     p
#> 1.5.9.13.17 9.862  5 0.079
#> 1.17        2.898  2 0.235

######
## DIF using score (Lagrange) test example

n <- 10
N <- 500

# generate 2PL data with DIF on Item_1's intercept in the focal group
a  <- matrix(rep(1, n), ncol = 1)
d1 <- matrix(rnorm(n), ncol = 1)
d2 <- d1
d2[1, 1] <- d1[1, 1] + 1          # shift Item_1's difficulty for the focal group

dat1 <- simdata(a, d1, N, itemtype = '2PL')
dat2 <- simdata(a, d2, N, itemtype = '2PL')
dat  <- rbind(dat1, dat2)
colnames(dat) <- paste0('Item_', 1:n)
group <- c(rep('Reference', N), rep('Focal', N))

# fully equality-constrained (no-DIF) baseline: all a1/d equated across
# groups, with the latent mean/variance freed in the focal group for
# identification (standard mirt DIF baseline)
mod_baseline <- multipleGroup(dat, model = 1, itemtype = '2PL', group = group,
                              invariance = c(colnames(dat), 'free_means', 'free_var'),
                              SE = TRUE)

values <- mod2values(mod_baseline)
(i1 <- values[values$item == 'Item_1', ])     # inspect parnum for this item
#>        group   item class name parnum value lbound ubound   est const nconst
#> 1      Focal Item_1  dich   a1      1 0.845   -Inf    Inf  TRUE     1   none
#> 2      Focal Item_1  dich    d      2 0.920   -Inf    Inf  TRUE     2   none
#> 3      Focal Item_1  dich    g      3 0.000      0      1 FALSE  none   none
#> 4      Focal Item_1  dich    u      4 1.000      0      1 FALSE  none   none
#> 43 Reference Item_1  dich   a1     43 0.845   -Inf    Inf  TRUE     1   none
#> 44 Reference Item_1  dich    d     44 0.920   -Inf    Inf  TRUE     2   none
#> 45 Reference Item_1  dich    g     45 0.000      0      1 FALSE  none   none
#> 46 Reference Item_1  dich    u     46 1.000      0      1 FALSE  none   none
#>    prior.type prior_1 prior_2
#> 1        none     NaN     NaN
#> 2        none     NaN     NaN
#> 3        none     NaN     NaN
#> 4        none     NaN     NaN
#> 43       none     NaN     NaN
#> 44       none     NaN     NaN
#> 45       none     NaN     NaN
#> 46       none     NaN     NaN

parnum_a1 <- i1$parnum[i1$name == 'a1' & i1$group == 'Focal']
parnum_d  <- i1$parnum[i1$name == 'd'& i1$group == 'Focal']

# joint 2-df score test: does freeing both a1 and d for Item_1 in the
# focal group improve fit relative to the constrained baseline?
lagrange(mod_baseline, list(c(parnum_a1, parnum_d)))
#>         X2 df p
#> 1.2 23.161  2 0

# compare to LR and Wald tests
mod_nest <- multipleGroup(dat, model = 1, itemtype = '2PL', group = group,
                              invariance = c(colnames(dat)[-1], 'free_means', 'free_var'),
                              SE = TRUE)
anova(mod_baseline, mod_nest)
#>                   AIC    SABIC       HQ      BIC    logLik     X2 df p
#> mod_baseline 12017.54 12055.64 12058.58 12125.51 -5986.771            
#> mod_nest     11978.24 12019.80 12023.01 12096.03 -5965.120 43.302  2 0
wald(mod_nest)
#>      a1.1       d.2   a1.5.47    d.6.48   a1.9.51   d.10.52  a1.13.55   d.14.56 
#>     0.914     1.446     1.007     0.430     1.176     0.716     1.208     1.811 
#>  a1.17.59   d.18.60  a1.21.63   d.22.64  a1.25.67   d.26.68  a1.29.71   d.30.72 
#>     1.238    -0.756     1.147    -0.313     0.850    -0.301     1.033    -1.009 
#>  a1.33.75   d.34.76  a1.37.79   d.38.80     a1.43      d.44 MEAN_1.83 COV_11.84 
#>     1.062     0.400     1.126     1.163     1.003     0.431     0.103     0.782 
wald(mod_nest, c('a1.1 = a1.43', 'd.2 = d.44'))
#>        W df p
#> 1 39.398  2 0

# separate 1-df tests: slope-only DIF vs. intercept-only DIF
lagrange(mod_baseline, list(parnum_a1, parnum_d))
#>       X2 df     p
#> 1  2.606  1 0.106
#> 2 23.144  1     0

# }