|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | """ | 
					
						
						|  | Parse *_NB_single_dataset.txt reports and emit LaTeX tables. | 
					
						
						|  |  | 
					
						
						|  | Now supports: | 
					
						
						|  | • Full "everything" longtable (as before) | 
					
						
						|  | • Option B: TWO COMPACT portrait tables (RMSE table + MAE table) | 
					
						
						|  |  | 
					
						
						|  | Examples | 
					
						
						|  | -------- | 
					
						
						|  | # Make compact + full tables for all reports in a dir | 
					
						
						|  | python3 make_nb_tables_from_reports.py --in ./plotting --out ./plotting/tex | 
					
						
						|  |  | 
					
						
						|  | # Only compact tables | 
					
						
						|  | python3 make_nb_tables_from_reports.py --in ./plotting --out ./plotting/tex --compact_only | 
					
						
						|  |  | 
					
						
						|  | # Specific files | 
					
						
						|  | python3 make_nb_tables_from_reports.py \ | 
					
						
						|  | --files ./plotting/qm9_NB_single_dataset.txt ./plotting/boilingpoint_NB_single_dataset.txt \ | 
					
						
						|  | --out ./plotting/tex --compact_only | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  | import argparse | 
					
						
						|  | import re | 
					
						
						|  | from pathlib import Path | 
					
						
						|  | from typing import Dict, List, Tuple | 
					
						
						|  | import pandas as pd | 
					
						
						|  |  | 
					
						
						|  | NUM = r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def esc_tex(s: str) -> str: | 
					
						
						|  | return s.replace("_", r"\_") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def parse_header(lines: List[str]) -> Dict[str, str]: | 
					
						
						|  | out = {} | 
					
						
						|  | for ln in lines[:60]: | 
					
						
						|  | m = re.search(r"^Dataset:\s*(.+?)\s+—", ln) | 
					
						
						|  | if m: | 
					
						
						|  | out["dataset"] = m.group(1).strip() | 
					
						
						|  | m = re.search(r"^Control exp_id:\s*(.+)$", ln) | 
					
						
						|  | if m: | 
					
						
						|  | out["control"] = m.group(1).strip() | 
					
						
						|  | m = re.search(r"^k folds:\s*(\d+),\s*alpha:\s*(" + NUM + r")", ln) | 
					
						
						|  | if m: | 
					
						
						|  | out["k"] = int(m.group(1)) | 
					
						
						|  | out["alpha"] = float(m.group(2)) | 
					
						
						|  | return out | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def find_block(lines: List[str], start_pat: str) -> Tuple[int, int]: | 
					
						
						|  | start = -1 | 
					
						
						|  | pat = re.compile(start_pat) | 
					
						
						|  | for i, ln in enumerate(lines): | 
					
						
						|  | if pat.search(ln): | 
					
						
						|  | start = i | 
					
						
						|  | break | 
					
						
						|  | if start < 0: | 
					
						
						|  | return (-1, -1) | 
					
						
						|  | end = len(lines) | 
					
						
						|  | for j in range(start + 1, len(lines)): | 
					
						
						|  | if not lines[j].strip(): | 
					
						
						|  | end = j | 
					
						
						|  | break | 
					
						
						|  | return (start, end) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def parse_nb_block(lines: List[str]) -> pd.DataFrame: | 
					
						
						|  | hdr_idx = None | 
					
						
						|  | for i, ln in enumerate(lines): | 
					
						
						|  | if re.search(r"^\s*comparison\s+", ln) and "mean_diff_RMSE" in ln: | 
					
						
						|  | hdr_idx = i | 
					
						
						|  | break | 
					
						
						|  | if hdr_idx is None: | 
					
						
						|  | return pd.DataFrame() | 
					
						
						|  |  | 
					
						
						|  | data_lines = [] | 
					
						
						|  | for ln in lines[hdr_idx + 1 :]: | 
					
						
						|  | s = ln.rstrip() | 
					
						
						|  | if not s: | 
					
						
						|  | break | 
					
						
						|  | if set(s) <= set("- "): | 
					
						
						|  | continue | 
					
						
						|  | data_lines.append(s) | 
					
						
						|  |  | 
					
						
						|  | rows = [] | 
					
						
						|  | for ln in data_lines: | 
					
						
						|  |  | 
					
						
						|  | m = re.match(r"^\s*(.+?)\s+(" + NUM + r"(?:\s+" + NUM + r"){9})\s*$", ln) | 
					
						
						|  | if not m: | 
					
						
						|  | parts = re.split(r"\s{2,}", ln.strip()) | 
					
						
						|  | if len(parts) < 11: | 
					
						
						|  | continue | 
					
						
						|  | comp, *nums = parts | 
					
						
						|  | else: | 
					
						
						|  | comp = m.group(1).strip() | 
					
						
						|  | nums = re.split(r"\s+", m.group(2).strip()) | 
					
						
						|  | if len(nums) != 10: | 
					
						
						|  | continue | 
					
						
						|  | vals = list(map(float, nums)) | 
					
						
						|  | rows.append( | 
					
						
						|  | { | 
					
						
						|  | "comparison": comp, | 
					
						
						|  | "mean_diff_RMSE": vals[0], | 
					
						
						|  | "t_NB_RMSE": vals[1], | 
					
						
						|  | "p_raw_RMSE": vals[2], | 
					
						
						|  | "mean_diff_MAE": vals[3], | 
					
						
						|  | "t_NB_MAE": vals[4], | 
					
						
						|  | "p_raw_MAE": vals[5], | 
					
						
						|  | "CI_RMSE_low": vals[6], | 
					
						
						|  | "CI_RMSE_high": vals[7], | 
					
						
						|  | "CI_MAE_low": vals[8], | 
					
						
						|  | "CI_MAE_high": vals[9], | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  | return pd.DataFrame(rows) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def parse_holm_block(lines: List[str], metric_label: str) -> pd.DataFrame: | 
					
						
						|  | hdr = None | 
					
						
						|  | for i, ln in enumerate(lines): | 
					
						
						|  | if re.search(r"^\s*comparison\s+", ln) and "p_raw" in ln and "p_holm" in ln: | 
					
						
						|  | hdr = i | 
					
						
						|  | break | 
					
						
						|  | if hdr is None: | 
					
						
						|  | return pd.DataFrame() | 
					
						
						|  |  | 
					
						
						|  | data_lines = [] | 
					
						
						|  | for ln in lines[hdr + 1 :]: | 
					
						
						|  | s = ln.rstrip() | 
					
						
						|  | if not s: | 
					
						
						|  | break | 
					
						
						|  | if set(s) <= set("- "): | 
					
						
						|  | continue | 
					
						
						|  | data_lines.append(s) | 
					
						
						|  |  | 
					
						
						|  | rows = [] | 
					
						
						|  | for ln in data_lines: | 
					
						
						|  | m = re.match(r"^\s*(.+?)\s+(" + NUM + r")\s+(" + NUM + r")\s+(\w+)\s*$", ln) | 
					
						
						|  | if m: | 
					
						
						|  | comp = m.group(1).strip() | 
					
						
						|  | p_raw = float(m.group(2)) | 
					
						
						|  | p_holm = float(m.group(3)) | 
					
						
						|  | sig = m.group(4).strip().lower().startswith("t") | 
					
						
						|  | else: | 
					
						
						|  | parts = re.split(r"\s{2,}", ln.strip()) | 
					
						
						|  | if len(parts) < 4: | 
					
						
						|  | continue | 
					
						
						|  | comp = parts[0].strip() | 
					
						
						|  | p_holm = float(parts[2]) | 
					
						
						|  | sig = parts[3].strip().lower().startswith("t") | 
					
						
						|  | rows.append( | 
					
						
						|  | { | 
					
						
						|  | "comparison": comp, | 
					
						
						|  | f"p_holm_{metric_label}": p_holm, | 
					
						
						|  | f"sig_{metric_label}": sig, | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  | return pd.DataFrame(rows) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def write_full_longtable(meta: Dict[str, str], df: pd.DataFrame, outpath: Path): | 
					
						
						|  | dataset = meta.get("dataset", "dataset") | 
					
						
						|  | control = meta.get("control", "control") | 
					
						
						|  | k = int(meta.get("k", 5)) | 
					
						
						|  | alpha = float(meta.get("alpha", 0.05)) | 
					
						
						|  |  | 
					
						
						|  | header = rf""" | 
					
						
						|  | % Auto-generated from NB_single_dataset report | 
					
						
						|  | % Requires: \usepackage{{booktabs,longtable,siunitx}} | 
					
						
						|  | \begin{{longtable}}{{@{{}}l | 
					
						
						|  | S S S S S S S | 
					
						
						|  | S | 
					
						
						|  | S S S S S S S | 
					
						
						|  | @{{}}}} | 
					
						
						|  | \caption{{Full NB-corrected one-sided tests (outer folds, $K={k}$, $\alpha={alpha}$) comparing control \texttt{{{esc_tex(control)}}} against each competitor on dataset \texttt{{{esc_tex(dataset)}}}. | 
					
						
						|  | Positive $\Delta$ (competitor $-$ control) favors the control (lower loss). | 
					
						
						|  | One-sided alternative $\mathbb{{E}}[\bar d^{{(L)}}] > 0$. | 
					
						
						|  | Holm step-down controls FWER per metric family (RMSE and MAE reported separately). | 
					
						
						|  | CIs are NB-style two-sided.}} | 
					
						
						|  | \label{{tab:{esc_tex(dataset)}_nb_full}}\\ | 
					
						
						|  | \toprule | 
					
						
						|  | & \multicolumn{{7}}{{c}}{{\textbf{{RMSE family}}}} & & | 
					
						
						|  | \multicolumn{{7}}{{c}}{{\textbf{{MAE family}}}}\\ | 
					
						
						|  | \cmidrule(lr){{2-8}}\cmidrule(lr){{10-16}} | 
					
						
						|  | Comparison | 
					
						
						|  | & {{\(\Delta\)RMSE}} | 
					
						
						|  | & {{$t_{{\text{{NB}}}}$}} | 
					
						
						|  | & {{p (raw)}} | 
					
						
						|  | & {{p\(_{{\text{{Holm}}}}\)}} | 
					
						
						|  | & {{\(\text{{Sig}}\)}} | 
					
						
						|  | & {{CI\(_{{\text{{low}}}}\)}} | 
					
						
						|  | & {{CI\(_{{\text{{high}}}}\)}} | 
					
						
						|  | & {{}} % spacer | 
					
						
						|  | & {{\(\Delta\)MAE}} | 
					
						
						|  | & {{$t_{{\text{{NB}}}}$}} | 
					
						
						|  | & {{p (raw)}} | 
					
						
						|  | & {{p\(_{{\text{{Holm}}}}\)}} | 
					
						
						|  | & {{\(\text{{Sig}}\)}} | 
					
						
						|  | & {{CI\(_{{\text{{low}}}}\)}} | 
					
						
						|  | & {{CI\(_{{\text{{high}}}}\)}}\\ | 
					
						
						|  | \midrule | 
					
						
						|  | \endfirsthead | 
					
						
						|  | \toprule | 
					
						
						|  | & \multicolumn{{7}}{{c}}{{\textbf{{RMSE family}}}} & & | 
					
						
						|  | \multicolumn{{7}}{{c}}{{\textbf{{MAE family}}}}\\ | 
					
						
						|  | \cmidrule(lr){{2-8}}\cmidrule(lr){{10-16}} | 
					
						
						|  | Comparison | 
					
						
						|  | & {{\(\Delta\)RMSE}} | 
					
						
						|  | & {{$t_{{\text{{NB}}}}$}} | 
					
						
						|  | & {{p (raw)}} | 
					
						
						|  | & {{p\(_{{\text{{Holm}}}}\)}} | 
					
						
						|  | & {{\(\text{{Sig}}\)}} | 
					
						
						|  | & {{CI\(_{{\text{{low}}}}\)}} | 
					
						
						|  | & {{CI\(_{{\text{{high}}}}\)}} | 
					
						
						|  | & {{}} % spacer | 
					
						
						|  | & {{\(\Delta\)MAE}} | 
					
						
						|  | & {{$t_{{\text{{NB}}}}$}} | 
					
						
						|  | & {{p (raw)}} | 
					
						
						|  | & {{p\(_{{\text{{Holm}}}}\)}} | 
					
						
						|  | & {{\(\text{{Sig}}\)}} | 
					
						
						|  | & {{CI\(_{{\text{{low}}}}\)}} | 
					
						
						|  | & {{CI\(_{{\text{{high}}}}\)}}\\ | 
					
						
						|  | \midrule | 
					
						
						|  | \endhead | 
					
						
						|  | \midrule | 
					
						
						|  | \multicolumn{{16}}{{r}}{{\emph{{Continued on next page}}}}\\ | 
					
						
						|  | \midrule | 
					
						
						|  | \endfoot | 
					
						
						|  | \bottomrule | 
					
						
						|  | \endlastfoot | 
					
						
						|  | """ | 
					
						
						|  | lines = [] | 
					
						
						|  | for _, r in df.iterrows(): | 
					
						
						|  | lines.append( | 
					
						
						|  | f"{esc_tex(r['comparison'])} & " | 
					
						
						|  | f"{r['mean_diff_RMSE']:.6f} & {r['t_NB_RMSE']:.6f} & {r['p_raw_RMSE']:.6f} & {r['p_holm_RMSE']:.6f} & {1 if r['sig_RMSE'] else 0} & {r['CI_RMSE_low']:.6f} & {r['CI_RMSE_high']:.6f} & & " | 
					
						
						|  | f"{r['mean_diff_MAE']:.6f} & {r['t_NB_MAE']:.6f} & {r['p_raw_MAE']:.6f} & {r['p_holm_MAE']:.6f} & {1 if r['sig_MAE'] else 0} & {r['CI_MAE_low']:.6f} & {r['CI_MAE_high']:.6f} \\\\" | 
					
						
						|  | ) | 
					
						
						|  | outpath.write_text( | 
					
						
						|  | header + "\n".join(lines) + "\n\\end{longtable}\n", encoding="utf-8" | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def write_compact_tables(meta: Dict[str, str], df: pd.DataFrame, outdir: Path): | 
					
						
						|  | """ | 
					
						
						|  | Produce two small portrait tables: | 
					
						
						|  | • <dataset>_nb_compact_rmse.tex | 
					
						
						|  | • <dataset>_nb_compact_mae.tex | 
					
						
						|  |  | 
					
						
						|  | Columns (per metric): Comparison, Δ, t_NB, CI_low, CI_high, p_Holm | 
					
						
						|  | """ | 
					
						
						|  | dataset = meta.get("dataset", "dataset") | 
					
						
						|  | control = meta.get("control", "control") | 
					
						
						|  | k = int(meta.get("k", 5)) | 
					
						
						|  |  | 
					
						
						|  | common_preamble = r""" | 
					
						
						|  | % Auto-generated compact NB tables | 
					
						
						|  | % Requires in preamble: \usepackage{booktabs,tabularx,siunitx} | 
					
						
						|  | % \sisetup{round-mode=places,round-precision=3,scientific-notation=true} | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | rmse_header = rf"""{common_preamble} | 
					
						
						|  | \begin{{table}}[t] | 
					
						
						|  | \centering | 
					
						
						|  | \small | 
					
						
						|  | \setlength{{\tabcolsep}}{{4pt}} | 
					
						
						|  | \caption{{RMSE: NB-corrected one-sided tests (outer folds, $K={k}$) on dataset \texttt{{{esc_tex(dataset)}}}; control \texttt{{{esc_tex(control)}}}. | 
					
						
						|  | Positive $\Delta$ (competitor $-$ control) favors control. Holm controls FWER.}} | 
					
						
						|  | \label{{tab:{esc_tex(dataset)}_nb_compact_rmse}} | 
					
						
						|  | \begin{{tabularx}}{{\linewidth}}{{@{{}}l S S S S S@{{}}}} | 
					
						
						|  | \toprule | 
					
						
						|  | Comparison & {{\(\Delta\)RMSE}} & {{$t_{{\text{{NB}}}}$}} & {{CI\(_{{\text{{low}}}}\)}} & {{CI\(_{{\text{{high}}}}\)}} & {{p\(_{{\text{{Holm}}}}\)}}\\ | 
					
						
						|  | \midrule | 
					
						
						|  | """ | 
					
						
						|  | rmse_rows = [] | 
					
						
						|  | for _, r in df.sort_values("p_holm_RMSE").iterrows(): | 
					
						
						|  | rmse_rows.append( | 
					
						
						|  | f"{esc_tex(r['comparison'])} & " | 
					
						
						|  | f"{r['mean_diff_RMSE']:.3f} & {r['t_NB_RMSE']:.3f} & " | 
					
						
						|  | f"{r['CI_RMSE_low']:.3f} & {r['CI_RMSE_high']:.3f} & {r['p_holm_RMSE']:.3g} \\\\" | 
					
						
						|  | ) | 
					
						
						|  | rmse_tex = ( | 
					
						
						|  | rmse_header | 
					
						
						|  | + "\n".join(rmse_rows) | 
					
						
						|  | + "\n\\bottomrule\n\\end{tabularx}\n\\end{table}\n" | 
					
						
						|  | ) | 
					
						
						|  | (outdir / f"{dataset}_nb_compact_rmse.tex").write_text(rmse_tex, encoding="utf-8") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | mae_header = rf"""{common_preamble} | 
					
						
						|  | \begin{{table}}[t] | 
					
						
						|  | \centering | 
					
						
						|  | \small | 
					
						
						|  | \setlength{{\tabcolsep}}{{4pt}} | 
					
						
						|  | \caption{{MAE: NB-corrected one-sided tests (outer folds, $K={k}$) on dataset \texttt{{{esc_tex(dataset)}}}; control \texttt{{{esc_tex(control)}}}. | 
					
						
						|  | Positive $\Delta$ (competitor $-$ control) favors control. Holm controls FWER.}} | 
					
						
						|  | \label{{tab:{esc_tex(dataset)}_nb_compact_mae}} | 
					
						
						|  | \begin{{tabularx}}{{\linewidth}}{{@{{}}l S S S S S@{{}}}} | 
					
						
						|  | \toprule | 
					
						
						|  | Comparison & {{\(\Delta\)MAE}} & {{$t_{{\text{{NB}}}}$}} & {{CI\(_{{\text{{low}}}}\)}} & {{CI\(_{{\text{{high}}}}\)}} & {{p\(_{{\text{{Holm}}}}\)}}\\ | 
					
						
						|  | \midrule | 
					
						
						|  | """ | 
					
						
						|  | mae_rows = [] | 
					
						
						|  | for _, r in df.sort_values("p_holm_MAE").iterrows(): | 
					
						
						|  | mae_rows.append( | 
					
						
						|  | f"{esc_tex(r['comparison'])} & " | 
					
						
						|  | f"{r['mean_diff_MAE']:.3f} & {r['t_NB_MAE']:.3f} & " | 
					
						
						|  | f"{r['CI_MAE_low']:.3f} & {r['CI_MAE_high']:.3f} & {r['p_holm_MAE']:.3g} \\\\" | 
					
						
						|  | ) | 
					
						
						|  | mae_tex = ( | 
					
						
						|  | mae_header | 
					
						
						|  | + "\n".join(mae_rows) | 
					
						
						|  | + "\n\\bottomrule\n\\end{tabularx}\n\\end{table}\n" | 
					
						
						|  | ) | 
					
						
						|  | (outdir / f"{dataset}_nb_compact_mae.tex").write_text(mae_tex, encoding="utf-8") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def process_file(path: Path, outdir: Path, make_full: bool, make_compact: bool): | 
					
						
						|  | txt = path.read_text(encoding="utf-8", errors="ignore").splitlines() | 
					
						
						|  | meta = parse_header(txt) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | nb_start, nb_end = find_block( | 
					
						
						|  | txt, r"^\s*--- NB-corrected t \(outer folds\) per competitor ---" | 
					
						
						|  | ) | 
					
						
						|  | if nb_start < 0: | 
					
						
						|  | raise RuntimeError(f"NB block not found in {path}") | 
					
						
						|  | df_nb = parse_nb_block(txt[nb_start:nb_end]) | 
					
						
						|  | if df_nb.empty: | 
					
						
						|  | raise RuntimeError(f"NB table parsing failed in {path}") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | hr_start, hr_end = find_block( | 
					
						
						|  | txt, r"^\s*--- Holm-adjusted p-values \(RMSE family\)\s*---" | 
					
						
						|  | ) | 
					
						
						|  | hm_start, hm_end = find_block( | 
					
						
						|  | txt, r"^\s*--- Holm-adjusted p-values \(MAE family\)\s*---" | 
					
						
						|  | ) | 
					
						
						|  | if hr_start < 0 or hm_start < 0: | 
					
						
						|  | raise RuntimeError(f"Holm blocks not found in {path}") | 
					
						
						|  | df_hr = parse_holm_block(txt[hr_start:hr_end], "RMSE") | 
					
						
						|  | df_hm = parse_holm_block(txt[hm_start:hm_end], "MAE") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | df = df_nb.merge(df_hr, on="comparison", how="left").merge( | 
					
						
						|  | df_hm, on="comparison", how="left" | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | dataset = meta.get("dataset", path.stem.replace("_NB_single_dataset", "")) | 
					
						
						|  | outdir.mkdir(parents=True, exist_ok=True) | 
					
						
						|  |  | 
					
						
						|  | if make_full: | 
					
						
						|  | full_path = outdir / f"{dataset}_nb_full_summary_from_report.tex" | 
					
						
						|  |  | 
					
						
						|  | df_full = df.sort_values(["p_raw_RMSE", "p_raw_MAE"]).reset_index(drop=True) | 
					
						
						|  | write_full_longtable(meta, df_full, full_path) | 
					
						
						|  |  | 
					
						
						|  | if make_compact: | 
					
						
						|  |  | 
					
						
						|  | write_compact_tables(meta, df, outdir) | 
					
						
						|  |  | 
					
						
						|  | print( | 
					
						
						|  | f"Processed: {path.name}  ->  {dataset} (full={make_full}, compact={make_compact})" | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def main(): | 
					
						
						|  | ap = argparse.ArgumentParser() | 
					
						
						|  | ap.add_argument( | 
					
						
						|  | "--in", | 
					
						
						|  | dest="indir", | 
					
						
						|  | type=str, | 
					
						
						|  | default=None, | 
					
						
						|  | help="Directory containing *_NB_single_dataset.txt files.", | 
					
						
						|  | ) | 
					
						
						|  | ap.add_argument( | 
					
						
						|  | "--files", nargs="*", default=None, help="Explicit list of report files." | 
					
						
						|  | ) | 
					
						
						|  | ap.add_argument( | 
					
						
						|  | "--out", | 
					
						
						|  | dest="outdir", | 
					
						
						|  | type=str, | 
					
						
						|  | required=True, | 
					
						
						|  | help="Output directory for .tex tables.", | 
					
						
						|  | ) | 
					
						
						|  | ap.add_argument( | 
					
						
						|  | "--compact_only", | 
					
						
						|  | action="store_true", | 
					
						
						|  | help="Only write the compact RMSE/MAE tables (skip the full longtable).", | 
					
						
						|  | ) | 
					
						
						|  | args = ap.parse_args() | 
					
						
						|  |  | 
					
						
						|  | outdir = Path(args.outdir) | 
					
						
						|  | if args.files: | 
					
						
						|  | files = [Path(f) for f in args.files] | 
					
						
						|  | elif args.indir: | 
					
						
						|  | files = sorted(Path(args.indir).glob("*_NB_single_dataset.txt")) | 
					
						
						|  | else: | 
					
						
						|  | raise SystemExit("Provide either --in DIR or --files file1 file2 ...") | 
					
						
						|  |  | 
					
						
						|  | if not files: | 
					
						
						|  | raise SystemExit("No *_NB_single_dataset.txt files found.") | 
					
						
						|  |  | 
					
						
						|  | for f in files: | 
					
						
						|  | process_file(f, outdir, make_full=not args.compact_only, make_compact=True) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if __name__ == "__main__": | 
					
						
						|  | main() | 
					
						
						|  |  |