SPB Git

spb/wp29_uqo

Public
9.6 KB · 194 lines python
1"""F4-conf-01: extract printed values from two source extracts and derive two quantities.2Run from workspace root: ~/wp29/venv/bin/python out/analysis.py3Values are parsed from the text; every matched source line is printed."""4import json, re, os5from scipy.stats import norm67DCR = "data/sources/arxiv-2609.27732v1.txt"8RI = "data/sources/arxiv-2606.14621v1.txt"9SID_D, SID_R = "arxiv-2609.27732v1", "arxiv-2606.14621v1"1011def lines(p):12    with open(p, encoding="utf-8") as f:13        return f.read().split("\n")1415def wc_l(p):16    with open(p, "rb") as f:17        return f.read().count(b"\n")1819def cells(s):20    return [c.strip() for c in s.split("|")]2122def block(L, title, end_marker):23    i = next(k for k, s in enumerate(L) if s.startswith(title))24    j = next(k for k in range(i + 1, len(L)) if L[k].startswith(end_marker))25    return [s for s in L[i:j] if s.strip()], i2627def num(c):28    """Return the printed number string (sign + decimals kept), stars stripped."""29    m = re.search(r"-?[\d,]*\.?\d+", c.replace("$", ""))30    return m.group(0)3132def to_num(s):33    s = s.replace(",", "")34    return int(s) if re.fullmatch(r"-?\d+", s) else float(s)3536def stars(c):37    return len(re.search(r"\**$", c.strip()).group(0))3839D = lines(DCR)40R = lines(RI)4142# ---- Table 643t6, _ = block(D, "Table 6:", "Note:")44hdr6 = cells(t6[1])  # ['', '1 km', '3 km', '5 km']45def t6row(label):46    for k, s in enumerate(t6):47        if cells(s)[0] == label:48            print("[T6 match]", s)49            return cells(s), (cells(t6[k + 1]) if label == "ATT" else None)50att6, se6 = t6row("ATT")51obs6, _ = t6row("Observations")52print("[T6 SE line]", " | ".join(se6))53col6 = {h: i for i, h in enumerate(hdr6) if h}5455# ---- Table 756t7, _ = block(D, "Table 7:", "Note:")57hdr7 = cells(t7[1])58col7 = {h: i for i, h in enumerate(hdr7)}59def t7cell(row, col):60    for k, s in enumerate(t7):61        if cells(s)[0] == row:62            print("[T7 match]", s, "  ||  SE line:", t7[k + 1])63            return cells(s)[col7[col]], cells(t7[k + 1])[col7[col]]64    raise KeyError(row)6566res = {"task_id": "F4-conf-01"}67loc = {}68def L6(row, col, extra=""):69    return f'{SID_D} | Table 6 | row "{row}" | column "{col}"' + extra70def L7(row, col, extra=""):71    return f'{SID_D} | Table 7 | row "{row}" | column "{col}"' + extra7273for km in ["5", "3"]:74    c = col6[f"{km} km"]75    res[f"dcr_att_{km}km"] = to_num(num(att6[c]))76    res[f"dcr_att_{km}km_se"] = to_num(num(se6[c]))77    loc[f"dcr_att_{km}km"] = L6("ATT", f"{km} km")78    loc[f"dcr_att_{km}km_se"] = L6("ATT", f"{km} km", " (standard error in parentheses on the line below the ATT row)")79res["dcr_obs_5km"] = to_num(num(obs6[col6["5 km"]]))80loc["dcr_obs_5km"] = L6("Observations", "5 km")8182for key, row, col in [("dcr_narcotics_att_3km", "Narcotics", "3 km"),83                      ("dcr_fatal_att_1km", "Drug-related (fatal)", "1 km"),84                      ("dcr_hiv_att_5km", "HIV", "5 km")]:85    v, se = t7cell(row, col)86    res[key] = to_num(num(v)); res[key + "_se"] = to_num(num(se))87    loc[key] = L7(row, col)88    loc[key + "_se"] = L7(row, col, " (standard error in parentheses on the line below)")89    if key == "dcr_hiv_att_5km":90        hiv_stars = stars(v)9192# HIV p-value: the extract prints only stars, no exact p-values93res["dcr_hiv_att_5km_p_value"] = None94loc["dcr_hiv_att_5km_p_value"] = (f"not reported in {SID_D} (Table 7 prints only significance stars; "95                                  f'cell row "HIV" column "5 km" carries no star)')96res["dcr_hiv_att_5km_significant_10pct"] = hiv_stars >= 19798# derived CI99z = 1.96100b, s = res["dcr_att_5km"], res["dcr_att_5km_se"]101res["dcr_att_5km_ci_low"] = round(b - z * s, 4)102res["dcr_att_5km_ci_high"] = round(b + z * s, 4)103for k in ["dcr_att_5km_ci_low", "dcr_att_5km_ci_high"]:104    loc[k] = f'{SID_D} | Table 6 | row "ATT" (estimate and SE) | column "5 km" | derived'105106# ---- RI Table 2107i2 = next(k for k, s in enumerate(R) if s.startswith("Outcome | Treatment | Control"))108hdr2 = cells(R[i2]); col2 = {h: i for i, h in enumerate(hdr2)}109def t2(row):110    for s in R[i2 + 1:]:111        if cells(s)[0] == row:112            print("[RI T2 match]", s)113            return cells(s)114ATE = "Average treatment effect estimate"115def parse_effect(c):116    parts = [p.strip() for p in c.split(";")]117    est = num(parts[0])118    ci = re.search(r"\[(.*?)\]", c).group(1)119    lo, hi = [x.strip() for x in re.split(r",\s(?=-?\d)", ci)]120    return to_num(est), to_num(num(lo)), to_num(num(hi)), parts121122def L2(row, col, extra=""):123    return f'{SID_R} | Table 2 | row "{row}" | column "{col}"' + extra124125w = t2("Wage")126est, lo, hi, parts = parse_effect(w[col2[ATE]])127res["ri_wage_ate"], res["ri_wage_ate_ci_low"], res["ri_wage_ate_ci_high"] = est, lo, hi128loc["ri_wage_ate"] = L2("Wage", ATE, " (point estimate)")129loc["ri_wage_ate_ci_low"] = L2("Wage", ATE, " (95% CI, lower bound)")130loc["ri_wage_ate_ci_high"] = L2("Wage", ATE, " (95% CI, upper bound)")131res["ri_wage_ate_se_reported"] = any(re.search(r"\bSE\b|standard error", p, re.I) for p in parts)132res["ri_wage_ate_se_derived"] = round((hi - lo) / (2 * z), 2)133loc["ri_wage_ate_se_derived"] = L2("Wage", ATE, " (95% CI bounds) | derived")134res["ri_control_mean_wage"] = to_num(num(w[col2["Control"]].split("(")[0]))135loc["ri_control_mean_wage"] = L2("Wage", "Control", " (mean; SD in parentheses not used)")136137r = t2("Reemployment")138res["ri_reemployment_ate_pp"] = parse_effect(r[col2[ATE]])[0]139loc["ri_reemployment_ate_pp"] = L2("Reemployment", ATE, " (point estimate)")140141u = t2("Weeks on UI")142est, lo, hi, _ = parse_effect(u[col2[ATE]])143res["ri_weeks_ui_ate"], res["ri_weeks_ui_ate_ci_low"], res["ri_weeks_ui_ate_ci_high"] = est, lo, hi144loc["ri_weeks_ui_ate"] = L2("Weeks on UI", ATE, " (point estimate)")145loc["ri_weeks_ui_ate_ci_low"] = L2("Weeks on UI", ATE, " (95% CI, lower bound)")146loc["ri_weeks_ui_ate_ci_high"] = L2("Weeks on UI", ATE, " (95% CI, upper bound)")147148# ---- consistency checks (printed for the report, not stored as keys)149wl, wh, we = res["ri_wage_ate_ci_low"], res["ri_wage_ate_ci_high"], res["ri_wage_ate"]150print(f"[check] wage CI half-widths: {we-wl} below, {wh-we} above (symmetric -> normal-type CI)")151print(f"[check] implied z for wage ATE = {we/res['ri_wage_ate_se_derived']:.3f}, "152      f"normal two-sided p = {2*norm.sf(abs(we/res['ri_wage_ate_se_derived'])):.4f} (printed p=0.022)")153print(f"[check] Table 6 5km SE {res['dcr_att_5km_se']} vs Table A19 CS-DR SE line:",154      [s for s in D if s.strip().startswith("| (0.218)")])155for n_ctl in (608, 640):156    print(f"[check] (treated + {n_ctl}) * 25 years:", [(t + n_ctl) * 25 for t in (7, 35, 50)])157import math158print(f"[check] exp(-0.354)-1 = {math.exp(-0.354)-1:.3f}; exp(0.390)-1 = {math.exp(0.390)-1:.3f}; "159      f"exp(-0.431)-1 = {math.exp(-0.431)-1:.3f}")160161res["locators"] = loc162res["derivation_notes"] = {163    "dcr_att_5km_ci": ("Normal approximation with critical value 1.96: CI = ATT +/- 1.96*SE = -0.431 +/- 1.96*0.210, "164                       "using the Table 6 printed values (estimate -0.431, clustered SE 0.210); rounded to 4 decimals. "165                       "The paper's own inference may use a different (e.g. bootstrap/t) critical value; not stated in the extract."),166    "ri_wage_ate_se": ("SE = (upper - lower)/(2*1.96) = (2141 - 166)/3.92, using the printed Table 2 95% CI; assumes a "167                       "symmetric normal-approximation (Wald) interval, which is consistent with the printed bounds "168                       "(half-widths 987 and 988) and with the printed p=0.022 (implied normal p ~ 0.022). Rounded to cents."),169}170res["feasibility"] = "complete"171res["feasibility_reason"] = None172res["data_issues"] = [173    {"issue": "Table A19 (CS-DR column) prints SE (0.218) for the same 5 km ATT (-0.431) for which Table 6 prints SE (0.210).",174     "handling": "Key definitions name Table 6, so 0.210 is used for dcr_att_5km_se and the derived CI; discrepancy documented."},175    {"issue": "Number of control regions is internally inconsistent: Tables 6/7 and Figure 6 note say 608 never-treated regions, the Table A19 note says 640; the printed Observations (16,175 / 16,875 / 17,250) equal (treated + 640) x 25 exactly, not (treated + 608) x 25.",176     "handling": "Observations reported as printed (17250); inconsistency flagged; nothing re-estimated."},177    {"issue": "Abstract says fatal hospitalisations fall by 35%, but Table 7 5 km fatal ATT -0.354 implies exp(-0.354)-1 = -29.8% (Section 6.1 says ~30%). Text also calls HIV estimates 'close to zero' though the 3/5 km point estimates are -0.239/-0.340 (insignificant).",178     "handling": "Narrative inconsistencies noted; table values used as required."},179    {"issue": "RESEA text reports the weeks-on-UI CI as magnitudes '[1.75 weeks, 2.24 weeks]' while Table 2 prints [-2.24, -1.75].",180     "handling": "Table 2 signed values used (lower bound -2.24 first)."},181    {"issue": "Rendering defect in the RESEA extract: 'p ¡ 0.001' (should read p < 0.001); Tables 2-3 were re-set by the extract editor.",182     "handling": "No keyed value affected; values taken from the re-set Table 2 as verbatim."},183    {"issue": "DCR Table 7 reports only significance stars, no exact p-values; RESEA Table 2 reports CIs and p-values but no standard errors.",184     "handling": "dcr_hiv_att_5km_p_value = null (not reported); RESEA wage SE derived from CI with stated convention; ri_wage_ate_se_reported = false."},185]186files = ["data/README.md", DCR, RI]187res["n_input_rows"] = {os.path.basename(p): wc_l(p) for p in files}188res["n_analysed"] = 25  # 25 value keys (15 DCR incl. p-value check, significance flag and 2 derived; 10 RESEA incl. SE-reported flag and 1 derived)189res["random_seed"] = None190191with open("out/results.json", "w") as f:192    json.dump(res, f, indent=2)193print(json.dumps({k: v for k, v in res.items() if k not in ("locators", "derivation_notes", "data_issues")}, indent=1))194
Files · main
Menu

Theme