Great Tables Publication-Quality Tables Skill
Master Great Tables for creating beautiful, publication-quality tables in Python with rich styling, conditional formatting, and export capabilities. Inspired by R's gt package.
When to Use This Skill
USE Great Tables when:
-
Publication tables - Creating tables for reports, papers, or presentations
-
Data presentation - Professional display of analysis results
-
Conditional formatting - Highlighting patterns with colors and icons
-
Complex layouts - Multi-level headers, grouped rows, footnotes
-
HTML reports - Interactive tables for web-based reports
-
Quick formatting - Need polished tables without manual styling
-
Dashboard components - Tables in Streamlit/Dash applications
-
Export requirements - Need PNG or PDF output
DON'T USE Great Tables when:
-
Large datasets - Over 1000 rows for display (use pagination)
-
Interactive editing - Need editable cells (use Streamlit data_editor)
-
Real-time updates - Streaming data display
-
Complex interactivity - Sorting, filtering (use DataTables or AG Grid)
-
Raw data exploration - Use pandas display or ydata-profiling
Prerequisites
Basic installation
pip install great_tables
With all optional dependencies
pip install great_tables pandas polars
For image export (PNG/PDF)
pip install great_tables webshot
Using uv (recommended)
uv pip install great_tables pandas polars
Verify installation
python -c "from great_tables import GT; print('Great Tables ready!')"
Core Capabilities
- Basic Table Creation
Simplest Usage:
from great_tables import GT import pandas as pd
Create sample data
df = pd.DataFrame({ "Name": ["Alice", "Bob", "Charlie", "Diana"], "Department": ["Engineering", "Marketing", "Engineering", "Sales"], "Salary": [95000, 78000, 88000, 92000], "Years": [5, 3, 4, 6] })
Create basic table
table = GT(df)
Display (in Jupyter) or save
table.save("basic_table.html")
With Title and Subtitle:
from great_tables import GT, md import pandas as pd
df = pd.DataFrame({ "Product": ["Widget A", "Widget B", "Gadget X", "Gadget Y"], "Revenue": [150000, 220000, 180000, 95000], "Units": [1500, 2200, 900, 950], "Growth": [0.12, 0.25, 0.08, -0.05] })
table = ( GT(df) .tab_header( title="Q4 2025 Sales Performance", subtitle="Product line revenue and growth metrics" ) )
table.save("sales_table.html")
With Source Notes:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Country": ["USA", "UK", "Germany", "Japan"], "GDP_Trillion": [25.5, 3.1, 4.2, 4.9], "Population_Million": [331, 67, 83, 125] })
table = ( GT(df) .tab_header( title="World Economic Indicators", subtitle="Top economies by GDP" ) .tab_source_note( source_note="Source: World Bank, 2024" ) .tab_source_note( source_note="GDP in trillion USD" ) )
table.save("economy_table.html")
- Column Formatting
Numeric Formatting:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Item": ["Product A", "Product B", "Product C"], "Price": [29.99, 149.50, 9.99], "Revenue": [1500000, 2250000, 890000], "Margin": [0.35, 0.42, 0.28], "Units": [50000, 15000, 89000] })
table = ( GT(df) .tab_header(title="Product Metrics")
# Format as currency
.fmt_currency(
columns="Price",
currency="USD"
)
# Format large numbers with suffixes
.fmt_number(
columns="Revenue",
use_seps=True,
decimals=0
)
# Format as percentage
.fmt_percent(
columns="Margin",
decimals=1
)
# Format with thousand separators
.fmt_integer(
columns="Units",
use_seps=True
)
)
table.save("numeric_formatting.html")
Date and Time Formatting:
from great_tables import GT import pandas as pd from datetime import datetime, date
df = pd.DataFrame({ "Event": ["Launch", "Update", "Maintenance", "Release"], "Date": [ date(2025, 1, 15), date(2025, 3, 22), date(2025, 6, 1), date(2025, 9, 30) ], "Timestamp": [ datetime(2025, 1, 15, 9, 0), datetime(2025, 3, 22, 14, 30), datetime(2025, 6, 1, 2, 0), datetime(2025, 9, 30, 10, 0) ] })
table = ( GT(df) .tab_header(title="Product Timeline")
# Format date
.fmt_date(
columns="Date",
date_style="day_month_year"
)
# Format datetime
.fmt_datetime(
columns="Timestamp",
date_style="yMd",
time_style="Hm"
)
)
table.save("date_formatting.html")
Custom Number Formatting:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Metric": ["Users", "Revenue", "Conversion", "Avg Order"], "Value": [1234567, 5678901.23, 0.0342, 156.789] })
table = ( GT(df) .tab_header(title="Dashboard Metrics")
# Custom suffixes for large numbers
.fmt_number(
columns="Value",
rows=[0], # First row only
compact=True # Use K, M, B suffixes
)
# Currency for second row
.fmt_currency(
columns="Value",
rows=[1],
currency="USD",
decimals=0
)
# Percentage for third row
.fmt_percent(
columns="Value",
rows=[2],
decimals=2
)
# Standard number for fourth row
.fmt_currency(
columns="Value",
rows=[3],
currency="USD",
decimals=2
)
)
table.save("custom_formatting.html")
- Styling and Colors
Background Colors:
from great_tables import GT from great_tables import style, loc import pandas as pd
df = pd.DataFrame({ "Category": ["Electronics", "Clothing", "Food", "Home"], "Q1": [150000, 95000, 120000, 85000], "Q2": [180000, 88000, 135000, 92000], "Q3": [165000, 102000, 128000, 78000], "Q4": [210000, 115000, 145000, 105000] })
table = ( GT(df) .tab_header(title="Quarterly Sales by Category")
# Style header row
.tab_style(
style=style.fill(color="#4a86e8"),
locations=loc.column_labels()
)
.tab_style(
style=style.text(color="white", weight="bold"),
locations=loc.column_labels()
)
# Alternate row colors
.tab_style(
style=style.fill(color="#f3f3f3"),
locations=loc.body(rows=[1, 3]) # Even rows
)
# Highlight specific cell
.tab_style(
style=style.fill(color="#90EE90"),
locations=loc.body(columns="Q4", rows=[0]) # Highest Q4
)
)
table.save("styled_table.html")
Text Styling:
from great_tables import GT from great_tables import style, loc import pandas as pd
df = pd.DataFrame({ "Rank": [1, 2, 3, 4, 5], "Company": ["TechCorp", "DataInc", "CloudSoft", "AILabs", "DevHub"], "Revenue_B": [125.4, 98.2, 87.5, 76.3, 65.8], "Change": [0.15, 0.08, -0.03, 0.22, -0.12] })
table = ( GT(df) .tab_header(title="Top Companies by Revenue")
# Bold first column
.tab_style(
style=style.text(weight="bold"),
locations=loc.body(columns="Rank")
)
# Italic company names
.tab_style(
style=style.text(style="italic"),
locations=loc.body(columns="Company")
)
# Color positive/negative changes
.tab_style(
style=style.text(color="green"),
locations=loc.body(columns="Change", rows=[0, 1, 3]) # Positive
)
.tab_style(
style=style.text(color="red"),
locations=loc.body(columns="Change", rows=[2, 4]) # Negative
)
# Format numbers
.fmt_currency(columns="Revenue_B", currency="USD", decimals=1)
.fmt_percent(columns="Change", decimals=1)
)
table.save("text_styled.html")
Borders and Spacing:
from great_tables import GT from great_tables import style, loc import pandas as pd
df = pd.DataFrame({ "Section": ["Introduction", "Methods", "Results", "Discussion"], "Pages": [5, 12, 18, 8], "Figures": [2, 6, 15, 3], "Tables": [0, 3, 8, 1] })
table = ( GT(df) .tab_header(title="Manuscript Structure")
# Add border below header
.tab_style(
style=style.borders(sides="bottom", color="black", weight="2px"),
locations=loc.column_labels()
)
# Add border below last row
.tab_style(
style=style.borders(sides="bottom", color="black", weight="2px"),
locations=loc.body(rows=[-1])
)
# Cell padding
.tab_options(
data_row_padding="10px",
column_labels_padding="12px"
)
)
table.save("bordered_table.html")
- Conditional Formatting
Color Scales:
from great_tables import GT from great_tables import style, loc from great_tables.data import countrypops import pandas as pd
Sample heatmap data
df = pd.DataFrame({ "Month": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], "North": [85, 92, 88, 95, 91, 97], "South": [72, 78, 81, 75, 82, 88], "East": [90, 85, 92, 89, 94, 91], "West": [68, 75, 79, 82, 78, 85] })
table = ( GT(df) .tab_header(title="Regional Performance Scores")
# Apply color scale to data columns
.data_color(
columns=["North", "South", "East", "West"],
palette=["#FF6B6B", "#FFEB3B", "#4CAF50"], # Red -> Yellow -> Green
domain=[60, 100]
)
)
table.save("color_scale.html")
Conditional Icons:
from great_tables import GT, html import pandas as pd
df = pd.DataFrame({ "Metric": ["Revenue", "Users", "Conversion", "NPS"], "Current": [1250000, 85000, 3.2, 72], "Previous": [1180000, 78000, 3.5, 68], "Change_Pct": [5.9, 9.0, -8.6, 5.9] })
def trend_icon(value): """Return trend icon based on value.""" if value > 0: return html('<span style="color: green;">▲</span>') # Up arrow elif value < 0: return html('<span style="color: red;">▼</span>') # Down arrow else: return html('<span style="color: gray;">▶</span>') # Right arrow
Add trend column
df["Trend"] = df["Change_Pct"].apply(trend_icon)
table = ( GT(df) .tab_header(title="Key Metrics Dashboard")
.fmt_number(columns="Current", use_seps=True, decimals=0)
.fmt_number(columns="Previous", use_seps=True, decimals=0)
.fmt_percent(columns="Change_Pct", decimals=1, scale_values=False)
)
table.save("conditional_icons.html")
Bar Charts in Cells:
from great_tables import GT, html import pandas as pd
df = pd.DataFrame({ "Product": ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"], "Sales": [85000, 120000, 65000, 95000, 110000], "Target": [100000, 100000, 100000, 100000, 100000] })
def create_bar(value, max_value=150000): """Create inline bar chart.""" width = min(value / max_value * 100, 100) color = "#4CAF50" if value >= 100000 else "#FF9800" return html(f''' <div style="background: #eee; width: 100px; height: 20px;"> <div style="background: {color}; width: {width}%; height: 100%;"></div> </div> ''')
df["Progress"] = df["Sales"].apply(create_bar)
table = ( GT(df) .tab_header(title="Sales Progress by Product") .fmt_number(columns="Sales", use_seps=True, decimals=0) .fmt_number(columns="Target", use_seps=True, decimals=0) )
table.save("bar_charts.html")
- Grouped Rows and Columns
Row Groups:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Region": ["North", "North", "South", "South", "East", "East", "West", "West"], "Product": ["Widget", "Gadget", "Widget", "Gadget", "Widget", "Gadget", "Widget", "Gadget"], "Sales": [45000, 32000, 38000, 41000, 52000, 28000, 35000, 39000], "Units": [450, 160, 380, 205, 520, 140, 350, 195] })
table = ( GT(df, groupname_col="Region") # Group by Region .tab_header( title="Sales by Region and Product", subtitle="Q4 2025 Performance" ) .fmt_currency(columns="Sales", currency="USD", decimals=0) .fmt_integer(columns="Units", use_seps=True)
# Style group labels
.tab_style(
style=[
style.fill(color="#e8e8e8"),
style.text(weight="bold")
],
locations=loc.row_groups()
)
)
table.save("row_groups.html")
Column Spanners:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Product": ["Widget A", "Widget B", "Gadget X"], "Q1_Sales": [25000, 32000, 18000], "Q1_Units": [250, 320, 90], "Q2_Sales": [28000, 35000, 22000], "Q2_Units": [280, 350, 110], "Q3_Sales": [31000, 38000, 25000], "Q3_Units": [310, 380, 125] })
table = ( GT(df) .tab_header(title="Quarterly Performance")
# Create column spanners
.tab_spanner(
label="Q1",
columns=["Q1_Sales", "Q1_Units"]
)
.tab_spanner(
label="Q2",
columns=["Q2_Sales", "Q2_Units"]
)
.tab_spanner(
label="Q3",
columns=["Q3_Sales", "Q3_Units"]
)
# Rename columns
.cols_label(
Q1_Sales="Sales",
Q1_Units="Units",
Q2_Sales="Sales",
Q2_Units="Units",
Q3_Sales="Sales",
Q3_Units="Units"
)
# Format numbers
.fmt_currency(columns=["Q1_Sales", "Q2_Sales", "Q3_Sales"], currency="USD", decimals=0)
.fmt_integer(columns=["Q1_Units", "Q2_Units", "Q3_Units"], use_seps=True)
)
table.save("column_spanners.html")
Nested Groups:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Division": ["Consumer", "Consumer", "Consumer", "Enterprise", "Enterprise", "Enterprise"], "Category": ["Electronics", "Home", "Fashion", "Software", "Hardware", "Services"], "Product": ["Phones", "Furniture", "Apparel", "Cloud", "Servers", "Consulting"], "Revenue": [150, 45, 78, 220, 180, 95], "Growth": [0.12, 0.05, -0.02, 0.25, 0.08, 0.15] })
table = ( GT(df, groupname_col="Division", rowname_col="Category") .tab_header( title="Business Unit Performance", subtitle="Annual Revenue (Millions USD)" ) .fmt_currency(columns="Revenue", currency="USD", decimals=0) .fmt_percent(columns="Growth", decimals=1) )
table.save("nested_groups.html")
- Footnotes and Annotations
Adding Footnotes:
from great_tables import GT from great_tables import loc import pandas as pd
df = pd.DataFrame({ "Company": ["TechCorp", "DataInc", "CloudSoft", "AILabs"], "Revenue_B": [125.4, 98.2, 87.5, 76.3], "Employees": [45000, 28000, 15000, 8500], "Founded": [1985, 1998, 2010, 2015] })
table = ( GT(df) .tab_header( title="Tech Companies Overview", subtitle="Leading technology firms" )
# Add footnote to title
.tab_footnote(
footnote="Revenue in billions USD",
locations=loc.title()
)
# Add footnote to specific column
.tab_footnote(
footnote="Full-time employees only",
locations=loc.column_labels(columns="Employees")
)
# Add footnote to specific cell
.tab_footnote(
footnote="Acquired by MegaCorp in 2024",
locations=loc.body(columns="Company", rows=[2])
)
# Source note
.tab_source_note(
source_note="Data as of December 2025"
)
.fmt_currency(columns="Revenue_B", currency="USD", decimals=1)
.fmt_integer(columns="Employees", use_seps=True)
)
table.save("footnotes.html")
Stubhead Labels:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Year": [2022, 2023, 2024, 2025], "Revenue": [85.2, 92.5, 105.8, 118.3], "Profit": [12.5, 15.8, 19.2, 23.1], "Margin": [0.147, 0.171, 0.181, 0.195] })
table = ( GT(df, rowname_col="Year") .tab_header(title="Financial Summary") .tab_stubhead(label="Fiscal Year") # Label for row names column
.fmt_currency(columns=["Revenue", "Profit"], currency="USD", decimals=1)
.fmt_percent(columns="Margin", decimals=1)
)
table.save("stubhead.html")
- Export Options
Export to HTML:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Item": ["A", "B", "C"], "Value": [100, 200, 150] })
table = GT(df).tab_header(title="Export Demo")
Save as HTML file
table.save("table.html")
Get HTML string
html_string = table.as_raw_html() print(html_string[:500]) # Preview
Export to Image (PNG):
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Product": ["Widget", "Gadget", "Tool"], "Price": [29.99, 49.99, 19.99], "Stock": [150, 85, 200] })
table = ( GT(df) .tab_header(title="Product Inventory") .fmt_currency(columns="Price", currency="USD") )
Save as PNG (requires webshot/chromedriver)
table.save("table.png")
Alternative: Use playwright
table.save("table.png", web_driver="playwright")
Inline Display in Notebooks:
from great_tables import GT import pandas as pd
df = pd.DataFrame({ "Name": ["Alice", "Bob", "Charlie"], "Score": [95, 87, 92] })
In Jupyter, just return the table object
table = GT(df).tab_header(title="Test Scores") table # Displays inline
Complete Examples
Example 1: Financial Report Table
from great_tables import GT, html from great_tables import style, loc import pandas as pd import numpy as np
def create_financial_report( data: pd.DataFrame, title: str = "Financial Report", output_path: str = "financial_report.html" ) -> GT: """ Create publication-quality financial report table.
Args:
data: Financial data with columns for metrics and periods
title: Report title
output_path: Output HTML path
Returns:
GT table object
"""
# Sample financial data
df = pd.DataFrame({
"Metric": [
"Revenue", "Cost of Sales", "Gross Profit",
"Operating Expenses", "EBITDA", "Depreciation",
"EBIT", "Interest Expense", "Pre-tax Income",
"Taxes", "Net Income"
],
"FY2023": [
1250000, 625000, 625000,
312500, 312500, 62500,
250000, 25000, 225000,
67500, 157500
],
"FY2024": [
1450000, 710500, 739500,
348000, 391500, 72500,
319000, 28000, 291000,
87300, 203700
],
"FY2025": [
1680000, 806400, 873600,
386400, 487200, 84000,
403200, 30000, 373200,
111960, 261240
],
"Change_Pct": [
15.9, 13.5, 18.1,
11.0, 24.4, 15.9,
26.4, 7.1, 28.3,
28.3, 28.3
]
})
# Identify key rows
key_rows = [2, 4, 6, 10] # Gross Profit, EBITDA, EBIT, Net Income
table = (
GT(df)
.tab_header(
title=title,
subtitle="Fiscal Year Comparison (USD)"
)
# Column labels
.cols_label(
Metric="",
FY2023="FY 2023",
FY2024="FY 2024",
FY2025="FY 2025",
Change_Pct="YoY Change"
)
# Format currencies
.fmt_currency(
columns=["FY2023", "FY2024", "FY2025"],
currency="USD",
decimals=0,
use_seps=True
)
# Format percentage
.fmt_percent(
columns="Change_Pct",
decimals=1,
scale_values=False
)
# Style header
.tab_style(
style=[
style.fill(color="#1a365d"),
style.text(color="white", weight="bold")
],
locations=loc.column_labels()
)
# Style key metric rows
.tab_style(
style=[
style.fill(color="#f0f4f8"),
style.text(weight="bold")
],
locations=loc.body(rows=key_rows)
)
# Borders
.tab_style(
style=style.borders(sides="bottom", color="#1a365d", weight="2px"),
locations=loc.column_labels()
)
.tab_style(
style=style.borders(sides="top", color="#1a365d", weight="2px"),
locations=loc.body(rows=[10])
)
# Positive/negative change colors
.tab_style(
style=style.text(color="#22543d"),
locations=loc.body(
columns="Change_Pct",
rows=lambda x: x["Change_Pct"] > 0
)
)
# Source note
.tab_source_note(
source_note="All figures in thousands USD. Change calculated FY2024 to FY2025."
)
# Options
.tab_options(
table_width="100%",
table_font_size="14px"
)
)
table.save(output_path)
print(f"Financial report saved to: {output_path}")
return table
Generate report
table = create_financial_report(df, "Annual Financial Report", "annual_report.html")
Example 2: Sales Dashboard Table
from great_tables import GT, html from great_tables import style, loc import pandas as pd import numpy as np
def create_sales_dashboard_table(output_path: str = "sales_dashboard.html") -> GT: """ Create sales dashboard table with KPIs and sparklines. """
# Sample data
np.random.seed(42)
df = pd.DataFrame({
"Region": ["North America", "Europe", "Asia Pacific", "Latin America", "Middle East"],
"Revenue": [4250000, 3180000, 2890000, 1520000, 890000],
"Target": [4000000, 3500000, 2500000, 1800000, 1000000],
"Units": [42500, 31800, 57800, 30400, 17800],
"Customers": [1250, 980, 1560, 620, 340],
"Growth": [0.12, -0.05, 0.28, 0.08, 0.15],
"Satisfaction": [4.5, 4.2, 4.7, 4.1, 4.3]
})
# Calculate achievement
df["Achievement"] = df["Revenue"] / df["Target"]
def achievement_bar(pct):
"""Create progress bar for achievement."""
width = min(pct * 100, 100)
if pct >= 1.0:
color = "#22c55e" # Green
elif pct >= 0.9:
color = "#eab308" # Yellow
else:
color = "#ef4444" # Red
return html(f'''
<div style="display: flex; align-items: center; gap: 8px;">
<div style="background: #e5e7eb; width: 60px; height: 12px; border-radius: 6px;">
<div style="background: {color}; width: {width}%; height: 100%; border-radius: 6px;"></div>
</div>
<span style="font-size: 12px;">{pct*100:.0f}%</span>
</div>
''')
def growth_indicator(value):
"""Create growth indicator with arrow."""
if value > 0:
arrow = "&#9650;" # Up
color = "#22c55e"
elif value < 0:
arrow = "&#9660;" # Down
color = "#ef4444"
else:
arrow = "&#9654;" # Right
color = "#6b7280"
return html(f'<span style="color: {color};">{arrow} {abs(value)*100:.1f}%</span>')
def star_rating(score):
"""Create star rating display."""
full_stars = int(score)
half_star = score - full_stars >= 0.5
empty_stars = 5 - full_stars - (1 if half_star else 0)
stars = "&#9733;" * full_stars
if half_star:
stars += "&#9734;"
stars += "&#9734;" * empty_stars
return html(f'<span style="color: #f59e0b;">{stars}</span> ({score:.1f})')
df["Achievement_Bar"] = df["Achievement"].apply(achievement_bar)
df["Growth_Display"] = df["Growth"].apply(growth_indicator)
df["Rating_Display"] = df["Satisfaction"].apply(star_rating)
table = (
GT(df[["Region", "Revenue", "Achievement_Bar", "Units", "Customers", "Growth_Display", "Rating_Display"]])
.tab_header(
title="Regional Sales Performance",
subtitle="Q4 2025 Dashboard"
)
.cols_label(
Region="Region",
Revenue="Revenue",
Achievement_Bar="Target Achievement",
Units="Units Sold",
Customers="Active Customers",
Growth_Display="YoY Growth",
Rating_Display="CSAT Score"
)
# Format numbers
.fmt_currency(columns="Revenue", currency="USD", decimals=0, use_seps=True)
.fmt_integer(columns=["Units", "Customers"], use_seps=True)
# Header style
.tab_style(
style=[
style.fill(color="#0f172a"),
style.text(color="white", weight="bold", size="13px")
],
locations=loc.column_labels()
)
# Alternating rows
.tab_style(
style=style.fill(color="#f8fafc"),
locations=loc.body(rows=[1, 3])
)
# Region column style
.tab_style(
style=style.text(weight="bold"),
locations=loc.body(columns="Region")
)
.tab_source_note("Data refreshed: January 17, 2026")
.tab_options(
table_width="100%",
data_row_padding="12px"
)
)
table.save(output_path)
return table
table = create_sales_dashboard_table("sales_dashboard.html")
Example 3: Scientific Data Table
from great_tables import GT from great_tables import style, loc import pandas as pd import numpy as np
def create_scientific_table(output_path: str = "scientific_table.html") -> GT: """ Create publication-quality scientific data table. """
# Experimental results data
df = pd.DataFrame({
"Treatment": ["Control", "Drug A (10mg)", "Drug A (50mg)", "Drug B (10mg)", "Drug B (50mg)"],
"N": [25, 24, 26, 25, 23],
"Mean": [45.2, 52.8, 61.3, 48.9, 58.7],
"SD": [8.5, 9.2, 10.1, 8.9, 11.2],
"SE": [1.7, 1.88, 1.98, 1.78, 2.34],
"CI_Lower": [41.7, 48.9, 57.2, 45.2, 53.8],
"CI_Upper": [48.7, 56.7, 65.4, 52.6, 63.6],
"P_Value": [None, 0.042, 0.001, 0.185, 0.003]
})
def format_ci(row):
"""Format confidence interval."""
return f"[{row['CI_Lower']:.1f}, {row['CI_Upper']:.1f}]"
df["95% CI"] = df.apply(format_ci, axis=1)
def format_pvalue(p):
"""Format p-value with significance markers."""
if p is None:
return "-"
elif p < 0.001:
return "<0.001***"
elif p < 0.01:
return f"{p:.3f}**"
elif p < 0.05:
return f"{p:.3f}*"
else:
return f"{p:.3f}"
df["P_Formatted"] = df["P_Value"].apply(format_pvalue)
table = (
GT(df[["Treatment", "N", "Mean", "SD", "SE", "95% CI", "P_Formatted"]])
.tab_header(
title="Table 1. Treatment Effects on Primary Outcome",
subtitle="Values represent endpoint measurements (units)"
)
.cols_label(
Treatment="Treatment Group",
N="n",
Mean="Mean",
SD="SD",
SE="SE",
P_Formatted="P-value"
)
# Format numbers
.fmt_number(columns=["Mean", "SD", "SE"], decimals=1)
# Center align numeric columns
.cols_align(
align="center",
columns=["N", "Mean", "SD", "SE", "95% CI", "P_Formatted"]
)
# Header style (minimal, scientific)
.tab_style(
style=[
style.text(weight="bold"),
style.borders(sides="bottom", weight="2px", color="black")
],
locations=loc.column_labels()
)
# Control row italic
.tab_style(
style=style.text(style="italic"),
locations=loc.body(rows=[0], columns="Treatment")
)
# Significant p-values bold
.tab_style(
style=style.text(weight="bold"),
locations=loc.body(rows=[1, 2, 4], columns="P_Formatted")
)
# Bottom border
.tab_style(
style=style.borders(sides="bottom", weight="2px", color="black"),
locations=loc.body(rows=[-1])
)
# Footnotes
.tab_footnote(
footnote="Standard deviation",
locations=loc.column_labels(columns="SD")
)
.tab_footnote(
footnote="Standard error of the mean",
locations=loc.column_labels(columns="SE")
)
.tab_source_note("*p<0.05, **p<0.01, ***p<0.001 vs. Control (Dunnett's test)")
.tab_source_note("CI = Confidence Interval")
.tab_options(
table_font_names="Times New Roman, serif",
table_font_size="12px"
)
)
table.save(output_path)
return table
table = create_scientific_table("experiment_results.html")
Integration Examples
Great Tables with Streamlit
import streamlit as st from great_tables import GT import pandas as pd
st.set_page_config(page_title="Table Demo", layout="wide") st.title("Great Tables in Streamlit")
Sample data
df = pd.DataFrame({ "Product": ["Widget A", "Widget B", "Gadget X"], "Price": [29.99, 49.99, 19.99], "Stock": [150, 85, 200], "Rating": [4.5, 4.2, 4.8] })
Create table
table = ( GT(df) .tab_header(title="Product Catalog") .fmt_currency(columns="Price", currency="USD") .fmt_number(columns="Rating", decimals=1) )
Display in Streamlit
st.html(table.as_raw_html())
Great Tables with Polars
from great_tables import GT import polars as pl
Create Polars DataFrame
df_polars = pl.DataFrame({ "name": ["Alice", "Bob", "Charlie"], "score": [95, 87, 92], "grade": ["A", "B+", "A-"] })
Convert to pandas for Great Tables
df_pandas = df_polars.to_pandas()
Create table
table = ( GT(df_pandas) .tab_header(title="Student Scores") .cols_label( name="Student", score="Score", grade="Grade" ) )
table.save("polars_table.html")
Best Practices
- Keep Tables Focused
GOOD: Select relevant columns
df_display = df[["Name", "Revenue", "Growth"]] table = GT(df_display)
AVOID: Displaying too many columns
table = GT(df) # If df has 20+ columns
- Use Appropriate Formatting
GOOD: Match format to data type
table = ( GT(df) .fmt_currency(columns="Price", currency="USD") .fmt_percent(columns="Growth", decimals=1) .fmt_integer(columns="Units", use_seps=True) )
AVOID: Generic number format for everything
- Limit Rows for Display
GOOD: Show summary or top N
df_top10 = df.nlargest(10, "Revenue") table = GT(df_top10)
AVOID: Displaying thousands of rows
- Use Color Sparingly
GOOD: Highlight key information
table.data_color( columns="Performance", palette=["#fee2e2", "#dcfce7"], # Subtle colors domain=[0, 100] )
AVOID: Rainbow color schemes
Troubleshooting
Common Issues
Issue: Table not displaying in Jupyter
Solution: Ensure rich display
from great_tables import GT table = GT(df) display(table) # Or just: table
Issue: HTML export looks different
Solution: Include all styling
table.save("output.html") # Includes CSS
Issue: Image export not working
Solution: Install webshot or use playwright
pip install webshot
or
pip install playwright playwright install chromium
table.save("output.png", web_driver="playwright")
Issue: Slow with large DataFrames
Solution: Limit rows
df_display = df.head(100) table = GT(df_display)
Issue: Special characters not rendering
Solution: Use html() helper
from great_tables import html cell_content = html("€ 100") # Euro symbol
Version History
-
1.0.0 (2026-01-17): Initial release
-
Basic table creation and styling
-
Column formatting (currency, percent, date)
-
Conditional formatting and color scales
-
Row and column grouping
-
Footnotes and annotations
-
Export to HTML and images
-
Complete report examples
-
Integration with Streamlit and Polars
-
Best practices and troubleshooting
Resources
-
Official Documentation: https://posit-dev.github.io/great-tables/
Create publication-quality tables with Great Tables - beautiful data presentation made easy!