The rows_add() function

Let’s make a simple gt table with the exibble dataset, using the row column for labels in the stub. We’ll add a single row to the bottom of the table with rows_add(). With name-value pairs, it’s possible to add values for the new body cells that correspond to columns available in the table. For any columns that are missed, the related body cells will receive NA values.

exibble |>
  gt(rowname_col = "row") |>
  rows_add(
    row = "row_9",
    num = 9.999E7,
    char = "ilama",
    fctr = "nine",
    group = "grp_b"
  )
num char fctr date time datetime currency group
row_1 1.111e-01 apricot one 2015-01-15 13:35 2018-01-01 02:22 49.950 grp_a
row_2 2.222e+00 banana two 2015-02-15 14:40 2018-02-02 14:33 17.950 grp_a
row_3 3.333e+01 coconut three 2015-03-15 15:45 2018-03-03 03:44 1.390 grp_a
row_4 4.444e+02 durian four 2015-04-15 16:50 2018-04-04 15:55 65100.000 grp_a
row_5 5.550e+03 NA five 2015-05-15 17:55 2018-05-05 04:00 1325.810 grp_b
row_6 NA fig six 2015-06-15 NA 2018-06-06 16:11 13.255 grp_b
row_7 7.770e+05 grapefruit seven NA 19:10 2018-07-07 05:22 NA grp_b
row_8 8.880e+06 honeydew eight 2015-08-15 20:20 NA 0.440 grp_b
row_9 9.999e+07 ilama nine NA NA NA NA grp_b

If you wanted to place a row somewhere in the middle of the table, we can use either of the .before or .after arguments in rows_add():

exibble |>
  gt(rowname_col = "row") |>
  rows_add(
    row = "row_4.5",
    num = 9.923E3,
    char = "elderberry",
    fctr = "eighty",
    group = "grp_a",
    .after = "row_4"
  )
num char fctr date time datetime currency group
row_1 1.111e-01 apricot one 2015-01-15 13:35 2018-01-01 02:22 49.950 grp_a
row_2 2.222e+00 banana two 2015-02-15 14:40 2018-02-02 14:33 17.950 grp_a
row_3 3.333e+01 coconut three 2015-03-15 15:45 2018-03-03 03:44 1.390 grp_a
row_4 4.444e+02 durian four 2015-04-15 16:50 2018-04-04 15:55 65100.000 grp_a
row_4.5 9.923e+03 elderberry eighty NA NA NA NA grp_a
row_5 5.550e+03 NA five 2015-05-15 17:55 2018-05-05 04:00 1325.810 grp_b
row_6 NA fig six 2015-06-15 NA 2018-06-06 16:11 13.255 grp_b
row_7 7.770e+05 grapefruit seven NA 19:10 2018-07-07 05:22 NA grp_b
row_8 8.880e+06 honeydew eight 2015-08-15 20:20 NA 0.440 grp_b

Putting a row at the beginning requires the use of the .before argument. We can use an index value for the row as in .before = 1 for maximum easiness:

exibble |>
  gt(rowname_col = "row") |>
  rows_add(
    row = "row_0",
    num = 0,
    char = "apple",
    fctr = "zero",
    group = "grp_a",
    .before = 1
  )
num char fctr date time datetime currency group
row_0 0.000e+00 apple zero NA NA NA NA grp_a
row_1 1.111e-01 apricot one 2015-01-15 13:35 2018-01-01 02:22 49.950 grp_a
row_2 2.222e+00 banana two 2015-02-15 14:40 2018-02-02 14:33 17.950 grp_a
row_3 3.333e+01 coconut three 2015-03-15 15:45 2018-03-03 03:44 1.390 grp_a
row_4 4.444e+02 durian four 2015-04-15 16:50 2018-04-04 15:55 65100.000 grp_a
row_5 5.550e+03 NA five 2015-05-15 17:55 2018-05-05 04:00 1325.810 grp_b
row_6 NA fig six 2015-06-15 NA 2018-06-06 16:11 13.255 grp_b
row_7 7.770e+05 grapefruit seven NA 19:10 2018-07-07 05:22 NA grp_b
row_8 8.880e+06 honeydew eight 2015-08-15 20:20 NA 0.440 grp_b

Again with exibble, we can create an example where we insert ‘spacer’ rows. These are rows without any content and merely serve to add extra vertical space to the table in specific locations. In this case, we’ll have a stub with row names and row groups (set up in the gt() call). The two rows being added will occupy the bottom row of each group. The only data defined for the two rows involves values for the row and group columns. It’s important that the data for group uses the group names already present in the data ("grp_a" and "grp_b"). The corresponding values for row will be "row_a_end" and "row_b_end", these will be used later expressions for targeting the rows. Here’s the code needed to generate spacer rows at the end of each row group:

exibble |>
  gt(rowname_col = "row", groupname_col = "group") |>
  rows_add(
    row = c("row_a_end", "row_b_end"),
    group = c("grp_a", "grp_b")
  ) |>
  tab_style(
    style = cell_borders(sides = "top", style = "hidden"),
    locations = list(
      cells_body(rows = ends_with("end")),
      cells_stub(rows = ends_with("end"))
    )
  ) |>
  sub_missing(missing_text = "") |>
  text_case_when(
    grepl("end", x) ~ "",
    .locations = cells_stub()
  ) |>
  opt_vertical_padding(scale = 0.5)
