
Table Generation
- 1.2k installs
- 255 repo stars
- Updated February 27, 2026
- lingzhi227/agent-research-skills
table-generation is an agent skill that converts JSON or CSV experimental results into publication-ready LaTeX tables with booktabs formatting and bold best results.
About
The table-generation skill turns experimental result files into publication-quality LaTeX tables for research papers. It accepts comparison, ablation, descriptive, custom, and multi-dataset layouts via the results_to_table.py script with flags for bold-best, underline-second, significance stars, captions, and labels. Comparison tables list methods as rows and metrics as columns with bold best and optional mean plus std formatting. Ablation tables highlight full-model rows and can mark component presence with checkmarks. Output always uses booktabs rules, caption and label macros, textbf for winners, threeparttable notes when needed, and compact column alignment. Rules forbid hallucinated numbers: every value must match the source logs exactly. Required LaTeX packages include booktabs, multirow, multicol, and threeparttable. The skill links upstream to data-analysis and experiment-code and downstream to paper-writing and paper-compilation workflows.
- Generates booktabs LaTeX tables from JSON or CSV via results_to_table.py.
- Supports comparison, ablation, descriptive, custom, and multi-dataset layouts.
- Bolds best results and can underline second-best or add significance stars.
- Requires exact numeric fidelity to experimental logs with no invented values.
- Documents required LaTeX packages and threeparttable note patterns.
Table Generation by the numbers
- 1,246 all-time installs (skills.sh)
- +34 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #225 of 1,879 Documentation skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
table-generation capabilities & compatibility
- Capabilities
- json and csv to latex table conversion · bold best and underline second formatting · multi dataset and ablation layouts · caption, label, and threeparttable notes · strict numeric fidelity enforcement
- Use cases
- documentation · data analysis
What table-generation says it does
Generate publication-quality LaTeX tables from experimental results.
Only include numbers from actual experimental logs — never hallucinate results
npx skills add https://github.com/lingzhi227/agent-research-skills --skill table-generationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.2k |
|---|---|
| repo stars | ★ 255 |
| Security audit | 3 / 3 scanners passed |
| Last updated | February 27, 2026 |
| Repository | lingzhi227/agent-research-skills ↗ |
How do I turn experiment JSON or CSV outputs into a paper-ready LaTeX results table without manual formatting errors?
Convert JSON or CSV experimental results into publication-ready LaTeX tables with booktabs styling, bold best values, and captions.
Who is it for?
Researchers preparing comparison, ablation, or descriptive tables for academic papers from real logged results.
Skip if: Skip when you need figure plots or when source data is missing or unverified.
When should I use this skill?
User needs result tables, comparison tables, or ablation tables for a paper from JSON or CSV data.
What you get
A complete LaTeX table with booktabs rules, caption, label, bold best cells, and optional significance or second-best styling.
- LaTeX table environments
- Booktabs tabular code
- Caption and label blocks
Files
Table Generation
Convert experimental results into publication-ready LaTeX tables.
Input
$0— Table type:comparison,ablation,descriptive,custom$1— Data source: JSON file, CSV file, or inline data
Scripts
Generate LaTeX table from JSON/CSV
python ~/.claude/skills/table-generation/scripts/results_to_table.py \
--input results.json --type comparison \
--bold-best max --caption "Performance comparison" \
--label tab:main_resultsSupports: comparison, ablation, descriptive, multi-dataset table types. Additional flags: --type multi-dataset for methods x datasets x metrics layout, --significance for p-value stars, --underline-second for second-best results.
References
- LaTeX table templates and examples:
~/.claude/skills/table-generation/references/table-templates.md
Table Types
comparison — Main results table
- Rows = methods (baselines + ours), Columns = metrics/datasets
- Bold the best result in each column
- Include mean +/- std when available
- Use
\multirowfor method categories (Supervised, Self-supervised, etc.)
ablation — Ablation study table
- Rows = variants (full model, minus component A, minus component B, ...)
- Columns = metrics
- Bold the full model result
- Use checkmarks for component presence
descriptive — Dataset/statistics table
- Dataset characteristics, hyperparameters, or summary statistics
- Clean formatting with proper units
custom — Free-form table
- User specifies layout and content
Required Packages
\usepackage{booktabs} % \toprule, \midrule, \bottomrule
\usepackage{multirow} % \multirow
\usepackage{multicol} % multi-column layouts
\usepackage{threeparttable} % table notesOutput Format
Always generate tables with: 1. booktabs rules (\toprule, \midrule, \bottomrule) 2. \caption{} and \label{tab:...} 3. Bold best results using \textbf{} 4. Table notes via threeparttable when needed 5. Proper alignment (l for text, c or r for numbers)
Rules
- Only include numbers from actual experimental logs — never hallucinate results
- All numbers must match the data source exactly
- Use
$\pm$for standard deviations - Use
\underline{}for second-best results when appropriate - Keep tables compact — avoid unnecessary columns
- Use
table*for wide tables spanning two columns - Add glossary/notes for abbreviated column headers
Related Skills
- Upstream: data-analysis, experiment-code
- Downstream: paper-writing-section, paper-compilation
- See also: figure-generation
LaTeX Table Templates Reference
Template 1: Simple Comparison Table (booktabs)
\begin{table}[htbp]
\centering
\caption{Performance comparison on benchmark datasets.}
\label{tab:main_results}
\begin{tabular}{lccc}
\toprule
Method & Dataset 1 & Dataset 2 & Dataset 3 \\
\midrule
Baseline 1 & 82.3$\pm$0.6 & 71.8$\pm$0.7 & 76.8$\pm$0.6 \\
Baseline 2 & 83.5$\pm$0.4 & 73.3$\pm$0.5 & 80.1$\pm$0.7 \\
Baseline 3 & 84.0$\pm$0.4 & 73.1$\pm$0.3 & 81.0$\pm$0.4 \\
\midrule
\textbf{Ours} & \textbf{84.2$\pm$0.4} & \textbf{73.4$\pm$0.4} & \textbf{81.1$\pm$0.4} \\
\bottomrule
\end{tabular}
\end{table}Template 2: Grouped Comparison with multirow
\begin{table}[htbp]
\centering
\caption{Experiment results for node classification. Accuracy (\%) reported.}
\label{tab:node_clf}
\begin{threeparttable}
\renewcommand\tabcolsep{10pt}
\renewcommand\arraystretch{1.05}
\begin{tabular}{c|c|ccc}
\toprule
& Method & Cora & CiteSeer & PubMed \\
\midrule
\multirow{2}{*}{Supervised}
& GCN & 81.5 & 70.3 & 79.0 \\
& GAT & 83.0$\pm$0.7 & 72.5$\pm$0.7 & 79.0$\pm$0.3 \\
\midrule
\multirow{4}{*}{Self-supervised}
& DGI & 82.3$\pm$0.6 & 71.8$\pm$0.7 & 76.8$\pm$0.6 \\
& MVGRL & 83.5$\pm$0.4 & 73.3$\pm$0.5 & 80.1$\pm$0.7 \\
& CCA-SSG & \underline{84.0$\pm$0.4} & 73.1$\pm$0.3 & \underline{81.0$\pm$0.4} \\
& \textbf{Ours} & \textbf{84.2$\pm$0.4} & \textbf{73.4$\pm$0.4} & \textbf{81.1$\pm$0.4} \\
\bottomrule
\end{tabular}
\begin{tablenotes}
\footnotesize
\item Best results in \textbf{bold}, second best \underline{underlined}.
\end{tablenotes}
\end{threeparttable}
\end{table}Template 3: Ablation Study
\begin{table}[htbp]
\centering
\caption{Ablation study on model components.}
\label{tab:ablation}
\begin{tabular}{lccc}
\toprule
Variant & Acc. (\%) & F1 (\%) & Params (M) \\
\midrule
Full model & \textbf{84.2} & \textbf{83.7} & 12.3 \\
\quad w/o Component A & 82.1 & 81.5 & 10.1 \\
\quad w/o Component B & 83.0 & 82.3 & 11.8 \\
\quad w/o Component C & 81.5 & 80.9 & 9.5 \\
\quad w/o A + B & 80.2 & 79.6 & 8.7 \\
\bottomrule
\end{tabular}
\end{table}Template 4: Ablation with Checkmarks
\begin{table}[htbp]
\centering
\caption{Component ablation analysis.}
\label{tab:component_ablation}
\begin{tabular}{ccc|cc}
\toprule
Comp. A & Comp. B & Comp. C & Accuracy & F1 \\
\midrule
\checkmark & \checkmark & \checkmark & \textbf{84.2} & \textbf{83.7} \\
& \checkmark & \checkmark & 82.1 & 81.5 \\
\checkmark & & \checkmark & 83.0 & 82.3 \\
\checkmark & \checkmark & & 81.5 & 80.9 \\
& & & 78.3 & 77.1 \\
\bottomrule
\end{tabular}
\end{table}Template 5: Wide Table (two-column, table*)
\begin{table*}[htbp]
\centering
\caption{Performance comparison across 4 datasets with multiple metrics.}
\label{tab:full_results}
\scriptsize
\begin{tabular}{l|cc|cc|cc|cc}
\toprule
& \multicolumn{2}{c|}{Dataset 1} & \multicolumn{2}{c|}{Dataset 2} & \multicolumn{2}{c|}{Dataset 3} & \multicolumn{2}{c}{Dataset 4} \\
Method & R@20 & N@20 & R@20 & N@20 & R@20 & N@20 & R@20 & N@20 \\
\midrule
Baseline 1 & 0.0466 & 0.0395 & 0.0944 & 0.0522 & 0.1763 & 0.2101 & 0.0211 & 0.0154 \\
Baseline 2 & 0.0526 & 0.0444 & 0.1030 & 0.0623 & 0.1833 & 0.2205 & 0.0327 & 0.0249 \\
\textbf{Ours} & \textbf{0.0793} & \textbf{0.0668} & \textbf{0.1578} & \textbf{0.0935} & \textbf{0.2613} & \textbf{0.3106} & \textbf{0.0585} & \textbf{0.0436} \\
\bottomrule
\end{tabular}
\end{table*}Template 6: Table with Notes (threeparttable)
\begin{table}[htbp]
\centering
\begin{threeparttable}
\caption{Statistical analysis of treatment effects.}
\label{tab:stats}
\begin{tabular}{lcccc}
\toprule
Variable & Coef. & SE & 95\% CI & $p$-value \\
\midrule
Treatment & 0.42 & 0.08 & (0.26, 0.58) & $<$0.001*** \\
Age & $-$0.03 & 0.01 & ($-$0.05, $-$0.01) & 0.012* \\
Gender (M) & 0.15 & 0.12 & ($-$0.09, 0.39) & 0.214 \\
\bottomrule
\end{tabular}
\begin{tablenotes}
\footnotesize
\item \textbf{CI}: Confidence Interval. \textbf{SE}: Standard Error.
\item Significance: * $p < 0.05$, ** $p < 0.01$, *** $p < 0.001$.
\end{tablenotes}
\end{threeparttable}
\end{table}Template 7: Dataset Statistics
\begin{table}[htbp]
\centering
\caption{Dataset statistics.}
\label{tab:datasets}
\begin{tabular}{lrrrr}
\toprule
Dataset & \#Train & \#Val & \#Test & \#Classes \\
\midrule
CIFAR-10 & 45,000 & 5,000 & 10,000 & 10 \\
CIFAR-100 & 45,000 & 5,000 & 10,000 & 100 \\
ImageNet & 1.2M & 50,000 & 100,000 & 1,000 \\
\bottomrule
\end{tabular}
\end{table}Template 8: Hyperparameter Table
\begin{table}[htbp]
\centering
\caption{Hyperparameter settings.}
\label{tab:hyperparams}
\begin{tabular}{ll}
\toprule
Hyperparameter & Value \\
\midrule
Learning rate & 0.001 \\
Batch size & 64 \\
Optimizer & AdamW \\
Weight decay & 0.01 \\
Epochs & 100 \\
Warmup steps & 1,000 \\
Hidden dim & 256 \\
\# Layers & 6 \\
Dropout & 0.1 \\
\bottomrule
\end{tabular}
\end{table}Formatting Rules
1. Bold best results: \textbf{84.2} for the best in each column 2. Underline second best: \underline{83.5} for runner-up 3. Standard deviations: Use $\pm$ (e.g., 84.2$\pm$0.4) 4. Alignment: l for text, c or r for numbers 5. Required packages: booktabs, multirow, threeparttable 6. *Use `table** for wide tables in two-column layouts 7. **Notes**: Use threeparttable + tablenotes for footnotes 8. **Thousands separator**: Use commas (1,000 not 1000) 9. **Negative numbers**: Use $-$0.03 not -0.03` for proper minus sign
#!/usr/bin/env python3
"""Convert experimental results to publication-quality LaTeX tables.
Self-contained: uses only stdlib.
Adapted from data-to-paper's df_to_latex and AI-Researcher's table patterns.
Usage:
python results_to_table.py --input results.json --type comparison
python results_to_table.py --input results.csv --type ablation --bold-best max
python results_to_table.py --input results.json --type comparison \
--caption "Main results" --label tab:main --bold-best max
"""
import argparse
import csv
import json
import os
import sys
def load_data(path: str) -> tuple[list[str], list[str], list[list[str]]]:
"""Load data from JSON or CSV. Returns (col_headers, row_headers, values)."""
ext = os.path.splitext(path)[1].lower()
if ext == ".json":
with open(path, encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, list) and len(data) > 0 and isinstance(data[0], dict):
# List of dicts: [{method: X, metric1: Y, ...}, ...]
col_headers = [k for k in data[0].keys() if k.lower() not in ("method", "model", "name", "variant")]
method_key = next((k for k in data[0].keys() if k.lower() in ("method", "model", "name", "variant")), None)
row_headers = [str(d.get(method_key, f"Row {i}")) for i, d in enumerate(data)] if method_key else [f"Row {i}" for i in range(len(data))]
values = [[str(d.get(c, "")) for c in col_headers] for d in data]
elif isinstance(data, dict):
# Nested dict: {method: {metric: value, ...}, ...}
row_headers = list(data.keys())
all_cols = set()
for v in data.values():
if isinstance(v, dict):
all_cols.update(v.keys())
col_headers = sorted(all_cols)
values = [[str(data[r].get(c, "")) if isinstance(data[r], dict) else str(data[r]) for c in col_headers] for r in row_headers]
else:
raise ValueError(f"Unsupported JSON structure: expected list of dicts or nested dict")
elif ext == ".csv":
with open(path, encoding="utf-8") as f:
reader = csv.reader(f)
rows = list(reader)
if len(rows) < 2:
raise ValueError("CSV must have at least a header row and one data row")
col_headers = rows[0][1:] # Skip first column (method name)
row_headers = [r[0] for r in rows[1:]]
values = [r[1:] for r in rows[1:]]
else:
raise ValueError(f"Unsupported file format: {ext}. Use .json or .csv")
return col_headers, row_headers, values
def parse_numeric(val: str) -> float | None:
"""Try to parse a numeric value, handling +/- notation."""
val = val.strip()
# Handle "84.2+/-0.4" or "84.2±0.4" or "84.2 +/- 0.4"
for sep in ["±", "+/-", "+-"]:
if sep in val:
val = val.split(sep)[0].strip()
break
try:
return float(val)
except ValueError:
return None
def find_best_indices(values: list[list[str]], col_headers: list[str],
bold_best: str) -> dict[int, int]:
"""Find the best value index in each column. Returns {col_idx: row_idx}."""
best = {}
if bold_best not in ("max", "min"):
return best
for col_idx in range(len(col_headers)):
best_val = None
best_row = None
for row_idx in range(len(values)):
if col_idx < len(values[row_idx]):
num = parse_numeric(values[row_idx][col_idx])
if num is not None:
if best_val is None:
best_val = num
best_row = row_idx
elif bold_best == "max" and num > best_val:
best_val = num
best_row = row_idx
elif bold_best == "min" and num < best_val:
best_val = num
best_row = row_idx
if best_row is not None:
best[col_idx] = best_row
return best
def escape_latex(text: str) -> str:
"""Escape special LaTeX characters in text."""
replacements = [
("_", r"\_"),
("%", r"\%"),
("&", r"\&"),
("#", r"\#"),
]
for old, new in replacements:
text = text.replace(old, new)
# Convert +/- to $\pm$
text = text.replace("±", "$\\pm$")
text = text.replace("+/-", "$\\pm$")
return text
def generate_comparison_table(col_headers, row_headers, values,
caption, label, bold_best, wide=False):
"""Generate a comparison table."""
best = find_best_indices(values, col_headers, bold_best)
# Determine alignment
num_cols = len(col_headers)
align = "l" + "c" * num_cols
lines = []
env = "table*" if wide else "table"
lines.append(f"\\begin{{{env}}}[htbp]")
lines.append("\\centering")
if caption:
lines.append(f"\\caption{{{escape_latex(caption)}}}")
if label:
lines.append(f"\\label{{{label}}}")
lines.append(f"\\begin{{tabular}}{{{align}}}")
lines.append(" \\toprule")
# Header row
header = " Method & " + " & ".join(escape_latex(h) for h in col_headers) + " \\\\"
lines.append(header)
lines.append(" \\midrule")
# Data rows
for row_idx, (row_name, row_vals) in enumerate(zip(row_headers, values)):
cells = [escape_latex(row_name)]
for col_idx, val in enumerate(row_vals):
val_str = escape_latex(val)
if best.get(col_idx) == row_idx:
val_str = f"\\textbf{{{val_str}}}"
cells.append(val_str)
lines.append(" " + " & ".join(cells) + " \\\\")
lines.append(" \\bottomrule")
lines.append("\\end{tabular}")
lines.append(f"\\end{{{env}}}")
return "\n".join(lines)
def generate_ablation_table(col_headers, row_headers, values,
caption, label, bold_best):
"""Generate an ablation table (first row assumed to be full model)."""
best = find_best_indices(values, col_headers, bold_best)
num_cols = len(col_headers)
align = "l" + "c" * num_cols
lines = []
lines.append("\\begin{table}[htbp]")
lines.append("\\centering")
if caption:
lines.append(f"\\caption{{{escape_latex(caption)}}}")
if label:
lines.append(f"\\label{{{label}}}")
lines.append(f"\\begin{{tabular}}{{{align}}}")
lines.append(" \\toprule")
# Header
header = " Variant & " + " & ".join(escape_latex(h) for h in col_headers) + " \\\\"
lines.append(header)
lines.append(" \\midrule")
# Data rows
for row_idx, (row_name, row_vals) in enumerate(zip(row_headers, values)):
cells = []
name = escape_latex(row_name)
if row_idx == 0:
cells.append(name)
else:
cells.append(f"\\quad {name}")
for col_idx, val in enumerate(row_vals):
val_str = escape_latex(val)
if best.get(col_idx) == row_idx:
val_str = f"\\textbf{{{val_str}}}"
cells.append(val_str)
lines.append(" " + " & ".join(cells) + " \\\\")
lines.append(" \\bottomrule")
lines.append("\\end{tabular}")
lines.append("\\end{table}")
return "\n".join(lines)
def generate_descriptive_table(col_headers, row_headers, values,
caption, label):
"""Generate a descriptive/statistics table (no bolding)."""
num_cols = len(col_headers)
align = "l" + "r" * num_cols
lines = []
lines.append("\\begin{table}[htbp]")
lines.append("\\centering")
if caption:
lines.append(f"\\caption{{{escape_latex(caption)}}}")
if label:
lines.append(f"\\label{{{label}}}")
lines.append(f"\\begin{{tabular}}{{{align}}}")
lines.append(" \\toprule")
header = " & " + " & ".join(escape_latex(h) for h in col_headers) + " \\\\"
lines.append(header)
lines.append(" \\midrule")
for row_name, row_vals in zip(row_headers, values):
cells = [escape_latex(row_name)]
for val in row_vals:
cells.append(escape_latex(val))
lines.append(" " + " & ".join(cells) + " \\\\")
lines.append(" \\bottomrule")
lines.append("\\end{tabular}")
lines.append("\\end{table}")
return "\n".join(lines)
def find_second_best_indices(values, col_headers, bold_best):
"""Find the second-best value index in each column."""
second = {}
if bold_best not in ("max", "min"):
return second
best = find_best_indices(values, col_headers, bold_best)
for col_idx in range(len(col_headers)):
second_val = None
second_row = None
best_row = best.get(col_idx)
for row_idx in range(len(values)):
if row_idx == best_row:
continue
if col_idx < len(values[row_idx]):
num = parse_numeric(values[row_idx][col_idx])
if num is not None:
if second_val is None:
second_val = num
second_row = row_idx
elif bold_best == "max" and num > second_val:
second_val = num
second_row = row_idx
elif bold_best == "min" and num < second_val:
second_val = num
second_row = row_idx
if second_row is not None:
second[col_idx] = second_row
return second
def generate_multi_dataset_table(col_headers, row_headers, values,
caption, label, bold_best,
underline_second=False):
"""Generate a multi-dataset table (methods x datasets x metrics)."""
best = find_best_indices(values, col_headers, bold_best)
second = find_second_best_indices(values, col_headers, bold_best) if underline_second else {}
num_cols = len(col_headers)
align = "l" + "c" * num_cols
lines = []
lines.append("\\begin{table*}[htbp]")
lines.append("\\centering")
if caption:
lines.append(f"\\caption{{{escape_latex(caption)}}}")
if label:
lines.append(f"\\label{{{label}}}")
lines.append(f"\\begin{{tabular}}{{{align}}}")
lines.append(" \\toprule")
header = " Method & " + " & ".join(escape_latex(h) for h in col_headers) + " \\\\"
lines.append(header)
lines.append(" \\midrule")
for row_idx, (row_name, row_vals) in enumerate(zip(row_headers, values)):
cells = [escape_latex(row_name)]
for col_idx, val in enumerate(row_vals):
val_str = escape_latex(val)
if best.get(col_idx) == row_idx:
val_str = f"\\textbf{{{val_str}}}"
elif second.get(col_idx) == row_idx and underline_second:
val_str = f"\\underline{{{val_str}}}"
cells.append(val_str)
lines.append(" " + " & ".join(cells) + " \\\\")
lines.append(" \\bottomrule")
lines.append("\\end{tabular}")
lines.append("\\end{table*}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Convert results to LaTeX table")
parser.add_argument("--input", required=True, help="Input file (.json or .csv)")
parser.add_argument("--type", choices=["comparison", "ablation", "descriptive", "multi-dataset"],
default="comparison", help="Table type (default: comparison)")
parser.add_argument("--bold-best", choices=["max", "min", "none"],
default="none", help="Bold best values (default: none)")
parser.add_argument("--caption", type=str, default="", help="Table caption")
parser.add_argument("--label", type=str, default="", help="Table label (e.g., tab:main)")
parser.add_argument("--wide", action="store_true", help="Use table* for two-column layout")
parser.add_argument("--output", type=str, help="Output .tex file (default: stdout)")
parser.add_argument("--significance", action="store_true", help="Add p-value significance stars")
parser.add_argument("--underline-second", action="store_true", help="Underline second-best results")
args = parser.parse_args()
col_headers, row_headers, values = load_data(args.input)
if args.type == "comparison":
latex = generate_comparison_table(
col_headers, row_headers, values,
args.caption, args.label, args.bold_best, args.wide
)
elif args.type == "ablation":
latex = generate_ablation_table(
col_headers, row_headers, values,
args.caption, args.label, args.bold_best
)
elif args.type == "descriptive":
latex = generate_descriptive_table(
col_headers, row_headers, values,
args.caption, args.label
)
elif args.type == "multi-dataset":
latex = generate_multi_dataset_table(
col_headers, row_headers, values,
args.caption, args.label, args.bold_best,
underline_second=args.underline_second
)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(latex)
print(f"Table written to {args.output}", file=sys.stderr)
else:
print(latex)
if __name__ == "__main__":
main()
Related skills
Forks & variants (2)
Table Generation has 2 known copies in the catalog totaling 14 installs. They canonicalize to this original listing.
- lingzhi227 - 13 installs
- lingzhi227 - 1 installs
How it compares
Pick table-generation over manual LaTeX typing when you need standard booktabs and multirow paper templates with consistent caption and label structure.
FAQ
What table types are supported?
comparison, ablation, descriptive, custom, and multi-dataset layouts via results_to_table.py.
Can it invent missing metrics?
No. Rules require numbers to match experimental logs exactly and forbid hallucinated results.
Which LaTeX packages are needed?
booktabs, multirow, multicol, and threeparttable for proper rules, spans, and notes.
Is Table Generation safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.