knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(meow)
The Prowise Learn algorithm (Vermeiren et al., 2025) extends the Elo-style Maths
Garden updates (vignette("maths-garden-update")) with paired item updates
that counteract rating drift --- the tendency for item difficulty estimates to
slide systematically over time.
Abilities are updated exactly as in Maths Garden:
$$\theta_j^{new} = \theta_j + K_\theta \sum_{i \in I_j} (S_{ij} - E(S_{ij})).$$
Item difficulties, however, are updated in consecutive pairs of items administered to the same respondent. For a pair (previous item, current item),
$$\kappa = 0.5\,\big(K_b (S_{now} - E_{now}) - K_b (S_{prev} - E_{prev})\big), \qquad b_{now} \mathrel{+}= \kappa, \quad b_{prev} \mathrel{-}= \kappa.$$
Because each pair adds $+\kappa$ to one item and $-\kappa$ to the other, the total difficulty mass is conserved, so items keep their relative positions and do not drift en masse. Expected responses use the Rasch model, $E(S_{ij}) = 1 / (1 + e^{-(\theta_j - b_i)})$.
meowPaired updates are inherently order dependent, so update_prowise_learn()
uses meow_long(R, admin), which returns the administered responses ordered by
respondent and then by administration order. Consecutive within-respondent rows
form the pairs; the per-item contributions are aggregated with tapply():
update_prowise_learn <- function(pers, item, R, admin, K_theta = 0.1, K_b = 0.1) { long <- meow_long(R, admin) E_Sij <- stats::plogis(pers$theta[long$id] - item$b[long$item]) # ability update (as in Maths Garden) dtheta <- tapply(long$resp - E_Sij, long$id, sum) pers$theta[as.integer(names(dtheta))] <- pers$theta[as.integer(names(dtheta))] + K_theta * dtheta # paired item updates over consecutive administrations n <- nrow(long) if (n >= 2) { nxt <- 2:n; prv <- 1:(n - 1) pair <- which(long$id[nxt] == long$id[prv]) if (length(pair) > 0) { now <- nxt[pair]; pre <- prv[pair] kappa <- 0.5 * (K_b * (long$resp[now] - E_Sij[now]) - K_b * (long$resp[pre] - E_Sij[pre])) add_now <- tapply(kappa, long$item[now], sum) add_pre <- tapply(-kappa, long$item[pre], sum) item$b[as.integer(names(add_now))] <- item$b[as.integer(names(add_now))] + add_now item$b[as.integer(names(add_pre))] <- item$b[as.integer(names(add_pre))] + add_pre } } list(pers = pers, item = item) }
sim <- meow( select_fun = select_max_info, update_fun = update_prowise_learn, data_loader = data_simple_1pl, data_args = list(N_persons = 100, N_items = 50), update_args = list(K_theta = 0.05, K_b = 0.05) ) head(sim$results[, 1:3])
admin carries the order of administration.Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.