The meow framework is
built around a central function, meow(). This function
relies on three other ingredients to build your simulation:
This vignette covers data loaders: what they are, how to use the bundled ones, and how to write your own.
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)
)
}resp objectresp 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.
pers_tru objectpers_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.
item_tru objectitem_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.
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.
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 0If 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.