Search code examples
rr-markdownkablekableextra

How can I bold each cell with minimum value for each column in R Markdown


I want to bold cells with minimum values for one of my data frames which I combined to be one. I want the minimum value in each column to be bolded.

The below R code shows my Minimum Working Example (MWE). The table consists of columns of five (5) randomly generated values from the normal distribution. The first three (3) show when std dev =1 while the last three (3) show when std dev =2. Each column is different based on its seed of generation and its std dev.

---
title: "Min Value to be Bolded"
output:  pdf_document
---

```{r}
set.seed(1)
df1 <- data.frame(
  seed1 = rnorm(5, mean = 0, sd = 1),
  seed2 = rnorm(5, mean = 0, sd = 1),
  seed3 = rnorm(5, mean = 0, sd = 1)
)
set.seed(1)
df2 <- data.frame(
  seed1 = rnorm(5, mean = 0, sd = 2),
  seed2 = rnorm(5, mean = 0, sd = 2),
  seed3 = rnorm(5, mean = 0, sd = 2)
)
df <- cbind(df1, df2)
df |>
  knitr::kable(format = "html", table.attr = "style='width:100%;'", digits = 2, align = 'c', caption = "5 Random Numbers from the Normal Dist at diff set and std dev") |>
  kableExtra::kable_styling(bootstrap_options = 'bordered') |>
  kableExtra::add_header_above(c('$sd = 1$' = 3, '$sd$ = 2' = 3)) |>
  row_spec(3, bold=T, hline_after = TRUE)
```

The output looks like this

Instead of me to get the minimum values bold. I got the third row bold all through. I can get the values on R like this:

lapply(df1, 2, FUN = min)

but I need help on how to get the cells bolded with kable or kableExtral.

Please note I do not mean to do that for the whole data frame, I only need it for just df1 or df2.


Solution

  • You may add library(formattable) and then

    df |> 
      lapply(\(x){ifelse(x == min(x), cell_spec(round(x,2), bold = TRUE), round(x,2))}) |>
      knitr::kable(format = "html", table.attr = "style='width:100%;'", digits = 2, align = 'c', 
                    caption = "5 Random Numbers from the Normal Dist at diff set and std dev", 
                    escape = FALSE) |>
      kableExtra::kable_styling(bootstrap_options = 'bordered') |>
      kableExtra::add_header_above(c('$sd = 1$' = 3, '$sd$ = 2' = 3))
    

    Or, if you only want for df1 you may start by

    df1 |> lapply(\(x){ifelse(x == min(x), cell_spec(round(x, 2), bold = TRUE), round(x, 2))}) |> cbind(df2) |> 
    

    And for df2

    df1 |> cbind(df2 |> lapply(\(x){ifelse(x == min(x), cell_spec(round(x, 2), bold = TRUE), round(x, 2))})) |>
    

    A remark: With my solution names of dataframe become all x. You could add as.data.frame after the lapply to get the original names, but in this case they become changed because df has the names duplicated which is not a suitable practice. Instead, using col.names you may write the desired ones, for example

    df |> 
      lapply(\(x){ifelse(x == min(x), cell_spec(round(x,2), bold = TRUE), round(x,2))}) |>
      as.data.frame() |>
      knitr::kable(format = "html", table.attr = "style='width:100%;'", digits = 2, align = 'c', 
                   caption = "5 Random Numbers from the Normal Dist at diff set and std dev", 
                   escape = FALSE, col.names = rep(c("seed1", "seed2", "seed3"), 2)) |>
      kableExtra::kable_styling(bootstrap_options = 'bordered') |>
      kableExtra::add_header_above(c('$sd = 1$' = 3, '$sd$ = 2' = 3)) 
    

    enter image description here

    Note also that as comment @phiver, I had to modify output: pdf_document to output: html_document, since otherwise code didn't work. To get a pdf, the last lines of the code should be as follows (then it works):

      knitr::kable(format = "latex",  digits = 2, align = 'c', 
                   caption = "5 Random Numbers from the Normal Dist at diff set and std dev", 
                   escape = FALSE, col.names = rep(c("seed1", "seed2", "seed3"), 2)) |>
      kableExtra::kable_styling(bootstrap_options = 'bordered', full_width = TRUE) |>
      kableExtra::add_header_above(c('$sd = 1$' = 3, '$sd$ = 2' = 3))