Reputation: 8454
I have the dataframe below:
product<-c("ab","ab","ab","ac","ac","ac")
shop<-c("sad","sad","sad","sadas","fghj","xzzv")
category<-c("a","a","a","b","b","b")
tempr<-c(35,35,14,24,14,5)
value<-c(0,0,-6,8,4,0)
store<-data.frame(product,shop,category,tempr,value)
from which I create store2
with:
store2 <- matrix(NA,ncol=length(unique(store$shop)),nrow=length(unique(store$product)))
colnames(store2) <- unique(store$shop)
rownames(store2) <- unique(store$product)
for(i in 1:ncol(store)) {
store2[store[i,'product'],store[i,'shop']] <- paste0(store[i,c('tempr')],'(',store[i,'value'],')')
}
I would like to create a datatable with the DT
package colored according to the values of this new dataframe. More specifically if the number inside the parenthesis is positive then the cell should be colored green. In any other case (negative, 0 or NA its should be colored red. This is an example :
Upvotes: 3
Views: 1619
Reputation: 6124
I don't necessarily recommend this approach because I am fairly against embedding one language in another, but I think it solves your ask. You can probably save the JavaScript to a file and load that into a variable, and that would be a better approach I think, but, in the interest of a self-contained solution, I have inlined it.
Based on the DT
documentation here, https://rstudio.github.io/DT/functions.html, it is clear that the desired approach is to use formatStyle
in some way.
DT
provides a few convenience methods to be used with formatStyle
, whose source is here: https://github.com/rstudio/DT/blob/0b9710f5a9391c634a3865961083740f1cbf657b/R/format.R, which I have based my solution on.
Basically, we need to pass some JavaScript to formatStyle
which will do all of the table styling based on a variable called value
. We will need to do something like this:
datatable(store2) %>% formatStyle(colnames(store2), backgroundColor=JS(jsFunc))
Where the variable jsFunc
is some JavaScript string. Since the source indicates this string must be an expression rather than a statement, and because this will be somewhat complicated, we will use an anonymous function which is immediately evaluated to perform the logic. This function must take a value and return a color based on that value. Here is the function we need.
function(value){
// find a number preceeded by an open parenthesis with an optional minus sign
var matches = /\((-?\d+)/.exec(value);
// ignore values which do not match our pattern, returning white as the background color
if(!matches || matches.length < 2) {
return 'white';
}
// attempt to convert the match we found into a number
var int = parseInt(matches[1]);
// if we can't ignore it and return a white color
if(isNaN(int)) {
return 'white';
}
// if the value is negative, return red
if(int < 0) {
return 'red'
}
// otherwise, by default, return green
return 'green';
}
We want to call this function immediately, so we wrap it in parenthesis, and pass the value argument to it.
(function(value){
// find a number preceeded by an open parenthesis with an optional minus sign
var matches = /\((-?\d+)/.exec(value);
// ignore values which do not match our pattern, returning white as the background color
if(!matches || matches.length < 2) {
return 'white';
}
// attempt to convert the match we found into a number
var int = parseInt(matches[1]);
// if we can't ignore it and return a white color
if(isNaN(int)) {
return 'white';
}
// if the value is negative, return red
if(int < 0) {
return 'red';
}
// otherwise, by default, return green
return 'green';
})(value)
We wrap this value in a multi-line R
string, escape any backslashes and double-quotes (I avoided using them), and assign it to the value jsFunc
.
jsFunc <- "(function(value){
// find a number preceeded by an open parenthesis with an optional minus sign
var matches = /\\((-?\\d+)/.exec(value);
// ignore values which do not match our pattern, returning white as the background color
if(!matches || matches.length < 2) {
return 'white';
}
// attempt to convert the match we found into a number
var int = parseInt(matches[1]);
// if we can't ignore it and return a white color
if(isNaN(int)) {
return 'white';
}
// if the value is negative, return red
if(int < 0) {
return 'red'
}
// otherwise, by default, return green
return 'green';
})(value)"
Finally, we can call formatStyle
using this variable
datatable(store2) %>% formatStyle(colnames(store2), backgroundColor=JS(jsFunc))
That should give us a result like:
Upvotes: 3
Reputation: 5673
I have an answer using data.table
and DT
, its a bit tricky. Here it is:
library(data.table)
library(DT)
store <- setDT(store)
store[,plouf := paste0(tempr,"(",value,")")]
store[,color := ifelse(value > 0,1,0)]
table1 <- dcast(store[,.SD[1],.SDcols = c("product","shop"),by = plouf],product ~ shop,value.var = "plouf")
table2 <- dcast(store[,.SD[1],.SDcols = c("product","shop","color"),by = plouf],product ~ shop,value.var = "color")
table2[,names(table2)[-1] := lapply(.SD,function(x){ifelse(is.na(x),0,x)}),.SDcols = names(table2)[-1] ]
setnames(table2,paste0(names(table1),"_col"))
plouf <- cbind(table1,table2[,-1])
datatable(plouf) %>%
formatStyle(names(table1)[-1],paste0(names(table1)[-1],"_col"), backgroundColor = styleEqual(c(0, 1), c('red', 'green')))
The explanation: first, i redo your store2
table using dacst
(passing to large format), which is more convenient, and that i ll reuse to define the colors:
store <- setDT(store)
store[,plouf := paste0(tempr,"(",value,")")]
table1 <- dcast(store[,.SD[1],.SDcols = c("product","shop"),by = plouf],product ~ shop,value.var = "plouf")
product fghj sad sadas xzzv
1: ab NA 35(0) NA NA
2: ac 14(4) 14(-6) 24(8) 5(0)
Here store[,.SD[1],.SDcols = c("product","shop"),by = plouf]
allow to have only one line per data you want, to avoid replica
Then I do the same, but with a variable that gives the color (1 for green, 0 for red):
store[,color := ifelse(value > 0,1,0)]
table2 <- dcast(store[,.SD[1],.SDcols = c("product","shop","color"),by = plouf],product ~ shop,value.var = "color")
product fghj sad sadas xzzv
1: ab NA 0 NA NA
2: ac 1 0 1 0
I tranform Nas into 0 in all column exept the first one:
table2[,names(table2)[-1] := lapply(.SD,function(x){ifelse(is.na(x),0,x)}),.SDcols = names(table2)[-1] ]
product fghj sad sadas xzzv
1: ab 0 0 0 0
2: ac 1 0 1 0
and modify the names of the second table (giving the colors):
setnames(table2,paste0(names(table1),"_col"))
you then bind the two
plouf <- cbind(table1,table2[,-1])
and use DT
where you specify different column : one for the target, one for the background color
datatable(plouf) %>%
formatStyle(names(table1)[-1],paste0(names(table1)[-1],"_col"), backgroundColor = styleEqual(c(0, 1), c('red', 'green')))
I had to change your data, because there wasn't an unique set for each shop. Here are the data:
product<-c("ab","ab","ac","ac","ac","ac")
shop<-c("sad","sad","sad","sadas","fghj","xzzv")
category<-c("a","a","a","b","b","b")
tempr<-c(35,35,14,24,14,5)
value<-c(0,0,-6,8,4,0)
store<-data.frame(product,shop,category,tempr,value)
Upvotes: 2