Commercial enterprise offerings

What's New in gt 1.2.0: Better Tables Through Collaboration

Written by Rich Iannone
2023-01-13
Table templates for gt 1.2.0 showing Quarterly Sales by Region and Student Test Scores for the Spring Semester 2025.

The gt v1.2.0 release represents an exciting milestone for the package, and I’m thrilled to share what’s new! This release is the result of close collaboration between Posit and our colleagues at GSK (GlaxoSmithKline), along with significant contributions from the open source community. Over the past several months, we’ve held regular meetings with the GSK team to discuss improvements that would benefit both pharmaceutical/clinical tables and the broader gt user base. As a result, we now have many wonderful enhancements that make gt even more powerful and versatile.

Celebrating Our Contributors

Before getting into new features, I want to highlight the incredible contributors who made this release possible. Shannon Haughton (@shannonhaughton), Ellis Hughes (@thebioengineer), and Romain François (@romainfrancois) submitted excellent PRs that: (1) improved LaTeX output, (2) enhanced Word document support, and (3) fixed a lot of bugs. Their domain expertise in clinical reporting has made gt much better at handling complex table structures.

Olivier Roy (@olivroy) deserves special recognition for code changes that significantly sped up the generation of large tables.

Now let’s explore what’s new in v1.2.0 (with some highlights from v1.1.0 and v1.0.0 as well).

Multi-Column Stubs: Hierarchical Row Labels

One of the most requested features has been the ability to create hierarchical row labels. With v1.1.0, the rowname_col argument in gt() can now accept a vector of column names, creating a multi-column stub where values form a visual hierarchy.

This is particularly useful for clinical trial tables with multiple levels of categorization and financial reports with account hierarchies. Really, any table where rows have natural parent-child relationships could benefit from this new form of structuring a stub.

Here’s an example that has a multi-column stub:

# Create a dataset with hierarchical structure
sales_data <- tibble(
    region = c("North", "North", "North", "South", "South", "South"),
    category = c(
        "Electronics", "Clothing", "Food", 
        "Electronics", "Clothing", "Food"
    ),
    Q1 = c(45000, 32000, 28000, 38000, 41000, 35000),
    Q2 = c(48000, 35000, 30000, 42000, 39000, 37000),
    Q3 = c(52000, 38000, 32000, 45000, 43000, 39000),
    Q4 = c(58000, 42000, 35000, 48000, 46000, 41000)
)

# Use multi-column stub for hierarchical layout
sales_data |>
    gt(rowname_col = c("region", "category")) |>
    tab_header(
        title = "Quarterly Sales by Region and Category",
        subtitle = "All values in USD"
    ) |>
    fmt_currency(
        columns = everything(),
        currency = "USD",
        decimals = 0
    ) |>
    tab_stubhead(label = c("Region", "Category")) |>
    tab_style(
        style = cell_fill(color = "gray95"),
        locations = cells_stub()
    )
Quarterly Sales by Region and Category
All values in USD
Region Category Q1 Q2 Q3 Q4
North Electronics $45,000 $48,000 $52,000 $58,000
Clothing $32,000 $35,000 $38,000 $42,000
Food $28,000 $30,000 $32,000 $35,000
South Electronics $38,000 $42,000 $45,000 $48,000
Clothing $41,000 $39,000 $43,000 $46,000
Food $35,000 $37,000 $39,000 $41,000

The multi-column stub creates a clean visual hierarchy, with repeating values in the first column automatically consolidated. The v1.2.0 release further improved multi-column stub support across all output formats: HTML, LaTeX, Word, and RTF.

Row-Wise Aggregation with summary_columns()

The new summary_columns() function enables horizontal aggregation across rows. This is a perfect complement to the existing summary_rows() function that aggregates vertically. This new function is incredibly useful when you need to calculate row totals, averages, or other statistics across multiple columns.

test_scores <-
    tibble(
        student = c("Alice", "Billy", "Courtney", "Dirk", "Eva"),
        Math = c(92, 78, 88, 95, 82),
        Science = c(88, 82, 91, 89, 78),
        English = c(85, 90, 87, 82, 94),
        History = c(90, 85, 83, 88, 86)
    )

test_scores |>
    gt(rowname_col = "student") |>
    tab_header(
        title = "Student Test Scores",
        subtitle = "Spring Semester 2025"
    ) |>
    summary_columns(
        columns = c(Math, Science, English, History),
        fns = list(
            ~ mean(.),
            ~ min(.),
            ~ max(.)
        ),
        new_col_names = c("average", "lowest", "highest"),
        new_col_labels = c("Average", "Lowest", "Highest"),
        fmt = ~ fmt_number(., decimals = 1)
    ) |>
    tab_spanner(
        label = "Subject Scores",
        columns = c(Math, Science, English, History)
    ) |>
    tab_spanner(
        label = "Summary",
        columns = c(average, lowest, highest)
    ) |>
    data_color(
        columns = average,
        palette = c("white", "steelblue"),
        domain = c(80, 95)
    )