num char fctr date time datetime currency
grp_a
row_1 1.111e-01 apricot one 2015-01-15 13:35 2018-01-01 02:22 49.950
row_2 2.222e+00 banana two 2015-02-15 14:40 2018-02-02 14:33 17.950
row_3 3.333e+01 coconut three 2015-03-15 15:45 2018-03-03 03:44 1.390
row_4 4.444e+02 durian four 2015-04-15 16:50 2018-04-04 15:55 65100.000







grp_b
row_5 5.550e+03
five 2015-05-15 17:55 2018-05-05 04:00 1325.810
row_6
fig six 2015-06-15
2018-06-06 16:11 13.255
row_7 7.770e+05 grapefruit seven
19:10 2018-07-07 05:22
row_8 8.880e+06 honeydew eight 2015-08-15 20:20
0.440







All missing values were substituted with an empty string (""), and that was done by using sub_missing(). We removed the top border of the new rows with a call to tab_style(), targeting those rows where the row labels end with "end". Finally, we get rid of the row labels with the use of text_case_when(), using a similar strategy of targeting the name of the row label.

Another application is starting from nothing (really just the definition of columns) and building up a table using several invocations of rows_add(). This might be useful in interactive or programmatic applications. Here’s an example where two columns are defined with dplyr::tibble() (and no rows are present initially); with two calls of rows_add(), two separate rows are added.

dplyr::tibble(
  time = lubridate::POSIXct(),
  event = character(0L)
) |>
  gt() |>
  rows_add(
    time = lubridate::ymd_hms("2022-01-23 12:36:10"),
    event = "start"
  ) |>
  rows_add(
    time = lubridate::ymd_hms("2022-01-23 13:41:26"),
    event = "completed"
  )
time event
2022-01-23 12:36:10 start
2022-01-23 13:41:26 completed

It’s possible to use formula syntax in rows_add() to perform column resolution along with attaching values for new rows. If we wanted to use an equivalent value for multiple cells in a new row, a valid input would be in the form of <expr> ~ <value vector>. In the following example, we create a simple table with six columns (the rendered gt table displays four columns and a stub column since the group column is used for row group labels). Let’s add a single row where some of the cell values added correspond to columns are resolved on the LHS of the formula expressions:

dplyr::tibble(
  group = c("Group A", "Group B", "Group B"),
  id = c("WG-025360", "WG-025361", "WG-025362"),
  a = c(1, 6, 2),
  b = c(2, 6, 2),
  quantity_x = c(83.58, 282.71, 92.20),
  quantity_y = c(36.82, 282.71, 87.34)
) |>
  gt(rowname_col = "id", groupname_col = "group") |>
  rows_add(
    starts_with("gr") ~ "Group A",
    id = "WG-025363",
    c(a, b) ~ 5,
    starts_with("quantity") ~ 72.63
  )
a b quantity_x quantity_y
Group A
WG-025360 1 2 83.58 36.82
WG-025363 5 5 72.63 72.63
Group B
WG-025361 6 6 282.71 282.71
WG-025362 2 2 92.20 87.34

We can see that using starts_with("gr") yields a successful match to the group column with the tangible result being an addition of a row to the "Group A" group (the added row is the second one in the rendered gt table). Through the use of c(a, b), it was possible to add the value 5 to both the a and b columns. A similar approach was taken with adding the 72.63 value to the quantity_x and quantity_y columns though we used the starts_with("quantity") expression to get gt to resolve those two columns.

You can start with an empty table (i.e., no columns and no rows) and add one or more rows to it. In the completely empty table scenario, where you would use something like dplyr::tibble() or data.frame() with gt(), the first rows_add() could have rows of arbitrary width. In other words, you get to generate table columns (and rows) with a completely empty table via rows_add(). Here’s an example of that:

gt(dplyr::tibble()) |>
  rows_add(
    msrp = c(29.95, 49.95, 79.95),
    item = c("Klax", "Rez", "Ys"),
    type = c("A", "B", "X")
  ) |>
  rows_add(
    msrp = 14.95,
    item = "D",
    type = "Z"
  )
msrp item type
29.95 Klax A
49.95 Rez B
79.95 Ys X
14.95 D Z

In the above, three columns and three rows were generated. The second usage of rows_add() had to use of a subset of those columns (all three were used to create a complete, new row).

We can also start with a virtually empty table: one that has columns but no actual rows. With this type of multi-column, zero-row table, one needs to use a subset of the columns when generating new rows through rows_add().

dplyr::tibble(
  msrp = numeric(0L),
  item = character(0L),
  type = character(0L)
) |>
  gt() |>
  rows_add(
    msrp = c(29.95, 49.95, 79.95, 14.95),
    item = c("Klax", "Rez", "Ys", "D"),
    type = c("A", "B", "X", "Z")
  ) |>
  cols_add(
    genre = c("puzzle", "action", "RPG", "adventure")
  ) |>
  fmt_currency() |>
  cols_move_to_end(columns = msrp)
item type genre msrp
Klax A puzzle $29.95
Rez B action $49.95
Ys X RPG $79.95
D Z adventure $14.95