Data Loaders

The meow framework is built around a central function, meow(). This function relies on three other ingredients to build your simulation:

  1. A data loader that either loads or generates the data to simulate on.
  2. An item selection function that selects the next item for each respondent.
  3. A parameter update function that updates the internal person and item parameters.

This vignette covers data loaders: what they are, how to use the bundled ones, and how to write your own.

What a data loader returns

A data loader returns a list with three named components: resp, pers_tru, and item_tru. The simplest loader, data_existing(), just reads them from files:

data_existing <- function(resp_path, pers_path, item_path) {
  list(
    resp = utils::read.csv(resp_path),
    pers_tru = utils::read.csv(pers_path),
    item_tru = utils::read.csv(item_path)
  )
}

The resp object

resp is a long-form data frame of item responses with three numeric columns, aligned to the standard used by the Item Response Warehouse: id (a 1-indexed respondent identifier), item (a 1-indexed item identifier), and resp (the response \(x_{ij}\), with \(x_{ij} \in \{0, 1\}\) for dichotomous items). meow() converts this long table into a respondent-by-item matrix once, before the simulation loop.

The pers_tru object

pers_tru is a data frame of true person parameters. Its first column, id, identifies respondents; the remaining columns are parameters (e.g. theta). Using a data frame lets you add parameters — for instance, extra dimensions for a multidimensional model — without changing the framework.

The item_tru object

item_tru is a data frame of true item parameters, with item as its first column followed by parameter columns such as b (difficulty) and a (discrimination). As with person parameters, you can add columns freely.

Function arguments

A data loader is the first thing meow() calls, so it has no required arguments — it may take whatever you pass through data_args. The only names to avoid are those the harness supplies to the other modules: pers, item, R, admin, adj_mat, and select_seed.

The bundled 1PL loader

data_simple_1pl() generates data from a 1PL model with standard-normal abilities and difficulties:

data_simple_1pl <- function(N_persons = 100, N_items = 50, data_seed = 242424) {
  set.seed(data_seed)
  pers_tru <- data.frame(id = 1:N_persons, theta = stats::rnorm(N_persons))
  item_tru <- data.frame(item = 1:N_items, b = stats::rnorm(N_items), a = 1)

  theta_mat <- matrix(pers_tru$theta, N_persons, N_items)
  diff_mat  <- matrix(item_tru$b, N_persons, N_items, byrow = TRUE)
  p <- stats::plogis(theta_mat - diff_mat)
  resp_mat <- matrix(stats::rbinom(length(p), 1, p), N_persons, N_items)

  resp <- data.frame(
    id = rep(seq_len(N_persons), each = N_items),
    item = rep(seq_len(N_items), times = N_persons),
    resp = as.vector(t(resp_mat))
  )
  set.seed(NULL)
  list(resp = resp, pers_tru = pers_tru, item_tru = item_tru)
}

It draws abilities \(\theta_i \sim \mathcal{N}(0, 1)\) and difficulties \(b_j \sim \mathcal{N}(0, 1)\), then generates responses from the 1PL item response function \[P(x_{ij} = 1 \mid \theta_i) = \frac{1}{1 + e^{-(\theta_i - b_j)}}.\]

data <- data_simple_1pl(N_persons = 6, N_items = 4)
str(data, max.level = 1)
#> List of 3
#>  $ resp    :'data.frame':    24 obs. of  3 variables:
#>  $ pers_tru:'data.frame':    6 obs. of  2 variables:
#>  $ item_tru:'data.frame':    4 obs. of  3 variables:
head(data$resp)
#>   id item resp
#> 1  1    1    1
#> 2  1    2    0
#> 3  1    3    1
#> 4  1    4    0
#> 5  2    1    0
#> 6  2    2    0

A note on random seeds

If your data loader uses random number generation and accepts a seed for reproducibility, clear the seed at the end with set.seed(NULL). Otherwise the seed persists into the rest of the simulation and makes the downstream item selection and parameter updates deterministic, which will prevent you from comparing multiple stochastic runs on the same data set.