SPB Git

spb/wp29_uqo

Public
10.1 KB · 195 lines python
1"""F4-conf-03: harmonise five printed effect sizes. Reads only data/; writes out/results.json.2No randomness (random_seed = null). Prints every source line relied upon."""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 wc_l(path):16    with open(path, "rb") as f:17        return f.read().count(b"\n")181920def find(src, pattern, nth=0):21    """Return (line_no, line) of the nth line in source matching regex pattern; print it."""22    lines = read_lines(os.path.join(SRC, src + ".txt"))23    hits = [(i + 1, l) for i, l in enumerate(lines) if re.search(pattern, l)]24    if len(hits) <= nth:25        raise RuntimeError(f"pattern {pattern!r} not found in {src}")26    no, line = hits[nth]27    print(f"[{src} L{no}] {line.strip()}")28    return no, line293031def cells(line):32    return [c.strip() for c in line.split("|")]333435def num(s):36    m = re.search(r"-?\d[\d,]*\.?\d*", s.replace("−", "-"))37    return float(m.group(0).replace(",", ""))383940effects = json.load(open(os.path.join(D, "effects.json"), encoding="utf-8"))41assert [e["id"] for e in effects] == ["E1", "E2", "E3", "E4", "E5"]4243H = {}44checks = {}4546# ---------- E1: DCR Table 6, ATT, 5 km (log points) -> % of counterfactual47s1 = "arxiv-2609.27732v1"48_, hdr = find(s1, r"^\| 1 km \| 3 km \| 5 km$")49ln, att = find(s1, r"^ATT \| -0\.399")50_, pct = find(s1, r"^% change \| -32\.9%")51col = cells(hdr).index("5 km")52b1_str = cells(att)[col]53b1 = num(b1_str)54v1 = 100 * (math.exp(b1) - 1)55printed_pct_5km = num(cells(pct)[col])56H["E1"] = dict(57    value=round(v1, 2), unit="percent of the counterfactual hospitalisation rate",58    transformation=f"100*(exp(b)-1) with b = {b1} (printed '{b1_str}') = 100*(exp({b1})-1) = {v1:.4f} %",59    harmonisable=True, missing_quantity=None,60    locator=f"{s1} | Table 6 | row 'ATT' (line {ln}) | column '5 km' (value {b1_str}); cross-check: row '% change', column '5 km' prints {cells(pct)[col]}")61checks["E1_matches_printed_pct_change_row"] = abs(round(v1, 1) - printed_pct_5km) < 1e-96263# ---------- E2: RESEA Table 2 Reemployment ATE (pp) / control mean (%)64s2 = "arxiv-2606.14621v1"65_, hdr2 = find(s2, r"^Outcome \| Treatment \| Control \| Treatment-control difference")66ln2, row2 = find(s2, r"^Reemployment \| ")67c2 = cells(hdr2)68r2 = cells(row2)69ctrl_str = r2[c2.index("Control")]70ate_str = r2[c2.index("Average treatment effect estimate")].split(";")[0]71p_ctrl, d_ate = num(ctrl_str), num(ate_str)72v2 = 100 * d_ate / p_ctrl73H["E2"] = dict(74    value=round(v2, 2), unit="percent of the control-group mean reemployment rate",75    transformation=f"100*Delta/p with Delta = {d_ate} pp (ATE) and p = {p_ctrl}% (control mean) = 100*{d_ate}/{p_ctrl} = {v2:.4f} %",76    harmonisable=True, missing_quantity=None,77    locator=f"{s2} | Table 2 | row 'Reemployment' (line {ln2}) | columns 'Average treatment effect estimate' ({ate_str}) and 'Control' ({ctrl_str})")7879# ---------- E3: MW Table 2 elasticity x printed % MW change80s3 = "arxiv-2505.04555v3"81ln3a, row3a = find(s3, r"^Employment elasticity w\.r\.t MW \|")82ln3b, row3b = find(s3, r"^% MW changes \|")83find(s3, r"about 4% minimum wage increase")  # note line (documented inconsistency)84eta_str, g_str = cells(row3a)[1], cells(row3b)[1]85eta, g = num(eta_str), num(g_str)86v3 = 100 * eta * g87H["E3"] = dict(88    value=round(v3, 3), unit="percent change in employment",89    transformation=f"100*eta*g with eta = {eta} and g = {g} (proportional MW change as printed) = 100*({eta})*({g}) = {v3:.4f} %",90    harmonisable=True, missing_quantity=None,91    locator=f"{s3} | Table 2 | row 'Employment elasticity w.r.t MW' (line {ln3a}), column 'Estimate (SE)' ({eta_str}); row '% MW changes' (line {ln3b}), column 'Estimate (SE)' ({g_str})")9293# ---------- E4: DCR Table 7 fatal, 5 km: absolute change per 100,000 needs a level94ln4, row4 = find(s1, r"^Drug-related \(fatal\) \|")95_, hdr7 = find(s1, r"^Outcome \| 1 km \| 3 km \| 5 km$")96b4_str = cells(row4)[cells(hdr7).index("5 km")]97b4 = num(b4_str)98rel4 = 100 * (math.exp(b4) - 1)99# Search the extract for any printed level of the outcome (mean / baseline rate) -- none expected.100lines1 = read_lines(os.path.join(SRC, s1 + ".txt"))101level_hits = [l for l in lines1 if re.search(r"(?i)mean|baseline rate|pre-treatment (mean|level)|dep\. var", l)]102checks["E4_no_outcome_level_printed"] = len(level_hits) == 0103H["E4"] = dict(104    value=None, unit="hospitalisations per 100,000 inhabitants per year",105    transformation=(f"Would require Delta = Y0*(exp(b)-1) with b = {b4} (printed '{b4_str}'); the relative change "106                    f"100*(exp({b4})-1) = {rel4:.2f} % is computable, but the counterfactual/baseline level Y0 is not printed"),107    harmonisable=False,108    missing_quantity=("baseline (pre-opening or counterfactual) level of the fatal drug-related hospitalisation rate per "109                      "100,000 inhabitants per year in the 5 km treated regions (e.g. treated-group pre-treatment mean); "110                      "the extract prints only log-point ATTs and no outcome means"),111    locator=f"{s1} | Table 7 | row 'Drug-related (fatal)' (line {ln4}) | column '5 km' (value {b4_str})")112113# ---------- E5: Benin Table 3, Treatment, col (4): 0-1 scale -> pp114s5 = "arxiv-2510.03658v1"115_, hdr5 = find(s5, r"^\| \(1\) \| \(2\) \| \(3\) \| \(4\)$")116ln5, row5 = find(s5, r"^Treatment \| ")117_, pc5 = find(s5, r"^% Change \| ")118_, mean5 = find(s5, r"^Mean Dep\. Var\. \| ")119i5 = cells(hdr5).index("(4)")120b5_str = cells(row5)[i5]121b5 = num(b5_str)122v5 = 100 * b5123H["E5"] = dict(124    value=round(v5, 2), unit="percentage points",125    transformation=f"100*beta with beta = {b5} (printed '{b5_str}', 0-1 probability scale) = {v5:.2f} pp",126    harmonisable=True, missing_quantity=None,127    locator=f"{s5} | Table 3 | row 'Treatment' (line {ln5}) | column '(4)' (value {b5_str})")128checks["E5_pct_change_row_consistent"] = abs(round(b5 / num(cells(mean5)[i5]), 4) - num(cells(pc5)[i5])) <= 1e-4129130# ---------- Source-consistency checks used to document data issues131_, a19h = find(s1, r"^\| CS-DR \| Sun & Abraham")132_, a19b = find(s1, r"^ATT \| -0\.431\*\* \| -0\.438")133_, a19se = find(s1, r"^\| \(0\.218\)")134_, a19p = find(s1, r"^% change \| -35\.0% \| -35\.5%")135_, t6se = find(s1, r"^\| \(0\.412\) \| \(0\.279\) \| \(0\.210\)")136find(s1, r"never-treated comparison regions \(640 regions\)")137find(s1, r"which fall by 35%")138find(s1, r"approximately 30% at 5 km")139find(s5, r"0\.0855/0\.455")140a19_mismatch = []141for name, bs, ps in zip(cells(a19h)[1:], cells(a19b)[1:], cells(a19p)[1:]):142    implied = round(100 * (math.exp(num(bs)) - 1), 1)143    if abs(implied - num(ps)) > 1e-9:144        a19_mismatch.append(f"{name}: b={num(bs)} implies {implied}% vs printed {ps}")145146data_issues = [147    {"issue": "DCR extract: the same 5 km CS-DR ATT (-0.431) carries SE (0.210) in Table 6 but (0.218) in Table A19.",148     "handling": "Point estimate identical in both tables; E1 uses Table 6 as specified. SEs not used (no uncertainty propagation)."},149    {"issue": "DCR extract: Tables 6/7 and A19 body report 608 control regions, while the A19 note says 640 never-treated comparison regions.",150     "handling": "Recorded only; does not affect any harmonised value."},151    {"issue": f"DCR extract: abstract says fatal hospitalisations 'fall by 35%', but Table 7 5 km ATT {b4} implies {rel4:.1f}% (Section 6.1 says ~30%).",152     "handling": "Table 7 value used for E4; abstract figure not used. E4 is in any case not harmonisable to an absolute change."},153    {"issue": "DCR extract Table A19: printed '% change' differs by 0.1 from 100*(exp(b)-1) for: " + ("; ".join(a19_mismatch) if a19_mismatch else "none"),154     "handling": "Attributed to rounding of the printed coefficients; no effect on E1 (5 km CS-DR value matches -35.0%)."},155    {"issue": "DCR extract: no level (mean or baseline) of any hospitalisation rate is printed.",156     "handling": "E4 (absolute change per 100,000) declared not harmonisable; missing quantity named."},157    {"issue": f"Minimum-wage extract: Table 2 note says 'about 4% minimum wage increase' while the row '% MW changes' prints {g_str} (4.7%).",158     "handling": f"Used the table row value {g_str} as the target definition instructs; with 4% the result would be about {100*eta*0.04:.2f}% (not reported as the estimate)."},159    {"issue": "RESEA extract: the control-group mean (75.0%) is an unadjusted aggregate mean, whereas the ATE (1.47 pp) adjusts for randomisation week; the aggregate control mean is only an approximation of the counterfactual level for the treated.",160     "handling": "Used the printed control mean as the target definition specifies ('percent of the control-group mean'); flagged as approximate."},161    {"issue": "Benin extract: abstract and Section 4.1 headline (0.0855, 18.79%, mean 0.455) refer to Table 2, which is not in the extract; footnote markers are rendered inline ('percent^9^9 9').",162     "handling": "E5 uses Table 3 column (4) (-0.0962) as specified; Table 3 '% Change' (-0.3685) is consistent with -0.0962/0.261."},163]164165n_input_rows = {"effects.json": len(effects), "README.md": wc_l(os.path.join(D, "README.md"))}166for f in sorted(os.listdir(SRC)):167    n_input_rows[f] = wc_l(os.path.join(SRC, f))168169n_harm = sum(1 for v in H.values() if v["value"] is not None)170n_not = sum(1 for v in H.values() if v["harmonisable"] is False)171results = {172    "task_id": "F4-conf-03",173    "harmonised": H,174    "n_harmonised": n_harm,175    "n_not_harmonisable": n_not,176    "feasibility": "complete",177    "feasibility_reason": None,178    "data_issues": data_issues,179    "n_input_rows": n_input_rows,180    "n_analysed": len(effects),181    "random_seed": None,182    "baseline_printed_conversions": {183        "E1_table6_pct_change_row_5km": printed_pct_5km,184        "E5_table3_pct_change_row_col4": num(cells(pc5)[i5]),185    },186    "internal_checks": checks,187}188assert n_harm + n_not == len(effects)189os.makedirs(os.path.join(ROOT, "out"), exist_ok=True)190with open(os.path.join(ROOT, "out", "results.json"), "w", encoding="utf-8") as f:191    json.dump(results, f, indent=2, ensure_ascii=False)192for k, v in H.items():193    print(f"{k}: value={v['value']} [{v['unit']}] harmonisable={v['harmonisable']}")194print("checks:", checks)195
Files · main
Menu

Theme