Let’s create a gt table using a small portion of the gtcars dataset. Over several columns (hp, hp_rpm, trq, trq_rpm, mpg_c, mpg_h) we’ll use tab_spanner() to add a spanner with the label "performance". This effectively groups together several columns related to car performance under a unifying label.
With the default gather = TRUE option, columns selected for a particular spanner will be moved so that there is no separation between them. This can be seen with the example below that uses a subset of the towny dataset. The starting column order is name, latitude, longitude, population_2016, density_2016, population_2021, and density_2021. The first two uses of tab_spanner() deal with making separate spanners for the two population and two density columns. After their use, the columns are moved to this new ordering: name, latitude, longitude, population_2016, population_2021, density_2016, and density_2021. The third and final call of tab_spanner() doesn’t further affect the ordering of columns.
While columns are moved, it is only the minimal amount of moving required (pulling in columns from the right) to ensure that columns are gathered under the appropriate spanners. With the last call, there are two more things to note: (1) label values can use the md() (or html()) helper functions to help create styled text, and (2) an id value may be supplied for reference later (e.g., for styling with tab_style() or applying footnotes with tab_footnote()).
It’s possible to stack multiple spanners atop each other with consecutive calls of tab_spanner(). It’s a bit like playing Tetris: putting a spanner down anywhere there is another spanner (i.e., there are one or more shared columns) means that second spanner will reside a level above the prior. Let’s look at a few examples to see how this works, and we’ll also explore a few lesser-known placement tricks. We’ll use a cut down version of exibble for this, set up a few level-1 spanners, and then place a level-2 spanner over two other spanners.
In the above example, we used the spanners argument to define where the "Numbers and Text"-labeled spanner should reside. For that, we supplied the "num_spanner" and "text_spanner" ID values for the two spanners associated with the num, currency, char, and fctr columns. Alternatively, we could have given those column names to the columns argument and achieved the same result. You could actually use a combination of spanners and columns to define where the spanner should be placed. Here is an example of just that:
And, again, we could have solely supplied all of the column names to columns instead of using this hybrid approach, but it is interesting to express the definition of spanners with this flexible combination.
What if you wanted to extend the above example and place a spanner above the date, time, and datetime columns? If you tried that in the manner as exemplified above, the spanner will be placed in the third level of spanners:
exibble_narrow_gt |>tab_spanner(label ="Date and Time Columns",columns =contains(c("date", "time")),id ="date_time_spanner" )
Date and Time Columns
Text, Dates, Times, Datetimes
Numeric Values
Text Values
date
time
datetime
row
group
num
currency
char
fctr
0.1111
49.95
apricot
one
2015-01-15
13:35
2018-01-01 02:22
row_1
grp_a
2.2220
17.95
banana
two
2015-02-15
14:40
2018-02-02 14:33
row_2
grp_a
33.3300
1.39
coconut
three
2015-03-15
15:45
2018-03-03 03:44
row_3
grp_a
Remember that the approach taken by tab_spanner() is to keep stacking atop existing spanners. But, there is space next to the "Text Values" spanner on the first level. You can either revise the order of tab_spanner() calls, or, use the level argument to force the spanner into that level (so long as there is space).
exibble_narrow_gt |>tab_spanner(label ="Date and Time Columns",columns =contains(c("date", "time")),level =1,id ="date_time_spanner" )
Text, Dates, Times, Datetimes
Numeric Values
Text Values
Date and Time Columns
row
group
num
currency
char
fctr
date
datetime
time
0.1111
49.95
apricot
one
2015-01-15
2018-01-01 02:22
13:35
row_1
grp_a
2.2220
17.95
banana
two
2015-02-15
2018-02-02 14:33
14:40
row_2
grp_a
33.3300
1.39
coconut
three
2015-03-15
2018-03-03 03:44
15:45
row_3
grp_a
That puts the spanner in the intended level. If there aren’t free locations available in the level specified you’ll get an error stating which columns cannot be used for the new spanner (this can be circumvented, if necessary, with the replace = TRUE option). If you choose a level higher than the maximum occupied, then the spanner will be dropped down. Again, these behaviors are indicative of Tetris-like rules which tend to work well for the application of spanners.
Using a subset of the towny dataset, we can create an interesting gt table. First, only certain columns are selected from the dataset, some filtering of rows is done, rows are sorted, and then only the first 10 rows are kept. After the data is introduced to gt(), we then apply some spanner labels using two calls of tab_spanner(). In the second of those, we incorporate unit notation text (within "{{"/"}“}) in the label to get a display of nicely-formatted units.