SPB Git

spb/wp10_uqo

Public

UQO Working Paper No. 10 — The assessment gap in Quebec: vertical and horizontal inequity in municipal property assessment.

20.8 KB · 434 lines python
1#!/usr/bin/env python32# Author: Simon-Pierre Boucher — contact@spboucher.ai3"""Step 05 — LaTeX tables.45Converts the step-02/03 CSV outputs (and the step-06 revision outputs,6``rev_*.csv``) into booktabs tables under ``results/tables/``. The paper inputs these files directly, so every number7in the manuscript is machine-generated.89Usage:  python scripts/05_make_tables.py10"""11import sys12from pathlib import Path1314import numpy as np15import pandas as pd1617sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))1819from wp10 import config  # noqa: E4022021RES = config.REPRODUCED22TAB = config.TABLES232425def _stars(coef: float, se: float) -> str:26    t = abs(coef / se) if se > 0 else 027    return "***" if t > 2.576 else "**" if t > 1.96 else "*" if t > 1.645 else ""282930def _f(x, nd=3):31    return f"{x:,.{nd}f}"323334def t_summary():35    s = pd.read_csv(RES / "summary_stats.csv")36    lines = [r"\begin{tabular}{lrrrrrr}", r"\toprule",37             r"Variable & $n$ & Mean & SD & P10 & Median & P90 \\",38             r"\midrule"]39    for _, r in s.iterrows():40        big = r["mean"] > 100041        nd = 0 if big else 3 if r["mean"] < 10 else 142        label = (str(r["variable"]).replace("($)", "(\\$)")43                 .replace("(m2)", "(m$^2$)"))44        lines.append(45            f"{label} & {r['n']:,.0f} & {_f(r['mean'], nd)} & "46            f"{_f(r['sd'], nd)} & {_f(r['p10'], nd)} & {_f(r['p50'], nd)} & "47            f"{_f(r['p90'], nd)} \\\\")48    lines += [r"\bottomrule", r"\end{tabular}"]49    (TAB / "summary_stats.tex").write_text("\n".join(lines))505152def t_iaao():53    d = pd.read_csv(RES / "iaao_overall.csv")54    lines = [r"\begin{tabular}{lrcccc}", r"\toprule",55             r"Sample & $n$ & Median ratio & COD & PRD & PRB \\",56             r"\midrule"]57    for _, r in d.iterrows():58        lines.append(59            f"{r['group']} & {r['n']:,.0f} & {_f(r['median_ratio'])} & "60            f"{_f(r['cod'], 1)} & {_f(r['prd'])} & {_f(r['prb'], 4)} \\\\")61        lines.append(62            f" & & \\footnotesize[{_f(r['median_lo'])}, {_f(r['median_hi'])}]"63            f" & \\footnotesize[{_f(r['cod_lo'], 1)}, {_f(r['cod_hi'], 1)}]"64            f" & \\footnotesize[{_f(r['prd_lo'])}, {_f(r['prd_hi'])}]"65            f" & \\footnotesize[{_f(r['prb_lo'], 4)}, {_f(r['prb_hi'], 4)}] \\\\")66    lines += [r"\bottomrule", r"\end{tabular}"]67    (TAB / "iaao.tex").write_text("\n".join(lines))686970def t_cities():71    d = pd.read_csv(RES / "iaao_cities.csv").sort_values("n", ascending=False)72    lines = [r"\begin{tabular}{lrccccc}", r"\toprule",73             r"Municipality & $n$ & Median ratio & COD & PRD & PRB & "74             r"\makecell{Share of years\\PRB $<$ 0} \\",75             r"\midrule"]76    for _, r in d.iterrows():77        lines.append(78            f"{r['muni']} & {r['n']:,.0f} & {_f(r['median_ratio'])} & "79            f"{_f(r['cod'], 1)} & {_f(r['prd'])} & {_f(r['prb'], 4)} & "80            f"{_f(r['share_years_prb_neg'], 2)} \\\\")81    lines += [r"\bottomrule", r"\end{tabular}"]82    (TAB / "iaao_cities.tex").write_text("\n".join(lines))838485def t_vertical():86    v = pd.read_csv(RES / "vertical.csv")87    pf = pd.read_csv(RES / "paglin_fogarty.csv", index_col=0).squeeze()88    pf = {k: (v if k == "estimator" else float(v)) for k, v in pf.items()}89    q = pd.read_csv(RES / "quantile.csv")9091    def row(label, vals):92        return label + " & " + " & ".join(vals) + r" \\"9394    cols = list(v["estimator"])95    lines = [r"\begin{tabular}{l" + "c" * len(cols) + "}", r"\toprule",96             row("", [f"({i+1})" for i in range(len(cols))]),97             row("", [c.replace(" (pooled)", "").replace(" (cell)", "")98                      .replace(" (rank instrument)", "") for c in cols]),99             r"\midrule",100             row(r"$\beta$ (ln sale price)",101                 [f"{_f(r['beta'])}{_stars(r['beta'] - 1, r['se'])}"102                  for _, r in v.iterrows()]),103             row("", [f"({_f(r['se'])})" for _, r in v.iterrows()]),104             row(r"$\gamma = \beta - 1$",105                 [f"{r['gamma']:+.3f}" for _, r in v.iterrows()]),106             r"\midrule",107             row("Cell fixed effects",108                 ["No", "Yes", "Yes"]),109             row("Error-in-price robust", ["No", "No", "Yes"]),110             row("Observations", [f"{r['n']:,.0f}" for _, r in v.iterrows()]),111             row("$R^2$", [f"{_f(r['r2'])}" for _, r in v.iterrows()]),112             r"\bottomrule", r"\end{tabular}"]113    (TAB / "vertical.tex").write_text("\n".join(lines))114    # stars in this table test H0: beta = 1 (proportionality), noted in caption115116    lines = [r"\begin{tabular}{lcc}", r"\toprule",117             r" & Coefficient & Std.\ error \\", r"\midrule",118             f"Intercept ($\\$$) & {pf['intercept']:,.0f} & "119             f"{pf['intercept_se']:,.0f} \\\\",120             f"Slope on sale price & {_f(pf['slope'])} & "121             f"{_f(pf['slope_se'])} \\\\",122             f"Observations & \\multicolumn{{2}}{{c}}{{{pf['n']:,.0f}}} \\\\",123             r"\bottomrule", r"\end{tabular}"]124    (TAB / "paglin_fogarty.tex").write_text("\n".join(lines))125126    lines = [r"\begin{tabular}{lccccc}", r"\toprule",127             "Quantile $\\tau$ & " + " & ".join(f"{t:.2f}" for t in q["tau"])128             + r" \\", r"\midrule",129             r"$\beta(\tau)$ & " + " & ".join(130                 f"{_f(r['beta'])}{_stars(r['beta'] - 1, r['se'])}"131                 for _, r in q.iterrows()) + r" \\",132             " & " + " & ".join(f"({_f(r['se'], 4)})" for _, r in q.iterrows())133             + r" \\",134             r"\bottomrule", r"\end{tabular}"]135    (TAB / "quantile.tex").write_text("\n".join(lines))136137138def t_heterogeneity():139    h = pd.read_csv(RES / "heterogeneity.csv")140    panels = {141        "Property class": ["Single-family", "Condominium", "Plex (2–5 units)",142                           "Cottage"],143        "Building age": ["Age < 20 y", "Age 20–60 y", "Age > 60 y"],144        "Assessed land share": ["Land share < 0.2", "Land share 0.2–0.4",145                                "Land share > 0.4"],146        "Roll lag at sale": ["Roll lag < 24 m", "Roll lag 24–48 m",147                             "Roll lag > 48 m"],148        "Municipality size": ["Muni < 1k sales", "Muni 1k–10k sales",149                              "Muni > 10k sales"],150        "Sale year": [f"Sales {y}" for y in range(2021, 2027)],151    }152    lines = [r"\begin{tabular}{lccr}", r"\toprule",153             r"Subsample & $\gamma$ & Std.\ error & $n$ \\"]154    for panel, keys in panels.items():155        lines.append(r"\midrule")156        lines.append(r"\multicolumn{4}{l}{\itshape " + panel + r"}\\")157        for k in keys:158            r = h[h["group"] == k]159            if r.empty:160                continue161            r = r.iloc[0]162            label = (k.replace("<", "$<$").replace(">", "$>$")163                      .replace("–", "--"))164            lines.append(165                f"\\quad {label} & {r['gamma']:+.3f}"166                f"{_stars(r['gamma'], r['se'])} & ({_f(r['se'])}) & "167                f"{r['n']:,.0f} \\\\")168    lines += [r"\bottomrule", r"\end{tabular}"]169    (TAB / "heterogeneity.tex").write_text("\n".join(lines))170171172def t_taxshift():173    t = pd.read_csv(RES / "taxshift.csv")174    lines = [r"\begin{tabular}{lcccc}", r"\toprule",175             r"Price decile & $n$ & \makecell{Mean excess\\burden (\%)} & "176             r"\makecell{Median excess\\burden (\%)} & Std.\ error \\",177             r"\midrule"]178    for _, r in t.iterrows():179        lines.append(180            f"{int(r['decile'])} & {r['n']:,.0f} & "181            f"{100 * r['mean_rel']:+.1f} & {100 * r['median_rel']:+.1f} & "182            f"({100 * r['se']:.2f}) \\\\")183    lines += [r"\bottomrule", r"\end{tabular}"]184    (TAB / "taxshift.tex").write_text("\n".join(lines))185186187def t_horizontal():188    h = pd.read_csv(RES / "horizontal.csv", index_col=0)189    meta = pd.read_csv(RES / "horizontal_meta.csv", index_col=0).squeeze()190    labels = {"age_dec": "Building age (decades)",191              "land_share": "Assessed land share",192              "is_condo": "Condominium",193              "is_plex": "Plex (2--5 units)",194              "is_cottage": "Cottage"}195    lines = [r"\begin{tabular}{lcc}", r"\toprule",196             r" & Coefficient & Std.\ error \\", r"\midrule"]197    for k, lab in labels.items():198        r = h.loc[k]199        lines.append(f"{lab} & {r['coef']:+.4f}{_stars(r['coef'], r['se'])} & "200                     f"({_f(r['se'], 4)}) \\\\")201    lines += [r"\midrule",202              f"Mean of dependent variable & \\multicolumn{{2}}{{c}}"203              f"{{{float(meta['mean_dep']):.3f}}} \\\\",204              f"Observations & \\multicolumn{{2}}{{c}}{{{float(meta['n']):,.0f}}} \\\\",205              r"\bottomrule", r"\end{tabular}"]206    (TAB / "horizontal.tex").write_text("\n".join(lines))207208209def t_robustness():210    r = pd.read_csv(RES / "robustness.csv")211    lines = [r"\begin{tabular}{lccccr}", r"\toprule",212             r"Sample variant & $\gamma_{\text{FE}}$ & Std.\ err. & "213             r"$\gamma_{\text{IV}}$ & Std.\ err. & $n$ \\",214             r"\midrule"]215    for _, x in r.iterrows():216        label = (str(x["variant"]).replace("<=", "$\\le$")217                 .replace(">=", "$\\ge$").replace("$100k", "\\$100k"))218        lines.append(219            f"{label} & {x['gamma_fe']:+.3f}{_stars(x['gamma_fe'], x['se_fe'])}"220            f" & ({_f(x['se_fe'])}) & "221            f"{x['gamma_iv']:+.3f}{_stars(x['gamma_iv'], x['se_iv'])} & "222            f"({_f(x['se_iv'])}) & {x['n']:,.0f} \\\\")223    lines += [r"\bottomrule", r"\end{tabular}"]224    (TAB / "robustness.tex").write_text("\n".join(lines))225226227# ---------------------------------------------------------------- revision tables (2026-10-17)228def _pct(x, nd=1):229    return f"{100 * x:.{nd}f}"230231232def t_rev_trim_grid():233    g = pd.read_csv(RES / "rev_trim_grid.csv")234    short = {"baseline": "Baseline (1/99 pct by vintage)",235             "3iqr": r"3 $\times$ IQR within cell",236             "1.5iqr": r"1.5 $\times$ IQR within cell (IAAO)",237             "2.5pct": "2.5/97.5 pct within cell",238             "5pct": "5/95 pct within cell"}239    lines = [r"\begin{tabular}{lrccccc}", r"\toprule",240             r"\multicolumn{7}{l}{\itshape Panel A. Province-wide statistics and within-cell elasticities}\\",241             r"\midrule",242             r"Outlier rule & \makecell{Dropped\\(\%)} & COD & PRD & PRB & "243             r"$\gamma_{\text{FE}}$ & $\gamma_{\text{IV}}$ \\", r"\midrule"]244    for _, r in g.iterrows():245        lines.append(246            f"{short[r['rule']]} & {_pct(r['share_dropped'])} & {_f(r['cod'], 1)} & {_f(r['prd'])} & "247            f"{r['prb']:+.3f} & {r['gamma_fe']:+.3f}{_stars(r['gamma_fe'], r['se_fe'])} & "248            f"{r['gamma_iv']:+.3f}{_stars(r['gamma_iv'], r['se_iv'])} \\\\")249        lines.append(f" & & & & & ({_f(r['se_fe'])}) & ({_f(r['se_iv'])}) \\\\")250    lines += [r"\midrule",251              r"\multicolumn{7}{l}{\itshape Panel B. Tax shift and municipal compliance}\\",252              r"\midrule",253              r"Outlier rule & \makecell{Decile 1\\(\%)} & \makecell{Decile 10\\(\%)} & "254              r"\makecell{Median\\muni.\ COD} & \makecell{COD $>$ 15\\(\%)} & "255              r"\makecell{PRB $<$ 0\\(\%)} & \makecell{PRB $<-0.05$\\(\%)} \\", r"\midrule"]256    for _, r in g.iterrows():257        lines.append(258            f"{short[r['rule']]} & {100 * r['d1_median']:+.1f} & {100 * r['d10_median']:+.1f} & "259            f"{_f(r['median_muni_cod'], 1)} & {_pct(r['share_muni_cod_gt_15'], 0)} & "260            f"{_pct(r['share_muni_prb_neg'], 0)} & {_pct(r['share_muni_prb_lt_m05'], 0)} \\\\")261    lines += [r"\bottomrule", r"\end{tabular}"]262    (TAB / "rev_trim_grid.tex").write_text("\n".join(lines))263264265def t_rev_spline():266    s = pd.read_csv(RES / "rev_spline.csv")267    lines = [r"\begin{tabular}{lcccr}", r"\toprule",268             r"\makecell[l]{Within-cell price\\quintile} & "269             r"\makecell{Range of demeaned\\$\ln SP$} & $\beta$ & $\gamma = \beta - 1$ & $n$ \\",270             r"\midrule"]271    for _, r in s.iterrows():272        lo = f"$< {r['hi']:.2f}$" if r['segment'] == 1 else (273            f"$\\ge {r['lo']:.2f}$" if r['segment'] == len(s) else274            f"$[{r['lo']:.2f},\\ {r['hi']:.2f})$")275        lines.append(f"{int(r['segment'])} & {lo} & {_f(r['beta'])}{_stars(r['beta'] - 1, r['se'])} "276                     f"({_f(r['se'])}) & {r['gamma']:+.3f} & {r['n']:,.0f} \\\\")277    lines += [r"\bottomrule", r"\end{tabular}"]278    (TAB / "rev_spline.tex").write_text("\n".join(lines))279280281def t_rev_taxshift_alt():282    t = pd.read_csv(RES / "rev_taxshift_alt.csv")283    o = pd.read_csv(RES / "rev_outlier_by_decile.csv").set_index("decile")["share_outlier"]284    cols = [("sale price", "cell median ratio"), ("sale price", "revenue-neutral (aggregate ratio)"),285            ("IAAO blended value proxy", "cell median ratio"),286            ("IAAO blended value proxy", "revenue-neutral (aggregate ratio)"),287            ("assessed value", "cell median ratio"), ("assessed value", "revenue-neutral (aggregate ratio)")]288    lines = [r"\begin{tabular}{lcccccc|c}", r"\toprule",289             r" & \multicolumn{2}{c}{Sorted on sale price} & \multicolumn{2}{c}{Sorted on blended proxy} & "290             r"\multicolumn{2}{c|}{Sorted on assessed value} & \\",291             r"\cmidrule(lr){2-3}\cmidrule(lr){4-5}\cmidrule(lr){6-7}",292             r"Decile & Median & Aggregate & Median & Aggregate & Median & Aggregate & "293             r"\makecell{IQR outliers\\(\%)} \\", r"\midrule"]294    for dec in range(1, 11):295        vals = []296        for sort, bench in cols:297            v = t[(t["sort"] == sort) & (t["benchmark"] == bench) & (t["decile"] == dec)]["median_rel"].iloc[0]298            vals.append(f"{100 * v:+.1f}")299        lines.append(f"{dec} & " + " & ".join(vals) + f" & {100 * o.loc[dec]:.1f} \\\\")300    lines += [r"\bottomrule", r"\end{tabular}"]301    (TAB / "rev_taxshift_alt.tex").write_text("\n".join(lines))302303304def t_rev_prb_blocks():305    a = pd.read_csv(RES / "rev_prb_blocks.csv")306    b = pd.read_csv(RES / "rev_prb_blocks_trimmed.csv")307    pw_a = pd.read_csv(RES / "iaao_overall.csv").set_index("group")["prb"]308    pw_b = pd.read_csv(RES / "rev_iaao_year_trimmed.csv").set_index("year")["prb"]309    lines = [r"\begin{tabular}{lrccccc}", r"\toprule",310             r"Blocks & $n$ blocks & \makecell{Median\\ratio} & \makecell{Median\\COD} & "311             r"\makecell{Median\\PRB} & \makecell{Share\\PRB $<$ 0} & \makecell{Province-\\wide PRB} \\"]312    for title, d, pw in (("Panel A. Baseline sample (1/99 trim by roll vintage)", a, pw_a),313                         ("Panel B. IAAO 1.5 $\\times$ IQR trimmed sample", b, pw_b)):314        lines += [r"\midrule", r"\multicolumn{7}{l}{\itshape " + title + r"}\\", r"\midrule"]315        for _, r in d.iterrows():316            g = str(r["group"])317            pwv = pw.get(g, pw.get(int(g)) if g.isdigit() else None) if g.isdigit() else None318            pw_txt = f"{pwv:+.3f}" if pwv is not None and pd.notna(pwv) else "---"319            label = g.replace("<", "$<$").replace(">", "$>$").replace("–", "--")320            lines.append(f"{label} & {r['n_blocks']:,.0f} & {_f(r['median_ratio'])} & {_f(r['median_cod'], 1)} & "321                         f"{r['median_prb']:+.3f} & {_f(r['share_prb_neg'], 2)} & {pw_txt} \\\\")322    lines += [r"\bottomrule", r"\end{tabular}"]323    (TAB / "rev_prb_blocks.tex").write_text("\n".join(lines))324325326def t_rev_cities():327    a = pd.read_csv(RES / "rev_cities.csv").rename(columns={"city": "muni"})328    b = pd.read_csv(RES / "rev_cities_trimmed.csv")329    m = a.merge(b, on="muni", suffixes=("", "_tr")).sort_values("n", ascending=False)330    lines = [r"\begin{tabular}{lrcccccccc}", r"\toprule",331             r" & & & \multicolumn{2}{c}{Baseline sample} & \multicolumn{5}{c}{IAAO 1.5 $\times$ IQR trimmed} \\",332             r"\cmidrule(lr){4-5}\cmidrule(lr){6-10}",333             r"Municipality & $n$ & \makecell{Condo\\share} & $\gamma_{\text{FE}}$ & $\gamma_{\text{IV}}$ & "334             r"COD & PRD & PRB & $\gamma_{\text{FE}}$ & $\gamma_{\text{IV}}$ \\", r"\midrule"]335    for _, r in m.iterrows():336        lines.append(337            f"{r['muni']} & {r['n']:,.0f} & {_f(r['share_condo'], 2)} & "338            f"{r['gamma_fe']:+.3f} & {r['gamma_iv']:+.3f} & {_f(r['cod'], 1)} & {_f(r['prd'])} & "339            f"{r['prb']:+.3f} & {r['gamma_fe_tr']:+.3f} & {r['gamma_iv_tr']:+.3f} \\\\")340    lines += [r"\bottomrule", r"\end{tabular}"]341    (TAB / "rev_cities.tex").write_text("\n".join(lines))342343344def t_rev_het_trimmed():345    h = pd.read_csv(RES / "heterogeneity.csv").set_index("group")346    t = pd.read_csv(RES / "rev_het_trimmed.csv")347    cl = pd.read_csv(RES / "rev_clusters.csv").set_index("group")["n_clusters"]348    lines = [r"\begin{tabular}{lccccr}", r"\toprule",349             r" & Baseline & \multicolumn{3}{c}{IAAO 1.5 $\times$ IQR trimmed} & \\",350             r"\cmidrule(lr){2-2}\cmidrule(lr){3-5}",351             r"Subsample & $\gamma_{\text{FE}}$ & $\gamma_{\text{FE}}$ & Std.\ error & $\gamma_{\text{IV}}$ & "352             r"\makecell{Clusters\\(munic.)} \\", r"\midrule"]353    for _, r in t.iterrows():354        g = r["group"]355        base = h["gamma"].get(g, np.nan) if g in h.index else np.nan356        base_txt = f"{base:+.3f}" if pd.notna(base) else "---"357        ncl = f"{int(cl[g]):,}" if g in cl.index else "---"358        label = g.replace("<", "$<$").replace(">", "$>$").replace("–", "--")359        lines.append(f"{label} & {base_txt} & {r['gamma']:+.3f}{_stars(r['gamma'], r['se'])} & "360                     f"({_f(r['se'])}) & {r['gamma_iv']:+.3f} & {ncl} \\\\")361    lines += [r"\bottomrule", r"\end{tabular}"]362    (TAB / "rev_het_trimmed.tex").write_text("\n".join(lines))363364365def t_rev_bounds():366    b = pd.read_csv(RES / "rev_bounds.csv").set_index("quantity")["value"]367    rows = [368        (r"Within-cell variance of $\ln SP$", _f(b["var_lnP_within"])),369        (r"Within-cell variance of $\ln AV$", _f(b["var_lnAV_within"])),370        (r"Within-cell covariance", _f(b["cov_within"])),371        (r"Within-cell correlation", _f(b["corr_within"])),372        (r"Forward slope $\hat\beta_{F}$ (Cheng FE)", _f(b["beta_forward"])),373        (r"Inverse reverse slope $1/\hat\beta_{R}$ (Kochin--Parks)", _f(b["beta_reverse_inverse"])),374        (r"Noise share $\lambda$ rationalising $\beta = 1$", _f(b["lambda_for_beta_1"])),375        (r"Implied transaction-noise s.d.\ for $\beta = 1$ (log points)", _f(b["noise_sd_for_beta_1"])),376        (r"Clapp IV $\hat\beta_{IV}$", _f(b["beta_iv"])),377        (r"Noise share $\lambda$ implied by $\hat\beta_{IV}$", _f(b["lambda_implied_by_iv"])),378        (r"Implied transaction-noise s.d.\ under $\hat\beta_{IV}$ (log points)", _f(b["noise_sd_implied_by_iv"])),379        (r"First stage: coefficient on $Z$ (s.e.)", f"{_f(b['first_stage_coef'])} ({_f(b['first_stage_se'])})"),380        (r"First stage: cluster-robust $F$", f"{b['first_stage_F_cluster']:,.0f}"),381        (r"First stage: $R^2$", _f(b["first_stage_r2"])),382        (r"Share of sales with $Z = -1$ / $Z = +1$", f"{_f(b['share_z_minus1'])} / {_f(b['share_z_plus1'])}"),383    ]384    lines = [r"\begin{tabular}{lc}", r"\toprule", r"Quantity & Value \\", r"\midrule"]385    lines += [f"{k} & {v} \\\\" for k, v in rows]386    lines += [r"\bottomrule", r"\end{tabular}"]387    (TAB / "rev_bounds.tex").write_text("\n".join(lines))388389390def t_rev_cod():391    c = pd.read_csv(RES / "rev_cod.csv")392    short = {"Municipality x year blocks (baseline)": r"Muni.\ $\times$ year blocks (baseline)",393             "Municipality x quarter blocks": r"Muni.\ $\times$ quarter blocks",394             "Municipality x year, time-adjusted ratios": r"Muni.\ $\times$ year, time-adjusted",395             "Province-wide, time-adjusted ratios": "Province-wide, time-adjusted"}396    lines = [r"\begin{tabular}{lrccccc}", r"\toprule",397             r"Time-control scheme & Munis & \makecell{Median\\COD} & "398             r"\makecell{COD\\IQR} & \makecell{COD $>$ 15\\(\%)} & \makecell{Median\\PRB} & "399             r"\makecell{PRB $<-0.05$\\(\%)} \\", r"\midrule"]400    for _, r in c.iterrows():401        lab = short.get(r["scheme"], r["scheme"])402        if pd.isna(r["n_munis"]):403            lines.append(f"{lab} & --- & {_f(r['median_cod'], 1)} & --- & --- & "404                         f"{r['median_prb']:+.3f} & --- \\\\")405        else:406            lines.append(f"{lab} & {r['n_munis']:,.0f} & "407                         f"{_f(r['median_cod'], 1)} & [{_f(r['p25_cod'], 1)}, {_f(r['p75_cod'], 1)}] & "408                         f"{_pct(r['share_cod_gt_15'], 0)} & {r['median_prb']:+.3f} & "409                         f"{_pct(r['share_prb_lt_m05'], 0)} \\\\")410    lines += [r"\bottomrule", r"\end{tabular}"]411    (TAB / "rev_cod.tex").write_text("\n".join(lines))412413414def main() -> None:415    config.ensure_dirs()416    t_summary()417    t_iaao()418    t_cities()419    t_vertical()420    t_heterogeneity()421    t_taxshift()422    t_horizontal()423    t_robustness()424    for fn in (t_rev_trim_grid, t_rev_spline, t_rev_taxshift_alt, t_rev_prb_blocks,425               t_rev_cities, t_rev_het_trimmed, t_rev_bounds, t_rev_cod):426        if (RES / 'rev_trim_grid.csv').exists():427            fn()428    made = sorted(p.name for p in TAB.glob("*.tex"))429    print(f"{len(made)} tables written:", ", ".join(made))430431432if __name__ == "__main__":433    main()434
Files · 9adbf6e50501616f413ea682834e467e3da0e5a5
Menu

Theme