Student Test Scores
Spring Semester 2025
Subject Scores
Summary
Math Science English History Average Lowest Highest
Alice 92.0 88.0 85.0 90.0 88.8 85.0 92.0
Billy 78.0 82.0 90.0 85.0 83.8 78.0 90.0
Courtney 88.0 91.0 87.0 83.0 87.2 83.0 91.0
Dirk 95.0 89.0 82.0 88.0 88.5 82.0 95.0
Eva 82.0 78.0 94.0 86.0 85.0 78.0 94.0

The summary_columns() function gives you full control over the aggregation functions, column placement (left or right), and formatting. It’s a powerful tool for adding computed statistics without leaving the gt workflow.

Formatting Numbers with SI Prefixes

The new fmt_number_si() function makes it easy to format large (or small) numbers using SI prefixes like kilo (k), mega (M), giga (G), and more. This is perfect for scientific data, engineering measurements, or any domain where values span multiple orders of magnitude.

storage_data <- 
    tibble(
        device = c(
            "USB Drive", "Laptop SSD", "External HDD", 
            "NAS Server", "Cloud Storage"
        ),
    capacity_bytes = c(32e9, 512e9, 2e12, 16e12, 100e12),
    transfer_speed = c(150e6, 3500e6, 180e6, 1000e6, 500e6)
    )

storage_data |>
    gt() |>
    tab_header(title = "Storage Device Specifications") |>
    cols_label(
        device = "Device",
        capacity_bytes = "Capacity",
        transfer_speed = "Transfer Speed"
    ) |>
    fmt_number_si(
        columns = capacity_bytes,
        unit = "B",
        decimals = 0
    ) |>
    fmt_number_si(
        columns = transfer_speed,
        unit = "B/s",
        decimals = 0
    )
Storage Device Specifications
Device Capacity Transfer Speed
USB Drive 32 GB 150 MB/s
Laptop SSD 512 GB 4 GB/s
External HDD 2 TB 180 MB/s
NAS Server 16 TB 1 GB/s
Cloud Storage 100 TB 500 MB/s

The function automatically selects the appropriate SI prefix to keep numbers readable, while optionally appending a unit. This eliminates the need for manual scaling and prefix selection.

Enhanced Output Format Support

A major focus of recent releases has been improving gt’s non-HTML output formats. Whether you need to generate PDF documents via LaTeX, create Word reports, or produce RTF files, gt now handles these formats with greater fidelity and fewer surprises.

LaTeX Improvements

Thanks to extensive work by Ellis Hughes (@thebioengineer) and Shannon Haughton (@shannonhaughton), LaTeX output has received significant improvements:

  • text within cells can now be properly aligned horizontally
  • there is better handling of special Unicode characters
  • hierarchical stubs now render correctly with multi-column stub support
  • column spanners now have correct widths
  • the new latex() helper allows you to insert raw LaTeX strings

Word Document Improvements

Word output (via as_word() and gtsave()) has also seen major enhancements:

  • multi-column stub support was added, courtesy of Romain François (@romainfrancois)
  • using <br> tags now works correctly for line breaks
  • captions no longer automatically add “Table N” prefixes
  • special characters from sub_small_vals() and sub_large_vals() now render correctly

RTF Improvements

  • multi-column stub support
  • stub alignment values are now honored
  • support for fmt_image() in RTF tables

Other Notable Improvements

There are a few more highlights worth mentioning. The gt() function now has an omit_na_group argument that lets you exclude rows with NA values from row group assignment, giving you more control over table structure. Numeric formatting functions gained a min_sep_threshold argument, which controls the minimum number of digits required before grouping separators are applied. The stub() helper now works correctly with multi-column stubs.

On the data side, the countrypops dataset has been updated to include population values through 2024, and the metro and films datasets have been refreshed. Behind the scenes, many internal pipes have been migrated from %>% to |>, getting the codebase more up-to-date with modern R practices.

Bug Fixes

This release includes numerous bug fixes across all output formats:

  • fmt_scientific() no longer errors on Inf/-Inf values
  • fixed issues with Unicode conversion in LaTeX output
  • resolved length recycling issues when using gt with Quarto
  • fixed grand summary row rendering in Word output
  • corrected multiple <tfoot> tags in HTML output

These little fixes reflect our commitment to making gt reliable across all the ways you might use it.

Wrapping Up

The v1.2.0 release of gt showcases what’s possible when a passionate community comes together to improve a package. We’re excited to keep this momentum going and continue introducing features that make your tables more expressive and easier to create. Whether you’re working on clinical reports, scientific publications, or business dashboards, we hope these improvements make your work just a little bit easier.

Thank you to all our contributors, and happy table making!

Rich Iannone

Software Engineer at Posit, PBC
Richard is a software engineer and table enthusiast. He and R go way back and he's been getting better at writing code in Python too. For the most part, Rich enjoys creating open source packages in R and Python so that people can do great things in their own work.