Repeat gt column labels on each page

Viewed 70

I am using Quarto with the gt R package to produce long tables of data and can't figure out how to get the column headers to appear at the top of each page in the PDF output.

For example table.qmd:

---
title: "Random Table"
format:
  pdf:
    keep-tex: true
---

```{r}
#| echo: false
#| warning: false

library(tidyverse)
library(gt)

random <- tibble(
  id = seq(1:50),
  random = runif(50)
)

gt(random)
```

When rendered to PDF output (quarto render table.qmd --to pdf) The column header (id, random) only appears on the first page. The top of the second page looks like this:

second page, no column header

I can edit the TeX document, adding \endhead after the column header, and I get the result I want on the second (and subsequent) pages.

   158 \begin{longtable}{rr}
   159 \toprule
   160 id & random \\
   161 \midrule
+  162 \endhead           

Like this:

second page, with column header

One option to add the \endhead would be to use a Pandoc filter to add the extra longtable command, but that sounds like a lot of effort for something so basic.

Is there a simple way with this workflow (Quarto options, pandoc, gt) to get column headers on each page in the PDF output?

3 Answers

I found a way to solve this problem for my documents using pandoc filters. I'm still hoping there is a more elegant solution or that this is a feature that could be added to gt, but here's what I did.

Add the filter to the pdf section of the YAML header:

format:
  pdf:
    filters:
      - endhead.lua

And here's the endhead.lua filter:

function RawBlock(elem)
    -- print(elem.text)
    return {
        pandoc.RawInline(
            "latex",
            string.gsub(
              elem.text, 
              "\\toprule\n(.-)\\midrule\n",
              "\\toprule\n%1\\midrule\n\\endhead\n"
            )
        ),
    }
end

gt within an R code block produces a RawBlock element in Pandoc's AST, so that's what needs to be changed using the filter. You can figure out the element you need to manipulate by adding keep-md: true to the YAML header of your Quarto doc, then after rendering, do:

$ pandoc -s -t native tables.md

That will show you the AST.

Once you know the Pandoc element, it's just a question of figuring out the regular expression that will add an \endhead element to the proper section of the table. I did this by finding the first \midrule after \toprule in each RawBlock (gt table). It took a bit for me to figure out how to use non-greedy capturing ((.-)) in order to only add \endhead once, right after the header of the table.

Your question is a topical issue.

So, I can suggest a next one solution.


As you know - we can represent our gt table as a LaTeX code: https://gt.rstudio.com/reference/as_latex.html

Returning to your example (!do it into console!):

library(gt)
library(tibble)

random <- tibble(
    id = seq(1:50),
    random = runif(50)
)

gt(random) %>% 
    as_latex() %>%
    as.character()

Now we see our tab in next way (a lot of LaTeX code, which we should copy):

[1] "\\captionsetup[table]{labelformat=empty,skip=1pt}\n\\begin{longtable}{rr}\n\\toprule\nid & random \\\\ \n\\midrule\n1 & 0.83452294 \\\\ \n2 & 0.43130524 \\\\ \n3 & 0.38702311 \\\\ \n4 & 0.75589167 \\\\ \n5 & 0.34432672 \\\\ \n6 & 0.75283676 \\\\ \n7 & 0.72770533 \\\\ \n8 & 0.20232485 \\\\ \n9 & 0.07900835 \\\\ \n10 & 0.80229610 \\\\ \n11 & 0.03800323 \\\\ \n12 & 0.03144921 \\\\ \n13 & 0.87364981 \\\\ \n14 & 0.47587592 \\\\ \n15 & 0.56736249 \\\\ \n16 & 0.83990532 \\\\ \n17 & 0.64138659 \\\\ \n18 & 0.76073451 \\\\ \n19 & 0.66013426 \\\\ \n20 & 0.54178832 \\\\ \n21 & 0.70443849 \\\\ \n22 & 0.81238956 \\\\ \n23 & 0.11305811 \\\\ \n24 & 0.12530444 \\\\ \n25 & 0.22497031 \\\\ \n26 & 0.35093560 \\\\ \n27 & 0.91268645 \\\\ \n28 & 0.19127469 \\\\ \n29 & 0.21638115 \\\\ \n30 & 0.24333619 \\\\ \n31 & 0.21178737 \\\\ \n32 & 0.50896038 \\\\ \n33 & 0.30918302 \\\\ \n34 & 0.44719810 \\\\ \n35 & 0.20445296 \\\\ \n36 & 0.67448123 \\\\ \n37 & 0.78824962 \\\\ \n38 & 0.62888684 \\\\ \n39 & 0.39583956 \\\\ \n40 & 0.99218439 \\\\ \n41 & 0.27330819 \\\\ \n42 & 0.20861772 \\\\ \n43 & 0.67226113 \\\\ \n44 & 0.11676375 \\\\ \n45 & 0.24479000 \\\\ \n46 & 0.28968861 \\\\ \n47 & 0.72271636 \\\\ \n48 & 0.58395987 \\\\ \n49 & 0.74914347 \\\\ \n50 & 0.32712809 \\\\ \n\\bottomrule\n\\end{longtable}\n"

Nice. Now we can transfer to our qmd-file:

---
format:
  pdf:
    documentclass: article
    keep-tex: true
include-in-header:
- \usepackage{amsmath, booktabs, caption, longtable}
---


