SPB Git

spb/wp29_uqo

Public
12.6 KB · 260 lines python
1"""F4-conf-01: extract printed values from two arXiv extracts, derive CI/SE, write out/results.json.2Run from workspace root: ~/wp29/venv/bin/python out/analysis.py3Reads only data/. No randomness."""4import json, re, os5from decimal import Decimal, ROUND_HALF_UP67ROOT = os.getcwd()8DCR_ID, RI_ID = "arxiv-2609.27732v1", "arxiv-2606.14621v1"9DCR_F = os.path.join(ROOT, "data/sources", DCR_ID + ".txt")10RI_F = os.path.join(ROOT, "data/sources", RI_ID + ".txt")11README_F = os.path.join(ROOT, "data/README.md")12Z = 1.96  # normal critical value for two-sided 95%131415def lines_of(p):16    with open(p, encoding="utf-8") as f:17        return f.read().split("\n")181920def wc_l(p):21    with open(p, "rb") as f:22        return f.read().count(b"\n")232425def num(tok):26    """Printed token -> number preserving printed value (strip stars, %, $, commas, parentheses)."""27    t = tok.strip().replace("$", "").replace(",", "").strip("()").rstrip("*").strip()28    t = re.sub(r"\s*(pp|weeks|wks)$", "", t)29    v = float(t)30    return int(v) if re.fullmatch(r"-?\d+", t) else v313233def stars(tok):34    return len(tok.strip()) - len(tok.strip().rstrip("*"))353637def show(tag, ln_no, text):38    print(f"[{tag}] line {ln_no}: {text}")394041# ---------------- DCR source ----------------42D = lines_of(DCR_F)434445def table_block(L, title_prefix, end_prefix="Note:"):46    s = next(i for i, l in enumerate(L) if l.startswith(title_prefix))47    e = next(i for i in range(s + 1, len(L)) if L[i].startswith(end_prefix))48    return s, e495051def dcr_table(title_prefix):52    s, e = table_block(D, title_prefix)53    nonempty = [(i, D[i]) for i in range(s + 1, e) if D[i].strip()]54    hdr_i, hdr = nonempty[0]55    cols = [c.strip() for c in hdr.split("|")][1:]56    rows = {}57    k = 158    while k < len(nonempty):59        i, l = nonempty[k]60        cells = [c.strip() for c in l.split("|")]61        label, vals = cells[0], cells[1:]62        se = None63        if k + 1 < len(nonempty) and nonempty[k + 1][1].lstrip().startswith("|") and "(" in nonempty[k + 1][1]:64            j, sl = nonempty[k + 1]65            se = ([c.strip() for c in sl.split("|")][1:], j, sl)66            k += 167        rows[label] = (vals, i, l, se)68        k += 169    return cols, rows707172def dcr_cell(title, row, col):73    cols, rows = dcr_table(title)74    vals, i, l, se = rows[row]75    c = cols.index(col)76    show(f"{DCR_ID} {title.split(':')[0]}", i + 1, l)77    out = {"est_tok": vals[c], "est": num(vals[c]), "stars": stars(vals[c])}78    if se is not None:79        show(f"{DCR_ID} {title.split(':')[0]} SE", se[1] + 1, se[2])80        out["se_tok"] = se[0][c]81        out["se"] = num(se[0][c])82    return out838485T6, T7 = "Table 6:", "Table 7:"86r = {}87loc = {}888990def L(src, tab, row, col, extra=""):91    return f'{src} | Table {tab} | row "{row}" | column "{col}"' + extra929394a5 = dcr_cell(T6, "ATT", "5 km")95a3 = dcr_cell(T6, "ATT", "3 km")96ob5 = dcr_cell(T6, "Observations", "5 km")97nar3 = dcr_cell(T7, "Narcotics", "3 km")98fat1 = dcr_cell(T7, "Drug-related (fatal)", "1 km")99hiv5 = dcr_cell(T7, "HIV", "5 km")100101r["dcr_att_5km"], r["dcr_att_5km_se"] = a5["est"], a5["se"]102loc["dcr_att_5km"] = L(DCR_ID, 6, "ATT", "5 km")103loc["dcr_att_5km_se"] = L(DCR_ID, 6, "ATT", "5 km", " (standard error in parentheses on the line below the coefficient row)")104r["dcr_att_3km"], r["dcr_att_3km_se"] = a3["est"], a3["se"]105loc["dcr_att_3km"] = L(DCR_ID, 6, "ATT", "3 km")106loc["dcr_att_3km_se"] = L(DCR_ID, 6, "ATT", "3 km", " (standard error in parentheses on the line below the coefficient row)")107r["dcr_obs_5km"] = int(ob5["est"])108loc["dcr_obs_5km"] = L(DCR_ID, 6, "Observations", "5 km")109r["dcr_narcotics_att_3km"], r["dcr_narcotics_att_3km_se"] = nar3["est"], nar3["se"]110loc["dcr_narcotics_att_3km"] = L(DCR_ID, 7, "Narcotics", "3 km")111loc["dcr_narcotics_att_3km_se"] = L(DCR_ID, 7, "Narcotics", "3 km", " (standard error in parentheses on the line below)")112r["dcr_fatal_att_1km"], r["dcr_fatal_att_1km_se"] = fat1["est"], fat1["se"]113loc["dcr_fatal_att_1km"] = L(DCR_ID, 7, "Drug-related (fatal)", "1 km")114loc["dcr_fatal_att_1km_se"] = L(DCR_ID, 7, "Drug-related (fatal)", "1 km", " (standard error in parentheses on the line below)")115r["dcr_hiv_att_5km"], r["dcr_hiv_att_5km_se"] = hiv5["est"], hiv5["se"]116loc["dcr_hiv_att_5km"] = L(DCR_ID, 7, "HIV", "5 km")117loc["dcr_hiv_att_5km_se"] = L(DCR_ID, 7, "HIV", "5 km", " (standard error in parentheses on the line below)")118119# exact p-value: search the extract for any printed p-value (p = / p= followed by digits)120pv_hits = [(i + 1, l) for i, l in enumerate(D) if re.search(r"\bp\s*=\s*0?\.\d", l)]121print(f"[{DCR_ID}] lines with an exact printed p-value: {len(pv_hits)}")122r["dcr_hiv_att_5km_p_value"] = None123loc["dcr_hiv_att_5km_p_value"] = (f"not reported in {DCR_ID} (Table 7 prints only the coefficient, the SE in "124                                  "parentheses and significance stars; no exact p-value is printed anywhere in the extract)")125r["dcr_hiv_att_5km_significant_10pct"] = hiv5["stars"] >= 1126print(f"[{DCR_ID}] HIV 5 km token '{hiv5['est_tok']}' has {hiv5['stars']} star(s) -> significant at 10%: {hiv5['stars'] >= 1}")127128129def r3(x):130    return float(Decimal(repr(x)).quantize(Decimal("0.001"), rounding=ROUND_HALF_UP))131132133lo_raw = a5["est"] - Z * a5["se"]134hi_raw = a5["est"] + Z * a5["se"]135r["dcr_att_5km_ci_low"], r["dcr_att_5km_ci_high"] = r3(lo_raw), r3(hi_raw)136loc["dcr_att_5km_ci_low"] = L(DCR_ID, 6, "ATT", "5 km", " — derived: ATT - 1.96*SE from the printed Table 6 values")137loc["dcr_att_5km_ci_high"] = L(DCR_ID, 6, "ATT", "5 km", " — derived: ATT + 1.96*SE from the printed Table 6 values")138139# ---------------- RESEA source ----------------140R = lines_of(RI_F)141142143def ri_table(header_start, after_marker):144    s = next(i for i, l in enumerate(R) if l.startswith(after_marker))145    h = next(i for i in range(s + 1, len(R)) if R[i].startswith(header_start))146    cols = [c.strip() for c in R[h].split("|")]147    rows = {}148    i = h + 1149    while i < len(R) and R[i].strip():150        cells = [c.strip() for c in R[i].split("|")]151        rows[cells[0]] = (cells, i)152        i += 1153    return cols, rows154155156def parse_effect(cell):157    parts = [p.strip() for p in cell.split(";")]158    est = num(parts[0])159    ci = re.search(r"\[(.*)\]", parts[2]).group(1)160    # CI bounds separated by ", " (thousands separators have no space)161    lo, hi = [x.strip() for x in ci.split(", ")]162    return {"est": est, "p": parts[1], "lo": num(lo), "hi": num(hi), "ci_tok": ci}163164165cols2, rows2 = ri_table("Outcome | Treatment", "[Table 2")166ATE_COL = "Average treatment effect estimate"167ci_ = cols2.index(ATE_COL)168cc = cols2.index("Control")169170171def ri_row(name):172    cells, i = rows2[name]173    show(f"{RI_ID} Table 2", i + 1, R[i])174    return cells175176177w = ri_row("Wage")178we = parse_effect(w[ci_])179r["ri_wage_ate"], r["ri_wage_ate_ci_low"], r["ri_wage_ate_ci_high"] = we["est"], we["lo"], we["hi"]180loc["ri_wage_ate"] = L(RI_ID, 2, "Wage", ATE_COL, " (point estimate)")181loc["ri_wage_ate_ci_low"] = L(RI_ID, 2, "Wage", ATE_COL, " (95% CI lower bound)")182loc["ri_wage_ate_ci_high"] = L(RI_ID, 2, "Wage", ATE_COL, " (95% CI upper bound)")183r["ri_wage_ate_se_reported"] = ("SE" in w[ci_]) or ("(" in w[ci_])184print(f"[{RI_ID}] wage ATE cell contains a standard error: {r['ri_wage_ate_se_reported']}")185r["ri_wage_ate_se_derived"] = round((we["hi"] - we["lo"]) / (2 * Z), 2)186loc["ri_wage_ate_se_derived"] = L(RI_ID, 2, "Wage", ATE_COL, " — derived: (CI upper - CI lower)/(2*1.96) from the printed Table 2 95% CI")187ctrl_tok = w[cc].split("(")[0].strip()188r["ri_control_mean_wage"] = num(ctrl_tok)189loc["ri_control_mean_wage"] = L(RI_ID, 2, "Wage", "Control", " (mean; SD in parentheses not used)")190191re_ = parse_effect(ri_row("Reemployment")[ci_])192r["ri_reemployment_ate_pp"] = re_["est"]193loc["ri_reemployment_ate_pp"] = L(RI_ID, 2, "Reemployment", ATE_COL, " (point estimate, pp)")194195wu = parse_effect(ri_row("Weeks on UI")[ci_])196r["ri_weeks_ui_ate"], r["ri_weeks_ui_ate_ci_low"], r["ri_weeks_ui_ate_ci_high"] = wu["est"], wu["lo"], wu["hi"]197loc["ri_weeks_ui_ate"] = L(RI_ID, 2, "Weeks on UI", ATE_COL, " (point estimate)")198loc["ri_weeks_ui_ate_ci_low"] = L(RI_ID, 2, "Weeks on UI", ATE_COL, " (95% CI lower bound)")199loc["ri_weeks_ui_ate_ci_high"] = L(RI_ID, 2, "Weeks on UI", ATE_COL, " (95% CI upper bound)")200201# Cross-check with Table 3 (duplicate of Table 2 ATE column) — informational only202cols3, rows3 = ri_table("Outcome | Difference in means", "[Table 3")203t3_w = parse_effect(rows3["Wage"][0][cols3.index("Difference in means")])204t3_match = (t3_w["est"], t3_w["lo"], t3_w["hi"]) == (we["est"], we["lo"], we["hi"])205print(f"[{RI_ID}] Table 3 'Difference in means' Wage equals Table 2 ATE Wage: {t3_match}")206207# ---------------- Cross-table checks feeding data_issues ----------------208cols_a19, rows_a19 = dcr_table("Table A19:")209a19 = rows_a19["ATT"]210a19_se = num(a19[3][0][cols_a19.index("CS-DR")])211a19_est = num(a19[0][cols_a19.index("CS-DR")])212print(f"[{DCR_ID}] Table A19 CS-DR 5 km: ATT {a19_est}, SE {a19_se}; Table 6 5 km: ATT {a5['est']}, SE {a5['se']}")213_, rows6 = dcr_table(T6)214obs = [num(x) for x in rows6["Observations"][0]]215treated = [num(x) for x in rows6["Treated regions"][0]]216ctrl6 = [num(x) for x in rows6["Control regions"][0]]217implied_ctrl = [o / 25 - t for o, t in zip(obs, treated)]218print(f"[{DCR_ID}] Observations/25 - treated = {implied_ctrl}; printed control regions = {ctrl6}")219220data_issues = [221    {"issue": f"{DCR_ID}: the 5 km CS-DR ATT -0.431 has SE (0.210) in Table 6 but SE ({a19_se}) in Table A19 (same estimator, buffer, window, and 17,250 observations).",222     "handling": "Used the Table 6 value (0.210) as the key definition names Table 6; the derived 95% CI uses 0.210. Flagged as an internal inconsistency of the source."},223    {"issue": f"{DCR_ID}: control-region count is 608 in Tables 6/7 and the figure notes but 640 in the Table A19 note; the Table 6 Observations (16,175 / 16,875 / 17,250) equal (640 + treated regions) x 25 in every column, not (608 + treated) x 25.",224     "handling": "Observations reported as printed (17250); inconsistency flagged only (the 25-period panel length is an inference, not stated in the extract)."},225    {"issue": f"{DCR_ID}: prose differs from tables — the abstract says fatal outcomes fall by 35% while the Table 7 5 km fatal ATT -0.354 corresponds to about -30% (Section 6.1 says ~30%); the text calls HIV estimates 'close to zero' while the 5 km HIV ATT is -0.340 (not starred).",226     "handling": "All keys taken from the tables named in TASK.md; prose not used."},227    {"issue": f"{RI_ID}: Section 3 text reports the weeks-on-UI ATE CI as magnitudes '[1.75 weeks, 2.24 weeks]' whereas Table 2 prints [-2.24, -1.75].",228     "handling": "Used Table 2 values with printed signs (lower bound -2.24 first)."},229    {"issue": f"{RI_ID}: rendering defect 'p ¡ 0.001' (for p < 0.001) in the Section 3 short-term weeks-on-UI sentence; Tables 2-3 were re-set by the extract editor into one row per outcome.",230     "handling": "Defective text not used; re-set table values used as declared verbatim; Table 3 column 1 Wage ATE cross-checked equal to Table 2."},231]232233n_input_rows = {"README.md": wc_l(README_F), DCR_ID + ".txt": wc_l(DCR_F), RI_ID + ".txt": wc_l(RI_F)}234235results = {**r,236           "locators": loc,237           "derivation_notes": {238               "dcr_att_5km_ci": ("Normal-approximation (Wald) 95% CI: ATT +/- 1.96 x SE using printed Table 6 values ATT = -0.431, SE = 0.210 "239                                  f"(unrounded {lo_raw:.4f}, {hi_raw:.4f}; reported rounded to 3 decimals as in the table). Assumes asymptotic normality "240                                  "of the CS-DR estimator and the clustered SE; not printed by the source. Using Table A19's SE 0.218 instead would give a wider interval."),241               "ri_wage_ate_se": ("SE = (upper - lower)/(2 x 1.96) from the printed Table 2 95% CI [166, 2,141] = 1975/3.92 "242                                  f"(unrounded {(we['hi'] - we['lo']) / (2 * Z):.4f}; reported to 2 decimals). Assumes the printed CI is a symmetric normal-approximation "243                                  "Wald interval (z = 1.96; with N = 23,549 a t critical value is practically identical); the CI bounds are rounded to whole dollars, so the "244                                  "derived SE is accurate only to about +/-0.3 USD. Source prints no SE.")},245           "task_id": "F4-conf-01",246           "feasibility": "complete",247           "feasibility_reason": None,248           "data_issues": data_issues,249           "n_input_rows": n_input_rows,250           "n_analysed": 22,251           "random_seed": None,252           }253254os.makedirs(os.path.join(ROOT, "out"), exist_ok=True)255with open(os.path.join(ROOT, "out/results.json"), "w", encoding="utf-8") as f:256    json.dump(results, f, indent=2, ensure_ascii=False)257print("wrote out/results.json")258for k in r:259    print(f"  {k} = {r[k]}")260
Files · main
Menu

Theme