|
| 1 | +#' Function to calculate the marginal moments for items and bundles fit via IRT models |
| 2 | +#' |
| 3 | +#' Given an estimated model and a prior density function, compute the marginal moments for either |
| 4 | +#' and item or a bundle of items. Currently limited to unidimensional IRT models. |
| 5 | +#' |
| 6 | +#' @param mod an object of class \code{'SingleGroupClass'} or \code{'MultipleGroupClass'} |
| 7 | +#' @param which.items vector indicating which items to use in the computation of the expected values. |
| 8 | +#' Default (\code{NULL}) uses all available items |
| 9 | +#' @param bundle logical; given \code{which.items}, should the composite of the response functions be used |
| 10 | +#' as a collective bundle? If \code{TRUE} then \code{\link{expected.test}} will be used, otherwise |
| 11 | +#' \code{\link{expected.item}} will be used |
| 12 | +#' @param group optional indicator to return only specific group information for multiple group models. |
| 13 | +#' Default compute moments for each group, returning a \code{list} |
| 14 | +#' @param density a density function to use for integration. Default assumes the latent traits are from a |
| 15 | +#' normal (Gaussian) distribution. Function definition must be of the form \code{function(quadrature, mean, sd)} |
| 16 | +#' as the values of the mean/variance are extracted and passed from the supplied model |
| 17 | +#' @param Theta_lim range of integration grid to use when forming expectations |
| 18 | +#' @param quadpts number of discrete quadrature to use in the computations |
| 19 | +#' @param ... additional arguments passed to the density function |
| 20 | +#' |
| 21 | +#' @author Phil Chalmers \email{rphilip.chalmers@@gmail.com} |
| 22 | +#' @references |
| 23 | +#' Chalmers, R., P. (2012). mirt: A Multidimensional Item Response Theory |
| 24 | +#' Package for the R Environment. \emph{Journal of Statistical Software, 48}(6), 1-29. |
| 25 | +#' \doi{10.18637/jss.v048.i06} |
| 26 | +#' |
| 27 | +#' @export |
| 28 | +#' @seealso \code{\link{expected.item}}, \code{\link{expected.test}} |
| 29 | +#' @examples |
| 30 | +#' |
| 31 | +#' \donttest{ |
| 32 | +#' |
| 33 | +#' # single group |
| 34 | +#' dat <- expand.table(deAyala) |
| 35 | +#' mod <- mirt(dat) |
| 36 | +#' TS <- rowSums(dat) |
| 37 | +#' |
| 38 | +#' # expected moments of total scores given model |
| 39 | +#' marginal_moments(mod) |
| 40 | +#' c(mean=mean(TS), var=var(TS)) # first two moments of data |
| 41 | +#' |
| 42 | +#' # same, however focusing on individual items |
| 43 | +#' marginal_moments(mod, bundle = FALSE) |
| 44 | +#' cbind(mean=colMeans(dat), var=apply(dat, 2, var)) # first two moments of data |
| 45 | +#' |
| 46 | +#' ############################################ |
| 47 | +#' ## same as above, however with multiple group model |
| 48 | +#' |
| 49 | +#' set.seed(1234) |
| 50 | +#' group <- sample(c('G1', 'G2'), nrow(dat), replace=TRUE) |
| 51 | +#' modMG <- multipleGroup(dat, group=group, |
| 52 | +#' invariance=c(colnames(dat), 'free_mean', 'free_var')) |
| 53 | +#' coef(modMG, simplify=TRUE) |
| 54 | +#' |
| 55 | +#' # expected moments of total scores given model |
| 56 | +#' marginal_moments(modMG) |
| 57 | +#' marginal_moments(modMG, group = 'G1') # specific group only |
| 58 | +#' |
| 59 | +#' # same, however focusing on individual items |
| 60 | +#' marginal_moments(modMG, bundle = FALSE) |
| 61 | +#' |
| 62 | +#' |
| 63 | +#' } |
| 64 | +marginal_moments <- function(mod, which.items = NULL, group = NULL, bundle = TRUE, |
| 65 | + density = NULL, Theta_lim = c(-6,6), quadpts = 121, ...){ |
| 66 | + stopifnot(extract.mirt(mod, 'nfact') == 1L) |
| 67 | + if(!is.null(group)) |
| 68 | + mod <- extract.group(mod, group=group) |
| 69 | + stopifnot(extract.mirt(mod, 'nfact') == 1) ## TODO: can make into a grid |
| 70 | + if(is(mod, 'MultipleGroupClass') && is.null(group)){ |
| 71 | + grp <- extract.mirt(mod, 'groupNames') |
| 72 | + out <- lapply(grp, \(g) |
| 73 | + marginal_moments(mod=mod, which.items=which.items, group=g, |
| 74 | + bundle=bundle, density=density, Theta_lim=Theta_lim, |
| 75 | + quadpts=quadpts, ...)) |
| 76 | + names(out) <- grp |
| 77 | + return(out) |
| 78 | + } |
| 79 | + stopifnot(is(mod, 'SingleGroupClass')) |
| 80 | + if(is.null(which.items)) |
| 81 | + which.items <- 1:extract.mirt(mod, 'nitems') |
| 82 | + Theta <- matrix(seq(Theta_lim[1], Theta_lim[2], length.out=quadpts)) |
| 83 | + if(is.null(density)) |
| 84 | + density <- function(theta, mean, sd) |
| 85 | + dnorm(theta, mean=mean, sd=sd) |
| 86 | + cfs <- coef(mod, simplify=TRUE) |
| 87 | + den <- density(Theta, mean=cfs$means[1], sd=sqrt(cfs$cov[1,1])) |
| 88 | + den <- den / sum(den) |
| 89 | + if(bundle){ |
| 90 | + Eitem <- expected.test(mod, Theta=Theta, which.items=which.items) |
| 91 | + E <- sum(Eitem * den) |
| 92 | + VAR <- sum((Eitem - E)^2 * den) |
| 93 | + SKEW <- sum((Eitem - E)^3 * den) / VAR^(3/2) |
| 94 | + KURT <- sum((Eitem - E)^4 * den) / VAR^2 |
| 95 | + ret <- data.frame(MEAN=E, VAR=VAR, SKEW=SKEW, KURT=KURT) |
| 96 | + } else { |
| 97 | + ret <- as.data.frame(matrix(NA, ncol=4, nrow=length(which.items))) |
| 98 | + rownames(ret) <- extract.mirt(mod, 'itemnames')[which.items] |
| 99 | + for(i in 1:length(which.items)){ |
| 100 | + pick <- which.items[i] |
| 101 | + ii <- extract.item(mod, item=pick) |
| 102 | + Eitem <- expected.item(ii, Theta=Theta) |
| 103 | + E <- sum(Eitem * den) |
| 104 | + VAR <- sum((Eitem - E)^2 * den) |
| 105 | + SKEW <- sum((Eitem - E)^3 * den) / VAR^(3/2) |
| 106 | + KURT <- sum((Eitem - E)^4 * den) / VAR^2 |
| 107 | + ret[pick, ] <- c(E, VAR, SKEW, KURT) |
| 108 | + } |
| 109 | + } |
| 110 | + ret <- as.mirt_df(ret) |
| 111 | + colnames(ret) <- c('MEAN', 'VAR', 'SKEW', 'KURT') |
| 112 | + ret |
| 113 | +} |
0 commit comments