--- title: "Item Selection Functions" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Item Selection Functions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(meow) ``` 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 ```r 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. ```{r, eval = FALSE} 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. ```{r} 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`: ```{r} 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) ``` # 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.