Groups and labels

library(tinytable)
options(tinytable_tt_digits = 3)
options(tinytable_latex_placement = "H")
x <- mtcars[1:4, 1:5]

The group_tt() function can label groups of rows (i) or columns (j).

Rows

The i argument accepts a named list of integers. The numbers identify the positions where row group labels are to be inserted. The names includes the text that should be inserted:

dat <- mtcars[1:9, 1:8]

tt(dat) |>
  group_tt(i = list(
    "I like (fake) hamburgers" = 3,
    "She prefers halloumi" = 4,
    "They love tofu" = 7))
mpg cyl disp hp drat wt qsec vs
21 6 160 110 3.9 2.62 16.5 0
21 6 160 110 3.9 2.88 17 0
I like (fake) hamburgers
22.8 4 108 93 3.85 2.32 18.6 1
She prefers halloumi
21.4 6 258 110 3.08 3.21 19.4 1
18.7 8 360 175 3.15 3.44 17 0
18.1 6 225 105 2.76 3.46 20.2 1
They love tofu
14.3 8 360 245 3.21 3.57 15.8 0
24.4 4 147 62 3.69 3.19 20 1
22.8 4 141 95 3.92 3.15 22.9 1

The numbers in the i list indicate that a label must be inserted at position # in the original table (without row groups). For example,

tt(head(iris)) |>
  group_tt(i = list("After 0" = 1, "After 3a" = 4, "After 3b" = 4, "After 5" = 6))
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
After 0
5.1 3.5 1.4 0.2 setosa
4.9 3 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
After 3a
After 3b
4.6 3.1 1.5 0.2 setosa
5 3.6 1.4 0.2 setosa
After 5
5.4 3.9 1.7 0.4 setosa

Styling row groups

We can style group rows in the same way as regular rows (caveat: not in Word or Markdown):

tab <- tt(dat) |>
  group_tt(i = list(
      "I like (fake) hamburgers" = 3,
      "She prefers halloumi" = 4,
      "They love tofu" = 7))

tab |> style_tt(
    i = c(3, 5, 9),
    align = "c",
    color = "white",
    background = "gray",
    bold = TRUE)
mpg cyl disp hp drat wt qsec vs
21 6 160 110 3.9 2.62 16.5 0
21 6 160 110 3.9 2.88 17 0
I like (fake) hamburgers
22.8 4 108 93 3.85 2.32 18.6 1
She prefers halloumi
21.4 6 258 110 3.08 3.21 19.4 1
18.7 8 360 175 3.15 3.44 17 0
18.1 6 225 105 2.76 3.46 20.2 1
They love tofu
14.3 8 360 245 3.21 3.57 15.8 0
24.4 4 147 62 3.69 3.19 20 1
22.8 4 141 95 3.92 3.15 22.9 1

Calculating the location of rows can be cumbersome. Instead of doing this by hand, we can use the “groupi” shortcut to style rows and “~groupi” (the complement) to style all non-group rows.

tab |> 
  style_tt("groupi", color = "white", background = "teal") |>
  style_tt("~groupi", j = 1, indent = 2)
mpg cyl disp hp drat wt qsec vs
21 6 160 110 3.9 2.62 16.5 0
21 6 160 110 3.9 2.88 17 0
I like (fake) hamburgers
22.8 4 108 93 3.85 2.32 18.6 1
She prefers halloumi
21.4 6 258 110 3.08 3.21 19.4 1
18.7 8 360 175 3.15 3.44 17 0
18.1 6 225 105 2.76 3.46 20.2 1
They love tofu
14.3 8 360 245 3.21 3.57 15.8 0
24.4 4 147 62 3.69 3.19 20 1
22.8 4 141 95 3.92 3.15 22.9 1

Automatic row groups

We can use the group_tt() function to group rows and label them using spanners (almost) automatically. For example,

# subset and sort data
df <- mtcars |> 
  head(10) |>
  sort_by(~am)

