Skip to contents

Breaks a long table into two or more blocks set beside each other, each with its own column labels. A sixty-row ranking becomes two columns of thirty, which is the usual treatment in print and the difference between an image that is unreadably tall and one that fits the frame.

Usage

gt_snake(
  gt_object,
  n_cols = 2,
  rows_per_col = NULL,
  gap = 20,
  fill = "",
  clean_gaps = TRUE
)

Arguments

gt_object

A gt table object to modify. Body-cell styling applied before this call is carried through the reshape; formatting and content transforms are not, so they still come after (see Details).

n_cols

Integer. The number of blocks to lay out. Defaults to 2.

rows_per_col

Integer. Rows in each block. Given this, n_cols is worked out from the data instead. Defaults to NULL.

gap

Numeric. The space between blocks in pixels, added as an empty spacer column so it does not disturb the alignment of the columns either side of it. Its borders, fill, and rules are scrubbed so it stays clean whitespace under any theme (see Details). 0 for no gap. Defaults to 20.

fill

Character. What to show in the trailing cells when the rows do not divide evenly. Defaults to "", an empty cell.

clean_gaps

Logical. Should the spacer columns be scrubbed of borders, fill, and rules so the gap reads as clean whitespace (see Details)? Set FALSE when you style the gap yourself, e.g. with a coloured border from gt::tab_style() and gt::cell_borders(). Defaults to TRUE.

Value

Returns a gt table laid out in blocks.

Details

The table is rebuilt from its data. Body-cell styles set beforehand, with gt_highlight_cells() or tab_style() on cells_body(), are carried through: each is moved to its block's suffixed column and its row within that block, so a fill on the long table lands on the same cell once snaked. This means you can color the natural, un-snaked grid and snake afterwards.

Formatting and content transforms are not styles and are not remapped, so fmt_*(), text_transform(), and gtExtras::gt_img_rows() still have to be applied after the snake, against the suffixed names above. Themes go afterwards too. Any heading and source notes already set are carried over.

To reshape a parallel frame (a precomputed mask, per-cell colors) to match the same blocks, see gt_snake_align().

gt::gt_split() also divides a table, but it returns a group of separate tables to be rendered one after another, and it errors on any table carrying body-cell styles, which every theme in this package sets.

Column names after snaking

Snaking is a reshape. The same columns appear once per block, so they cannot all keep the same name: each is suffixed with its block number. A four-column table split into two blocks becomes eight columns.

before                     after gt_snake(n_cols = 2)
-----------------------    -----------------------------------------
team wins losses win_pct   team_1 wins_1 losses_1 win_pct_1   <- rows 1-15
                           team_2 wins_2 losses_2 win_pct_2   <- rows 16-30

The labels are untouched, so the table still reads team wins losses win_pct twice over. Only the names you address in code change.

That gives three ways to target columns afterwards:

# every block of one column, via a tidyselect helper
fmt_number(starts_with("win_pct"), decimals = 3)

# one block only, by its full name
tab_style(cell_text(weight = "bold"), cells_body(columns = team_2))

# the whole table, exactly as usual
cols_align(columns = everything(), align = "center")

Reach for starts_with() rather than listing team_1, team_2 by hand: it keeps working if you change n_cols later. Watch out for prefixes that overlap, though. With columns named pts and pts_pg, starts_with("pts") catches both blocks of both; matches("^pts_[0-9]+$") catches only pts.

The gap between blocks is itself an empty column, named .gap1, .gap2, and so on. It holds nothing and carries no label. To target the spacers in a later call, use starts_with(".gap") or matches("^\\.gap"); everything() selects them too. Set gap = 0 if you would rather it did not exist.

Because the gap is a real column, a theme or gt_border_grid() would otherwise draw its column borders and row rules straight through it, leaving the "whitespace" boxed in. When clean_gaps = TRUE (the default), the body of each spacer and the inner edges of the columns either side are cleared with scoped !important CSS, so it wins even against borders applied after the snake; the spacer's own column label is cleared through gt directly, which holds up even when tab_spanner() merges header cells. The table's id is set (or carried over from the input) so the CSS can be scoped to it. Set clean_gaps = FALSE to style the gap yourself instead.

See also

gt_stack_tables() for stacking separate tables vertically.

Examples

library(gt)

nba <- subset(
  sdv_example_standings, league == "nba",
  c(team, wins, losses, win_pct)
)
nba <- nba[order(-nba$win_pct), ]

# three blocks of ten
gt(nba) |>
  gt_snake(n_cols = 3) |>
  gt_theme_broadsheet()
