I have two data.frames (each originally a dimension from a larger parent 3-d array). One holds numeric values. The other has T/F values indicating whether the confidence interval for each value in the first array overlaps a reference confidence interval. The confidence intervals are different for every value in the array, so in formatting the table I can’t refer to constants, only to the array of T/F values.
I want to show a table of the first array, with background color of each cell based on the second array. So that formattable can see the columns with T/F values, I created a single data frame that binds the columns from both 3rd dimensions. In the real data there are ~20 columns of numeric values. Here is a simplified example:
orig.data <- array(dim = c(3, 4, 2))
dimnames(orig.data) <- list (c("site1", "site2", "site3"), c("model1", "model2", "model3",
"model4"), c("mean.val", "is.in.CI"))
orig.data[,,1] <- round(runif(12, 2, 10), 2)
orig.data[,,2] <- as.logical(round(runif(12, 0, 1)))
ft.data <- data.frame(orig.data[,,2], stringsAsFactors = F)
colnames(ft.data) <- paste0("match.", colnames(ft.data))
ft.data <- cbind(data.frame(orig.data[,,1], stringsAsFactors = F),
ft.data)
I can create the table formatting I want by calling each column by name. There are two special considerations. First, for the first four columns, the choice of background color is conditional on a second column. Second, the last four columns I would like to hide. Is there a way to do this with apply or some similar succinct dynamic syntax?
Here is the long version that I’d like to consolidate.
yes.color <- "lightgreen"
no.color <- "pink"
formattable::formattable(ft.data, list(
`model1` = formatter("span", style = ~ style(display = "block",
"border-radius" = "4px", "padding-right" = "4px",
"background-color" = ifelse(`match.model1`, yes.color, no.color))),
`model2` = formatter("span", style = ~ style(display = "block",
"border-radius" = "4px", "padding-right" = "4px",
"background-color" = ifelse(`match.model2`, yes.color, no.color))),
`model3` = formatter("span", style = ~ style(display = "block",
"border-radius" = "4px", "padding-right" = "4px",
"background-color" = ifelse(`match.model3`, yes.color, no.color))),
`model4` = formatter("span", style = ~ style(display = "block",
"border-radius" = "4px", "padding-right" = "4px",
"background-color" = ifelse(`match.model4`, yes.color, no.color))),
match.model1 = F,
match.model2 = F,
match.model3 = F,
match.model4 = F))
This question is similar to the second of my questions and is unanswered: Loop, hide columns and r formattable
Failed attempts to automate hiding the T/F columns follow. I don’t have any ideas for automating the 2-column references.
formattable(ft.data[, 1:4])
If I omit the columns with the T/F designations, the formatter doesn’t know they exist.
area(col = 5:8) = F)) # no effect
Outside the formattable command, create a string:
formatter.string <- paste( unlist(paste0("match.", c(“model1”, “model2”, “model3”, “model4”), " = #F,\n\t")), collapse='')
then within the list for formattable, add
eval(parse(formatter.string)))) # no effect OR
lapply(5:8, function(m.col){m.col = F}) # also no effect
This is the best I could come up with using some eval / parse magic:
format <- sapply(names(ft.data)[1:4],function(x)
{
eval( #evaluate the following expression
parse(text= #parse the following string to an expression
sub("_SUB_", #find "_SUB_"
paste0("`match.",x,"`"), #replace with name of column
"formatter(\"span\", style = ~ style(display = \"block\", #in the string containing the formatter call
\"border-radius\" = \"4px\", \"padding-right\" = \"4px\",
\"background-color\" = ifelse(_SUB_, yes.color, no.color)))")))
},simplify=F,USE.NAMES = T)
#hiding part. Same concept as above
hide <- sapply(names(ft.data[5:8]), function(x) eval(parse(text=sub("_SUB_",x,"_SUB_ = F"))),
simplify=F,USE.NAMES=T)
formattable::formattable(ft.data,c(format,hide))
Related
I'm trying to apply conditional formatting based on data within a row. I've tried a number of libraries including DT, Reactablefmtr and formatter. The idea is to put it into shiny to present table of findings.
How do I make this function more dynamic to not call it for each row but reference it to the norm variable?
# the table
fin_ratios <- data.frame(
descr = c("Ratio 1", "Ratio 2"),
norm = c(10, 20),
`2021` = c(11, 19),
`2022` = c(9, 21)
)
The code to style the table:
library(formattable)
custom_color_tile <- function (x, x_norm = 10) {
formatter("span",
style = x ~ style(display = "block",
padding = "0 4px",
`color` = "white",
`border-radius` = "4px",
`background-color` = ifelse(x >= x_norm, "green", "red")))
}
fin_ratios %>%
formattable(
list(
area(col = 3:4, row = 1) ~ custom_color_tile(x_norm = 10),
area(col = 3:4, row = 2) ~ custom_color_tile(x_norm = 20)
)
)
Your function is already working. Instead of using single values for x_norm, you can use the norm variable as a vector fin_ratios$norm.
formattable(fin_ratios,
list(area(col = 3:4) ~ custom_color_tile(x_norm = fin_ratios$norm)))
You may just pay attention, if you want to color certain rows. Than you have to select the rows of the norm variable as well.
# color just first row
formattable(fin_ratios,
list(area(col = 3:4,
row = 1) ~ custom_color_tile(x_norm = fin_ratios$norm[1])))
I will need to do color conditional formatting for 1 particular column, format it to percentage, and export the file as .xlsx. Note that I have 5 data frames that I will run this rule code with, and compile them into 1 workbook each in different sheets. I am stuck on the part where I can't seem to set the conditional rule if I formatted the percentage in it. And vice versa, if I conditional format it first, I'm not sure how I can format percentage for that column. Please refer to my code below.
## Dataframe
cost_table <- read.table(text = "FRUIT COST SUPPLY_RATE
1 APPLE 15 0.026377
2 ORANGE 14 0.01122
3 KIWI 13 0.004122
5 BANANA 11 0.017452
6 AVOCADO 10 0.008324 " , header = TRUE)
## This is the line where I label the %. However if I do that, conditional formatting will not recognize it in the rule
cost_table$SUPPLY_RATE <- label_percent(accuracy = 0.01)(cost_table$SUPPLY_RATE)
## Creating workbook and sheet
Fruits_Table <- createWorkbook()
addWorksheet(Fruits_Table,"List 1")
writeData(Fruits_Table,"List 1",cost_table)
## Style color for conditional formatting
posStyle <- createStyle(fontColour = "#006100", bgFill = "#C6EFCE")
negStyle <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE")
## If Supply rate is above 1.5%, it will be green, if it's equivalent or below, it will be red
conditionalFormatting(Fruits_Table, "List 1",
cols = 3,
rows = 2:6, rule = "C2> 0.015", style = posStyle
)
conditionalFormatting(Fruits_Table, "List 1",
cols = 3,
rows = 2:6, rule = "C2<= 0.015", style = negStyle
)
The output should be as shown below.
Regarding Borderline info
What I'm looking at is to apply outside border for c2:c6.
To clarify my purpose, the final output will be shown as below. I have some other codes to format the borders for the headers and column A:B. Because of the percentage style, it affected my borderline.
You don't need to use label_percent from scales package.
You can apply the percentage format along with the color rules to the workbook by using style and then addStyle functions. Another thing, I found in the documented examples of conditionalFormatting that you don't need to specify the column name (such as C) in the rule argument if your rule apply to only one column with no relation to values in another column.
Here is the code that I used:
Fruits_Table <- createWorkbook()
addWorksheet(Fruits_Table,"List 1")
writeData(Fruits_Table,"List 1",cost_table)
conditionalFormatting(Fruits_Table, "List 1",
cols = 3,
rows = 2:6, rule = "> 0.015", style = posStyle)
conditionalFormatting(Fruits_Table, "List 1",
cols = 3,
rows = 2:6, rule = "<= 0.015",
style = negStyle)
percent_style <- createStyle(numFmt = "PERCENTAGE")
addStyle(Fruits_Table,"List 1", style = ,percent_style, rows = 2:6, cols = 3)
I tried that code and it works.
saveWorkbook(Fruits_Table, "my_fruits_table.xlsx", )
Updated to add borderline info
In case you want to create borderline along with the percentage format, you can use border and borderStyle as follows:
percent_border_style<- createStyle(numFmt = "PERCENTAGE",
border = "TopBottomLeftRight",
borderStyle = "medium" )
addStyle(Fruits_Table,"List 1",
style = ,percent_border_style,
rows = 2:6, cols = 3)
saveWorkbook(Fruits_Table, "borderline_fruits_table.xlsx", )
Here is the borderline result
In case you want to customize different styles to different cells, as you explained in your comment, you need to createStyle for a particular style, and then you use addStyle to apply that particular style to a particular cell. So, you need to specify the row and the column for each style. To keep the percentage format style, you also need to keep numFmt to each addStyle.
Here is an example code to apply outside borders to the targeted column. The code customizes borders to three groups of cells:
top_side_line <- createStyle(numFmt = "PERCENTAGE",
border = "TopLeftRight",
borderStyle = "medium")
side_line <- createStyle(numFmt = "PERCENTAGE",
border = "LeftRight",
borderStyle = "medium")
bottom_side_line <- createStyle(numFmt = "PERCENTAGE",
border = "BottomLeftRight",
borderStyle = "medium")
addStyle(Fruits_Table,"List 1",
style = top_side_line, rows = 2, cols = 3)
addStyle(Fruits_Table,"List 1",
style = side_line, rows = 3:5, cols = 3)
addStyle(Fruits_Table,"List 1",
style = bottom_side_line, rows = 6, cols = 3)
saveWorkbook(Fruits_Table, "newline_fruits_table.xlsx")
Here is the result:
I am trying to build a formattable with two columns, one a metric category and the other a trend indicator. For the trend I would like to use various direction arrows using the extended characters in the loaded font set. These can be referenced using the "&#xnnnn;" notation.
However, whenever I specify a specific formatter for the trend the translation fails and the arrow character is not displayed (the string representation is!).
In the code below, the first print works (using textout1); the second fails.
library(formattable)
metric <- "Quality"
trend <- "↑"
thetext <- data.frame("Metric" = metric, "Trend" = trend)
f1 <- formattable::formatter("span", style = ~ style(color = "#0066CC", "font-family" = "Cambria"))
f2 <- formattable::formatter("span", style = ~ style(color = "#00FF00", "font-family" = "Cambria",
font.weight = "bold"))
textout1 <- formattable::formattable(thetext, align = c("l", "l"), list("Metric" = f1))
textout2 <- formattable::formattable(thetext, align = c("l", "l"), list("Metric" = f1, "Trend" = f2))
print (formattable(textout1))
print (formattable(textout2))
I'm trying to set the background colour of a datatables row based on a vector with raw HTML (that I do not escape so that it renders a superscript). At this point I can do one or the other: set the background colour correctly by escaping the HTML, or set the superscript correctly by not escaping the HTML, but not both at the same time.
We can use DT::formatStyle with DT::styleEqual to set the background colour of specific rows of a datatable based on a variable in our table; for example, setting the background to gray when V1 == 'Crackers':
library(DT)
df <- data.frame(
V1 = c('Cheese<sup>1</sup>', 'Crackers', 'Taters'),
v2 = c(10, 4, 7))
datatable(df, escape = FALSE) %>%
formatStyle('V1',
target = 'row',
backgroundColor = styleEqual(
levels = 'Crackers',
values = 'gray'))
What I'd like to do is set the background colour of a specific row whose V1 value contains a non-escaped HTML superscript (i.e., Cheese<sup>1</sup>). Note that we set escape = FALSE to correctly render the superscript. Setting the levels argument of styleEqual to that HTML-containing field does not work:
datatable(df, escape = FALSE) %>%
formatStyle('V1',
target = 'row',
backgroundColor = styleEqual(
levels = c('Cheese<sup>1</sup>', 'Crackers'),
values = c('gray', 'gray')))
The issue might be that styleEqual calls htmltools::htmlEscape on the levels argument, i.e.,
htmltools::htmlEscape('Cheese<sup>1</sup>')
[1] "Cheese<sup>1</sup>"
which will obviously not directly match with Cheese<sup>1</sup>.
If we revert to the default escape setting, i.e., datatable(df, escape = TRUE) %>% ..., then we get the correct background colour, but of course, the superscript does not work.
I found a workaround that involved modifying the styleEqual function, adding an extra argument escape that allows us to skip the call to htmlEscape. For example,
styleEqual2 <- function (levels, values, default = NULL, escape = TRUE)
{
n = length(levels)
if (n != length(values))
stop("length(levels) must be equal to length(values)")
if (!is.null(default) && (!is.character(default) || length(default) !=
1))
stop("default must be null or a string")
if (n == 0)
return("''")
if ((is.character(levels) || is.factor(levels)) && escape)
levels = htmltools::htmlEscape(levels)
levels = DT:::jsValues(levels)
values = DT:::jsValues(values)
js = ""
for (i in seq_len(n)) {
js = paste0(js, sprintf("value == %s ? %s : ",
levels[i], values[i]))
}
default = if (is.null(default))
"null"
else DT:::jsValues(default)
JS(paste0(js, default))
}
datatable(df, escape = FALSE) %>%
formatStyle('V1',
target = 'row',
backgroundColor = styleEqual2(
levels = c('Cheese<sup>1</sup>', 'Crackers'),
values = c('gray', 'gray'),
escape = FALSE))
Is there a better way to do this without modifying the source code?
Interesting issue. I've found a trick: use a list instead of a character vector for the levels argument.
datatable(df, escape = FALSE) %>%
formatStyle('V1',
target = 'row',
backgroundColor = styleEqual(
levels = list('Cheese<sup>1</sup>', 'Crackers'),
values = c('gray', 'gray')))
In this way, is.character(levels) and is.factor(levels) are both FALSE.
I am trying to use ggbiplotfrom ggfortify package. It seems its working fine but I am getting warning message as follows,
mdl <- pls::plsr(mpg ~ ., data = mtcars, scale = T)
scrs <- data.frame(pls::scores(mdl)[])
loads <- data.frame(pls::loadings(mdl)[])
ggfortify::ggbiplot(scrs, loads,
label.label = rownames(scrs), asp = 1, label = T, label.size = 3,
loadings = T, loadings.label = T, loadings.label.label = rownames(loads))
Warning messages:
1: In if (value %in% columns) { :
the condition has length > 1 and only the first element will be used
2: In if (value %in% columns) { :
the condition has length > 1 and only the first element will be used
Have I taken any wrong step or is it a bug.
According to the ggbiplot documentation, the label.label= parameter expects the column names from which to pull the names; it does not expect a vector of names. Same goes for loadings.label.label=. (ggplot and most tidyverse functions don't like rownames very much -- better to make them a proper column)
scrs$ID <- rownames(scrs)
loads$ID <- rownames(loads)
ggfortify::ggbiplot(scrs, loads,
label.label = "ID", asp = 1, label = T, label.size = 3,
loadings = T, loadings.label = T, loadings.label.label = "ID")