Per-item ability-risk lambda tuning via coordinate descent
Source:R/risk.R
tune_lambda_ability_risk_item.RdFinds a per-item vector of lambda values lambda_j in [0, 1] that minimizes
propagated ability-score risk E[g' Sigma_gamma g] using coordinate descent on the
items. Each coordinate step holds the other lambda_{j'} fixed and selects lambda_j
by direct 1-D optimization (method = "optimize", the default, continuous) or
over lambda_grid (method = "grid").
Usage
tune_lambda_ability_risk_item(
lambda_grid = seq(0, 1, by = 0.1),
observed,
predicted,
generated,
target_resp = NULL,
theta_true = NULL,
n_quad = 31,
initial_pars = NULL,
n_pass = 1,
init_lambda = 0,
method = c("optimize", "grid"),
bounds = c(-6, 6),
max_discrimination = 10,
control = list(maxit = 300),
...
)Arguments
- lambda_grid
Numeric vector of candidate lambda values in
[0, 1]to try for each item independently.- observed, predicted, generated
Response matrices passed to
fit_mixed_subjects_mml().- target_resp
Target scoring population. If omitted,
observedis used.- theta_true
Optional true theta values, used to add squared scoring error to the risk.
- n_quad
Number of quadrature nodes.
- initial_pars
Optional starting item parameters.
- n_pass
Number of coordinate-descent passes (default 1).
- init_lambda
Starting lambda vector for coordinate descent. Supply the global scalar optimum from
tune_lambda_ability_risk()(e.g.init_lambda = 0.5) to start the search around a useful operating point. Starting from all-zeros is not recommended: each single-item improvement is too small to detect when other items are at zero. A scalar is broadcast to all items; a vector of lengthn_itemssets per-item starting values.- method
How each item's lambda is chosen at a coordinate step:
"optimize"(default, direct 1-D optimization overrange(lambda_grid), continuous) or"grid"(evaluate every value inlambda_grid).- bounds
Bounds passed to
score_theta().- max_discrimination
Upper bound on plausible item discrimination; any candidate fit whose maximum
|a|exceeds it is treated as degenerate and skipped. Seetune_lambda_ability_risk()for the rationale. Default 10.- control
Control list passed to
stats::optim().- ...
Additional arguments passed to
fit_mixed_subjects_mml().
Value
A list with lambda (per-item vector), item (item names),
n_pass, method, and final_fit (the fit_mixed_subjects_mml() fit at
the selected lambda).
Details
Calls fit_mixed_subjects_mml() with a per-item lambda vector at each
candidate evaluation. Because the lambda is a vector, that function
switches to its frozen expected-count Q-function path — posteriors are
frozen at initial_pars, not recomputed continuously. This is an
approximation; see the @note below. The resulting lambda vector can be
used directly with fit_mixed_subjects_mml().
Computational cost. Each pass refits per item per candidate lambda:
method = "grid" does n_items × length(lambda_grid) fits; method = "optimize" does roughly n_items × 12 (the optimizer's evaluations plus the
endpoints). Use n_pass = 1 (the default) for a single greedy sweep, which is
usually sufficient.
Note
Approximation status. The coordinate descent fits use the frozen
expected-count Q-function (not the full marginal-MML objective) because the
IRT marginal likelihood integrates over the joint response pattern and does
not decompose item-wise. The approach is approximately correct when
initial_pars is close to the converged parameters. Report per-item
results as experimental / approximate.
See also
tune_lambda_ppi_score_item() for the faster PPI++-score version;
tune_lambda_ability_risk() for the global scalar version.
Examples
set.seed(1)
pars <- data.frame(a = c(1, 1.2, 0.9), d = c(0, -0.5, 0.3))
observed <- simulate_2pl(rnorm(40), pars)
generated <- simulate_2pl(rnorm(100), pars)
tuned <- tune_lambda_ability_risk_item(
c(0, 0.5), observed, observed, generated,
initial_pars = pars, n_quad = 5, control = list(maxit = 30)
)
tuned$lambda
#> [1] 0.5 0.5 0.5