SPB Git

spb/wp29_uqo

Public
10.1 KB · 188 lines python
1"""F4-conf-03: harmonise five effect sizes from printed quantities only.2Run from workspace root: ~/wp29/venv/bin/python out/analysis.py3Reads data/ only; prints every source line relied upon; writes out/results.json."""4import json, math, os, re56ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))7D = os.path.join(ROOT, "data")8SRC = os.path.join(D, "sources")91011def lines(fn):12    with open(os.path.join(SRC, fn), encoding="utf-8") as f:13        return f.read().split("\n")141516def find(fn, pattern, after=None):17    """Return (line_no, text) of the first line matching regex `pattern` (after line `after`)."""18    L = lines(fn)19    for i, t in enumerate(L, 1):20        if after is not None and i <= after:21            continue22        if re.search(pattern, t):23            print(f"[{fn}:{i}] {t.strip()}")24            return i, t25    raise ValueError(f"pattern {pattern!r} not found in {fn}")262728def cells(t):29    return [c.strip() for c in t.split("|")]303132def num(s):33    return float(re.sub(r"[^0-9.\-]", "", s.replace("−", "-")))343536H = {}3738# ---- E1: Armendariz Table 6, ATT row, 5 km column (log points -> percent)39f1 = "arxiv-2609.27732v1.txt"40i6, _ = find(f1, r"^Table 6:")41ih, th = find(f1, r"^\| 1 km \| 3 km \| 5 km", after=i6)42ia, ta = find(f1, r"^ATT \|", after=i6)43ip, tp = find(f1, r"^% change \|", after=i6)44col5 = cells(th).index("5 km")  # header has a leading empty cell, same as row label position45b1_str = cells(ta)[col5]46b1 = num(b1_str)47pct_printed = cells(tp)[col5]48v1 = 100 * (math.exp(b1) - 1)49H["E1"] = {50    "value": round(v1, 2),51    "unit": "percent change in the drug-related hospitalisation rate relative to the counterfactual rate",52    "transformation": f"100*(exp(b)-1) with b = {b1_str.rstrip('*')} log points -> 100*(exp(-0.431)-1) = {v1:.2f}% (printed '% change' row: {pct_printed})",53    "harmonisable": True,54    "missing_quantity": None,55    "locator": f"arxiv-2609.27732v1 | Table 6 | row 'ATT' | column '5 km' (extract line {ia}); cross-check: row '% change', column '5 km' (line {ip})",56}5758# ---- E2: RESEA Table 2, Reemployment row (pp -> percent of control mean)59f2 = "arxiv-2606.14621v1.txt"60ih2, th2 = find(f2, r"^Outcome \| Treatment \| Control")61ir2, tr2 = find(f2, r"^Reemployment \|", after=ih2)62hdr = cells(th2); row = cells(tr2)63ctrl_str = row[hdr.index("Control")]64ate_cell = row[hdr.index("Average treatment effect estimate")]65ate_str = ate_cell.split(";")[0].strip()66p0 = num(ctrl_str); d2 = num(ate_str)67v2 = 100 * d2 / p068H["E2"] = {69    "value": round(v2, 2),70    "unit": "percent change in the reemployment probability relative to the control-group mean",71    "transformation": f"100*Delta/p with Delta = {ate_str} and p = {ctrl_str} (control mean) -> 100*1.47/75.0 = {v2:.2f}%",72    "harmonisable": True,73    "missing_quantity": None,74    "locator": f"arxiv-2606.14621v1 | Table 2 | row 'Reemployment' | columns 'Average treatment effect estimate' (Delta) and 'Control' (p) (extract line {ir2})",75}7677# ---- E3: Kanayama Table 2 elasticity times printed % MW change78f3 = "arxiv-2505.04555v3.txt"79i2, _ = find(f3, r"^Table 2:")80ie, te = find(f3, r"^Employment elasticity w\.r\.t MW \|", after=i2)81ig, tg = find(f3, r"^% MW changes \|", after=i2)82find(f3, r"^Note: The table reports the effects of a minimum wage increase", after=i2)83eta_str = cells(te)[1]; g_str = cells(tg)[1]84eta = num(eta_str); g = num(g_str)85v3 = 100 * eta * g86H["E3"] = {87    "value": round(v3, 2),88    "unit": "percent change in employment implied for the observed 2023 minimum-wage increase",89    "transformation": f"100*eta*g with eta = {eta_str} and g = {g_str} (proportional MW change printed in Table 2) -> 100*(-0.388)*0.047 = {v3:.4f}%",90    "harmonisable": True,91    "missing_quantity": None,92    "locator": f"arxiv-2505.04555v3 | Table 2 | row 'Employment elasticity w.r.t MW' | column 'Estimate (SE)' (line {ie}); row '% MW changes' | column 'Estimate (SE)' (line {ig})",93}9495# ---- E4: Armendariz Table 7 fatal, 5 km: absolute change needs a level96i7, _ = find(f1, r"^Table 7:")97ih7, th7 = find(f1, r"^Outcome \| 1 km \| 3 km \| 5 km", after=i7)98if7, tf7 = find(f1, r"^Drug-related \(fatal\) \|", after=i7)99b4_str = cells(tf7)[cells(th7).index("5 km")]100b4 = num(b4_str)101rel4 = 100 * (math.exp(b4) - 1)102print(f"E4 intermediate (relative change only, not the target): 100*(exp({b4})-1) = {rel4:.2f}%")103# Check whether any baseline/mean level of the fatal rate is printed in the extract104lvl = [t for t in lines(f1) if re.search(r"(?i)(mean|baseline|pre-treatment|average).*(rate|per 100,000)", t) and re.search(r"\d+\.\d+\s*(per|hospitali)", t)]105print(f"E4: lines printing a level of the fatal hospitalisation rate: {len(lvl)}")106H["E4"] = {107    "value": None,108    "unit": "hospitalisations per 100,000 inhabitants per year",109    "transformation": f"Would require R0*(exp(b)-1) with b = {b4_str.rstrip('*')} (relative change {rel4:.2f}%); R0 is not printed in the extract",110    "harmonisable": False,111    "missing_quantity": "the counterfactual (or pre-opening baseline) level of the fatal drug-related hospitalisation rate per 100,000 inhabitants per year for the treated regions within 5 km (e.g. a pre-treatment mean of the outcome in levels); the extract prints only log-point ATTs and no outcome level",112    "locator": f"arxiv-2609.27732v1 | Table 7 | row 'Drug-related (fatal)' | column '5 km' (extract line {if7})",113}114115# ---- E5: Tossou Table 3, Treatment row, column (4): 0-1 -> pp116f5 = "arxiv-2510.03658v1.txt"117i3, _ = find(f5, r"^Table 3:")118ih5, th5 = find(f5, r"^\| \(1\) \| \(2\) \| \(3\) \| \(4\)", after=i3)119it5, tt5 = find(f5, r"^Treatment \|", after=i3)120im5, tm5 = find(f5, r"^Mean Dep\. Var\. \|", after=i3)121c4 = cells(th5).index("(4)")122b5_str = cells(tt5)[c4]123b5 = num(b5_str)124v5 = 100 * b5125print(f"E5 cross-check: relative change = b/mean = {b5}/{num(cells(tm5)[c4])} = {b5/num(cells(tm5)[c4]):.4f}")126H["E5"] = {127    "value": round(v5, 2),128    "unit": "percentage points (change in the probability of infant death or stillbirth)",129    "transformation": f"100*beta with beta = {b5_str} (0-1 scale) -> {v5:.2f} pp (exact rescaling)",130    "harmonisable": True,131    "missing_quantity": None,132    "locator": f"arxiv-2510.03658v1 | Table 3 | row 'Treatment' | column '(4)' (extract line {it5})",133}134135# ---- data issues (detected by inspection; key checks recomputed here)136iA19, tA19 = find(f1, r"^\| \(0\.218\)")137find(f1, r"never-treated comparison regions \(640 regions\)")138find(f1, r"fatal outcomes, which fall by 35%")139find(f1, r"by approximately 30% at 5 km")140find(f3, r"about 4% minimum wage increase")141find(f2, r"increased reemployment by 1\.5 percentage points")142find(f5, r"by 0\.0855 \(a 18\.79 percentage change\)")143issues = [144    {"issue": "Armendariz: the same CS-DR 5 km ATT (-0.431) has standard error (0.210) in Table 6 but (0.218) in Table A19.",145     "handling": "Point estimate identical, so E1 is unaffected; no uncertainty propagated. Recorded only."},146    {"issue": "Armendariz: Tables 6/7 and the Figure 6 note state 608 never-treated control regions; the Table A19 note states 640.",147     "handling": "Does not enter any transformation; recorded only."},148    {"issue": f"Armendariz: abstract says fatal hospitalisations 'fall by 35%', but the Table 7 5 km estimate -0.354 implies 100*(exp(-0.354)-1) = {rel4:.1f}%, matching Section 6.1 ('approximately 30% at 5 km'). The abstract figure appears to repeat the overall effect.",149     "handling": "Used the Table 7 coefficient as instructed; E4 target (absolute change) is in any case not reachable."},150    {"issue": "Armendariz: Figure 7 note mentions 'four diagnostic sub-categories' and the Table 7 note lists ICD codes for four, while five outcomes (incl. fatal) are reported; the definition of the fatal outcome is not given in the extract.",151     "handling": "Recorded; does not affect the harmonised values."},152    {"issue": "Kanayama: Table 2 note describes the 2023 increase as 'about 4%' while the table row '% MW changes' prints 0.047.",153     "handling": "Used the printed table value 0.047 as the task requires (target definition points to the table). With 0.04 the implied change would be about -1.55%; the 0.047-based -1.82% is consistent with missing+excess jobs (-0.03+0.012 = -0.018) and with the abstract's ~2% decline."},154    {"issue": "RESEA: abstract rounds the reemployment ATE to 1.5 pp; Table 2 prints 1.47 pp. The 'Control' column is the raw aggregate control mean, not a block-adjusted counterfactual (the paper notes raw means are confounded by randomisation week).",155     "handling": "Used 1.47 pp and the printed control mean 75.0% as the target definition specifies (percent of control-group mean)."},156    {"issue": "Tossou: abstract/Section 4.1 headline (0.0855, 18.79 percentage change, mean 0.455) refers to a 'table 2' that is not in the extract and calls a relative change a 'percentage change'; Table 3 '% Change' is a relative change (b/mean, -0.0962/0.261 = -0.3686 vs printed -0.3685, rounding), not percentage points. Footnote markers are garbled by rendering ('^9^9 9').",157     "handling": "E5 uses Table 3 column (4) only; the target is percentage points (100*beta), distinct from the printed relative change."},158    {"issue": "RESEA extract: rendering defect 'p ¡ 0.001' (for p < 0.001) in Section 3 text.",159     "handling": "Irrelevant to the harmonised values; recorded."},160]161162n_rows = {}163for fn in sorted(os.listdir(SRC)):164    with open(os.path.join(SRC, fn), encoding="utf-8") as f:165        n_rows["sources/" + fn] = f.read().count("\n")166with open(os.path.join(D, "README.md"), encoding="utf-8") as f:167    n_rows["README.md"] = f.read().count("\n")168with open(os.path.join(D, "effects.json"), encoding="utf-8") as f:169    eff = json.load(f)170n_rows["effects.json"] = len(eff)171assert [e["id"] for e in eff] == ["E1", "E2", "E3", "E4", "E5"]172173res = {174    "task_id": "F4-conf-03",175    "harmonised": H,176    "n_harmonised": sum(v["value"] is not None for v in H.values()),177    "n_not_harmonisable": sum(not v["harmonisable"] for v in H.values()),178    "feasibility": "complete",179    "feasibility_reason": None,180    "data_issues": issues,181    "n_input_rows": n_rows,182    "n_analysed": len(H),183    "random_seed": None,184}185with open(os.path.join(ROOT, "out", "results.json"), "w", encoding="utf-8") as f:186    json.dump(res, f, indent=2, ensure_ascii=False)187print(json.dumps({k: H[k]["value"] for k in H}))188
Files · main
Menu

Theme