Great Tables v0.18.0: Easy Column Spanners and More!
Author
Rich Iannone
Published
July 18, 2025
The development of Great Tables continues! We’re excited to announce the release of v0.18.0, which brings several powerful new features. These features make it even easier to create beautiful, informative tables. The key additions in this release include new methods (and a tweak to an existing one):
.fmt_datetime(): added format_str= parameter for extra customization
Let’s explore each of these interesting new features!
Quick spanner creation with tab_spanner_delim()
Working with data that has hierarchical column names can be tedious when manually creating spanners. The new .tab_spanner_delim() method automates this process by intelligently splitting column names based on a delimiter and creating the appropriate spanner structure.
Here’s a practical example using the towny dataset, which contains population data for a collection of municipalities across multiple census years. Let’s start by looking at the most populated cities and examining their column structure:
from great_tables import GTfrom great_tables.data import townyimport polars as plimport polars.selectors as cs# Create a smaller version of the `towny` datasettowny_mini = ( pl.from_pandas(towny) .filter(pl.col("csd_type") =="city") .sort("population_2021", descending=True) .select("name", cs.starts_with("population_"), cs.starts_with("density_") ) .head(5))# Let's look at the column namesprint(towny_mini.columns)
Notice how the column names have a clear hierarchical structure with underscores as delimiters. Let’s now create a table that takes advantage of this structure:
( GT(towny_mini, rowname_col="name") .tab_spanner_delim(delim="_") .fmt_integer(columns=cs.contains("population")) .fmt_number(columns=cs.contains("density"), decimals=1) .tab_header(title="Population and Density Trends from Census Data") .opt_align_table_header(align="left"))
Population and Density Trends from Census Data
population
density
1996
2001
2006
2011
2016
2021
1996
2001
2006
2011
2016
2021
Toronto
2,385,421
2,481,494
2,503,281
2,615,060
2,731,571
2,794,356
3,779.8
3,932.0
3,966.5
4,143.6
4,328.3
4,427.8
Ottawa
721,136
774,072
812,129
883,391
934,243
1,017,449
258.6
277.6
291.3
316.8
335.1
364.9
Mississauga
544,382
612,925
668,599
713,443
721,599
717,961
1,859.6
2,093.8
2,283.9
2,437.1
2,465.0
2,452.6
Brampton
268,251
325,428
433,806
523,906
593,638
656,480
1,008.9
1,223.9
1,631.5
1,970.4
2,232.7
2,469.0
Hamilton
467,799
490,268
504,559
519,949
536,917
569,353
418.3
438.4
451.2
464.9
480.1
509.1
The .tab_spanner_delim() method recognizes the underscore delimiter and creates a hierarchical structure: "population" and "density" become top-level spanners, with the years (1996, 2001, 2021) as the final column labels. This creates a clean, organized appearance that clearly groups related metrics together. And, this one method can be used instead of a combination of .cols_label() and .tab_spanner() (which requires a separate invocation per spanner added).
Beautiful boolean formatting with fmt_tf()
Boolean data is common in analytical tables, but raw True/False values can look unprofessional. The new .fmt_tf() method provides elegant ways to display boolean data using symbols, words, or custom formatting.
Here’s a simple example showing different tf_style= options:
from great_tables import GTimport polars as pl# Create a simple DF with boolean databool_df = pl.DataFrame({"feature": ["Premium Sound", "Leather Seats", "Sunroof", "Navigation"],"model_a": [True, False, True, True],"model_b": [True, True, False, True],"model_c": [False, True, True, False]})( GT(bool_df, rowname_col="feature") .fmt_tf(tf_style="check-mark", colors=["green", "red"]) .tab_header(title="Car Features Comparison", subtitle="Using check-mark style"))
Car Features Comparison
Using check-mark style
model_a
model_b
model_c
Premium Sound
✔
✔
✘
Leather Seats
✘
✔
✔
Sunroof
✔
✘
✔
Navigation
✔
✔
✘
You can also use different symbols and colors for a more distinctive look:
The .fmt_tf() method transforms boolean values into visually appealing symbols that make it easy to quickly scan and compare data across rows and columns.
Rotating column labels with cols_label_rotate()
When dealing with many columns or long column names, horizontal space becomes precious. The .cols_label_rotate() method solves this by rotating column labels vertically, allowing for more compact table layouts.
Here’s an example where we use the gtcars dataset to create a table which communicates a feature matrix:
This example demonstrates how both the .fmt_tf() and .cols_label_rotate() methods can work well together. The boolean columns use checkmarks (✓/✗) with custom colors=, while the rotated labels save horizontal space in this dense feature matrix. The combination allows you to put more information into a compact and still readable format.
Enhanced datetime formatting with fmt_datetime()
The .fmt_datetime() method now supports custom format strings through the new format_str= parameter, giving you complete control over how datetime values appear in your tables.
Here’s an example using the included gibraltar weather dataset:
The custom datetime formatting string in format_str="%b %d %Y (%a) - %I:%M %p" creates a readable datetime format that’s perfect for weather reporting, showing the day of week, month, day, year, and the time in 12-hour format.
Acknowledgements and what’s next
We’re grateful to all the contributors who made this release possible. These new features represent significant improvements for creating space-efficient tables while also maximizing visual appeal.
The combination of these features lets you now create complex, professional tables with hierarchical column structures, boolean indicators, space-saving labels, and nicely formatted datetime displays.
We’re always happy to get feedback and hear about how you’re using Great Tables: