
Table Generation
- 13 installs
- 255 repo stars
- Updated February 27, 2026
- lingzhi227/claude-skills
This is a copy of table-generation by lingzhi227 - installs and ranking accrue to the original listing.
Helps with ai & agent building tasks.
About
table-generation is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- table-generation
- AI & Agent Building
- AI-coding skill
Table Generation by the numbers
- 13 all-time installs (skills.sh)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lingzhi227/claude-skills --skill table-generationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 13 |
|---|---|
| repo stars | ★ 255 |
| Last updated | February 27, 2026 |
| Repository | lingzhi227/claude-skills ↗ |
What it does
Helps with ai & agent building tasks.
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()