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,
,
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:
Best practices
-
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. - Never un-administer an item that has already been given.
-
Implement stopping rules by returning
adminunchanged when you want to end the simulation. -
Strongly prefer matrix operations over per-row
loops where possible; use
meow_long()only when long data is genuinely more convenient.
