Tables

Use attaviz.table() to create a publication table from a pandas DataFrame. It supplies Attaviz typography, colors, alignment, and spacing, then returns a native Great Tables GT object. Chain Great Tables methods to format values, rename columns, and add footnotes.

Install table support

uv add "attaviz[tables]"
# or
pip install "attaviz[tables]"

Tables work without calling attaviz.enable().

Create your first table

Start with one row per observation and keep numbers numeric. These illustrative observations include a missing completion rate to show how missing data appears. The final table expression displays the result in a notebook or Quarto page.

import pandas as pd
import attaviz

participation = pd.DataFrame({
    "country": ["Indonesia", "Malaysia", "Philippines"],
    "participants": [12345, 8900, 6750],
    "completion": [0.876, 0.923, None],
    "funding": [1250000, 920000, 740000],
})

table = attaviz.table(
    participation,
    title="Program participation by country",
    subtitle="Illustrative counts, completion rates, and funding",
    source="Source: illustrative example data. N/A indicates unavailable data.",
)
table
Program participation by country
Illustrative counts, completion rates, and funding
country participants completion funding
Indonesia 12345 0.876 1250000
Malaysia 8900 0.923 920000
Philippines 6750 N/A 740000
Source: illustrative example data. N/A indicates unavailable data.

The table preserves row order and numeric precision. Missing cells display as N/A; numeric columns align right and text columns align left. Include the “Source:” prefix yourself. Tables use their own title, subtitle, and source arguments; the chart frame() helper does not apply to tables.

Format counts, percentages, and currency

Use the original column names in formatting methods, even after changing their display labels with cols_label():

formatted = (
    table
    .cols_label(
        country="Country",
        participants="Participants",
        completion="Completion",
        funding="Funding (USD)",
    )
    .fmt_integer(columns="participants")
    .fmt_percent(columns="completion", decimals=1)
    .fmt_currency(columns="funding", currency="USD", decimals=0)
)
formatted
Program participation by country
Illustrative counts, completion rates, and funding
Country Participants Completion Funding (USD)
Indonesia 12,345 87.6% $1,250,000
Malaysia 8,900 92.3% $920,000
Philippines 6,750 N/A $740,000
Source: illustrative example data. N/A indicates unavailable data.

The first row now shows 12,345, 87.6%, and $1,250,000. Percent formatting expects fractions: store 0.876 for 87.6%. If your data already contains 87.6, use .fmt_percent(columns="completion", decimals=1, scale_values=False). For ordinary decimal values, use .fmt_number(columns="column_name", decimals=1).

These methods change the display while retaining the underlying values. D3 number/date strings are for Altair charts; use Great Tables formatting methods for tables.

Row density and wide tables

The default uses 6 px vertical cell padding, 9 px horizontal padding, and 1.5 line height. Wrapped body text aligns to the top, and column headings follow the same horizontal alignment as their values. Open Sans and tabular numerals keep numeric columns readable without switching fonts.

Use subtle striping when it helps readers follow a row across many columns. opt_row_striping() uses Attaviz’s light grey. This example removes the internal horizontal rules so the table uses one main row-tracking aid:

wide_data = participation.assign(
    eligible=[18000, 12000, 9500],
    duration_months=[12, 18, 12],
)[["country", "eligible", "participants", "completion", "duration_months", "funding"]]

wide_table = (
    attaviz.table(
        wide_data,
        title="Program coverage and results",
        subtitle="Illustrative participation, completion, duration, and funding",
        source="Source: illustrative example data. N/A indicates unavailable data.",
    )
    .cols_label(
        country="Country",
        eligible="Eligible people",
        participants="Participants",
        completion="Completion (%)",
        duration_months="Duration (months)",
        funding="Funding (USD)",
    )
    .fmt_integer(columns=["eligible", "participants", "duration_months"])
    .fmt_percent(columns="completion", decimals=1)
    .fmt_currency(columns="funding", currency="USD", decimals=0)
    .opt_row_striping()
    .tab_options(table_body_hlines_style="none")
)
wide_table
Program coverage and results
Illustrative participation, completion, duration, and funding
Country Eligible people Participants Completion (%) Duration (months) Funding (USD)
Indonesia 18,000 12,345 87.6% 12 $1,250,000
Malaysia 12,000 8,900 92.3% 18 $920,000
Philippines 9,500 6,750 N/A 12 $740,000
Source: illustrative example data. N/A indicates unavailable data.

Keep the default light rules for smaller tables. You can adjust density with native .tab_options(data_row_padding="9px") when wrapped text needs more room; rows grow with their content rather than being forced to a fixed height.

Group columns and explain a value

Add a spanner above related columns and a footnote on the completion heading:

from great_tables import loc

publication_table = (
    formatted
    .tab_spanner(label="Program results", columns=["participants", "completion"])
    .tab_footnote(
        "Completion is the share of participants who finished the program.",
        locations=loc.column_labels(columns="completion"),
    )
)
publication_table
Program participation by country
Illustrative counts, completion rates, and funding
Country Program results Funding (USD)
Participants Completion1
Indonesia 12,345 87.6% $1,250,000
Malaysia 8,900 92.3% $920,000
Philippines 6,750 N/A $740,000
Source: illustrative example data. N/A indicates unavailable data.
1 Completion is the share of participants who finished the program.

Use loc.body(columns="completion", rows=[2]) instead to attach a footnote to the third row’s completion cell. Great Tables uses zero-based row positions.

Prepare row order and index labels

Sort or filter the DataFrame before passing it to table(). The DataFrame index is omitted; if it contains labels you need, turn it into a column first:

ranked = participation.set_index("country").sort_values("participants", ascending=False)

attaviz.table(
    ranked.reset_index(),
    title="Countries ordered by participation",
).fmt_integer(columns="participants")
Countries ordered by participation
country participants completion funding
Indonesia 12,345 0.876 1250000
Malaysia 8,900 0.923 920000
Philippines 6,750 N/A 740000

Export and share

To save the completed table, run these commands in your notebook or script:

publication_table.write_raw_html("participation.html", make_page=True)

HTML export creates a standalone page and needs no browser. You can also get HTML as a string with publication_table.as_raw_html() for embedding in another page.

For image or PDF output:

publication_table.gtsave("participation.png")
publication_table.gtsave("participation.pdf")

Image and PDF export use headless Chrome through Great Tables’ nokap dependency. See the Great Tables export guide for environment setup.

API and defaults

attaviz.table(data, *, title=None, subtitle=None, source=None)
  • data must be a non-empty pandas DataFrame with unique string column names. Attaviz copies it, preserving row order and omitting the index.
  • title, subtitle, and source are optional plain text. A subtitle requires a title. Titles, subtitles, source notes, column names, and unformatted text cells are escaped as plain text.
  • Styling uses Attaviz’s medium typography and spacing, dark text on white, left-aligned headings, content-based widths, wrapping text, and subtle horizontal rules. Quarto table processing is disabled to preserve these defaults when embedding the table in documentation. Open Sans must be available in the viewing environment for an exact font match; otherwise a sans-serif fallback is used.

The WBG guide supplies the typography and colors; horizontal rules and other table layout choices are Attaviz defaults. These are static publication tables; sorting and pagination are outside this helper’s defaults.

See the table gallery for more rendered examples.