SPB Git

spb/wp29_uqo

Public
12.6 KB · 211 lines python
1"""F4-conf-01: extract printed treatment effects / uncertainty from two source extracts.2Parses data/sources/*.txt, prints every matched source line, derives two quantities,3runs internal consistency checks, writes out/results.json. Deterministic; no randomness."""4import json, math, re, os5from scipy.stats import norm67ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))8SRC = os.path.join(ROOT, "data", "sources")9DCR_ID, RI_ID = "arxiv-2609.27732v1", "arxiv-2606.14621v1"10Z = 1.96  # normal critical value for 95% two-sided intervals1112def read(name):13    with open(os.path.join(ROOT, "data", name), encoding="utf-8") as f:14        return f.read()1516def nlines(txt):  # equals `wc -l` (newline count)17    return txt.count("\n")1819dcr_txt = read(f"sources/{DCR_ID}.txt"); ri_txt = read(f"sources/{RI_ID}.txt"); readme = read("README.md")20dcr = dcr_txt.split("\n"); ri = ri_txt.split("\n")2122def block(lines, start_pat, end_pat):23    s = next(i for i, l in enumerate(lines) if l.startswith(start_pat))24    e = next(i for i in range(s + 1, len(lines)) if lines[i].startswith(end_pat))25    return s, e2627def cells(line):28    return [c.strip() for c in line.split("|")]2930def num(cell):31    """Return (printed string without stars/parentheses, float, stars)."""32    m = re.match(r"^\(?(-?[\d,]*\.?\d+)\)?(\**)$", cell.strip())33    s = m.group(1).replace(",", "")34    return s, float(s), m.group(2)3536def reg_table(start_pat, end_pat):37    s, e = block(dcr, start_pat, end_pat)38    rows = [(s + i, l) for i, l in enumerate(dcr[s:e]) if l.strip()]39    header = None; out = {}; last = None40    for ln, l in rows[1:]:41        c = cells(l)42        if header is None:43            header = c; header_ln = ln; continue44        label = c[0]45        if label == "" and last is not None:  # SE line belongs to previous coef row46            out[last]["se"] = (ln + 1, l, c[1:])47        else:48            out[label] = {"coef": (ln + 1, l, c[1:])}; last = label49    return header_ln + 1, header, out5051def get(tab, row, col, kind="coef"):52    hln, header, rows = tab53    j = header.index(col) - 154    ln, line, c = rows[row][kind]55    print(f"  [line {ln}] {line.strip()}   -> row '{row}', col '{col}' ({kind}): {c[j]}")56    return num(c[j])5758print(f"== {DCR_ID} Table 6")59t6 = reg_table("Table 6:", "Note:")60att5 = get(t6, "ATT", "5 km"); att5se = get(t6, "ATT", "5 km", "se")61att3 = get(t6, "ATT", "3 km"); att3se = get(t6, "ATT", "3 km", "se")62obs5 = get(t6, "Observations", "5 km")63print(f"== {DCR_ID} Table 7")64t7 = reg_table("Table 7:", "Note:")65nar3 = get(t7, "Narcotics", "3 km"); nar3se = get(t7, "Narcotics", "3 km", "se")66fat1 = get(t7, "Drug-related (fatal)", "1 km"); fat1se = get(t7, "Drug-related (fatal)", "1 km", "se")67hiv5 = get(t7, "HIV", "5 km"); hiv5se = get(t7, "HIV", "5 km", "se")68print(f"== {DCR_ID} Table A19")69ta19 = reg_table("Table A19:", "Note:")70a19 = get(ta19, "ATT", "CS-DR"); a19se = get(ta19, "ATT", "CS-DR", "se")7172# exact p-value for HIV 5 km: does the extract print one? search for p= near HIV73hiv_p_printed = re.search(r"HIV[^\n]*p\s*=\s*0\.\d+", dcr_txt) is not None74print(f"  exact p-value printed for HIV ATT anywhere in {DCR_ID}: {hiv_p_printed}")7576# ---- RESEA Table 277print(f"== {RI_ID} Table 2")78s = next(i for i, l in enumerate(ri) if l.startswith("Outcome | Treatment | Control"))79hdr = cells(ri[s]); t2 = {}80for i in range(s + 1, len(ri)):81    if not ri[i].strip(): break82    c = cells(ri[i]); t2[c[0]] = (i + 1, ri[i], c)8384def ri_cell(row, col):85    ln, line, c = t2[row]; v = c[hdr.index(col)]86    print(f"  [line {ln}] {line.strip()}\n      -> row '{row}', col '{col}': {v}")87    return v8889def parse_effect(v):90    # simpler robust split91    pt, p, ci = [x.strip() for x in v.split(";")]92    est = re.sub(r"[^\d.\-]", "", pt.replace("-$", "-"))93    lo, hi = re.match(r"95% CI:\s*\[(-?[\d,.]+),\s*(-?[\d,.]+)\]", ci).groups()94    return est, p, lo.replace(",", ""), hi.replace(",", "")9596ATE = "Average treatment effect estimate"97w_est, w_p, w_lo, w_hi = parse_effect(ri_cell("Wage", ATE))98r_est, r_p, r_lo, r_hi = parse_effect(ri_cell("Reemployment", ATE))99u_est, u_p, u_lo, u_hi = parse_effect(ri_cell("Weeks on UI", ATE))100cm = ri_cell("Wage", "Control")101ctrl_wage = re.match(r"\$([\d,]+)", cm).group(1).replace(",", "")102w_se_printed = bool(re.search(r"\b(SE|s\.e\.|standard error)\b", t2["Wage"][1][len("Wage"):], re.I))103print(f"  standard error printed for wage ATE: {w_se_printed}")104105def asnum(s):106    return int(s) if re.fullmatch(r"-?\d+", s) else float(s)107108# ---- derived quantities109ci_lo = round(att5[1] - Z * att5se[1], 3); ci_hi = round(att5[1] + Z * att5se[1], 3)110w_se = round((float(w_hi) - float(w_lo)) / (2 * Z), 1)111112# ---- consistency checks (reported as data issues where they fail)113checks = []114def star_ok(coef, se, stars):115    p = 2 * norm.sf(abs(coef / se))116    exp = "***" if p < .01 else "**" if p < .05 else "*" if p < .10 else ""117    return p, exp == stars118for name, (c, s_) in {"T6 5km": (att5, att5se), "T6 3km": (att3, att3se), "T7 Narc 3km": (nar3, nar3se),119                      "T7 fatal 1km": (fat1, fat1se), "T7 HIV 5km": (hiv5, hiv5se), "A19 CS-DR": (a19, a19se)}.items():120    p, ok = star_ok(c[1], s_[1], c[2]); checks.append((name, round(p, 4), ok))121    print(f"  star check {name}: z-based p={p:.4f}, stars '{c[2]}' consistent={ok}")122w_p_implied = 2 * norm.sf(float(w_est) / w_se)123w_mid = (float(w_lo) + float(w_hi)) / 2124print(f"  wage ATE: CI midpoint={w_mid}, implied p (normal)={w_p_implied:.4f} vs printed {w_p}")125hiv_p_implied = 2 * norm.sf(abs(hiv5[1] / hiv5se[1]))126print(f"  (not reported) normal-approx p for HIV 5km would be {hiv_p_implied:.3f}; NOT used as a result")127128data_issues = [129    {"issue": f"Same estimate (CS-DR, 5 km ATT -0.431) is printed with SE (0.210) in Table 6 but SE ({a19se[0]}) in Appendix Table A19 of {DCR_ID}.",130     "handling": "Keys name Table 6, so dcr_att_5km_se = 0.210 and the derived CI uses 0.210; A19 value not used."},131    {"issue": f"Control-group size inconsistent in {DCR_ID}: Tables 6/7 and Figure 6 note say 608 never-treated regions; Table A19 note says 640.",132     "handling": "Recorded only; no requested key depends on it."},133    {"issue": f"{DCR_ID} abstract says fatal hospitalisations 'fall by 35%' and narcotics 'rise by a comparable amount'; Table 7 5 km fatal ATT -0.354 log points corresponds to exp(-0.354)-1 = -29.8% (Section 6.1 says ~30%), and narcotics +0.390 to +47.7%. The abstract appears to read log points as percent.",134     "handling": "Recorded; values extracted as printed in Table 7 (log points), no conversion applied."},135    {"issue": f"{DCR_ID} text says 'HIV estimates are insignificant and close to zero', while the 5 km HIV ATT is -0.340 (SE 0.294), not small in magnitude though insignificant.",136     "handling": "Recorded; significance flag follows the table's stars (none => not significant at 10%)."},137    {"issue": f"{DCR_ID} prints no exact p-values, only significance stars.",138     "handling": "dcr_hiv_att_5km_p_value = null; no p-value was back-computed into the results."},139    {"issue": f"{RI_ID} running text reports the weeks-on-UI CI as magnitudes '[1.75 weeks, 2.24 weeks]' for a 'reduction', whereas Table 2 prints the signed interval [-2.24, -1.75].",140     "handling": "Used Table 2 signed values (lower bound -2.24 first)."},141    {"issue": f"Rendering defect in {RI_ID} text: 'p ¡ 0.001' (should read p < 0.001); Tables 2-3 were re-set by the extract editor.",142     "handling": "No effect on extracted values, which come from the re-set Table 2 whose values are stated to be verbatim."},143    {"issue": f"{RI_ID} prints no standard errors for ATEs, only p-values and 95% CIs; Table 2 caption says CIs follow 'the methodology described in the main text', which is omitted from the extract.",144     "handling": "SE derived as CI width / (2*1.96) assuming a symmetric normal-approximation interval; check: implied p = %.3f vs printed %s." % (w_p_implied, w_p)},145]146bad = [c for c in checks if not c[2]]147if bad:148    data_issues.append({"issue": "Star/SE mismatch: " + str(bad), "handling": "Recorded; printed values kept."})149150L = lambda src, t, r, c: f'{src} | Table {t} | row "{r}" | column "{c}"'151res = {152    "task_id": "F4-conf-01",153    "dcr_att_5km": asnum(att5[0]), "dcr_att_5km_se": asnum(att5se[0]),154    "dcr_att_3km": asnum(att3[0]), "dcr_att_3km_se": asnum(att3se[0]),155    "dcr_obs_5km": int(obs5[0]),156    "dcr_narcotics_att_3km": asnum(nar3[0]), "dcr_narcotics_att_3km_se": asnum(nar3se[0]),157    "dcr_fatal_att_1km": asnum(fat1[0]), "dcr_fatal_att_1km_se": asnum(fat1se[0]),158    "dcr_hiv_att_5km": asnum(hiv5[0]), "dcr_hiv_att_5km_se": asnum(hiv5se[0]),159    "dcr_hiv_att_5km_p_value": None,160    "dcr_hiv_att_5km_significant_10pct": "*" in hiv5[2],161    "dcr_att_5km_ci_low": ci_lo, "dcr_att_5km_ci_high": ci_hi,162    "ri_wage_ate": asnum(w_est), "ri_wage_ate_ci_low": asnum(w_lo), "ri_wage_ate_ci_high": asnum(w_hi),163    "ri_wage_ate_se_reported": w_se_printed, "ri_wage_ate_se_derived": w_se,164    "ri_control_mean_wage": asnum(ctrl_wage),165    "ri_reemployment_ate_pp": asnum(r_est),166    "ri_weeks_ui_ate": asnum(u_est), "ri_weeks_ui_ate_ci_low": asnum(u_lo), "ri_weeks_ui_ate_ci_high": asnum(u_hi),167    "locators": {168        "dcr_att_5km": L(DCR_ID, 6, "ATT", "5 km"),169        "dcr_att_5km_se": L(DCR_ID, 6, "ATT", "5 km") + " | standard error (parenthesised line below)",170        "dcr_att_3km": L(DCR_ID, 6, "ATT", "3 km"),171        "dcr_att_3km_se": L(DCR_ID, 6, "ATT", "3 km") + " | standard error (parenthesised line below)",172        "dcr_obs_5km": L(DCR_ID, 6, "Observations", "5 km"),173        "dcr_narcotics_att_3km": L(DCR_ID, 7, "Narcotics", "3 km"),174        "dcr_narcotics_att_3km_se": L(DCR_ID, 7, "Narcotics", "3 km") + " | standard error (parenthesised line below)",175        "dcr_fatal_att_1km": L(DCR_ID, 7, "Drug-related (fatal)", "1 km"),176        "dcr_fatal_att_1km_se": L(DCR_ID, 7, "Drug-related (fatal)", "1 km") + " | standard error (parenthesised line below)",177        "dcr_hiv_att_5km": L(DCR_ID, 7, "HIV", "5 km"),178        "dcr_hiv_att_5km_se": L(DCR_ID, 7, "HIV", "5 km") + " | standard error (parenthesised line below)",179        "dcr_hiv_att_5km_p_value": f"not reported in {DCR_ID} (Table 7 prints only significance stars; no exact p-values anywhere in the extract)",180        "dcr_att_5km_ci_low": L(DCR_ID, 6, "ATT", "5 km") + " | derived from printed coefficient and SE (lower bound)",181        "dcr_att_5km_ci_high": L(DCR_ID, 6, "ATT", "5 km") + " | derived from printed coefficient and SE (upper bound)",182        "ri_wage_ate": L(RI_ID, 2, "Wage", ATE) + " | point estimate",183        "ri_wage_ate_ci_low": L(RI_ID, 2, "Wage", ATE) + " | 95% CI lower bound",184        "ri_wage_ate_ci_high": L(RI_ID, 2, "Wage", ATE) + " | 95% CI upper bound",185        "ri_wage_ate_se_derived": L(RI_ID, 2, "Wage", ATE) + " | derived from printed 95% CI bounds (SE not reported in source)",186        "ri_control_mean_wage": L(RI_ID, 2, "Wage", "Control") + " | mean",187        "ri_reemployment_ate_pp": L(RI_ID, 2, "Reemployment", ATE) + " | point estimate",188        "ri_weeks_ui_ate": L(RI_ID, 2, "Weeks on UI", ATE) + " | point estimate",189        "ri_weeks_ui_ate_ci_low": L(RI_ID, 2, "Weeks on UI", ATE) + " | 95% CI lower bound",190        "ri_weeks_ui_ate_ci_high": L(RI_ID, 2, "Weeks on UI", ATE) + " | 95% CI upper bound",191    },192    "derivation_notes": {193        "dcr_att_5km_ci": "Normal approximation, critical value 1.96: ATT +/- 1.96*SE = -0.431 +/- 1.96*0.210 using Table 6 printed values; rounded to 3 decimals. Assumes asymptotic normality of the clustered-SE ATT estimator (no t / small-cluster correction).",194        "ri_wage_ate_se": "SE = (upper - lower)/(2*1.96) = (2141 - 166)/3.92 from Table 2 printed 95%% CI, assuming a symmetric normal-approximation (Wald) interval; rounded to 0.1 USD. With N = 23,549 a t critical value is indistinguishable from 1.96. Consistency check: implied two-sided p = %.3f vs printed p=0.022." % w_p_implied,195    },196    "feasibility": "complete",197    "feasibility_reason": None,198    "data_issues": data_issues,199    "n_input_rows": {"README.md": nlines(readme), f"{DCR_ID}.txt": nlines(dcr_txt), f"{RI_ID}.txt": nlines(ri_txt)},200    "n_analysed": 26,201    "random_seed": None,202}203os.makedirs(os.path.join(ROOT, "out"), exist_ok=True)204with open(os.path.join(ROOT, "out", "results.json"), "w") as f:205    json.dump(res, f, indent=2, ensure_ascii=False)206print(json.dumps({k: v for k, v in res.items() if k not in ("locators", "data_issues", "derivation_notes")}, indent=1))207# sensitivity figures quoted in report.md (not stored as results)208print("sensitivity: CI with z=2.0:", round(att5[1]-2*att5se[1],3), round(att5[1]+2*att5se[1],3),209      "| CI with A19 SE:", round(att5[1]-Z*a19se[1],3), round(att5[1]+Z*a19se[1],3),210      "| fatal 5km % =", round(100*(math.exp(-0.354)-1),1), "| narcotics 5km % =", round(100*(math.exp(0.390)-1),1))211
Files · main
Menu

Theme