# draw table
tt(df) |> group_tt(i = df$am)
mpg cyl disp hp drat wt qsec vs am gear carb
0
21.4 6 258 110 3.08 3.21 19.4 1 0 3 1
18.7 8 360 175 3.15 3.44 17 0 0 3 2
18.1 6 225 105 2.76 3.46 20.2 1 0 3 1
14.3 8 360 245 3.21 3.57 15.8 0 0 3 4
24.4 4 147 62 3.69 3.19 20 1 0 4 2
22.8 4 141 95 3.92 3.15 22.9 1 0 4 2
19.2 6 168 123 3.92 3.44 18.3 1 0 4 4
1
21 6 160 110 3.9 2.62 16.5 0 1 4 4
21 6 160 110 3.9 2.88 17 0 1 4 4
22.8 4 108 93 3.85 2.32 18.6 1 1 4 1

Row matrix insertion

While the traditional group_tt(i = list(...)) approach is useful for adding individual labeled rows, sometimes you need to insert multiple rows of data at specific positions. The matrix insertion feature provides a more efficient way to do this.

Instead of creating multiple named list entries, you can specify row positions as an integer vector in i and provide a character matrix in j. This is particularly useful when you want to insert the same content (like headers or separators) at multiple positions:

rowmat <- matrix(colnames(iris))

tt(head(iris, 7)) |>
  group_tt(i = c(2, 5), j = rowmat)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5.1 3.5 1.4 0.2 setosa
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
4.9 3 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
4.6 3.4 1.4 0.3 setosa

The matrix is expected to have the same number of columns as the table. However, if you provide a single-column matrix with a number of elements that is a multiple of the table’s column count, it will be automatically reshaped to match the table structure. This makes it easy to provide data in a linear format:

rowmat <- matrix(c(
  "-", "-", "-", "-", "-",
  "/", "/", "/", "/", "/"))

tt(head(iris, 7)) |> group_tt(i = 2, j = rowmat) 
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5.1 3.5 1.4 0.2 setosa
- - - - -
/ / / / /
4.9 3 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
4.6 3.4 1.4 0.3 setosa

We can also insert rows of the group matrix in different positions:

tt(head(iris, 7)) |> group_tt(i = c(1, 8), j = rowmat)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
- - - - -
5.1 3.5 1.4 0.2 setosa
4.9 3 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
4.6 3.4 1.4 0.3 setosa
/ / / / /

Columns

The syntax for column groups is very similar, but we use the j argument instead. The named list specifies the labels to appear in column-spanning labels, and the values must be a vector of consecutive and non-overlapping integers that indicate which columns are associated to which labels:

tt(dat) |>
  group_tt(
    j = list(
      "Hamburgers" = 1:3,
      "Halloumi" = 4:5,
      "Tofu" = 7))
Hamburgers Halloumi Tofu
mpg cyl disp hp drat wt qsec vs
21 6 160 110 3.9 2.62 16.5 0
21 6 160 110 3.9 2.88 17 0
22.8 4 108 93 3.85 2.32 18.6 1
21.4 6 258 110 3.08 3.21 19.4 1
18.7 8 360 175 3.15 3.44 17 0
18.1 6 225 105 2.76 3.46 20.2 1
14.3 8 360 245 3.21 3.57 15.8 0
24.4 4 147 62 3.69 3.19 20 1
22.8 4 141 95 3.92 3.15 22.9 1

We can stack several extra headers on top of one another:

x <- mtcars[1:4, 1:5]
tt(x) |>
  group_tt(j = list("Foo" = 2:3, "Bar" = 5)) |>
  group_tt(j = list("Hello" = 1:2, "World" = 4:5))
Hello World
Foo Bar
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Styling column groups

To style column headers, we use zero or negative indices:

tt(x) |>
  group_tt(j = list("Foo" = 2:3, "Bar" = 5)) |>
  group_tt(j = list("Hello" = 1:2, "World" = 4:5)) |>
  style_tt(i = 0, color = "orange") |>
  style_tt(i = -1, color = "teal") |>
  style_tt(i = -2, color = "yellow")
Hello World
Foo Bar
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Alternatively, we can use string shortcuts:

tt(x) |>
  group_tt(j = list("Foo" = 2:3, "Bar" = 5)) |>
  group_tt(j = list("Hello" = 1:2, "World" = 4:5)) |>
  style_tt("groupj", color = "orange") |>
  style_tt("colnames", color = "teal")
Hello World
Foo Bar
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Here is a table with both row and column headers, as well as some styling:

