Fit AR(p) on logit(scores) by conditional Gaussian MLE (CSS). scores: (T, R) p: AR order (default 1) Returns: (gammas_hat: (p,), mus_hat: (T,), residual_std: float, result)
(scores: np.ndarray, p: int = 1)
| 57 | |
| 58 | |
| 59 | def fit(scores: np.ndarray, p: int = 1): |
| 60 | """ |
| 61 | Fit AR(p) on logit(scores) by conditional Gaussian MLE (CSS). |
| 62 | |
| 63 | scores: (T, R) |
| 64 | p: AR order (default 1) |
| 65 | Returns: (gammas_hat: (p,), mus_hat: (T,), residual_std: float, result) |
| 66 | """ |
| 67 | T, R = scores.shape |
| 68 | if R <= p: |
| 69 | raise ValueError("Number of rounds must exceed p.") |
| 70 | |
| 71 | eps = 1e-6 |
| 72 | Z = logit(np.clip(scores, eps, 1 - eps)) |
| 73 | |
| 74 | mu0 = Z.mean(axis=1) # (T,) |
| 75 | game0 = np.full(p, 0.3, dtype=float) |
| 76 | |
| 77 | def pack(game: np.ndarray, mu: np.ndarray) -> np.ndarray: |
| 78 | return np.concatenate([game, mu]) |
| 79 | |
| 80 | def unpack(theta: np.ndarray) -> tuple[np.ndarray, np.ndarray]: |
| 81 | game = theta[:p] |
| 82 | mu = theta[p:] |
| 83 | return game, mu |
| 84 | |
| 85 | valid_mask = np.ones((T, R), dtype=bool) |
| 86 | valid_mask[:, :p] = False |
| 87 | N = valid_mask.sum() |
| 88 | |
| 89 | def nll(theta: np.ndarray) -> float: |
| 90 | game, mu = unpack(theta) |
| 91 | res = ar_residuals_logit(scores, game, mu) |
| 92 | e = res[valid_mask] |
| 93 | sigma2 = (e @ e) / N |
| 94 | return 0.5 * N * np.log(sigma2) |
| 95 | |
| 96 | theta0 = pack(game0, mu0) |
| 97 | lower_game = np.full(p, -0.999) |
| 98 | upper_game = np.full(p, 0.999) |
| 99 | mu_bounds = [(-np.inf, np.inf)] * T |
| 100 | bounds = list(zip(lower_game, upper_game)) + mu_bounds |
| 101 | |
| 102 | result = minimize(nll, theta0, method="L-BFGS-B", bounds=bounds) |
| 103 | game_hat, mu_hat = unpack(result.x) |
| 104 | res_hat = ar_residuals_logit(scores, game_hat, mu_hat) |
| 105 | residual_std = np.nanstd(res_hat) |
| 106 | |
| 107 | return game_hat, mu_hat, residual_std, result |
| 108 | |
| 109 | |
| 110 | def residuals_same_as_previous(scores: np.ndarray): |
no test coverage detected