The meow framework is built around a central function,
meow(). This function relies on three other ingredients to
build your simulation:
- A data loader that either loads or generates the data to simulate on.
- An item selection function that selects the next item for each respondent.
- 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
,
with
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 and difficulties , then generates responses from the 1PL item response function
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 0A 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.
