SPB Git

spb/wp29_uqo

Public
10.1 KB · 204 lines python
1"""F4-conf-03: harmonise five effect sizes reported under different definitions.2Reads only data/; writes out/results.json deterministically. No randomness."""3import json, math, os, re45ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))6D = os.path.join(ROOT, "data")7SRC = os.path.join(D, "sources")8910def read_lines(path):11    with open(path, encoding="utf-8") as f:12        return f.read().split("\n")131415def find_line(lines, src, pattern):16    """Return (line_no, text) of the unique line matching regex `pattern`; print it."""17    hits = [(i + 1, l) for i, l in enumerate(lines) if re.search(pattern, l)]18    if len(hits) != 1:19        raise RuntimeError(f"{src}: pattern {pattern!r} matched {len(hits)} lines")20    n, l = hits[0]21    print(f"[{src} L{n}] {l}")22    return n, l232425def cells(line):26    return [c.strip() for c in line.split("|")]272829def num(s):30    """Parse a printed number, stripping stars, %, pp, $, commas."""31    m = re.search(r"-?\d[\d,]*\.?\d*", s.replace("−", "-"))32    return float(m.group(0).replace(",", ""))333435def wc_l(path):36    with open(path, "rb") as f:37        return f.read().count(b"\n")383940effects = json.load(open(os.path.join(D, "effects.json"), encoding="utf-8"))41ids = [e["id"] for e in effects]42assert ids == ["E1", "E2", "E3", "E4", "E5"]4344n_input_rows = {45    "effects.json": len(effects),46    "README.md": wc_l(os.path.join(D, "README.md")),47}48for fn in sorted(os.listdir(SRC)):49    n_input_rows[fn] = wc_l(os.path.join(SRC, fn))5051harm = {}52baseline = {}5354# ---------------- E1: DCR Table 6, ATT, 5 km (log points -> relative %)55s = "arxiv-2609.27732v1"56L = read_lines(os.path.join(SRC, s + ".txt"))57_, hdr = find_line(L, s, r"^\| 1 km \| 3 km \| 5 km$")58assert cells(hdr)[3] == "5 km"59n_att, att = find_line(L, s, r"^ATT \| -0\.399")60b1_str = cells(att)[3]61b1 = num(b1_str)62n_pc, pc = find_line(L, s, r"^% change \| -32\.9%")63printed_pc = cells(pc)[3]64v1 = 100 * (math.exp(b1) - 1)65baseline["E1"] = b166harm["E1"] = {67    "value": round(v1, 4),68    "unit": "percent change relative to the counterfactual hospitalisation rate",69    "transformation": f"100*(exp(b)-1) with b = {b1_str.rstrip('*')} log points -> 100*(exp(-0.431)-1) = {v1:.4f}% (printed '% change' row: {printed_pc})",70    "harmonisable": True,71    "missing_quantity": None,72    "locator": f"{s} | Table 6 | row 'ATT' (extract line {n_att}) | column '5 km' = {b1_str}; cross-check row '% change' (line {n_pc}) column '5 km' = {printed_pc}",73}7475# ---------------- E4: DCR Table 7, fatal, 5 km (log points -> absolute per 100,000)76n_fat, fat = find_line(L, s, r"^Drug-related \(fatal\) \|")77b4_str = cells(fat)[3]78b4 = num(b4_str)79baseline["E4"] = b480rel4 = 100 * (math.exp(b4) - 1)81# Check whether any baseline level of the fatal rate is printed: search for a mean/baseline row82level_rows = [l for l in L if re.search(r"(?i)(mean|baseline|pre-treatment).*(rate|fatal)|(rate|fatal).*(mean)", l)83              and re.search(r"\d", l) and "|" in l]84print(f"[{s}] table rows printing a baseline/mean level of the fatal rate: {len(level_rows)}")85harm["E4"] = {86    "value": None,87    "unit": "hospitalisations per 100,000 inhabitants per year",88    "transformation": f"absolute change = R0*(exp(b)-1) with b = {b4_str.rstrip('*')}; R0 not printed, so not computable. Only the relative change is reachable: 100*(exp(-0.354)-1) = {rel4:.4f}%",89    "harmonisable": False,90    "missing_quantity": "baseline (counterfactual / pre-opening mean) level of the fatal drug-related hospitalisation rate per 100,000 inhabitants per year for the 5 km treated regions; not printed in the extract",91    "locator": f"{s} | Table 7 | row 'Drug-related (fatal)' (extract line {n_fat}) | column '5 km' = {b4_str}",92}9394# ---------------- E2: RESEA Table 2, Reemployment (pp -> % of control mean)95s = "arxiv-2606.14621v1"96L = read_lines(os.path.join(SRC, s + ".txt"))97n_h2, h2 = find_line(L, s, r"^Outcome \| Treatment \| Control \|")98hc = cells(h2)99n_re, re_ = find_line(L, s, r"^Reemployment \| 75")100rc = cells(re_)101ctrl_str = rc[hc.index("Control")]102ate_cell = rc[hc.index("Average treatment effect estimate")]103ate_str = ate_cell.split(";")[0]104p = num(ctrl_str)105d = num(ate_str)106v2 = 100 * d / p107baseline["E2"] = d108harm["E2"] = {109    "value": round(v2, 4),110    "unit": "percent change relative to the control-group mean reemployment rate",111    "transformation": f"100*Delta/p with Delta = {ate_str} and p = {ctrl_str} -> 100*1.47/75.0 = {v2:.4f}%",112    "harmonisable": True,113    "missing_quantity": None,114    "locator": f"{s} | Table 2 | row 'Reemployment' (extract line {n_re}) | column 'Average treatment effect estimate' = {ate_str}; column 'Control' = {ctrl_str}",115}116117# ---------------- E3: MW Table 2 elasticity x printed MW change118s = "arxiv-2505.04555v3"119L = read_lines(os.path.join(SRC, s + ".txt"))120n_el, el = find_line(L, s, r"^Employment elasticity w\.r\.t MW \|")121eta_str = cells(el)[1]122n_g, gl = find_line(L, s, r"^% MW changes \|")123g_str = cells(gl)[1]124find_line(L, s, r"about 4% minimum wage increase")125eta, g = num(eta_str), num(g_str)126v3 = 100 * eta * g127baseline["E3"] = eta128harm["E3"] = {129    "value": round(v3, 4),130    "unit": "percent change in employment",131    "transformation": f"100*eta*g with eta = {eta_str} and g = {g_str} (proportional MW change as printed) -> 100*(-0.388)*0.047 = {v3:.4f}% (first-order approximation)",132    "harmonisable": True,133    "missing_quantity": None,134    "locator": f"{s} | Table 2 | row 'Employment elasticity w.r.t MW' (extract line {n_el}) | column 'Estimate (SE)' = {eta_str}; row '% MW changes' (line {n_g}) | column 'Estimate (SE)' = {g_str}",135}136137# ---------------- E5: Benin Table 3 col (4) LPM coefficient -> pp138s = "arxiv-2510.03658v1"139L = read_lines(os.path.join(SRC, s + ".txt"))140_, h5 = find_line(L, s, r"^\| \(1\) \| \(2\) \| \(3\) \| \(4\)$")141col = cells(h5).index("(4)")142n_t, tl = find_line(L, s, r"^Treatment \| -0\.0911")143b5_str = cells(tl)[col]144n_m, ml = find_line(L, s, r"^Mean Dep\. Var\. \|")145n_c, cl = find_line(L, s, r"^% Change \|")146b5 = num(b5_str)147v5 = 100 * b5148baseline["E5"] = b5149harm["E5"] = {150    "value": round(v5, 4),151    "unit": "percentage points",152    "transformation": f"100*beta with beta = {b5_str} (0-1 probability scale) -> {v5:.2f} pp (exact rescaling)",153    "harmonisable": True,154    "missing_quantity": None,155    "locator": f"{s} | Table 3 | row 'Treatment' (extract line {n_t}) | column '(4)' = {b5_str}",156}157158# ---------------- internal cross-checks used to document data issues159chk_e1 = round(v1, 1) == num(printed_pc)160mean5 = num(cells(ml)[col]); pc5 = num(cells(cl)[col])161chk_e5 = b5 / mean5162print(f"check E1: computed {v1:.2f}% vs printed {printed_pc}: match={chk_e1}")163print(f"check E4 relative: {rel4:.2f}% (abstract says 35%, Sec 6.1 says ~30%)")164print(f"check E5 relative: {b5}/{mean5} = {chk_e5:.4f} vs printed % Change {pc5}")165166data_issues = [167    {"issue": "arxiv-2609.27732v1: abstract states fatal drug-related hospitalisations 'fall by 35%%', but Table 7 5 km fatal ATT -0.354 implies 100*(exp(-0.354)-1) = %.1f%%, consistent with Section 6.1's 'approximately 30%%'." % rel4,168     "handling": "Internal inconsistency of the source; E4 is based on the Table 7 cell only; the abstract figure is not used."},169    {"issue": "arxiv-2609.27732v1: no baseline/counterfactual level of the fatal hospitalisation rate (per 100,000 per year) is printed in the extract.",170     "handling": "E4 declared not harmonisable (value null) with missing_quantity named; no external value substituted."},171    {"issue": "arxiv-2609.27732v1: Table A19 CS-DR column prints SE (0.218) for the same -0.431 estimate that Table 6 (5 km) prints with SE (0.210); A19 note says 640 never-treated regions while the tables print 608.",172     "handling": "Recorded only; SEs are not used (no uncertainty propagation) and E1 uses Table 6 as specified."},173    {"issue": "arxiv-2609.27732v1: Table A19 '% change' row: -0.362 -> 100*(exp(b)-1) = -30.37% printed as -30.3%; -0.149 -> -13.84% printed as -13.9% (small rounding discrepancies, possibly from unrounded coefficients).",174     "handling": "Recorded only; these cells are not inputs to any harmonised item. For E1 the computed value matches the printed -35.0%."},175    {"issue": "arxiv-2505.04555v3: Table 2 note describes 'about 4% minimum wage increase' while the table row '% MW changes' prints 0.047 (4.7%).",176     "handling": "Target definition requires the size printed in the table; g = 0.047 used. With 4% instead, the implied change would be smaller in magnitude (not reported as a result)."},177    {"issue": "arxiv-2510.03658v1: Section 4.1 text quotes a 'table 2' estimate (-0.0855, mean 0.455) not included in the extract, mislabels relative changes as 'percentage change', and interprets the relative change as lives saved per 100 deaths; Table 3 col (4) printed %% Change -0.3685 vs -0.0962/0.261 = %.4f (rounding). Footnote markers render as '^9^9 9'." % chk_e5,178     "handling": "Only Table 3 row 'Treatment', column (4) is used for E5; the target (percentage points) needs no mean, so these issues do not affect E5."},179    {"issue": "arxiv-2606.14621v1: the 'Control' column (75.0%) is a raw group mean, while the ATE (1.47 pp) adjusts for randomisation week; abstract rounds the ATE to 1.5 pp; 'p ¡ 0.001' rendering defect in text.",180     "handling": "Control-group mean used as the denominator exactly as the target definition specifies; printed table value 1.47 pp used, not the rounded abstract value."},181]182183n_harm = sum(1 for v in harm.values() if v["value"] is not None)184n_not = sum(1 for v in harm.values() if v["harmonisable"] is False)185results = {186    "task_id": "F4-conf-03",187    "harmonised": {k: harm[k] for k in sorted(harm)},188    "n_harmonised": n_harm,189    "n_not_harmonisable": n_not,190    "feasibility": "complete",191    "feasibility_reason": None,192    "data_issues": data_issues,193    "n_input_rows": n_input_rows,194    "n_analysed": len(effects),195    "random_seed": None,196    "baseline_reported_estimates": baseline,197}198os.makedirs(os.path.join(ROOT, "out"), exist_ok=True)199with open(os.path.join(ROOT, "out", "results.json"), "w", encoding="utf-8") as f:200    json.dump(results, f, indent=2, ensure_ascii=False)201for k, v in harm.items():202    print(f"{k}: value={v['value']} [{v['unit']}] harmonisable={v['harmonisable']}")203print(f"n_harmonised={n_harm} n_not_harmonisable={n_not} feasibility=complete")204
Files · main
Menu

Theme