dat <- mtcars[1:9, 1:8]
tt(dat) |>
  group_tt(
    i = list(
      "I like (fake) hamburgers" = 3,
      "She prefers halloumi" = 4,
      "They love tofu" = 7
    ),
    j = list(
      "Hamburgers" = 1:3,
      "Halloumi" = 4:5,
      "Tofu" = 7
    )
  ) |>
  style_tt(
    i = c(3, 5, 9),
    align = "c",
    background = "teal",
    color = "white"
  ) |>
  style_tt(i = -1, color = "teal")
Hamburgers Halloumi Tofu
mpg cyl disp hp drat wt qsec vs
21 6 160 110 3.9 2.62 16.5 0
21 6 160 110 3.9 2.88 17 0
I like (fake) hamburgers
22.8 4 108 93 3.85 2.32 18.6 1
She prefers halloumi
21.4 6 258 110 3.08 3.21 19.4 1
18.7 8 360 175 3.15 3.44 17 0
18.1 6 225 105 2.76 3.46 20.2 1
They love tofu
14.3 8 360 245 3.21 3.57 15.8 0
24.4 4 147 62 3.69 3.19 20 1
22.8 4 141 95 3.92 3.15 22.9 1

Column names with delimiters

Group labels can be specified using column names with delimiters. For example, some of the columns in this data frame have group identifiers. Note that the first column does not have a group identifier, and that the last column has a group identifier but no column name.

dat <- data.frame(
  "A__D" = rnorm(3),
  "A_B_D" = rnorm(3),
  "A_B_" = rnorm(3),
  "_C_E" = rnorm(3),
  check.names = FALSE
)

tt(dat) |> group_tt(j = "_")
A
B C
D D B E
-1.402 0.615 0.806 0.456
-0.307 0.194 -0.694 0.581
-2.502 0.563 1.571 -1.747

Case studies

Repeated column names

In some contexts, users wish to repeat the column names to treat them as group labels. Consider this dataset:

library(tinytable)
library(magrittr)

dat = data.frame(
  Region = as.character(state.region),
  State = row.names(state.x77), 
  state.x77[, 1:3]) |>
  sort_by(~ Region + State) |>
  subset(Region %in% c("North Central", "Northeast"))
dat = do.call(rbind, by(dat, dat$Region, head, n = 3))
row.names(dat) = NULL
dat
         Region         State Population Income Illiteracy
1 North Central      Illinois      11197   5107        0.9
2 North Central       Indiana       5313   4458        0.7
3 North Central          Iowa       2861   4628        0.5
4     Northeast   Connecticut       3100   5348        1.1
5     Northeast         Maine       1058   3694        0.7
6     Northeast Massachusetts       5814   4755        1.1

Here, we may want to repeat the column names for every region. The group_tt() function does not support this directly, but it is easy to achieve this effect by:

  1. Insert column names as new rows in the data.
  2. Creat a row group variable (here: region)
  3. Style the column names and group labels

Normally, we would call style_tt(i = "groupi") to style the row groups, but here we need the actual indices to also style one row below the groups. We can use the @group_index_i slot to get the indices of the row groups.

region_names <- unique(dat$Region)
region_indices <- rep(match(region_names, dat$Region), each = 2)

rowmat <- do.call(rbind, lapply(region_names, function(name) {
  rbind(
    c(name, rep("", 3)),
    colnames(dat)[2:5]
  )
}))

rowmat
     [,1]            [,2]         [,3]     [,4]        
[1,] "North Central" ""           ""       ""          
[2,] "State"         "Population" "Income" "Illiteracy"
[3,] "Northeast"     ""           ""       ""          
[4,] "State"         "Population" "Income" "Illiteracy"
tab <- tt(dat[, 2:5], colnames = FALSE) |>
  group_tt(i = region_indices, j = rowmat)

idx <- tab@group_index_i[c(TRUE, diff(tab@group_index_i) != 1)]

tab |>
  style_tt(i = idx, j = 1, align = "c", colspan = 4, background = "lightgrey", line = "b") |>
  style_tt(i = idx + 1, line = "tb")
North Central
State Population Income Illiteracy
Illinois 11197 5107 0.9
Indiana 5313 4458 0.7
Iowa 2861 4628 0.5
Northeast
State Population Income Illiteracy
Connecticut 3100 5348 1.1
Maine 1058 3694 0.7
Massachusetts 5814 4755 1.1