Measurement invariance under treatment and life events
What this is for
A fractions program for struggling fifth graders raises its students’ test scores by more than a standard deviation. Did it raise every item, or a handful? Differential item functioning ended on a vocabulary trial in which one word, expedition, carried over a quarter of the raw effect. This lesson takes that finding further. Experience, whether a program, the loss of a spouse or a second sitting of the same test, can change how a measure works, and a randomized trial is the one setting where we can separate that change from a change in the respondents. We read treatment as a grouping variable, use the pretest as a placebo, fit a model that gives every item its own treatment effect, and ask what the spread of those effects does to the average effect we report.
Goals
By the end of this lesson you should be able to:
- say how experience can change a measure (alpha, beta and gamma change; response shift) as well as respondents’ standing on it;
- use randomization to read treatment DIF as caused by the treatment, and a pretest as a placebo check on the whole analysis;
- write down and fit the item-level heterogeneous treatment effects (IL-HTE) model with
lme4, and interpret \(\sigma_\zeta\) and \(\rho\); - explain what item-level effects do to the precision and generalizability of an average effect, and how they can produce a treatment-by-pretest interaction that no respondent has;
- say, for a given use of the scores, what would make the items that move most a threat or the signal.
Core ideas
When experience changes the measure
After a spouse dies, older adults in the Health and Retirement Study report more depressive symptoms on an eight-item, yes/no version of the CES-D (Radloff, 1977), and the rise fades within about a year (Domingue, Duncan, Harrati & Belsky, 2021). The items don’t share the rise equally: loneliness and sadness carry most of it (the preprint, Domingue et al., 2019, Supplemental Figures S7–S8). Is that more depression, or depression measured differently? A widow who now reports feeling lonely may be no more depressed, in the sense the other items capture, than a year earlier; the loneliness item is also answering a question about her circumstances. The IRW has no item-level data from this study, so the result stays a cited figure.
The possibility has names. Golembiewski, Billingsley and Yeager (1976) distinguished three kinds of change. Alpha change is a change in level on an instrument that stays the same; beta change, a change in the scale respondents use to answer; gamma change, a redefinition of the construct itself (Livingston et al., 2022, give a short account). In health research, Sprangers and Schwartz (1999) call the last two response shift: a change in what a self-evaluation means, from new internal standards, new values or a new conception of the construct. Each is a failure of invariance across occasions. Gamma change is a change in the factor structure; beta change, unequal loadings or intercepts (Jabrayilov, Emons, de Jong & Sijtsma, 2017; Oort, 2005; Widaman, Ferrer & Conger, 2010).
Swap “group” for “occasion” and the fairness lesson carries over: a change in level is impact across time, and an item whose meaning shifts is DIF across time. A program, a bereavement and a second sitting are all experiences between two measurements, and each can change the measure along with the person.
Treatment as the grouping, and the pretest as a placebo
After a bereavement we can’t separate change in the person from change in the measure, since who is bereaved isn’t random. A randomized trial removes the problem. At the pretest, treated and control respondents come from the same population, so there is no impact and, since the program hasn’t happened yet, no reason for any item to work differently. Any treatment DIF found then is a false positive, and the pretest becomes a placebo test for the whole analysis, from the handling of waves to the matching score. At the posttest the arms differ only by assignment, so an item that works differently for treated respondents does so because of the treatment (Gilbert, Kim & Miratrix, 2023; Ahmed et al., 2025).
What the treatment causes is still relative. If the program raised every item by the same amount, the matched comparison would find nothing: that is impact, the average effect. Treatment DIF is each item’s departure from the average.
Try it: the pretest as a placebo
A simulated trial: respondents in each arm answer the same Rasch items twice. At the pretest nobody has been treated. At the posttest everyone has grown by 0.5 logits, treated respondents by a further 0.5 on average, and each item’s effect departs from that average with the spread you set. Each dot is an item’s Mantel–Haenszel delta by treatment (positive: easier for treated respondents with the same total), orange when \(p < .05\).
Step through the samples: the pretest flags hover around what chance allows, while the posttest, with the spread at 0.5, flags about a third of the items. Now set the spread to 0. Every item moves by the same amount and the posttest looks like the pretest again: a treatment that lifts all items equally shows up as impact, not DIF.
A treatment effect for every item
Rather than test items one at a time, we can model the variation. The IL-HTE model (Gilbert, Kim & Miratrix, 2023; Gilbert, Himmelsbach, Soland, Joshi & Domingue, 2025) is the Rasch model with persons and items both random (De Boeck & Wilson, 2004) and a random item slope on treatment, \(T_j\): \[\begin{aligned} \text{logit}\Pr(x_{ij} = 1) &= \theta_j - b_i + \zeta_i T_j,\\ \theta_j &= \beta_0 + \beta_1 T_j + \varepsilon_j, \qquad \varepsilon_j \sim N(0, \sigma_\theta^2),\\ \begin{pmatrix} b_i \\ \zeta_i \end{pmatrix} &\sim N\!\left(\begin{pmatrix} 0 \\ 0 \end{pmatrix}, \begin{pmatrix} \sigma_b^2 & \rho\sigma_b\sigma_\zeta \\ \rho\sigma_b\sigma_\zeta & \sigma_\zeta^2 \end{pmatrix}\right). \end{aligned}\] \(\beta_1\) is the average effect on \(\theta\); item \(i\)’s effect is \(\beta_1 + \zeta_i\). \(\sigma_\zeta\) is the spread of item effects around the average. At zero every item moves by \(\beta_1\), which is what a Rasch model fitted to both arms assumes: treatment changes \(\theta\) and nothing else. \(\rho\) says whether harder items (positive) or easier items (negative) move more. Dividing \(\beta_1\) and \(\sigma_\zeta\) by \(\sigma_\theta\) puts them in standard deviations of ability.
It is a generalized linear mixed model, so lme4 fits it (Bates, Mächler, Bolker & Walker, 2015; De Boeck et al., 2011):
glmer(resp ~ treat + (1 | id) + (treat | item), data, family = binomial)lme4’s item intercept is an easiness, \(-b_i\), so the correlation it prints is \(-\rho\). Is \(\sigma_\zeta\) zero? Compare this model with one that has (1 | item) by a likelihood ratio. A variance can’t be negative, so the null value sits on the boundary of the parameter space, and halving the \(\chi^2\) \(p\)-value is the usual correction (Self & Liang, 1987).
Try it: item effects around an average
Twenty items with difficulties spread around the average respondent. Set the average effect \(\beta_1\), the spread \(\sigma_\zeta\) and the correlation \(\rho\) between difficulty and effect. The five items that respond most are orange.
With \(\sigma_\zeta\) at 0 every dot sits on the dashed line, and a test built from any five items would show the same effect in logits. At the default, the five most responsive items average more than twice the overall effect and the five least about zero: two short forms of one test, one program, two conclusions. Move \(\rho\) to −0.8 and the orange dots move left, among the easy items.
What item-level effects do to the estimate
Does the spread change the average? Not by itself: the sum score, a constant-effect model and the IL-HTE model estimate much the same average, as the fractions trial below shows. It changes three other things.
Precision. The items are a sample from those that could have been written, and if they respond unequally, a different sample would give a different average. Treating the items as fixed leaves that variation out, which understates standard errors and inflates false positives (Gilbert, Kim & Miratrix, 2023). Across 15 trials, Ahmed et al. (2025) found one item driving nearly 40% of an effect, and item-level patterns that could make 31% of the significant effects null once counted as uncertainty.
Generalizability. \(\sigma_\zeta\) says how far the effect on a fresh set of items from the same domain could fall from the one observed. A claim about the construct, rather than about these items, needs it (Gilbert, Himmelsbach, et al., 2025).
Interactions. Trialists ask who benefits most by interacting treatment with a pretest. Suppose every respondent gains the same amount on \(\theta\), but easier items respond more. In the treated arm the items’ effective difficulties, \(b_i - \zeta_i\), are more spread out than in the control arm, the sum score rises less steeply with \(\theta\), and a sum-score regression finds a negative treatment-by-pretest interaction that no respondent has. From sum scores alone the two stories can’t be told apart; item-level data can tell them apart (Gilbert, Miratrix, Joshi & Domingue, 2025).
Try it: an interaction nobody has
Treatment adds 0.5 logits to every respondent’s \(\theta\), whatever their pretest; the 20 items’ effects have the spread and correlation you set. The curves are the expected sum score at each pretest level in each arm, computed from the model, so there is no sampling noise.
With every item moving equally the interaction is already slightly negative, from the sum score’s bounded range. At the default, where easier items move more, it is five times that, with no heterogeneity among respondents at all. Set \(\rho\) to +0.8 and it changes sign.
Bias, or what the program taught?
Is an item that moves most a biased item? It may be the item closest to what an intervention taught. For evaluating the intervention, that can be the signal: the program changed what it set out to change, and the near items show it. For a claim of general gain, it is a threat: a test made mostly of near items overstates transfer, in the sense of Barnett and Ceci’s (2002) taxonomy of near and far transfer. Whether item-level heterogeneity is bias depends on the interpretation and the use, as Validity as an argument would lead you to expect. Group-specific item parameters can also be judged by prediction: do they predict new responses better than shared ones? The InterModel Vigorish measures that (Domingue et al., 2024). Explaining which items move, with features such as alignment to the curriculum, is a job for What is an item?.
Simulate
The code simulates a trial with 800 respondents and 20 items. Treatment raises every respondent’s \(\theta\) by 0.5, whatever their pretest; the items’ departures have \(\sigma_\zeta = 0.8\) and \(\rho = -0.8\) (easier items move more). It fits a sum-score regression, a constant-effect model and the IL-HTE model, each with treatment, pretest and their product. Loading lme4 takes 10 to 20 seconds the first time the page runs code; the fits take a few seconds more.
With the seed as written, the IL-HTE model recovers \(\sigma_\zeta\) = 0.78 and \(\rho\) = −0.78, and its item effects correlate 0.99 with the true ones. The interaction, 0 in truth, is −0.08 SD in the sum-score regression and −0.10 logits in the constant-effect model; the IL-HTE model puts it at 0.04. Its standard error is about 0.06 in both models, so a single run is noisy: try other seeds. Set rho <- 0.8 and the constant-effect interaction turns positive (0.03); set sigma_zeta <- 0 and the three models agree.
Download this code to run it with more respondents, or many replications, in your own R session.
With real data
Download the code; it needs lme4, and the model fits take about a minute.
A fractions trial, before and after
Our main table is gilbert_meta_20, from a randomized trial of a small-group fractions intervention for fifth graders performing well below grade level (Jayanthi et al., 2021; data at doi:10.7910/DVN/Z4WMFE), part of the trial collection of Gilbert, Himmelsbach, et al. (2025). Its job is to be a trial with a pretest, so the placebo check is real. Students who scored between the 15th and 37th percentiles on a fractions screener were randomized within teacher, and the table holds the final analytic sample of 186 (data description). Items are scored 1 for correct.
Code
# The IRW table, from the CSV link on its landing page (pinned to one version).
url20 <- "https://redivis.com/api/v1/tables/datapages.item_response_warehouse:v60_0.gilbert_meta_20/rows?format=csv"
f20 <- read.csv(url20)
f20 <- f20[!is.na(f20$resp), ]
# Waves: 0 = pretest, 1 = posttest. How many items and students in each?
t(sapply(split(f20, f20$wave), function(x) c(items = length(unique(x$item)),
students = length(unique(x$id))))) items students
0 37 186
1 93 186
Code
pre_items <- unique(f20$item[f20$wave == 0])
post_items <- unique(f20$item[f20$wave == 1])
c(pretest_items_also_at_posttest = sum(pre_items %in% post_items))pretest_items_also_at_posttest
37
Code
# Items come in four blocks (by the prefix of their names):
table(wave = f20$wave[!duplicated(f20[, c("wave", "item")])],
block = sub("_+q.*$", "", f20$item[!duplicated(f20[, c("wave", "item")])])) block
wave procedures trans_math tuf4 tuf5
0 11 0 26 0
1 24 25 26 18
Code
kids20 <- f20[!duplicated(f20$id), c("id", "treat")]
table(treat = kids20$treat)treat
0 1
99 87
On waves: wave 0 is the pretest and wave 1 the posttest. The posttest repeats all 37 pretest items (26 from the fourth-grade Test for Understanding of Fractions, tuf4, and 11 fractions procedures) and adds 56: the fifth-grade form (tuf5), more procedures, and a block the dataset labels TransMath (trans_math). Eighteen posttest responses are missing, all from one student, who drops out of the complete-case analyses.
We match on the total score and test uniform DIF, the kind a shift in one item’s treatment effect produces, with the logistic regression of the DIF lesson (Swaminathan & Rogers, 1990). With 37 or 93 tests, we also control the false discovery rate (Benjamini & Hochberg, 1995), and give the \(\Delta R^2\) categories of Jodoin and Gierl (2001).
Code
# One wide matrix per wave: a row per student, a column per item (1 = correct).
wide <- function(long, items) {
x <- long[long$item %in% items, ]
A <- tapply(x$resp, list(x$id, x$item), function(v) v[1])
A <- A[complete.cases(A), items]
list(X = A, treat = kids20$treat[match(rownames(A), kids20$id)])
}
# Logistic-regression DIF for one item, matching on the total score, as in the DIF
# lesson but for uniform DIF only, the kind a shift in one item's treatment effect
# produces: the 1-df likelihood-ratio test of M0 (total) against M1 (total and
# treatment), and the change in Nagelkerke's R^2 between them.
nagelkerke <- function(m, ll0, n) {
cs <- 1 - exp(2 * (ll0 - as.numeric(logLik(m))) / n)
cs / (1 - exp(2 * ll0 / n))
}
lr_item <- function(y, g, total) {
d <- data.frame(y = y, g = g, total = total)
ll0 <- as.numeric(logLik(glm(y ~ 1, binomial, d)))
m0 <- glm(y ~ total, binomial, d)
m1 <- glm(y ~ total + g, binomial, d)
c(beta_treat = unname(coef(m1)["g"]),
p = anova(m0, m1, test = "LRT")[2, "Pr(>Chi)"],
dR2 = nagelkerke(m1, ll0, nrow(d)) - nagelkerke(m0, ll0, nrow(d)))
}
# Every item; p-values also after the Benjamini-Hochberg false-discovery-rate
# correction; Jodoin-Gierl categories (A below .035, B to .07, C above, and A
# whenever the test is not significant).
treatment_dif <- function(W) {
tot <- rowSums(W$X)
out <- as.data.frame(t(sapply(colnames(W$X), function(i) lr_item(W$X[, i], W$treat, tot))))
out$p_BH <- p.adjust(out$p, "BH")
out$jg <- ifelse(out$p >= 0.05 | out$dR2 < 0.035, "A", ifelse(out$dR2 < 0.07, "B", "C"))
out
}
count_flags <- function(r) c(items = nrow(r), p_below_05 = sum(r$p < 0.05),
expected_by_chance = round(0.05 * nrow(r), 1),
after_BH = sum(r$p_BH < 0.05), B = sum(r$jg == "B"), C = sum(r$jg == "C"))The pretest is also our sanity table: before treatment, its flags estimate the false-positive rate. It has 3 flags at .05 among 37 items, about what chance allows, and none survives the correction (the table below).
Code
W0 <- wide(f20[f20$wave == 0, ], pre_items) # pretest: before any instruction
W1 <- wide(f20[f20$wave == 1, ], post_items) # posttest: all 93 items
W1s <- wide(f20[f20$wave == 1, ], pre_items) # posttest, the pretest's 37 items only
dif0 <- treatment_dif(W0); dif1 <- treatment_dif(W1); dif1s <- treatment_dif(W1s)
rbind(pretest = count_flags(dif0), posttest = count_flags(dif1),
"posttest, same 37 items" = count_flags(dif1s)) items p_below_05 expected_by_chance after_BH B C
pretest 37 3 1.9 0 3 0
posttest 93 12 4.7 5 8 2
posttest, same 37 items 37 9 1.9 5 3 1
With 186 students the \(\Delta R^2\) categories are generous: all three pretest flags reach B, so here the correction is the better guide. After the program the same 37 items give 9 flags, 5 of which survive; across all 93, 12 and 5, with 8 items in B and 2 in C.
Code
# The posttest items in B or C, largest first. beta_treat > 0: easier for treated
# students with the same total.
d1 <- dif1[dif1$jg != "A", ]
d1$p_control <- colMeans(W1$X[W1$treat == 0, rownames(d1)])
d1$p_treated <- colMeans(W1$X[W1$treat == 1, rownames(d1)])
round(d1[order(-d1$dR2), c("beta_treat", "p", "dR2", "p_control", "p_treated")], 3)| beta_treat | p | dR2 | p_control | p_treated | |
|---|---|---|---|---|---|
| trans_math__q10 | 1.287 | 0.000 | 0.083 | 0.384 | 0.767 |
| tuf4_q21 | -1.466 | 0.001 | 0.072 | 0.333 | 0.337 |
| procedures__q1h | -1.803 | 0.004 | 0.066 | 0.111 | 0.151 |
| tuf4_q04 | 1.133 | 0.005 | 0.052 | 0.556 | 0.872 |
| trans_math__q2 | 0.996 | 0.006 | 0.050 | 0.515 | 0.802 |
| procedures__q1b | 1.266 | 0.002 | 0.042 | 0.172 | 0.628 |
| procedures__q2d | 1.617 | 0.001 | 0.039 | 0.172 | 0.674 |
| procedures__q2b | 1.546 | 0.001 | 0.038 | 0.212 | 0.721 |
| tuf4_q07 | -0.860 | 0.020 | 0.038 | 0.374 | 0.326 |
| tuf5_q02 | -0.937 | 0.014 | 0.036 | 0.515 | 0.570 |
Code
table(block = sub("_+q.*$", "", rownames(d1)), sign = ifelse(d1$beta_treat > 0, "favours treated", "favours control")) sign
block favours control favours treated
procedures 1 3
trans_math 0 2
tuf4 2 1
tuf5 1 0
Most items that favour treated students come from the procedures and TransMath blocks; several from the understanding tests (tuf4, tuf5) go the other way, moved less than the rest of the test.
The IL-HTE model summarizes all of this in a few numbers:
Code
suppressPackageStartupMessages(library(lme4))
# The IL-HTE model (Gilbert, Kim & Miratrix, 2023), in lme4's parameterization:
# logit P(x_ij = 1) = beta0 + beta1 T_j + theta_j + e_i + zeta_i T_j,
# with theta_j ~ N(0, s_theta^2) and (e_i, zeta_i) bivariate normal. lme4's item
# intercept e_i is an easiness, the negative of the lesson's difficulty b_i, so the
# correlation it reports has the opposite sign to rho in the lesson; we flip it.
# Following the IRW vignette, effects are standardized by s_theta from the model with
# a constant effect, and sigma_zeta is tested by a likelihood-ratio test against
# that model, halving the p-value because a variance of zero sits on the boundary
# (Self & Liang, 1987).
ilhte <- function(long) {
m0 <- glmer(resp ~ treat + (1 | id) + (1 | item), long, family = binomial)
m1 <- glmer(resp ~ treat + (1 | id) + (treat | item), long, family = binomial)
s_theta <- sqrt(VarCorr(m0)$id[1])
vc <- VarCorr(m1)$item
lr <- as.numeric(2 * (logLik(m1) - logLik(m0)))
list(m0 = m0, m1 = m1, s_theta = s_theta,
summary = c(students = length(unique(long$id)), items = length(unique(long$item)),
effect_constant_SD = unname(fixef(m0)["treat"]) / s_theta,
effect_average_SD = unname(fixef(m1)["treat"]) / s_theta,
sigma_zeta_SD = sqrt(vc[2, 2]) / s_theta,
rho_difficulty = -attr(vc, "correlation")[1, 2],
LR = lr, p = 0.5 * pchisq(lr, 2, lower.tail = FALSE)))
}Code
fit0 <- ilhte(f20[f20$wave == 0, ])
fit1 <- ilhte(f20[f20$wave == 1, ])
fit1s <- ilhte(f20[f20$wave == 1 & f20$item %in% pre_items, ])
tab <- rbind(pretest = fit0$summary, posttest = fit1$summary,
"posttest, same 37 items" = fit1s$summary)
data.frame(round(tab[, 1:7], 2), p = sprintf("%.2g", tab[, "p"]))| students | items | effect_constant_SD | effect_average_SD | sigma_zeta_SD | rho_difficulty | LR | p | |
|---|---|---|---|---|---|---|---|---|
| pretest | 186 | 37 | 0.18 | 0.24 | 0.59 | 0.43 | 1.65 | 0.22 |
| posttest | 186 | 93 | 1.11 | 1.13 | 0.70 | 0.05 | 140.41 | 1.6e-31 |
| posttest, same 37 items | 186 | 37 | 1.19 | 1.23 | 0.83 | 0.21 | 97.58 | 3.2e-22 |
Code
# sigma_zeta in logits, and the SD of theta it is divided by:
round(sapply(list(pretest = fit0, posttest = fit1), function(f)
c(sigma_zeta_logits = sqrt(VarCorr(f$m1)$item[2, 2]), s_theta = f$s_theta)), 2) pretest posttest
sigma_zeta_logits 0.18 0.59
s_theta 0.31 0.84
At the pretest \(\sigma_\zeta\) is not distinguishable from zero (\(p\) = 0.22). Its 0.59 SD looks large only because \(\sigma_\theta\) is small there: the screener selected students from a narrow band, so a logit is a large share of an SD (in logits, \(\sigma_\zeta\) is 0.18 at the pretest and 0.59 at the posttest). At the posttest the average effect is 1.13 SD, \(\sigma_\zeta\) is 0.70 SD and the likelihood ratio is 140. On the 37 repeated items alone, \(\sigma_\zeta\) is 0.83 SD.
Code
# The same posttest effect three ways, each in within-arm SDs: the sum score, and
# theta from the constant-effect and IL-HTE models.
tot1 <- rowSums(W1$X)
gap <- mean(tot1[W1$treat == 1]) - mean(tot1[W1$treat == 0])
sd_within <- sqrt(mean(tapply(tot1, W1$treat, var))) # pooled within-arm SD, like s_theta
sum_effect <- gap / sd_within
round(c(sum_score = sum_effect, theta_constant = unname(fit1$summary["effect_constant_SD"]),
theta_ilhte = unname(fit1$summary["effect_average_SD"])), 2) sum_score theta_constant theta_ilhte
1.08 1.11 1.13
Code
# Item-specific effects (beta1 + zeta_i, in SDs of theta), by block.
eff1 <- (fixef(fit1$m1)["treat"] + ranef(fit1$m1)$item[, "treat"]) / fit1$s_theta
names(eff1) <- rownames(ranef(fit1$m1)$item)
round(t(sapply(split(eff1, sub("_+q.*$", "", names(eff1))), function(e)
c(items = length(e), mean = mean(e), min = min(e), max = max(e)))), 2) items mean min max
procedures 24 1.43 0.39 2.49
trans_math 25 1.14 0.24 2.14
tuf4 26 1.01 -0.64 2.19
tuf5 18 0.85 0.01 1.91
The sum score, \(\theta\) with a constant effect and \(\beta_1\) from the IL-HTE model agree on the average: 1.08, 1.11 and 1.13 SD. The spread sits underneath. Procedures items moved most, 1.43 SD on average against 0.85 for the fifth-grade understanding form, as the paper’s own effects by measure would suggest: largest for fractions procedures (\(g\) = 1.07), 0.66 to 0.78 for proficiency and understanding (Jayanthi et al., 2021, abstract). Within every block, item effects span more than a standard deviation.
Health knowledge in India
For contrast, gilbert_meta_37: health knowledge ten months after an entertainment-education programme in India. Respondents in the programme watched short films on hygiene, midwives and child health, condoms and AIDS, and night blindness; the control arm watched films on financial planning (Carpena, 2024, Supplementary Material A; data at doi:10.7910/DVN/EJ03JR). Its job: item-level effects whose content we can read. Items are knowledge questions scored 1 for a correct answer, and several are options of one select-all-that-apply question (ways to contract AIDS, times to wash hands). The table’s treat pools the paper’s two film arms, with and without cash incentives for the test, against the control arm. There is one wave, and nothing is missing.
Code
url37 <- "https://redivis.com/api/v1/tables/datapages.item_response_warehouse:v60_0.gilbert_meta_37/rows?format=csv"
f37 <- read.csv(url37)
p37 <- f37[!duplicated(f37$id), ]
c(respondents = nrow(p37), items = length(unique(f37$item)), missing = sum(is.na(f37$resp)))respondents items missing
839 21 0
Code
table(treat = p37$treat)treat
0 1
414 425
Code
fit37 <- ilhte(f37)
data.frame(t(round(fit37$summary[1:7], 2)), p = sprintf("%.2g", fit37$summary[["p"]]))| students | items | effect_constant_SD | effect_average_SD | sigma_zeta_SD | rho_difficulty | LR | p |
|---|---|---|---|---|---|---|---|
| 839 | 21 | 0.49 | 0.48 | 0.28 | 0.33 | 15.7 | 0.00019 |
Code
# Item-specific effects in logits (beta1 + zeta_i), with the proportion correct in
# each arm, largest effect first.
re <- ranef(fit37$m1)$item
items37 <- data.frame(effect_logits = fixef(fit37$m1)["treat"] + re[, "treat"],
p_control = tapply(f37$resp[f37$treat == 0], f37$item[f37$treat == 0], mean)[rownames(re)],
p_treated = tapply(f37$resp[f37$treat == 1], f37$item[f37$treat == 1], mean)[rownames(re)],
row.names = rownames(re))
round(items37[order(-items37$effect_logits), ], 2)| effect_logits | p_control | p_treated | |
|---|---|---|---|
| nightblindness_nutrition | 0.81 | 0.12 | 0.27 |
| nightblindness_heard | 0.75 | 0.61 | 0.78 |
| nightblindness_curable | 0.68 | 0.46 | 0.63 |
| aids_pregnant | 0.61 | 0.02 | 0.04 |
| pregnancy_breastmilk | 0.53 | 0.62 | 0.73 |
| aids_needle | 0.50 | 0.23 | 0.33 |
| aids_pregnant_baby | 0.45 | 0.25 | 0.34 |
| condom_hiv | 0.43 | 0.83 | 0.89 |
| pregnancy_dai_certificate | 0.41 | 0.69 | 0.77 |
| aids_sex | 0.39 | 0.71 | 0.77 |
| diar_know | 0.34 | 0.31 | 0.38 |
| aids_heard | 0.31 | 0.86 | 0.88 |
| wash_defecation | 0.31 | 0.91 | 0.93 |
| aids_touch | 0.30 | 0.84 | 0.87 |
| condom_std | 0.29 | 0.85 | 0.87 |
| aids_blood | 0.29 | 0.21 | 0.25 |
| wash_eating | 0.29 | 0.86 | 0.88 |
| aids_curable | 0.24 | 0.46 | 0.49 |
| condom_disease | 0.19 | 0.20 | 0.20 |
| wash_cooking | 0.17 | 0.36 | 0.37 |
| condom_pregnancy | 0.12 | 0.93 | 0.91 |
The average effect is 0.48 SD and \(\sigma_\zeta\) is 0.28 SD (\(p\) = 0.00019), a much smaller spread than in the fractions trial. The three items that moved most are the three about night blindness (0.68 to 0.81 logits), the subject of one of the programme’s five modules: a single film about a boy who can’t see at dusk (Supplementary Material A). The paper’s own results by topic agree: night blindness had the largest effect of the four topics, and the lowest knowledge in the control arm (Carpena, 2024, longer-term knowledge by topic). Knowing to wash hands before cooking and knowing that condoms prevent pregnancy moved least (0.17 and 0.12 logits). \(\rho\) = 0.33: harder items moved somewhat more.
Code
# Who gains most? The treatment-by-baseline interaction, with a constant effect for
# every item and with IL-HTE (std_baseline: the IRW's standardized baseline score).
mB <- glmer(resp ~ treat * std_baseline + (1 | item) + (1 | id), f37, family = binomial)
mC <- glmer(resp ~ treat * std_baseline + (treat | item) + (1 | id), f37, family = binomial)
round(rbind(constant_items = summary(mB)$coef["treat:std_baseline", 1:2],
ilhte = summary(mC)$coef["treat:std_baseline", 1:2]), 3) Estimate Std. Error
constant_items 0.024 0.065
ilhte 0.020 0.066
Does IL-HTE change the answer to “who gained most?” here? Barely: the treatment-by-baseline interaction is 0.024 logits with a constant effect for every item and 0.020 with IL-HTE, both well inside their standard errors. With a spread of 0.28 SD, against 0.70 in the fractions trial, and a moderate \(\rho\), there is little for the constant-effect model to misread.
What should a trialist take from this? Two positions rest on different assumptions. If the outcome’s items are the target (the state test a program will be judged on), the average over these items is the estimand, and the sum score answers it. If the items stand for a domain (fractions, health knowledge), the effect on the domain is the estimand, and the spread of item effects is part of its uncertainty. Fitting the IL-HTE model beside the sum-score analysis shows what the data can support under each. In the fractions trial the three analyses agree on the average (1.08, 1.11 and 1.13 SD), the pretest placebo passes, and items on the same test moved by amounts more than a standard deviation apart. Whether item-specific effects are worth their parameters can also be put to held-out responses, with the IMV of Model fit and out-of-sample prediction. An average effect is a claim about items as well as people.
Problems
- Raw effects and difficulty. Under the IL-HTE model with every \(\zeta_i = 0\), and \(\theta \sim N(0, 1)\) in control and \(N(\beta_1, 1)\) in the treated arm, write the effect on item \(i\)’s expected proportion correct as a function of \(b_i\). Show that it is largest for items near the average ability and small for very easy or very hard items. Why, then, can raw differences in proportion correct and item effects on the logit scale rank the items differently, and which would you use to say which items a program moved?
- How much do the flagged items carry? (PS5#4). In
gilbert_meta_20at the posttest, drop the 10 items in B or C and re-estimate the effect on the sum score and on \(\theta\). How much of the effect did they carry? Now drop instead the 10 items with the smallest estimated IL-HTE effects. Which short form would a program developer prefer, and which would you report? - The interaction, with and without item slopes (PS7#1). In
gilbert_meta_37, estimate the treatment-by-baseline interaction on the sum score, with a constant-effect model and with IL-HTE. From the IL-HTE fit, compute the SD of the items’ effective difficulties in each arm. Using the Go deeper, explain why the three estimates are so close here. - Bereavement. After spousal loss, the CES-D items on loneliness and sadness carry most of the rise in symptoms (Domingue et al., 2019). Set out what you would have to assume to report “depression rose”, and what you would have to assume to report “the CES-D measured something different”. Given item-level data from waves before and after the loss, which model would you fit, and what result would lead you to each report?
- Near and far. You are designing the outcome measure for a vocabulary intervention that teaches 40 words. Design a test that can separate near transfer (taught words) from far transfer (untaught words of similar frequency), and say how you would model item-level effects so that the two can be compared. What would you report if the near items moved and the far items didn’t?
- Challenge: evidence for or against? Suppose the items that move most in a trial are exactly those most aligned with the intervention’s content. Is item-level heterogeneity then evidence against the measure’s validity for evaluating the intervention, evidence for it, or neither? Does your answer change if the same test is also used to report students’ general proficiency? This one is open.
Across the IRW
How common are item-level treatment effects across the trials in the warehouse? Following the IRW’s IL-HTE vignette, compute.R takes every table with a treatment indicator and at least 100 respondents, keeps the dichotomous items (at least 3), samples at most 5,000 respondents, and fits this lesson’s two models. It departs from the vignette on waves: the outcome is the first wave after randomization, read from the processing notes where they have been checked and otherwise by a stated rule, and any pretest wave is fitted too, as a placebo.
Code
dd_path <- file.path("..", "deepdives", "invariance-experience", "results.rds")
dd <- if (file.exists(dd_path)) readRDS(dd_path) else NULL
if (!is.null(dd)) {
s <- dd$summary[order(-dd$summary$sigma_zeta_sd), ]
print(data.frame(table = s$table, wave = s$wave, respondents = s$n_persons, items = s$n_items,
effect_SD = round(s$ate_sd, 2), sigma_zeta_SD = round(s$sigma_zeta_sd, 2),
rho = round(s$rho, 2), p = signif(s$p, 2)), row.names = FALSE)
if (!is.null(dd$placebo)) {
cat("\nPlacebo (pretest waves):\n")
print(data.frame(table = dd$placebo$table, wave = dd$placebo$wave,
sigma_zeta_SD = round(dd$placebo$sigma_zeta_sd, 2), p = signif(dd$placebo$p, 2)),
row.names = FALSE)
}
if (nrow(dd$failed)) { cat("\nFailed:\n"); print(dd$failed, row.names = FALSE) }
} table wave respondents items effect_SD sigma_zeta_SD rho p
gilbert_meta_20 1 186 93 1.13 0.70 0.05 1.6e-31
gilbert_meta_37 NA 839 21 0.48 0.28 0.33 1.9e-04
gilbert_meta_89 1 1789 80 -0.02 0.02 -1.00 4.4e-01
Placebo (pretest waves):
table wave sigma_zeta_SD p
gilbert_meta_20 0 0.59 0.22
gilbert_meta_89 0 0.01 0.47
Failed:
table error
wekker_2018_mfsq fewer than 3 dichotomous items
buczel_2022_inoculation_belief fewer than 3 dichotomous items
Run on 25 September 2026: 3 tables analysed, 2 failed. Item-level variation is significant at .05 in 2 of the 3.
The pilot checks the pipeline. gilbert_meta_20 and gilbert_meta_37 reproduce this lesson’s estimates, and gilbert_meta_20’s pretest again shows no item-level variation. In gilbert_meta_89 the arms end up level, and so do the items: the average effect and \(\sigma_\zeta\) are both near zero, at the pretest and after. The two tables that failed have no dichotomous items. The full run will say how often items respond unequally across the IRW’s trials, and whether the pretest placebo passes where there is one. The vignette itself is the place to read more.
Going further
- Gilbert, J. B., Kim, J. S., & Miratrix, L. W. (2023). Modeling item-level heterogeneous treatment effects with the explanatory item response model: Leveraging large-scale online assessments to pinpoint the impact of educational interventions. Journal of Educational and Behavioral Statistics, 48(6), 889–913. https://doi.org/10.3102/10769986231171710. The IL-HTE model, and what ignoring it does to standard errors.
- Gilbert, J. B., Himmelsbach, Z., Soland, J., Joshi, M., & Domingue, B. W. (2025). Estimating heterogeneous treatment effects with item-level outcome data: Insights from item response theory. Journal of Policy Analysis and Management, 44(4), 1417–1449. https://doi.org/10.1002/pam.70025. The model across 75 datasets from 48 trials, the collection this lesson’s tables come from.
- Gilbert, J. B., Miratrix, L. W., Joshi, M., & Domingue, B. W. (2025). Disentangling person-dependent and item-dependent causal effects: Applications of item response theory to the estimation of treatment effect heterogeneity. Journal of Educational and Behavioral Statistics, 50(1), 72–101. https://doi.org/10.3102/10769986241240085. The interaction of the Go deeper, derived and simulated.
- Ahmed, I., Bertling, M., Zhang, L., Ho, A. D., Loyalka, P., Xue, H., Rozelle, S., & Domingue, B. W. (2025). Heterogeneity of item-treatment interactions masks complexity and generalizability in randomized controlled trials. Journal of Research on Educational Effectiveness, 18(4), 854–877. https://doi.org/10.1080/19345747.2024.2361337. DIF by treatment across 15 trials.
- Sprangers, M. A. G., & Schwartz, C. E. (1999). Integrating response shift into health-related quality of life research: A theoretical model. Social Science & Medicine, 48(11), 1507–1515. https://doi.org/10.1016/S0277-9536(99)00045-3. Response shift, defined, with a model of how it arises.
- Oort, F. J. (2005). Using structural equation modeling to detect response shifts and true change. Quality of Life Research, 14(3), 587–598. https://doi.org/10.1007/s11136-004-0830-y. Response shift as non-invariance across occasions.
- Widaman, K. F., Ferrer, E., & Conger, R. D. (2010). Factorial invariance within longitudinal structural equation models: Measuring the same construct across time. Child Development Perspectives, 4(1), 10–18. https://doi.org/10.1111/j.1750-8606.2009.00110.x. Longitudinal invariance in a few pages.
- Fischer, G. H. (2017). Measuring change using Rasch models. In W. J. van der Linden (Ed.), Handbook of item response theory: Vol. 3. Applications (pp. 387–404). Chapman and Hall/CRC. https://doi.org/10.1201/9781315117430. A handbook chapter on measuring change with Rasch models.
- De Boeck, P., & Wilson, M. (Eds.). (2004). Explanatory item response models: A generalized linear and nonlinear approach. Springer. https://doi.org/10.1007/978-1-4757-3990-9. Item response models as mixed models, the frame the IL-HTE model sits in.
- Domingue, B. W., Duncan, L., Harrati, A., & Belsky, D. W. (2021). Short-term mental health sequelae of bereavement predict long-term physical health decline in older adults: U.S. Health and Retirement Study analysis. The Journals of Gerontology: Series B, 76(6), 1231–1240. https://doi.org/10.1093/geronb/gbaa044. The spousal-loss example; the item-level figures are in the supplement to the preprint (https://doi.org/10.1101/19009878).
- From the IRW: the IL-HTE vignette, the corpus question of Across the IRW.
Data sources
Data from the Item Response Warehouse (each table name links to its IRW page); references from IRW biblio.
gilbert_meta_20: Jayanthi, M., Gersten, R., Schumacher, R. F., Dimino, J., Smolkowski, K., & Spallone, S. (2021). Improving struggling fifth-grade students’ understanding of fractions: A randomized controlled trial of an intervention that stresses both concepts and procedures. Exceptional Children, 88(1), 81-100. Gersten, Russell; Jayanthi, Madhavi; Dimino, Joseph; Karp, Karen, 2019, “Impact of a Tier 2 Fractions Intervention on 5th Grade At-Risk Students’ Fractions Achievement”, https://doi.org/10.7910/DVN/Z4WMFE, Harvard Dataverse, V1, UNF:6:8pMQK/5+cfpvcr4ACTvAmQ== [fileUNF]. Licence: CC0 1.0.gilbert_meta_37: Carpena, F. (2024). Entertainment-Education for Better Health: Insights from a Field Experiment in India. The Journal of Development Studies, 60(5), 745-762. Carpena, Fenella, 2024, “Replication Data for: Entertainment-Education for Better Health: Insights from a Field Experiment in India”, https://doi.org/10.7910/DVN/EJ03JR, Harvard Dataverse, V1, UNF:6:pX0ZhtBxkRpvQC6AOfVg4g== [fileUNF]. Licence: CC0 1.0.
For instructors
- Source: EDUC 252 problem sets 5 (#4: treatment DIF in
gilbert_meta_20, and the spousal-loss question) and 7 (#1: the treatment-by-baseline interaction with and without random item slopes), with the code inben-domingue/252:ps5/rctdif.Randps7/ilhte.R(which usedgilbert_meta_2). There are no slides; the widgets, the simulation and the two-table analysis are new. - Timing: about 50 minutes for the core ideas and widgets; the real-data section works as a 30-minute lab (the model fits take about a minute locally). Problems 4 and 6 work as small-group discussions.
- Held back: explaining which items move with item features (in What is an item?); then-tests and other designs for response shift are named only; item-level effects in polytomous outcomes are left out, as in the deep dive; life events with real data wait for an IRW table with item-level data around a bereavement.
- Data:
gilbert_meta_20has a pretest (wave 0, 37 items) and a posttest (wave 1, the same 37 items and 56 more); the table doesn’t say what thetrans_mathblock is beyond its label.gilbert_meta_37is the endline survey only, and itstreatpools two film arms against the control arm. Itsstd_baselineclosely tracks the number right on three knowledge questions asked at baseline (Carpena, 2024, baseline summary statistics), from our check against the replication data. - Across the IRW:
deepdives/invariance-experience/compute.Ris run by hand (it needs a Redivis token); the page reads itsresults.rds. The pilot took about three minutes for five tables. - Solutions: held back in
solutions/invariance-experience.qmd(not published). Problem 6 is open.