SPB Git

Repository Public Research preview

05_make_tables.py 20.5 KB 457 lines python

1#!/usr/bin/env python32# Author: Simon-Pierre Boucher — contact@spboucher.ai3"""Step 05 — LaTeX tables.45Converts the step-02/03 CSVs into booktabs tables under6``results/tables/``; the paper inputs these files directly.78Usage:  python scripts/05_make_tables.py9"""10import sys11from pathlib import Path1213import pandas as pd1415sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))1617from wp11 import config  # noqa: E4021819RES = config.REPRODUCED20TAB = config.TABLES212223def _f(x, nd=3):24    return f"{x:,.{nd}f}"252627def _esc(s: str) -> str:28    return (s.replace("–", "--").replace("~", r"$\sim$")29            .replace("<", "$<$").replace(">", "$>$"))303132def t_summary():33    s = pd.read_csv(RES / "summary_stats.csv")34    lines = [r"\begin{tabular}{lrrrrrr}", r"\toprule",35             r"Variable & $n$ & Mean & SD & P10 & Median & P90 \\",36             r"\midrule"]37    for _, r in s.iterrows():38        nd = 0 if r["mean"] > 1000 else 239        label = (str(r["variable"]).replace("($)", "(\\$)")40                 .replace("(m2)", "(m$^2$)"))41        lines.append(42            f"{label} & {r['n']:,.0f} & {_f(r['mean'], nd)} & "43            f"{_f(r['sd'], nd)} & {_f(r['p10'], nd)} & {_f(r['p50'], nd)} & "44            f"{_f(r['p90'], nd)} \\\\")45    lines += [r"\bottomrule", r"\end{tabular}"]46    (TAB / "summary_stats.tex").write_text("\n".join(lines))474849def _race_table(fname_csv: str, fname_tex: str):50    r = pd.read_csv(RES / fname_csv)51    lines = [r"\begin{tabular}{lcccc c}", r"\toprule",52             r"Model & MdAPE (\%) & MAPE (\%) & RMSE$_{\ln}$ & "53             r"$R^2_{\ln}$ & Fit (s) \\"]54    for g in ["A. Functional form", "B. Time effects",55              "C. Spatial controls", "D. Estimation method"]:56        sub = r[r["group"] == g]57        if sub.empty:58            continue59        lines.append(r"\midrule")60        lines.append(r"\multicolumn{6}{l}{\itshape " + g + r"}\\")61        best = sub["mdape"].min()62        for _, x in sub.iterrows():63            name = _esc(str(x["name"]))64            md = f"\\textbf{{{_f(x['mdape'], 1)}}}" \65                if x["mdape"] == best else _f(x["mdape"], 1)66            lines.append(67                f"\\quad {name} & {md} & {_f(x['mape'], 1)} & "68                f"{_f(x['rmse_ln'])} & {_f(x['r2_ln'])} & "69                f"{_f(x['seconds'], 0)} \\\\")70    lines += [r"\bottomrule", r"\end{tabular}"]71    (TAB / fname_tex).write_text("\n".join(lines))727374def t_generalization():75    a = pd.read_csv(RES / "horserace_random.csv")76    b = pd.read_csv(RES / "horserace_temporal.csv")77    m = a.merge(b, on=["name", "group"], suffixes=("_r", "_t"))78    m = m[m["name"].str.match(r"(A5|B4|C4|D)")]79    lines = [r"\begin{tabular}{lcc c cc}", r"\toprule",80             r" & \multicolumn{2}{c}{Random holdout} & &"81             r" \multicolumn{2}{c}{Forward-in-time} \\",82             r"\cmidrule{2-3}\cmidrule{5-6}",83             r"Model & MdAPE (\%) & $R^2_{\ln}$ & & MdAPE (\%) & "84             r"$R^2_{\ln}$ \\", r"\midrule"]85    for _, x in m.iterrows():86        lines.append(87            f"{_esc(str(x['name']))} & {_f(x['mdape_r'], 1)} & "88            f"{_f(x['r2_ln_r'])} & & {_f(x['mdape_t'], 1)} & "89            f"{_f(x['r2_ln_t'])} \\\\")90    lines += [r"\bottomrule", r"\end{tabular}"]91    (TAB / "generalization.tex").write_text("\n".join(lines))929394def t_boxcox():95    b = pd.read_csv(RES / "boxcox.csv")96    lines = [r"\begin{tabular}{l" + "c" * len(b) + "}", r"\toprule",97             r"$\lambda$ & " + " & ".join(_f(x, 2) for x in b["lambda"])98             + r" \\", r"\midrule",99             r"Profile log-likelihood$^{\dagger}$ & "100             + " & ".join(f"{(x - b['loglik'].max())/1000:,.1f}"101                          for x in b["loglik"]) + r" \\",102             r"\bottomrule", r"\end{tabular}"]103    (TAB / "boxcox.tex").write_text("\n".join(lines))104105106def t_learning():107    l = pd.read_csv(RES / "learning.csv")108    piv = l.pivot_table(index="n_train", columns="model", values="mdape")109    cols = list(piv.columns)110    lines = [r"\begin{tabular}{r" + "c" * len(cols) + "}", r"\toprule",111             "Training sales & " + " & ".join(_esc(c) for c in cols) + r" \\",112             r"\midrule"]113    for n, row in piv.iterrows():114        lines.append(f"{n:,.0f} & "115                     + " & ".join(_f(v, 1) for v in row) + r" \\")116    lines += [r"\bottomrule", r"\end{tabular}"]117    (TAB / "learning.tex").write_text("\n".join(lines))118119120def t_segments():121    s = pd.read_csv(RES / "segments.csv")122    piv = s.pivot_table(index="segment", columns="model", values="mdape")123    ns = s.groupby("segment")["n"].first()124    lines = [r"\begin{tabular}{lrcc}", r"\toprule",125             r"Segment & $n$ (test) & OLS quadratic & Gradient boosting \\",126             r"\midrule"]127    for seg, row in piv.iterrows():128        label = _esc(str(seg)).replace("Class: ", "").replace("_", " ")129        lines.append(f"{label.capitalize()} & {ns[seg]:,.0f} & "130                     f"{_f(row['OLS quadratic'], 1)} & "131                     f"{_f(row['Gradient boosting'], 1)} \\\\")132    lines += [r"\bottomrule", r"\end{tabular}"]133    (TAB / "segments.tex").write_text("\n".join(lines))134135136# ---------------------------------------------------------------- revision 1.1137SHORT = {"A1 Linear (levels)": "A1 Linear", "A2 Semi-log": "A2 Semi-log",138         "A3 Log-log": "A3 Log-log", "A4 Box-Cox": "A4 Box--Cox",139         "A5 Semi-log + quadratics": "A5 Quadratics",140         "A6 Semi-log + splines": "A6 Splines",141         "B1 No time effects": "B1 No time FE", "B2 Year FE": "B2 Year FE",142         "B3 Quarter FE": "B3 Quarter FE", "B4 Month FE": "B4 Month FE",143         "C1 No spatial controls": "C1 No spatial FE",144         "C2 Municipality FE": "C2 Municipality FE",145         "C3 Grid-cell FE (~5.5 km)": "C3 Grid 5.5 km",146         "C4 Grid-cell FE (~1.1 km)": "C4 Grid 1.1 km",147         "D1 OLS (muni + month FE)": "D1 OLS", "D2 Ridge (cross-validated)":148         "D2 Ridge", "D3 Random forest": "D3 Random forest",149         "D4 Gradient boosting": "D4 Boosting",150         "D5 Gradient boosting, no coordinates": "D5 Boosting, no coord.",151         "D6 Spatial k-NN comparables": "D6 $k$-NN comparables"}152A6, D4 = "A6 Semi-log + splines", "D4 Gradient boosting"153154155def _stars(p):156    return "$^{***}$" if p < 0.01 else "$^{**}$" if p < 0.05 else \157        "$^{*}$" if p < 0.10 else ""158159160def t_inference():161    """MdAPE with cluster-bootstrap 95% CI, difference versus the spline162    hedonic (A6) with its CI, and the clustered DM t statistic on the APE163    loss differential, under both holdouts."""164    lines = [r"\begin{tabular}{l ccc c ccc}", r"\toprule",165             r" & \multicolumn{3}{c}{Random holdout} & &"166             r" \multicolumn{3}{c}{Forward-in-time holdout} \\",167             r"\cmidrule{2-4}\cmidrule{6-8}",168             r"Model & MdAPE [95\% CI] & $\Delta$ vs.\ A6 [95\% CI] & "169             r"$t_{\mathrm{DM}}$ & & MdAPE [95\% CI] & "170             r"$\Delta$ vs.\ A6 [95\% CI] & $t_{\mathrm{DM}}$ \\",171             r"\midrule"]172    R = {s: pd.read_csv(RES / f"inference_{s}.csv").set_index("model")173         for s in ("random", "temporal")}174    groups = {"A": "A. Functional form", "B": "B. Time effects",175              "C": "C. Spatial controls", "D": "D. Estimation method"}176    cur = None177    for m in SHORT:178        if m[0] != cur:179            cur = m[0]180            lines.append(r"\multicolumn{8}{l}{\itshape " + groups[cur]181                         + r"}\\")182        cells = [r"\quad " + SHORT[m]]183        for s in ("random", "temporal"):184            x = R[s].loc[m]185            cells.append(f"{x['mdape']:.1f} [{x['ci_lo']:.1f}, "186                         f"{x['ci_hi']:.1f}]")187            if m == A6:188                cells += ["---", "---"]189            else:190                cells.append(f"{x['diff_vs_ref']:+.1f} [{x['diff_lo']:+.1f}, "191                             f"{x['diff_hi']:+.1f}]")192                cells.append(f"{x['dm_ape_vs_a6_t']:.1f}"193                             + _stars(x['dm_ape_vs_a6_p']))194            if s == "random":195                cells.append("")196        lines.append(" & ".join(cells) + r" \\")197    lines += [r"\bottomrule", r"\end{tabular}"]198    (TAB / "inference.tex").write_text("\n".join(lines))199200201def t_retransformation():202    """Median-consistent versus Duan-smeared (mean-consistent) predictions."""203    sel = ["A2 Semi-log", "A3 Log-log", "A4 Box-Cox",204           "A5 Semi-log + quadratics", "A6 Semi-log + splines",205           "D2 Ridge (cross-validated)", "D3 Random forest",206           "D4 Gradient boosting"]207    R = {s: pd.read_csv(RES / f"horserace_{s}.csv").set_index("name")208         for s in ("random", "temporal")}209    lines = [r"\begin{tabular}{l c cc cc c cc cc}", r"\toprule",210             r" & & \multicolumn{4}{c}{Random holdout} & &"211             r" \multicolumn{4}{c}{Forward-in-time holdout} \\",212             r"\cmidrule{3-6}\cmidrule{8-11}",213             r" & & \multicolumn{2}{c}{MdAPE (\%)} & "214             r"\multicolumn{2}{c}{$R^2_{\ln}$} & & "215             r"\multicolumn{2}{c}{MdAPE (\%)} & "216             r"\multicolumn{2}{c}{$R^2_{\ln}$} \\",217             r"\cmidrule{3-4}\cmidrule{5-6}\cmidrule{8-9}\cmidrule{10-11}",218             r"Model & $\hat{s}$ & Median & Mean & Median & Mean & & "219             r"Median & Mean & Median & Mean \\", r"\midrule"]220    for m in sel:221        a, b = R["random"].loc[m], R["temporal"].loc[m]222        sm = "---" if pd.isna(a["smear"]) else f"{a['smear']:.3f}"223        lines.append(224            f"{SHORT[m]} & {sm} & {a['mdape']:.1f} & {a['mdape_mean']:.1f} & "225            f"{a['r2_ln']:.3f} & {a['r2_ln_mean']:.3f} & & "226            f"{b['mdape']:.1f} & {b['mdape_mean']:.1f} & "227            f"{b['r2_ln']:.3f} & {b['r2_ln_mean']:.3f} \\\\")228    lines += [r"\bottomrule", r"\end{tabular}"]229    (TAB / "retransformation.tex").write_text("\n".join(lines))230231232def t_same_period():233    sp = pd.read_csv(RES / "same_period.csv").set_index("model")234    rl = pd.read_csv(RES / "relevel.csv")235    rl = rl[rl["scheme"] == "temporal"].set_index("model")236    sel = ["A1 Linear (levels)", "A5 Semi-log + quadratics",237           "A6 Semi-log + splines", "B4 Month FE",238           "C3 Grid-cell FE (~5.5 km)", "C4 Grid-cell FE (~1.1 km)",239           "D2 Ridge (cross-validated)", "D3 Random forest",240           "D4 Gradient boosting", "D5 Gradient boosting, no coordinates",241           "D6 Spatial k-NN comparables"]242    lines = [r"\begin{tabular}{l cc c c c c}", r"\toprule",243             r" & \multicolumn{2}{c}{Random split} & Forward split & "244             r"Staleness & Level error & Re-levelled \\",245             r"\cmidrule{2-3}\cmidrule{4-4}",246             r"Model & All test sales & 2025--26 sales & 2025--26 sales & "247             r"cost & $\hat{b}$ & MdAPE \\", r"\midrule"]248    for m in sel:249        x = sp.loc[m]250        lines.append(f"{SHORT[m]} & {x['mdape_random_all']:.1f} & "251                     f"{x['mdape_random_2025_26']:.1f} & "252                     f"{x['mdape_forward_2025_26']:.1f} & "253                     f"{x['mdape_forward_2025_26'] - x['mdape_random_2025_26']:+.1f} & "254                     f"{rl.loc[m, 'median_log_resid']:+.3f} & "255                     f"{rl.loc[m, 'mdape_relevelled']:.1f} \\\\")256    n1, n2 = int(sp["n_random_2025_26"].iloc[0]), int(sp["n_forward"].iloc[0])257    lines += [r"\midrule", f"Test sales & 102,929 & {n1:,} & {n2:,} & & & \\\\",258              r"\bottomrule", r"\end{tabular}"]259    (TAB / "same_period.tex").write_text("\n".join(lines))260261262def t_pairs():263    pr = pd.read_csv(RES / "pairs.csv")264    lines = [r"\begin{tabular}{ll c cc c cc}", r"\toprule",265             r" & & & \multicolumn{2}{c}{Random holdout} & &"266             r" \multicolumn{2}{c}{Forward-in-time} \\",267             r"\cmidrule{4-5}\cmidrule{7-8}",268             r"Comparison & & MdAPE & $\Delta$MdAPE [95\% CI] & "269             r"$t_{\mathrm{DM}}$ APE / sq.\ log & & "270             r"$\Delta$MdAPE [95\% CI] & $t_{\mathrm{DM}}$ APE / sq.\ log \\",271             r"\midrule"]272    pairs = list(dict.fromkeys(zip(pr["model_a"], pr["model_b"])))273    for a, b in pairs:274        r = pr[(pr["model_a"] == a) & (pr["model_b"] == b)].set_index("scheme")275        x, y = r.loc["random"], r.loc["temporal"]276        cells = [SHORT[a] + " vs.", SHORT[b],277                 f"{x['mdape_a']:.1f} / {x['mdape_b']:.1f}",278                 f"{x['diff']:+.2f} [{x['diff_lo']:+.2f}, {x['diff_hi']:+.2f}]",279                 f"{x['dm_ape_t']:.1f}{_stars(x['dm_ape_p'])} / "280                 f"{x['dm_sqlog_t']:.1f}{_stars(x['dm_sqlog_p'])}", "",281                 f"{y['diff']:+.2f} [{y['diff_lo']:+.2f}, {y['diff_hi']:+.2f}]",282                 f"{y['dm_ape_t']:.1f}{_stars(y['dm_ape_p'])} / "283                 f"{y['dm_sqlog_t']:.1f}{_stars(y['dm_sqlog_p'])}"]284        lines.append(" & ".join(cells) + r" \\")285    lines += [r"\bottomrule", r"\end{tabular}"]286    (TAB / "pairs.tex").write_text("\n".join(lines))287288289def t_group_splits():290    R = {s: pd.read_csv(RES / f"horserace_{s}.csv").set_index("name")291         for s in ("random", "group_parcel", "group_coord")}292    lines = [r"\begin{tabular}{l cc c cc c cc}", r"\toprule",293             r" & \multicolumn{2}{c}{Random 80/20} & &"294             r" \multicolumn{2}{c}{Parcel-grouped} & &"295             r" \multicolumn{2}{c}{Building-grouped} \\",296             r"\cmidrule{2-3}\cmidrule{5-6}\cmidrule{8-9}",297             r"Model & MdAPE & $R^2_{\ln}$ & & MdAPE & $R^2_{\ln}$ & & "298             r"MdAPE & $R^2_{\ln}$ \\", r"\midrule"]299    groups = {"A": "A. Functional form", "B": "B. Time effects",300              "C": "C. Spatial controls", "D": "D. Estimation method"}301    cur = None302    for m in SHORT:303        if m[0] != cur:304            cur = m[0]305            lines.append(r"\multicolumn{9}{l}{\itshape " + groups[cur]306                         + r"}\\")307        cells = [r"\quad " + SHORT[m]]308        for i, s in enumerate(("random", "group_parcel", "group_coord")):309            x = R[s].loc[m]310            cells += [f"{x['mdape']:.1f}", f"{x['r2_ln']:.3f}"]311            if i < 2:312                cells.append("")313        lines.append(" & ".join(cells) + r" \\")314    ns = [f"{int(R[s]['n_test'].iloc[0]):,}" for s in315          ("random", "group_parcel", "group_coord")]316    lines += [r"\midrule",317              f"Test sales & {ns[0]} & & & {ns[1]} & & & {ns[2]} & \\\\",318              r"\bottomrule", r"\end{tabular}"]319    (TAB / "group_splits.tex").write_text("\n".join(lines))320321322def t_leakage_decomp():323    L = pd.read_csv(RES / "leakage.csv")324    sel = ["A5 Semi-log + quadratics", "A6 Semi-log + splines",325           "D3 Random forest", "D4 Gradient boosting",326           "D5 Gradient boosting, no coordinates",327           "D6 Spatial k-NN comparables"]328    lines = [r"\begin{tabular}{l cc c cc}", r"\toprule",329             r" & \multicolumn{2}{c}{Same parcel in training} & &"330             r" \multicolumn{2}{c}{Same coordinates in training} \\",331             r"\cmidrule{2-3}\cmidrule{5-6}",332             r"Model & Yes & No & & Yes & No \\", r"\midrule"]333    def cell(key, flag, m, seg="all"):334        x = L[(L["key"] == key) & (L["seen_in_training"] == flag)335              & (L["model"] == m) & (L["segment"] == seg)]336        return f"{x['mdape'].iloc[0]:.1f}" if len(x) else "---"337    def n(key, flag, seg="all"):338        x = L[(L["key"] == key) & (L["seen_in_training"] == flag)339              & (L["segment"] == seg)]340        return f"{int(x['n'].iloc[0]):,}"341    lines.append(r"\multicolumn{6}{l}{\itshape All test sales}\\")342    for m in sel:343        lines.append(f"\\quad {SHORT[m]} & {cell('in_train_parcel', True, m)} & "344                     f"{cell('in_train_parcel', False, m)} & & "345                     f"{cell('in_train_coord', True, m)} & "346                     f"{cell('in_train_coord', False, m)} \\\\")347    lines.append(f"\\quad Test sales & {n('in_train_parcel', True)} & "348                 f"{n('in_train_parcel', False)} & & {n('in_train_coord', True)} & "349                 f"{n('in_train_coord', False)} \\\\")350    lines.append(r"\midrule")351    lines.append(r"\multicolumn{6}{l}{\itshape Condominiums only}\\")352    for m in ("A5 Semi-log + quadratics", "D4 Gradient boosting"):353        lines.append(f"\\quad {SHORT[m]} & {cell('in_train_parcel', True, m, 'condo')} & "354                     f"{cell('in_train_parcel', False, m, 'condo')} & & "355                     f"{cell('in_train_coord', True, m, 'condo')} & "356                     f"{cell('in_train_coord', False, m, 'condo')} \\\\")357    lines.append(f"\\quad Test sales & {n('in_train_parcel', True, 'condo')} & "358                 f"{n('in_train_parcel', False, 'condo')} & & "359                 f"{n('in_train_coord', True, 'condo')} & "360                 f"{n('in_train_coord', False, 'condo')} \\\\")361    lines += [r"\bottomrule", r"\end{tabular}"]362    (TAB / "leakage.tex").write_text("\n".join(lines))363364365def t_nested_fe():366    N = pd.read_csv(RES / "nested_fe.csv")367    R = {s: pd.read_csv(RES / f"horserace_{s}.csv").set_index("name")368         for s in ("random", "temporal")}369    rows = [("C2 Municipality FE", "C2 Municipality FE (1,115 cells)"),370            ("C3 Grid-cell FE (~5.5 km)", "C3 Grid 5.5 km (5,255 cells)"),371            ("C4 Grid-cell FE (~1.1 km)", "C4 Grid 1.1 km (30,548 cells)"),372            ("C3' Municipality + grid-cell FE (~5.5 km)",373             "C3$'$ Municipality + grid 5.5 km"),374            ("C4' Municipality + grid-cell FE (~1.1 km)",375             "C4$'$ Municipality + grid 1.1 km")]376    lines = [r"\begin{tabular}{l cc c cc}", r"\toprule",377             r" & \multicolumn{2}{c}{Random holdout} & &"378             r" \multicolumn{2}{c}{Forward-in-time} \\",379             r"\cmidrule{2-3}\cmidrule{5-6}",380             r"Spatial controls & MdAPE (\%) & $R^2_{\ln}$ & & MdAPE (\%) & "381             r"$R^2_{\ln}$ \\", r"\midrule"]382    for key, label in rows:383        vals = []384        for s in ("random", "temporal"):385            if key in R[s].index:386                x = R[s].loc[key]387            else:388                x = N[(N["name"] == key) & (N["scheme"] == s)].iloc[0]389            vals += [f"{x['mdape']:.1f}", f"{x['r2_ln']:.3f}"]390        lines.append(f"{label} & {vals[0]} & {vals[1]} & & {vals[2]} & "391                     f"{vals[3]} \\\\")392    lines += [r"\bottomrule", r"\end{tabular}"]393    (TAB / "nested_fe.tex").write_text("\n".join(lines))394395396def t_hgb_sensitivity():397    H = pd.read_csv(RES / "hgb_sensitivity.csv")398    names = list(dict.fromkeys(H["name"]))399    lines = [r"\begin{tabular}{l cc c cc}", r"\toprule",400             r" & \multicolumn{2}{c}{Random holdout} & &"401             r" \multicolumn{2}{c}{Forward-in-time} \\",402             r"\cmidrule{2-3}\cmidrule{5-6}",403             r"Gradient-boosting configuration & MdAPE (\%) & $R^2_{\ln}$ & & "404             r"MdAPE (\%) & $R^2_{\ln}$ \\", r"\midrule"]405    for nm in names:406        a = H[(H["name"] == nm) & (H["scheme"] == "random")].iloc[0]407        b = H[(H["name"] == nm) & (H["scheme"] == "temporal")].iloc[0]408        label = _esc(nm.replace("D4 ", "").replace("%", r"\%"))409        label = label[0].upper() + label[1:]410        if not pd.isna(b.get("best_iter", float("nan"))):411            label += f" [{int(a['best_iter'])} / {int(b['best_iter'])} it.]"412        lines.append(f"{label} & {a['mdape']:.1f} & {a['r2_ln']:.3f} & & "413                     f"{b['mdape']:.1f} & {b['r2_ln']:.3f} \\\\")414    lines += [r"\bottomrule", r"\end{tabular}"]415    (TAB / "hgb_sensitivity.tex").write_text("\n".join(lines))416417418def t_by_quarter():419    Q = pd.read_csv(RES / "by_quarter.csv")420    piv = Q.pivot_table(index="quarter", columns="model", values="mdape")421    ns = Q.groupby("quarter")["n"].first()422    cols = ["A5 Semi-log + quadratics", "A6 Semi-log + splines",423            "D2 Ridge (cross-validated)", "D3 Random forest",424            "D4 Gradient boosting"]425    lines = [r"\begin{tabular}{lr" + "c" * len(cols) + "}", r"\toprule",426             r"Test quarter & $n$ & " + " & ".join(SHORT[c] for c in cols)427             + r" \\", r"\midrule"]428    for q, row in piv.iterrows():429        lines.append(f"{q} & {ns[q]:,.0f} & "430                     + " & ".join(f"{row[c]:.1f}" for c in cols) + r" \\")431    lines += [r"\bottomrule", r"\end{tabular}"]432    (TAB / "by_quarter.tex").write_text("\n".join(lines))433434435def main() -> None:436    config.ensure_dirs()437    t_summary()438    _race_table("horserace_random.csv", "horserace_random.tex")439    _race_table("horserace_temporal.csv", "horserace_temporal.tex")440    t_generalization()441    t_boxcox()442    t_learning()443    t_segments()444    for fn in (t_inference, t_retransformation, t_same_period, t_pairs,445               t_group_splits, t_leakage_decomp, t_nested_fe,446               t_hgb_sensitivity, t_by_quarter):447        try:448            fn()449        except FileNotFoundError as e:   # step 06 not run yet450            print(f"  skipped {fn.__name__}: {e}")451    made = sorted(p.name for p in TAB.glob("*.tex"))452    print(f"{len(made)} tables written:", ", ".join(made))453454455if __name__ == "__main__":456    main()457
Files ee13d5eb5ea78768e48162f402a0c16de2539b97
Menu

Theme