|
1 | | -"""Accuracy-and-efficiency recovery study for public polytomous CAT. |
| 1 | +"""Accuracy, uncertainty-calibration, and efficiency recovery for polytomous CAT. |
2 | 2 |
|
3 | 3 | A GRM bank is calibrated from synthetic responses and then passed to |
4 | 4 | ``cat_simulate_polytomous`` for examinees with known true abilities. The test |
5 | | -requires both acceptable theta recovery and materially fewer administered |
6 | | -items than the full bank, so a silent fallback to non-adaptive item order can |
7 | | -fail even when correlation remains acceptable. |
| 5 | +requires bounded bias/MAE/RMSE, calibrated Rust-returned posterior uncertainty, |
| 6 | +and materially fewer administered items than the full bank. Correlation remains |
| 7 | +supplementary: a highly correlated CAT can still be biased or understate score |
| 8 | +uncertainty, while a silent fallback to non-adaptive item order can still meet |
| 9 | +score-recovery bounds. |
8 | 10 |
|
9 | 11 | Dodd, De Ayala, and Koch (1995) evaluate computerized adaptive testing with |
10 | 12 | polytomous IRT items and motivate measuring adaptive efficiency together with |
11 | 13 | score quality. This regression uses the package's Rust-owned item-information, |
12 | | -selection, scoring, and stopping path; the Python formula below only generates |
13 | | -synthetic GRM responses. |
| 14 | +selection, scoring, posterior-SD, and stopping path; Python only generates the |
| 15 | +synthetic GRM responses and summarizes recovery against known simulation truth. |
| 16 | +The interval ``theta_eap ± 1.96 * theta_sd`` is an explicit normal approximation |
| 17 | +based on the returned posterior mean and posterior SD, not a claim of an exact |
| 18 | +posterior credible interval. |
14 | 19 |
|
15 | 20 | Reference |
16 | 21 | --------- |
|
37 | 42 | # correlation ~0.91 using a mean of ~8.7 of 40 items -- comparable accuracy |
38 | 43 | # to the full-bank GRM recovery test (~0.38/~0.92) at roughly a fifth of |
39 | 44 | # the items. Margins are loose enough to tolerate a minor fast-mlsirm |
40 | | -# version bump while still catching a real regression in either accuracy |
41 | | -# or the adaptive-selection efficiency CAT exists to provide. |
| 45 | +# version bump while still catching a real regression in recovery, |
| 46 | +# uncertainty calibration, or the adaptive-selection efficiency CAT exists |
| 47 | +# to provide. |
42 | 48 | # |
43 | 49 | # MAX_MEAN_ITEMS_USED is deliberately close to the measured ~8.7 (not a |
44 | 50 | # loose N_ITEMS * 0.5): the same fixture/seed with adaptive=False (random |
45 | 51 | # item order) measures mean_items_used ~14.97, which still clears rmse/ |
46 | 52 | # correlation bounds -- so a bound of 12 is what actually catches a silent |
47 | 53 | # fallback to non-adaptive selection. |
48 | 54 | MAX_THETA_RMSE = 0.65 |
| 55 | +MAX_THETA_MAE = 0.52 |
| 56 | +MAX_ABS_THETA_BIAS = 0.20 |
| 57 | +MIN_NORMAL_APPROX_COVERAGE = 0.80 |
49 | 58 | MIN_THETA_CORRELATION = 0.7 |
50 | 59 | MAX_MEAN_ITEMS_USED = 12 |
51 | 60 |
|
@@ -95,15 +104,35 @@ def test_cat_recovers_theta_using_substantially_fewer_items_than_the_full_bank() |
95 | 104 | seed=SEED, |
96 | 105 | ) |
97 | 106 | theta_eap = cat_result["theta_eap"] |
| 107 | + theta_sd = cat_result["theta_sd"] |
98 | 108 | n_used = cat_result["n_used"] |
99 | 109 |
|
100 | | - rmse = float(np.sqrt(np.mean((theta_eap - true_theta) ** 2))) |
| 110 | + assert np.all(np.isfinite(theta_sd)) |
| 111 | + assert np.all(theta_sd > 0.0) |
| 112 | + |
| 113 | + error = theta_eap - true_theta |
| 114 | + bias = float(np.mean(error)) |
| 115 | + mae = float(np.mean(np.abs(error))) |
| 116 | + rmse = float(np.sqrt(np.mean(error**2))) |
| 117 | + normal_approx_coverage = float( |
| 118 | + np.mean(np.abs(error) <= 1.96 * theta_sd) |
| 119 | + ) |
101 | 120 | correlation = float(np.corrcoef(theta_eap, true_theta)[0, 1]) |
102 | 121 | mean_items_used = float(n_used.mean()) |
103 | 122 |
|
| 123 | + assert abs(bias) < MAX_ABS_THETA_BIAS, ( |
| 124 | + f"CAT theta bias {bias:.3f} exceeded ±{MAX_ABS_THETA_BIAS}" |
| 125 | + ) |
| 126 | + assert mae < MAX_THETA_MAE, ( |
| 127 | + f"CAT theta MAE {mae:.3f} exceeded {MAX_THETA_MAE}" |
| 128 | + ) |
104 | 129 | assert rmse < MAX_THETA_RMSE, ( |
105 | 130 | f"CAT theta RMSE {rmse:.3f} exceeded {MAX_THETA_RMSE}" |
106 | 131 | ) |
| 132 | + assert normal_approx_coverage >= MIN_NORMAL_APPROX_COVERAGE, ( |
| 133 | + "CAT theta normal-approximation coverage " |
| 134 | + f"{normal_approx_coverage:.3f} below {MIN_NORMAL_APPROX_COVERAGE}" |
| 135 | + ) |
107 | 136 | assert correlation > MIN_THETA_CORRELATION, ( |
108 | 137 | f"CAT theta correlation {correlation:.3f} below {MIN_THETA_CORRELATION}" |
109 | 138 | ) |
|
0 commit comments