Skip to main content

Minimal pd.DataFrame wrapper to create better tables for LaTeX and HTML.

Project description

better-tables

Python PyPI

A lightweight Python package to render pandas DataFrames into clean LaTeX and HTML tables for research reports, dashboards and publication-ready documents.

  • Lightweight DataFrame wrapper: a unified entry point and object to turn a DataFrame into a report-ready table

  • Research-oriented: built for statistics, econometrics and regression-style outputs, with native significance support

  • MultiIndex-friendly: smart handling of complex row and column MultiIndex tables

  • Style and formatting focused: flexible formatting rules and advanced styling options for precise rendering control

Intro-Table


Install

pip install better-tables

Development (uv)

git clone https://github.com/thibault-charbonnier/better-tables.git
cd better-tables
uv sync

Quickstart

import pandas as pd
from better_tables import build_table

df = pd.DataFrame(...)

table = build_table(
    df=df,
    title="Performance Summary",
    caption="Illustrative performance metrics of our strategies (2020-2025).",
    style="academic",
    highlight_mode="max",
    highlight_axis="columns",
)

latex = table.to_latex()
html = table.to_html()

Core features

1) Clean rendering from DataFrame

better-tables is built around one high-level abstraction: ReportTable.

You start from a pandas.DataFrame, add optional metadata and formatting rules, and render the result to LaTeX or HTML.


2) Per-metric formatting

Formatting can be applied by rows or by columns.

Supported formatting options include:

  • plain numeric formatting
  • percentage formatting
  • custom decimal precision
  • fully custom formatter functions
formats = {
    "Return": {"kind": "percent", "percent_input": "ratio", "decimals": 2},
    "Volatility": {"kind": "percent", "percent_input": "ratio", "decimals": 1},
    "Volume": {"kind": "number", "formatter": lambda v: f"{int(v):,}"},
}
format_axis = "rows" # "rows" or "columns"

Percent semantics

Percentage formatting is explicit:

  • kind="percent" means the output should display %
  • percent_input="ratio" means 0.123 -> 12.3%
  • percent_input="percent" means 12.3 -> 12.3%

Note: you can use a dedicated MetricFormat object instead of a raw dictionary, with the same arguments.`

MetricFormat(
    kind="number",              # "number" or "percent"
    percent_input="ratio",      # "ratio" or "percent"
    decimals=2,
    formatter=None,
)

# Both are accepted :
formats = {
    "Return": MetricFormat(kind="percent", percent_input="ratio", decimals=3),
    "Volatility": {"kind": "percent", "percent_input": "ratio", "decimals": 1}
}

3) Conditional highlighting

Highlight the maximum or minimum values by row or by column.

The visual appearance of the highlight is controlled by the selected style (see the dedicated section below).

table = build_table(
    df=df,
    highlight_mode="max",
    highlight_axis="columns",
)

4) Significance rendering for statistics/econometrics tables

better-tables supports a compact significance workflow for econometrics and regression-style outputs, driven by the following parameters:

  • significance_stat=None | "pvalue" | "stderr" | "tstat" : significance statistic to look for and put between parenthesis
  • significance_layout="stack" | "inline" : whether to place the value in parentheses directly after the coefficient (inline) or on a new line below it (stack)`
  • significance_thresholds=(0.10, 0.05, 0.01) : pvalue thresholds for significance stars

We use the following simple convention :

  • If significance_stat is not None then for every column col, the code will look for col_{stat} where stat is the selected statistic ("pvalue" | "stderr" | "tstat") and the two will be merged as : {col_value} ({stat_value}) and will keep the name of the original column.
  • If significance_stat is None all columns are left unchanged.

Example with p-values

df = pd.DataFrame(
    {
        "Est_Coeff": [0.03, 0.41, -0.28],
        "Est_Coeff_pvalue": [0.041, 0.002, 0.018],
    },
    index=["Intercept", "GDP growth", "Inflation"],
)