team wins losses win_pct team wins losses win_pct team wins losses win_pct
OKC 64 18 0.780 TOR 46 36 0.561 MIL 32 50 0.390
SA 62 20 0.756 ATL 46 36 0.561 CHI 31 51 0.378
DET 60 22 0.732 PHI 45 37 0.549 NO 26 56 0.317
BOS 56 26 0.683 ORL 45 37 0.549 DAL 26 56 0.317
DEN 54 28 0.659 PHX 45 37 0.549 MEM 25 57 0.305
NY 53 29 0.646 CHA 44 38 0.537 UTAH 22 60 0.268
LAL 53 29 0.646 MIA 43 39 0.524 SAC 22 60 0.268
CLE 52 30 0.634 POR 42 40 0.512 BKN 20 62 0.244
HOU 52 30 0.634 LAC 42 40 0.512 IND 19 63 0.232
MIN 49 33 0.598 GS 37 45 0.451 WSH 17 65 0.207
# or set the block length and let the count follow gt(nba) |> gt_snake(rows_per_col = 8, gap = 30) |> gt_theme_swiss()
team wins losses win_pct team wins losses win_pct team wins losses win_pct team wins losses win_pct
OKC 64 18 0.780 HOU 52 30 0.634 MIA 43 39 0.524 MEM 25 57 0.305
SA 62 20 0.756 MIN 49 33 0.598 POR 42 40 0.512 UTAH 22 60 0.268
DET 60 22 0.732 TOR 46 36 0.561 LAC 42 40 0.512 SAC 22 60 0.268
BOS 56 26 0.683 ATL 46 36 0.561 GS 37 45 0.451 BKN 20 62 0.244
DEN 54 28 0.659 PHI 45 37 0.549 MIL 32 50 0.390 IND 19 63 0.232
NY 53 29 0.646 ORL 45 37 0.549 CHI 31 51 0.378 WSH 17 65 0.207
LAL 53 29 0.646 PHX 45 37 0.549 NO 26 56 0.317



CLE 52 30 0.634 CHA 44 38 0.537 DAL 26 56 0.317



# formatting after the reshape: note the suffixed names gt(nba) |> gt_snake(n_cols = 3) |> gt_theme_swiss() |> fmt_number(starts_with("win_pct"), decimals = 3) |> cols_align(columns = everything(), align = "center")
team wins losses win_pct team wins losses win_pct team wins losses win_pct
OKC 64 18 0.780 TOR 46 36 0.561 MIL 32 50 0.390
SA 62 20 0.756 ATL 46 36 0.561 CHI 31 51 0.378
DET 60 22 0.732 PHI 45 37 0.549 NO 26 56 0.317
BOS 56 26 0.683 ORL 45 37 0.549 DAL 26 56 0.317
DEN 54 28 0.659 PHX 45 37 0.549 MEM 25 57 0.305
NY 53 29 0.646 CHA 44 38 0.537 UTAH 22 60 0.268
LAL 53 29 0.646 MIA 43 39 0.524 SAC 22 60 0.268
CLE 52 30 0.634 POR 42 40 0.512 BKN 20 62 0.244
HOU 52 30 0.634 LAC 42 40 0.512 IND 19 63 0.232
MIN 49 33 0.598 GS 37 45 0.451 WSH 17 65 0.207
# and picking out a single block gt(nba) |> gt_snake(n_cols = 3) |> gt_theme_swiss() |> tab_style( gt::cell_text(weight = "bold"), gt::cells_body(columns = team_1) )
team wins losses win_pct team wins losses win_pct team wins losses win_pct
OKC 64 18 0.780 TOR 46 36 0.561 MIL 32 50 0.390
SA 62 20 0.756 ATL 46 36 0.561 CHI 31 51 0.378
DET 60 22 0.732 PHI 45 37 0.549 NO 26 56 0.317
BOS 56 26 0.683 ORL 45 37 0.549 DAL 26 56 0.317
DEN 54 28 0.659 PHX 45 37 0.549 MEM 25 57 0.305
NY 53 29 0.646 CHA 44 38 0.537 UTAH 22 60 0.268
LAL 53 29 0.646 MIA 43 39 0.524 SAC 22 60 0.268
CLE 52 30 0.634 POR 42 40 0.512 BKN 20 62 0.244
HOU 52 30 0.634 LAC 42 40 0.512 IND 19 63 0.232
MIN 49 33 0.598 GS 37 45 0.451 WSH 17 65 0.207