SPB Git forge

spb/wp18_uqo

Public
6commits 1branches 0releases
5.4 MBsize
maindefault branch
yesterdaylast push
TeX 53.2% Python 46.6%
4.2 KB · 106 lines python
Raw Blame History
1"""WP18 — experiment 10: robustness summary (discounting, preferences, transaction costs, grid2resolution, solver tolerance, simulation sample size, seed).34Reads results/data/<group>/<name>.json written by simulation.py and writes5results/data/robustness_summary.csv (key moments of every scenario next to the baseline, with6the relative deviation), results/data/grid_sensitivity.csv (grid / tolerance / sample-size7variants only) and results/data/diagnostics_table.csv (convergence of every run).8Usage: python -m src.robustness9"""10from __future__ import annotations1112import json13from pathlib import Path1415import numpy as np16import pandas as pd1718from .model import RESULTS_DIR1920DATA = RESULTS_DIR / "data"2122KEY_MOMENTS = [23    ("p", lambda d: d["p"]),24    ("rho", lambda d: d["rho"]),25    ("ownership", lambda d: d["moments"]["ownership_rate_all"]),26    ("ownership_under35", lambda d: d["moments"]["ownership_rate_under35"]),27    ("threshold_age1_zmid", lambda d: d["thresholds"]["1"][str(len(d["thresholds"]["1"]) // 2)]),28    ("gini_nw", lambda d: d["gini_nw"]),29    ("mean_nw", lambda d: d["mean_nw"]),30    ("mean_gift", lambda d: d["moments"]["mean_gift_received"]),31    ("lambda_age7", lambda d: d["mobility"]["lambda_persistence"]),32    ("q_RO_age3", lambda d: d["mobility"]["q_RO_age3"]),33    ("q_OO_age3", lambda d: d["mobility"]["q_OO_age3"]),34    ("rank_rank_nw", lambda d: d["mobility"]["rank_rank_slope_nw"]),35    ("nw_gap_age4", lambda d: d["gaps"]["nw_gap_owner_renter_age4"]),36    ("share_mortgage", lambda d: d["moments"]["share_with_mortgage"]),37]3839GRID_SCENARIOS = ("grid_fine", "grid_coarse", "tol_tight", "sample_small", "sample_large", "seed_alt")404142def load_all() -> dict[str, dict]:43    out = {}44    for f in sorted(DATA.glob("*/*.json")):45        if f.parent.name in ("welfare", "multiplicity"):46            continue47        with open(f) as fh:48            d = json.load(fh)49        if isinstance(d, dict) and "moments" in d:50            out[d["name"]] = d51    return out525354def _safe(fn, d):55    try:56        v = fn(d)57        return float(v) if v is not None else np.nan58    except (KeyError, TypeError, IndexError):59        return np.nan606162def build_summary(runs: dict[str, dict]) -> pd.DataFrame:63    base = runs.get("baseline")64    rows = []65    for name, d in runs.items():66        row = dict(name=name, group=d.get("group"), equilibrium=d.get("equilibrium"), converged=d.get("converged"),67                   time_sec=d.get("time_sec"), excess_owner=d.get("excess_owner"), excess_rental=d.get("excess_rental"),68                   bellman_owner_mean=(d.get("bellman") or {}).get("owner_mean"),69                   bellman_renter_mean=(d.get("bellman") or {}).get("renter_mean"))70        for k, fn in KEY_MOMENTS:71            v = _safe(fn, d)72            row[k] = v73            if base is not None:74                b = _safe(fn, base)75                row[f"{k}_dev"] = (v - b) if np.isfinite(v) and np.isfinite(b) else np.nan76        rows.append(row)77    return pd.DataFrame(rows)787980def main():81    runs = load_all()82    if not runs:83        raise SystemExit("no scenario results found under results/data — run `make simulate` first")84    df = build_summary(runs)85    df.to_csv(DATA / "robustness_summary.csv", index=False)86    grid = df[df.name.isin(GRID_SCENARIOS + ("baseline",))].copy()87    grid.to_csv(DATA / "grid_sensitivity.csv", index=False)88    diag = df[["name", "group", "equilibrium", "converged", "time_sec", "excess_owner", "excess_rental",89               "bellman_owner_mean", "bellman_renter_mean"]]90    diag.to_csv(DATA / "diagnostics_table.csv", index=False)91    # max absolute deviation across the numerical variants, as a share of the baseline value92    base = df[df.name == "baseline"].iloc[0]93    dev = {}94    for k, _ in KEY_MOMENTS:95        vals = grid[k].values96        b = base[k]97        dev[k] = float(np.nanmax(np.abs(vals - b)) / max(abs(b), 1e-9)) if np.isfinite(b) else np.nan98    with open(DATA / "grid_sensitivity_max_relative_deviation.json", "w") as fh:99        json.dump(dev, fh, indent=1)100    print(df[["name", "equilibrium", "converged", "p", "ownership", "gini_nw", "lambda_age7"]].to_string())101    print("max relative deviation across numerical variants:", dev)102103104if __name__ == "__main__":105    main()106