table = build_table(
    df=df,
    style="academic",
    significance_stat="pvalue",
    significance_layout="stack",
)

Notes:

  • stars are computed only from p-values
  • if significance_stat="stderr" or "tstat", the auxiliary statistic is shown in parentheses but no stars are added
  • if significance_stat=None, no merging is performed

Significance table


5) MultiIndex support

MultiIndex handling is supported natively, without any additional parameters.

Row MultiIndex

A row MultiIndex is rendered as multiple stub columns on the left, with repeated labels displayed sparsely.

Row MultiIndex table

Column MultiIndex

A column MultiIndex is rendered as a grouped multi-level header.

Column MultiIndex table


6) LaTeX and HTML output

Two backends are supported through the same API :

  • to_latex() for publication-grade LaTeX output
  • to_html() for notebooks, dashboards or web integration
latex = table.to_latex()
html = table.to_html()

Save as .tex or compile to PDF

Please note that compiling to PDF requires a LaTeX compiler as pdflatex, xelatex or lualatex. Users must ensure that the specified engine is installed and available in the system PATH.

For example, MiKTeX can be installed from: https://miktex.org/download.

table.to_latex(save_as_tex=True, path="outputs/my_table.tex")
table.to_latex(save_as_pdf=True, path="outputs/my_table.pdf")

7) Built-in styles

Three predefined styles are currently available, each designed for a slightly different reporting context.

  • academic
    The default style, designed for research notes, reports and publication-oriented tables. It offers a good trade-off between a light minimalist layout and a more explicit, highly readable table structure. This is the best default choice for regression tables, performance summaries and most professional documents.

  • minimal
    A stripped-down style with no outer border and no internal lines.
    It is useful when you want a very light visual footprint, for example in dashboards or notebooks.

  • boxed
    A more explicit, framed style with a full box, internal horizontal lines and inner vertical separators. It is helpful when readability through clear cell separation matters more than visual minimalism, for example in dense operational tables.

You can also override specific style fields without redefining an entire style:

table = build_table(
    ...
    style_options={
        "bold_index": True,
        "highlight_bold": True,
        "highlight_color": "red",
    },
)

API reference

build_table

Main convenience constructor.

build_table(
    df,
    *,
    title=None,
    caption=None,
    style="academic",
    title_align="center",
    caption_align="left",
    title_position="above",
    caption_position="below",
    formats=None,
    format_axis="rows",
    note=None,
    style_options=None,
    highlight_mode=None,
    highlight_axis="rows",
    default_format=None,
    significance_stat=None,
    significance_layout="stack",
    significance_thresholds=(0.10, 0.05, 0.01),
)

ReportTable

Main rendering object returned by the build_table method.

Public methods

  • to_latex(save_as_tex=False, save_as_pdf=False, path=None, latex_engine=None, float_env=True)
  • to_html()

License

MIT License

Copyright (c) 2026 better-tables Contributors

See LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

better_tables-0.2.1.tar.gz (22.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

better_tables-0.2.1-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

Details for the file better_tables-0.2.1.tar.gz.

File metadata

  • Download URL: better_tables-0.2.1.tar.gz
  • Upload date:
  • Size: 22.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for better_tables-0.2.1.tar.gz
Algorithm Hash digest
SHA256 a7fe3830fe99a1088706f2f7e08e1e5c7a56be0281a110c4d8fefb726a91462d
MD5 0ac201c1302f40f1e3efbf3547b9b838
BLAKE2b-256 a3cdca8fad53cd576b57c03ac660dc4f73e2c2a160fa222dc8882526da803af4

See more details on using hashes here.

File details

Details for the file better_tables-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: better_tables-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 23.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for better_tables-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f35d2facfe4b61d58f27953591f7187656d60bda2a3d884dd9334103e6457eb2
MD5 14b77d112550b4133ae5fb38b1c9fbbc
BLAKE2b-256 edb66e662403e71c1d626396fff839cda20b625efd02c4e14837e650ef094e89

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page