Item Selection Functions

Item selection functions, as you may have guessed, implement the rules that determine which item is administered next to each respondent. They are one of the three core swappable components of a meow simulation. For the full module contract, see vignette("extending-meow"); this vignette focuses on the bundled selectors and how to write your own.

Function signature

Every item selection function has the signature

select_fun <- function(pers, item, R, admin, adj_mat, ...) {
  # ... decide which items to administer ...
  return(admin)
}

Any select_fun() receives the current person parameter estimates (pers), item parameter estimates (item), the full respondent-by-item response matrix (R), the integer administration matrix (admin), and the item co-exposure matrix (adj_mat). It then returns an administration matrix with the newly chosen cells marked with a non-zero integer value. The simulation harness itself will correctly record the order of administration, so you only need to mark newly administered items with non-zero values.

The default behavior when early stopping rules are not implemented in selection functions is run the simulation until it has administered every item to every respondent. The unadministered items for respondent i can be retrieved using which(admin[i, ] == 0), and the respondents who still have items to respond to can be found using which(rowSums(admin == 0) > 0).

Bundled selectors

Sequential

select_sequential() administers the lowest-numbered remaining item to each respondent, producing a fixed linear form.

select_sequential <- function(pers, item, R, admin, adj_mat = NULL) {
  if (!any(admin != 0)) {
    admin[, seq_len(min(5, ncol(admin)))] <- 1L  # seed the first five items
    return(admin)
  }
  unadmin <- admin == 0
  has <- which(rowSums(unadmin) > 0)
  nextcol <- max.col(unadmin[has, , drop = FALSE] + 0, ties.method = "first")
  admin[cbind(has, nextcol)] <- 1L
  admin
}

Random

select_random() draws one remaining item per respondent at random. It accepts a select_seed for reproducibility, which it clears after use.

Maximum information

select_max_info() administers the remaining item with the greatest 2PL Fisher information, \(I(\theta) = a^2 P(\theta)(1 - P(\theta))\), evaluated at each respondent’s current ability estimate. The information for every respondent-by-item combination is computed as a single matrix, and the maximum is taken per row.

Network distance

select_max_dist() and select_max_dist_enhanced() treat the item pool as a network whose edge weights are derived from the co-exposure adjacency matrix adj_mat. They administer the item farthest (by shortest-path distance) from the items a respondent has already seen, breaking ties using maximum information. See vignette("network-item-selection").

Writing a custom selector

A custom selector need only follow the prescribed function signature and return an updated admin matrix. Here we administer the item whose difficulty is closest to a respondent’s current ability, which can be seen as maximizing information when all items are assumed to be equally discriminating.

select_targeted <- function(pers, item, R, admin, adj_mat = NULL) {
  if (!any(admin != 0)) {
    admin[, seq_len(min(5, ncol(admin)))] <- 1L
    return(admin)
  }
  for (i in which(rowSums(admin == 0) > 0)) {
    remaining <- which(admin[i, ] == 0)
    gap <- abs(item$b[remaining] - pers$theta[i])
    admin[i, remaining[which.min(gap)]] <- 1L
  }
  admin
}

Use it in a simulation by passing it as select_fun:

sim <- meow(
  select_fun  = select_targeted,
  update_fun  = update_theta_mle,
  data_loader = data_simple_1pl,
  data_args   = list(N_persons = 50, N_items = 30),
  fix         = "item"
)
nrow(sim$results)
#> [1] 26

Best practices

  1. Handle the first iteration with if (!any(admin != 0)) to seed an initial set of items so that you have baseline parameter estimates to start from.
  2. Never un-administer an item that has already been given.
  3. Implement stopping rules by returning admin unchanged when you want to end the simulation.
  4. Strongly prefer matrix operations over per-row loops where possible; use meow_long() only when long data is genuinely more convenient.