ErwinTATP
ErwinTATP

Reputation: 234

Scientific formats, subscripts and superscripts in RMarkdown table (docx output)

Let's say I have the following rmd:

---
title: "Table won't work"
author: "Exhausted student"
date: "2022/01/28"
output: 
  bookdown::word_document2
---

```{r table, echo=F, warning=F, message=F}
library(tidyverse)
a <- tibble(
  constants = c("c", "NA", "h", "e", "H2O"),
  values = c(2.998e8, 6.022e23, 6.626e-34, -1.602e-19, 18.02)
)

knitr::kable(a, digits = 35)
```

which produces this table in Word.

The Problem

I need the scientific format to use superscripts and multiply sign (i.e. 2.998 × 108), and some cells requires subscript (e.g. NA and H2O).

The final table should look like this. How can I do that?

What I've tried/would never try

  1. huxtable package and its markdown() function: I managed to format some contents as H~2~O, then enable markdown across table by huxtable(a) %>% `markdown<-`(TRUE). Which did not recognize the syntax, and apparently would not work in forseeable future according to the author.
  2. flextable and as_sub(): Produces right format. I pass the lables to flextable::compose(), where the labels were something like as_paragraph(list_values = list("H", as_sub("2"), "O"). The code is obviously too lengthy; plus i have to manipulate cells one-by-one. Technically still doable, but I do have tables with 100+ cells needed formatting.
  3. Output first, format in Word later: Again, needed manipulation one-by-one. Would be an option if it would work everything out automatically.
  4. Convincing the school bureaucrats to accept html/pdf/latex as output: this is forever an impossible option.
  5. Format outside word then export formatted table as image: strictly forbidden in the reports.

Edit: the table works now! Great thanks to Maël's answer, but please check my own findings to see my final result:

well-formatted table

Upvotes: 7

Views: 2636

Answers (4)

ErwinTATP
ErwinTATP

Reputation: 234

The expSup() function in Maël's answer converted the scientific formats into markdown format. For my script, I modified the function a little:

exp_sup <- function(x, digits = 3) {
  sprintf(paste0("%05.", digits, "f $\\times$ 10^%d^"), x / 10^floor(log10(abs(x))), floor(log10(abs(x))))
}

I changed "f x 10^%d^" to "f $\\times$ 10^%d^", so that it displays proper multiply symbol (×).

Using the format in flextable

The format works great in Kable. However, a large portion of my workflow requires flextable to make caption/cross reference/publication style/etc. Unfortunately, although the expSup function automatically formats scientific notations into markdowns, it cannot make markdown syntax work in flextable.

However, ftExtra::colformat_md() can. Hence, by combining the modified exp_sup() function with ftExtra, I was finally able to produce an academic-looking table:

final formatted results

Below is the code for my final output; if you are also trying to produce reproducible academic reports with lots of tables in Word format, hope this helps!

---
title: "The tables work!"
author: "Satisfied Student"
date: "2022/01/28"
output: 
  bookdown::word_document2:
    reference_docx: styleRef.docx
---
```{r setup, include = F}
library(easypackages)
packages(
  "tidyverse",
  "flextable", # This works best for my workflow
  "ftExtra", # For markdown formatting work in flextable
  "officer" # You can customize appearance/format/etc. of caption *prefixes*
)

knitr::opts_chunk$set(
  warning = FALSE,
  message = FALSE,
  echo = FALSE,

  # Make the table caption format definable in reference_docx styles
  tab.cap.style = "Table Caption",

  # Make "Table 1:" prefixes not bold
  tab.cap.fp_text = fp_text_lite(bold = FALSE)

  # The tab.cap settings MUST be in a separate chunk from tables
)

# Converts scientific format to markdown
exp_sup <- function(x, digits = 3) {
  sprintf(paste0("%05.", digits, "f $\\times$ 10^%d^"), x / 10^floor(log10(abs(x))), floor(log10(abs(x))))
}
# The $\\times$ makes proper multiply symbols
```

```{r table}
a <- tibble(
  constants = c("c", "N~A~", "h", "e", "H~2~O"),
  values = c(2.998e8, 6.022e23, 6.626e-34, -1.602e-19, 18.02)
)

a %>%
  mutate(values = exp_sup(values)) %>%
  flextable() %>%
  set_caption(
    caption = "(ref:foo)", # Produces formatted caption text
    style = "Table Caption"
  ) %>%
  colformat_md() %>% # Subscript/superscript works in flextable now!
  theme_booktabs() %>% # The 3-part-table used in academics
  align(align = "center", part = "all") %>% #Align everything to center
  set_table_properties(layout = "autofit") # Comfortable width/height every cell
```

(ref:foo) A scientifically formatted `flextable` with ^superscripts^ and ~subscripts~

Updated: using markdown in table caption

Previously I used text reference (eg.(ref:foo)) to use markdown formatting in table captions. This won't work now, since the developer has removed markdown rendering abilities of table captions since version 0.8.3.

To fix the issue, you can write your markdown caption as a string, then pass it to ftExtra::as_paragraph_md() in set_caption(). For example:

tab_caption <- "A scientifically formatted `flextable` with ^superscripts^ and ~subscripts~"

a %>%
  flextable() %>%
  set_caption(
    caption = ftExtra::as_paragraph_md(tab_caption), # <- Here
    style = "Table Caption"
  ) %>%
  colformat_md() %>%
  theme_apa() %>% # The 3-part-table used in academics
  align(align = "center", part = "all") %>% 
  set_table_properties(layout = "autofit") 

It should be noted that the rendered caption will not follow the Table Caption style in the reference docx. To specify the style of the caption, you have to use flextable::set_flextable_defaults() to set the default to the caption style you want, and then set the table format separately:

set_flextable_defaults( # Caption styles
  font.family = "Times New Roman",
  font.size = 12,
  padding.bottom = 6,
  padding.top = 12,
  line_spacing = 1.5,
  table_align = "center",
)
 
# The table styles
    
table_defaults <- function(x) {
  x |>
    flextable::border_inner_h(
      part = "header", border = officer::fp_border(color = "black")
    ) |>
    flextable::align(align = "center", part = "all") |>
    flextable::valign(valign = "top", part = "body") |>
    flextable::padding(
      padding.top = 3,
      padding.bottom = 3,
      padding.left = 5,
      padding.right = 5,
      part = "all"
    ) |>
    flextable::line_spacing(
      space = 1.15,
      part = "all"
    ) |>
    flextable::fontsize(size = 11, part = "all") |>
    flextable::set_table_properties(layout = "autofit")
}

a <- table_defaults(a)

Upvotes: 3

Vishal A.
Vishal A.

Reputation: 1381

Your code should look like this:

```
{r table, echo=F, warning=F, message=F}
library(tidyverse)
a <- tibble(
  constants = c("c", "NA", "h", "e", "$H_2O$"),
  values = c("$2.998 * {10^{8}}$", "$6.022 * {10^{-23}}$", "$6.626 * {10^{-34}}$", "$-1.602 * {10 ^{-19}}$", "$1.802 * {10^{1}}$")
)

knitr::kable(format = "html", a, digits = 35)
```

Which will give you the output like this:

enter image description here

Upvotes: -1

Donald Seinen
Donald Seinen

Reputation: 4419

An option that probably falls in the no-go zone for this open issue:

  1. Create a html document,
  2. Insert html tags for subscripts,
  3. Open the html file (not the viewer),
  4. ctrl+c, then ctrl+v in your word file.
---
output: html_document
---

```{r table, echo=F, warning=F, message=F}
library(tidyverse)
library(gt)
a <- tibble(
    constants = c("c", "N<sub>A</sub>", "h","e","H<sub>2</sub>O"),
    values = c(2.998e8, 6.022e23, 6.626e-34, -1.602e-19, 18.02)
)
a %>%
  mutate(constants = map(constants, html)) %>%
  gt() %>%
  fmt_scientific(values)

Upvotes: 1

Ma&#235;l
Ma&#235;l

Reputation: 51914

You can use tildes (~) to put in subscript and carets (^) for superscripts; and use sprintf to get the expected digit format:

---
title: "Table won't work"
author: "Exhausted student"
date: "2022/01/28"
output: 
  bookdown::word_document2
---

```{r table, echo=F, warning=F, message=F}
library(tidyverse)

expSup <- function(x, digits=3) {
  sprintf(paste0("%05.", digits, "f x 10^%d^"), x/10^floor(log10(abs(x))), floor(log10(abs(x))))
}

a <- tibble(
  constants = c("c", "N~A~", "h", "e", "H~2~0"),
  values = expSup(c(2.998e8, 6.022e-23, 6.626e-34, -1.602e-19, 18.02))
)

knitr::kable(a)
```

enter image description here

Upvotes: 3

Related Questions