Help with conditional formatting across multiple columns in dataframe

I created a table using the dplyr package, but I want to color-code the resulting median values based on the range shown in the formatting step. This worked, but the formatted result is HTML for reactable and I want the dataframe itself color-coded so that I can export to Excel (with formatting ). I've searched for a solution for several days now, and resulted in nothing but HTML results .
This table has years going across, so I'd prefer to use a vector and not have to write a condition for each one.

#>I want to format the results of stateYR created below
library(dplyr)
library(tidyr)

stateYR <- DF  %>% 
  group_by(state, YR) %>%
  summarize(ratio = median(ratio , na.rm = TRUE)) %>%
  mutate_if(is.numeric, round, 2) %>%
  pivot_wider(id_cols = state, names_from = YR, values_from = ratio, values_fill = list(ratio = 0)) 
 

library(reactable)
library(tidyverse)

columns <-
  stateYR %>%
  colnames() %>%
  set_names() %>%
  keep(~ .x %in% colnames(stateYR)[2:15]) %>%
  map(~ {
    colDef(
      style = function(value) {
        ds_color <- ifelse(value < 0, "red",
                           ifelse(value >=0 & value <0.05, "green", 
                                  ifelse(value >=.05 & value <.1, "coral",
                                         ifelse(value >= .1, "red"))))
        list(background = ds_color)
      }
    )
  })
reactable(stateYR, columns = columns)  

results <-reactable(stateYR)
1 Like

Check out the openxlsx::Formatting vignette. it has examples.
vignette("Formatting", "openxlsx")

Is not exporting the formatting. What am I doing wrong here? The values are already rounded to 2 decimal places so I could avoid overlap with the two BETWEEN functions.


library(openxlsx)
library(tidyverse)

red <- createStyle(fontColour= "#000000", bgFill = "#CA0020")
coral <- createStyle(fontColour= "#000000", bgFill = "#E66101")
green <- createStyle(fontColour= "#000000", bgFill = "#4DAC26")

wb <-createWorkbook()
addWorksheet(wb, "Sheet 1")
writeData(wb, "Sheet 1", stateYR, keepNA = TRUE) 

# format
conditionalFormatting(wb, "Sheet 1",
                      cols = 2:14,
                      rows = 2:nrow(stateYR),
                      
                      rule = "<0", 
                      style = red)
                      conditionalFormatting(wb, "Sheet 1",
                                            cols = 2:14,
                                            rows = 2:nrow(stateYR),
                                                      type = "between",
                                                      rule = "c(0, .04)", 
                                                      style = green)
                                            conditionalFormatting(wb, "Sheet 1",
                                                                  cols = 2:14,
                                                                  rows = 2:nrow(stateYR),
                                                                            type = "between",
                                                                            rule = "c(0.05 , .09)", 
                                                                            style = coral)
                                                                  conditionalFormatting(wb, "Sheet 1",
                                                                                        cols = 2:14,
                                                                                        rows = 2:nrow(stateYR),
                                                                                                 
                                                                                                  rule = ">=0.1", 
                                                                                                  style = red)
                                                                                        openXL(wb)

For example:

library(openxlsx)
library(tidyverse)

DF <- data.frame(
  state = sample(LETTERS, 100, replace = TRUE),
  YR = sample(2012:2024, 100, replace = TRUE),
  ratio = runif(100, c(-1, 1))
)

stateYR <- DF %>%
  group_by(state, YR) %>%
  summarize(ratio = median(ratio, na.rm = TRUE)) %>%
  mutate_if(is.numeric, round, 2) %>%
  pivot_wider(
    id_cols = state,
    names_from = YR,
    values_from = ratio,
    values_fill = list(ratio = 0)
  )


wb <- createWorkbook()
addWorksheet(wb, "Sheet 1")

redStyle <- createStyle(fontColour = "#000000", bgFill = "#CA0020")
coralStyle <- createStyle(fontColour = "#000000", bgFill = "#E66101")
greenStyle <- createStyle(fontColour = "#000000", bgFill = "#4DAC26")

writeData(wb, "Sheet 1", stateYR, startCol = 1, startRow = 1, rowNames = FALSE)

# format
conditionalFormatting(
  wb,
  "Sheet 1",
  cols = 2:ncol(stateYR),
  rows = 2:nrow(stateYR),
  rule = "<0",
  style = redStyle
)

conditionalFormatting(
  wb,
  "Sheet 1",
  cols = 2:ncol(stateYR),
  rows = 2:nrow(stateYR),
  type = "between",
  rule = c(0, .04),
  style = greenStyle
)

conditionalFormatting(
  wb,
  "Sheet 1",
  cols = 2:ncol(stateYR),
  rows = 2:nrow(stateYR),
  type = "between",
  rule = c(0.05, .09),
  style = coralStyle
)

conditionalFormatting(
  wb,
  "Sheet 1",
  cols = 2:ncol(stateYR),
  rows = 2:26,
  rule = ">=0.1",
  style = redStyle
)

saveWorkbook(wb, "conditionalFormattingExample.xlsx", TRUE)

openXL(wb)

I got mine to work by removing the quotes around c(…).

Dumb question : is it possible to perform this formatting directly on a data frame? I only know of the reactable function but it isn’t good for very large tables

Store the conditional formatting in the dataframe? As some sort of metadata? I don’t think so.