Comparing Models Out of Sample with the IMV
Source:vignettes/articles/model-comparison-imv.Rmd
model-comparison-imv.RmdFit two IRT models to the same data and the more flexible one always fits better in sample. The question worth asking is whether it predicts better on responses it has not seen, and by how much. The InterModel Vigorish (IMV) answers the second half of that: it puts the improvement on the scale of a weighted coin, so “the 2PL is 3% better than the Rasch model” means something concrete rather than being a difference of log-likelihoods no one can picture.
Two functions do the work. irw_predict() turns a fitted
mirt model plus a set of id/item
pairs into predicted probabilities, and irw_imv() compares
two such sets of predictions against the observed responses.
What the IMV measures
Each model’s predictions are summarized by their geometric mean likelihood, which is then converted into the weight of a coin with the same entropy. A model that predicts every response perfectly is a coin that always lands the same way; a model that predicts nothing is a fair coin. The IMV is the proportional gain in that weight going from the baseline model to the comparison model.
Two quick reference points, with no model fitting involved:
set.seed(1)
truth <- rbinom(2000, 1, 0.8)
# Same predictions twice: no gain.
irw_imv(truth, p1 = rep(0.8, 2000), p2 = rep(0.8, 2000))
#> [1] 0
# Knowing the base rate, versus guessing at random.
irw_imv(truth, p1 = rep(0.5, 2000), p2 = rep(0.8, 2000))
#> [1] 0.5919269The statistic is not symmetric — it is the gain from p1
to p2, so swapping the arguments gives a negative number of
a different magnitude, not a sign flip.
Getting the data
In normal use you would fetch a table from the warehouse:
df <- irw_fetch("4thgrade_math_sirt")That needs Redivis credentials, which this documentation build does
not have, so the rest of the article uses irw_simdata().
Discriminations are set by hand on a wide ladder, from an item that
barely separates people to one that separates them sharply. That is
exactly the situation where a 2PL has something to say that a Rasch
model cannot — with discriminations packed near 1, the two models make
nearly the same predictions and the comparison below is mostly
noise.
set.seed(20)
a_true <- seq(0.4, 2.5, length.out = 12)
df <- irw_simdata(n_id = 1000, n_item = 12, model = "2PL", a = a_true, seed = 20)
str(df)
#> 'data.frame': 12000 obs. of 3 variables:
#> $ id : int 1 2 3 4 5 6 7 8 9 10 ...
#> $ item: int 1 1 1 1 1 1 1 1 1 1 ...
#> $ resp: int 0 1 0 0 1 0 0 1 1 1 ...Cross-validating over responses
Hold out a random quarter of the responses — not of the people — so that everyone still appears in the training data and therefore still has an ability estimate. In each fold, fit both models to the training responses and score the held-out ones.
nfold <- 4
fold <- sample(rep_len(1:nfold, nrow(df)))
omega <- numeric(nfold)
for (k in seq_len(nfold)) {
train <- df[fold != k, ]
test <- df[fold == k, ]
wide <- irw_long2resp(train, id_density_threshold = NULL)
items <- wide[, setdiff(names(wide), "id"), drop = FALSE]
m_rasch <- mirt(items, 1, itemtype = "Rasch", verbose = FALSE)
m_2pl <- mirt(items, 1, itemtype = "2PL", verbose = FALSE)
# Persons need an ability estimate from the training fit to be scored.
test <- test[test$id %in% wide$id, ]
z <- irw_predict(m_rasch, wide, newdata = test)
names(z)[names(z) == "p"] <- "p1"
z$p2 <- irw_predict(m_2pl, wide, newdata = z[, c("id", "item")])$p
omega[k] <- irw_imv(z, p1 = "p1", p2 = "p2")
}
round(omega, 4)
#> [1] 0.0252 0.0170 0.0130 0.0117
mean(omega)
#> [1] 0.01672153The 2PL buys a small but consistent improvement across folds: moving from the Rasch model to the 2PL is like trading a coin of one weight for a coin roughly 1.7% more predictable.
Two details in that loop matter more than they look:
-
id_density_threshold = NULLkeepsirw_long2resp()from dropping sparse respondents. With responses split across folds, everyone looks sparser than they are, and a person filtered out of the training frame has no ability estimate for the test rows. -
irw_predict()is givenwide, with itsidcolumn still attached, rather than the item-only matrix passed tomirt(). That column is how abilities fromfscores()get matched back to people; without it there is nothing to stop a silent misalignment between rows and ids.
Reading the number
The IMV is a relative measure and inherits the baseline’s units, so it is only interpretable alongside the comparison being made. A few things to hold on to:
- Sign. Positive means the second model predicts better out of sample. Negative is a real and common result: extra parameters that do not correspond to anything in the data cost predictive accuracy.
- Scale. Values in psychometric model comparisons are typically small. A 0.01 gain is not nothing — it is a coin 1% more predictable than the baseline’s.
- Out of sample. Comparing in-sample predictions rewards the more flexible model by construction. The cross-validation loop is not optional decoration.
Reference
Domingue, B. W., Rahal, C., Faul, J., Freese, J., Kanopka, K., Rigos, A., Stenhaug, B., & Tripathi, A. (2021). InterModel Vigorish (IMV): A novel approach for quantifying predictive accuracy with binary outcomes. https://doi.org/10.31235/osf.io/8sgz5