"""F5-conf-02: finite-sample bias of OLS AR(1) coefficient. Regenerates out/results.json from data/ only.""" import json, os, hashlib import numpy as np import pandas as pd ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DATA = os.path.join(ROOT, "data", "design.csv") OUT = os.path.join(ROOT, "out") df = pd.read_csv(DATA) d = dict(zip(df["parameter"].astype(str).str.strip(), df["value"].astype(str).str.strip())) issues = [] required = ["n", "rho", "sigma", "intercept", "initial_condition", "n_replications", "seed"] missing = [k for k in required if k not in d] if missing: raise SystemExit(f"design missing parameters: {missing}") if df["parameter"].duplicated().any(): issues.append({"issue": "duplicated parameter rows", "handling": "first occurrence used"}) n = int(d["n"]); rho = float(d["rho"]); sigma = float(d["sigma"]) R = int(d["n_replications"]); seed = int(d["seed"]) assert d["intercept"].lower() == "yes" and d["initial_condition"].lower() == "stationary" assert abs(rho) < 1 and sigma > 0 and n >= 3 and R >= 2 # ---- Simulation (c = 0). Draw order: y_1 for all reps, then innovations (R x (n-1)). rng = np.random.default_rng(seed) y = np.empty((R, n)) y[:, 0] = rng.normal(0.0, sigma / np.sqrt(1.0 - rho**2), size=R) eps = rng.normal(0.0, sigma, size=(R, n - 1)) for t in range(1, n): y[:, t] = rho * y[:, t - 1] + eps[:, t - 1] # ---- OLS of y_t on (1, y_{t-1}), t = 2..n (slope = demeaned cross-product ratio) x = y[:, :-1]; z = y[:, 1:] xc = x - x.mean(axis=1, keepdims=True); zc = z - z.mean(axis=1, keepdims=True) rho_hat = (xc * zc).sum(axis=1) / (xc * xc).sum(axis=1) # cross-check one replication with lstsq X0 = np.column_stack([np.ones(n - 1), x[0]]) b0 = np.linalg.lstsq(X0, z[0], rcond=None)[0] assert abs(b0[1] - rho_hat[0]) < 1e-10 finite = np.isfinite(rho_hat) n_excl = int((~finite).sum()) rho_hat_used = rho_hat[finite] Rused = rho_hat_used.size mean_ = float(rho_hat_used.mean()) sd_ = float(rho_hat_used.std(ddof=1)) bias_mc = mean_ - rho bias_mc_se = sd_ / np.sqrt(Rused) bias_an = -(1 + 3 * rho) / n se_asy = float(np.sqrt((1 - rho**2) / n)) # supplementary (not required keys): formula with effective sample size n-1, median bias bias_an_nm1 = -(1 + 3 * rho) / (n - 1) results = { "task_id": "F5-conf-02", "bias_analytical": bias_an, "bias_mc": bias_mc, "bias_mc_se": float(bias_mc_se), "rho_hat_mean": mean_, "rho_hat_sd_mc": sd_, "discrepancy_se_units": float((bias_mc - bias_an) / bias_mc_se), "se_asymptotic": se_asy, "n_replications": int(R), "feasibility": "complete", "feasibility_reason": None, "data_issues": issues, "n_input_rows": {"design.csv": int(len(df))}, "n_analysed": int(Rused), "random_seed": seed, # supplementary diagnostics "supp_bias_analytical_n_minus_1": bias_an_nm1, "supp_discrepancy_se_units_n_minus_1": float((bias_mc - bias_an_nm1) / bias_mc_se), "supp_bias_mc_ci95_lower": float(bias_mc - 1.96 * bias_mc_se), "supp_bias_mc_ci95_upper": float(bias_mc + 1.96 * bias_mc_se), "supp_relative_error_analytical": float((bias_an - bias_mc) / bias_mc), "supp_rho_hat_median": float(np.median(rho_hat_used)), "supp_frac_rho_hat_ge_1": float((rho_hat_used >= 1).mean()), "supp_sd_ratio_mc_to_asymptotic": float(sd_ / se_asy), "supp_n_nonfinite_excluded": n_excl, "supp_design_sha256": hashlib.sha256(open(DATA, "rb").read()).hexdigest(), } os.makedirs(OUT, exist_ok=True) with open(os.path.join(OUT, "results.json"), "w") as f: json.dump(results, f, indent=2) pd.DataFrame([ {"step": "S0", "rule": "Monte Carlo replications simulated", "n_before": R, "n_after": R, "n_removed": 0, "rationale": "all R replications from design"}, {"step": "S1", "rule": "drop replications with non-finite rho_hat", "n_before": R, "n_after": Rused, "n_removed": n_excl, "rationale": "degenerate regressor would make OLS undefined"}, ]).to_csv(os.path.join(OUT, "exclusions.csv"), index=False) print(json.dumps({k: results[k] for k in ["bias_analytical", "bias_mc", "bias_mc_se", "discrepancy_se_units"]}, indent=2))