```{r, results='asis', echo=F}

k <- "\\captionsetup[table]{labelformat=empty,skip=1pt}\n\\begin{longtable}{rr}\n\\toprule\nid & random \\\\ \n\\midrule\n1 & 0.83452294 \\\\ \n2 & 0.43130524 \\\\ \n3 & 0.38702311 \\\\ \n4 & 0.75589167 \\\\ \n5 & 0.34432672 \\\\ \n6 & 0.75283676 \\\\ \n7 & 0.72770533 \\\\ \n8 & 0.20232485 \\\\ \n9 & 0.07900835 \\\\ \n10 & 0.80229610 \\\\ \n11 & 0.03800323 \\\\ \n12 & 0.03144921 \\\\ \n13 & 0.87364981 \\\\ \n14 & 0.47587592 \\\\ \n15 & 0.56736249 \\\\ \n16 & 0.83990532 \\\\ \n17 & 0.64138659 \\\\ \n18 & 0.76073451 \\\\ \n19 & 0.66013426 \\\\ \n20 & 0.54178832 \\\\ \n21 & 0.70443849 \\\\ \n22 & 0.81238956 \\\\ \n23 & 0.11305811 \\\\ \n24 & 0.12530444 \\\\ \n25 & 0.22497031 \\\\ \n26 & 0.35093560 \\\\ \n27 & 0.91268645 \\\\ \n28 & 0.19127469 \\\\ \n29 & 0.21638115 \\\\ \n30 & 0.24333619 \\\\ \n31 & 0.21178737 \\\\ \n32 & 0.50896038 \\\\ \n33 & 0.30918302 \\\\ \n34 & 0.44719810 \\\\ \n35 & 0.20445296 \\\\ \n36 & 0.67448123 \\\\ \n37 & 0.78824962 \\\\ \n38 & 0.62888684 \\\\ \n39 & 0.39583956 \\\\ \n40 & 0.99218439 \\\\ \n41 & 0.27330819 \\\\ \n42 & 0.20861772 \\\\  \n43 & 0.67226113 \\\\ \n44 & 0.11676375 \\\\ \n45 & 0.24479000 \\\\ \n46 & 0.28968861 \\\\ \n47 & 0.72271636 \\\\ \n48 & 0.58395987 \\\\ \n49 & 0.74914347 \\\\ \n50 & 0.32712809 \\\\ \n\\bottomrule\n\\end{longtable}\n"

knitr::asis_output(k)

```

About asis_output look here

Output1:

We see that this break between 43 and 44. enter image description here

Considering the place for the header - updating our code (adding \\toprule\nid & random \\\\ \n\\midrule between 42 and 43:

New code:

---
format:
  pdf:
    documentclass: article
    keep-tex: true
include-in-header:
- \usepackage{amsmath, booktabs, caption, longtable}
---


```{r, results='asis', echo=F}

k <- "\\captionsetup[table]{labelformat=empty,skip=1pt}\n\\begin{longtable}{rr}\n\\toprule\nid & random \\\\ \n\\midrule\n1 & 0.83452294 \\\\ \n2 & 0.43130524 \\\\ \n3 & 0.38702311 \\\\ \n4 & 0.75589167 \\\\ \n5 & 0.34432672 \\\\ \n6 & 0.75283676 \\\\ \n7 & 0.72770533 \\\\ \n8 & 0.20232485 \\\\ \n9 & 0.07900835 \\\\ \n10 & 0.80229610 \\\\ \n11 & 0.03800323 \\\\ \n12 & 0.03144921 \\\\ \n13 & 0.87364981 \\\\ \n14 & 0.47587592 \\\\ \n15 & 0.56736249 \\\\ \n16 & 0.83990532 \\\\ \n17 & 0.64138659 \\\\ \n18 & 0.76073451 \\\\ \n19 & 0.66013426 \\\\ \n20 & 0.54178832 \\\\ \n21 & 0.70443849 \\\\ \n22 & 0.81238956 \\\\ \n23 & 0.11305811 \\\\ \n24 & 0.12530444 \\\\ \n25 & 0.22497031 \\\\ \n26 & 0.35093560 \\\\ \n27 & 0.91268645 \\\\ \n28 & 0.19127469 \\\\ \n29 & 0.21638115 \\\\ \n30 & 0.24333619 \\\\ \n31 & 0.21178737 \\\\ \n32 & 0.50896038 \\\\ \n33 & 0.30918302 \\\\ \n34 & 0.44719810 \\\\ \n35 & 0.20445296 \\\\ \n36 & 0.67448123 \\\\ \n37 & 0.78824962 \\\\ \n38 & 0.62888684 \\\\ \n39 & 0.39583956 \\\\ \n40 & 0.99218439 \\\\ \n41 & 0.27330819 \\\\ \n42 & 0.20861772 \\\\ \\toprule\nid & random \\\\ \n\\midrule \n43 & 0.67226113 \\\\ \n44 & 0.11676375 \\\\ \n45 & 0.24479000 \\\\ \n46 & 0.28968861 \\\\ \n47 & 0.72271636 \\\\ \n48 & 0.58395987 \\\\ \n49 & 0.74914347 \\\\ \n50 & 0.32712809 \\\\ \n\\bottomrule\n\\end{longtable}\n"

knitr::asis_output(k)

```

The desired output:

enter image description here


So, the main idea of this method: first render of qmd-file -> detect the break -> manually add the header.

Since we only need to add the \endhead after the \midrule, one option could be inserting \endhead after the \midrule (Using the answer from @manro),


---
title: "Random Table"
format:
  pdf:
    keep-tex: true
---

```{r}
#| echo: false
#| warning: false
#| results: asis

library(tidyverse)
library(gt)

random <- tibble(
  id = seq(1:50),
  random = runif(50)
)

t <- gt(random) %>%
  as_latex() %>%
  as.character()

knitr::asis_output(gsub("(.*\\\\begin\\{longtable\\}.*?\\midrule\\\n)", t, replacement = "\\1\\\\endhead\\\n"))
```


enter image description here


Related