How to aggregate a reactive table in shiny? - shiny

I am trying to aggregate a reactive table from Shiny. My structure is similar to this example
library(shiny)
runApp(list(
ui=pageWithSidebar(headerPanel("Adding entries to table"),
sidebarPanel(textInput("text1", "Column 1"),
textInput("text2", "Column 2"),
actionButton("update", "Update Table")),
mainPanel(tableOutput("table1"))),
server=function(input, output, session) {
values <- reactiveValues()
values$df <- data.frame(Column1 = NA, Column2 = NA)
newEntry <- observe({
if(input$update > 0) {
newLine <- isolate(c(input$text1, input$text2))
isolate(values$df <- rbind(values$df, newLine))
}
})
output$table1 <- renderTable({values$df})
}))
I am trying with several ways, for example:
output$table2 <- renderTable({
as.data.frame(values$Column1, list(values$Column2), sum
})
But until now I could not have the expected result. Do you have an idea, please?

Are you looking for something like a row sum? I used the apply function with sum, but the
output$table1 <- renderTable({cbind(values$df, Rowsum = apply(values$df, 1, function(x) sum(as.numeric(x))))})
Also, if you would like to remove the first empty line, you could use the advice in the SO question you linked
It looks like this:
Complete code:
library(shiny)
runApp(list(
ui=pageWithSidebar(headerPanel("Adding entries to table"),
sidebarPanel(textInput("text1", "Column 1"),
textInput("text2", "Column 2"),
actionButton("update", "Update Table")),
mainPanel(tableOutput("table1"))),
server=function(input, output, session) {
values <- reactiveValues()
values$df <- data.frame(Column1 = numeric(0), Column2 = numeric(0))
newEntry <- observe({
if(input$update > 0) {
newLine <- isolate(c(input$text1, input$text2))
isolate(values$df[nrow(values$df) + 1,] <- c(input$text1, input$text2))
}
})
output$table1 <- renderTable({cbind(values$df, Rowsum = apply(values$df, 1, function(x) sum(as.numeric(x))))})
}))
Please note, that there is no exception handling implemented.

Related

Sorting the numbers when units are added

I am trying to add the units to numbers in the dataframe. But I see after formatting, sorting is not working as expected (since the column is now characters). I need to sort as per numbers only (Millions coming at last). But this is not happening
library(shiny)
library(DT)
ui <- fluidPage(
DTOutput("tab")
)
server <- function(input, output, session) {
format_numbers <- function (df, column_name){
df[[column_name]] <- ifelse(nchar(df[[column_name]]) <= 5, paste(format(round(df[[column_name]] / 1e3, 1), trim = TRUE), "K"),
paste(format(round(df[[column_name]] / 1e6, 1), trim = TRUE), "M"))
}
df <- data.frame(x = c(12345,35666,2646575,345))
df$x <- format_numbers(df, "x")
output$tab <- renderDT({
datatable(df,escape = F)
})
}
shinyApp(ui, server)
You can add a new column and sort by the original one :
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
DTOutput("tab")
)
server <- function(input, output, session) {
format_numbers <- function (df, column_name){
df[[column_name]] <- ifelse(nchar(df[[column_name]]) <= 5, paste(format(round(df[[column_name]] / 1e3, 1), trim = TRUE), "K"),
paste(format(round(df[[column_name]] / 1e6, 1), trim = TRUE), "M"))
}
df <- data.frame(x = c(12345,35666,2646575,345))
df$NewX = format_numbers(df, "x")
df <- df %>% arrange(x)
output$tab <- renderDT({
datatable((df %>% select(-x)),escape = F)
})
}
shinyApp(ui, server)

How can we highlight cells in R shiny when we use the replace button?

The code below reads a CSV file and displays the Datatable in the Main panel. The field in 'Column to search' is automatically detected. I've created a field named 'Replace' and a field called 'by' that can be used to replace certain values in a column's cell.
I want to highlight that cell in any colour, preferably orange, wherever the values are replaced.
Could someone please explain how I can do this in R shiny?
CSV
ID Type Category values
21 A1 B1 030,066,008,030,066,008
22 C1 D1 020,030,075,080,095,100
23 E1 F1 030,085,095,060,201,030
Expected Output:
If I change 030 to 100 in the columns 'values,' I want that cell (in column Values and Row 2) to be coloured.
code
library(shiny)
library(DT)
library(stringr)
library(dplyr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File", accept = ".csv"),
checkboxInput("header", "Header", TRUE),
selectInput("col", "Column to search:", NULL),
textInput("old", "Replace:"),
textInput("new", "By:"),
actionButton("replace", "Replace!"),
),
mainPanel(
DTOutput("table1")
)
)
)
server <- function(input, output, session) {
my_data <- reactiveVal(NULL)
observeEvent(input$file1, {
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
# validate(need(ext == "csv", "Please upload a csv file"))
my_data(read.csv2(file$datapath, header = input$header))
updateSelectInput(session, "col", choices = names(my_data()))
})
observeEvent(input$replace, {
req(input$col)
dat <- req(my_data())
traf <- if (is.numeric(dat[[input$col]])) as.numeric else identity
my_data(dat %>%
mutate(!!rlang::sym(input$col) :=
stringr::str_replace_all(!!rlang::sym(input$col),
input$old,
input$new) %>%
traf()))
})
output$table1 <- renderDT(
req(my_data())
)
}
shinyApp(ui, server)
I thought of a possible workaround that consists in using DT::formatStyle() to color each modified cell. One drawback of using this approach is that the csv imported will have twice as many columns (because i will need them to tell formatStyle() in which cells it has to add colors). However, the additional cols can be hidden so they don't appear displayed, but they will be present in the object passed to datatable. The additional columns are required if the cells need to stay colored after each edit, if that's not the case, then one extra column will suffice. Notice that the good news is that only R code is used here.
The first step will be to create the additional columns, so after the .csv file is read into reactive my_data():
#create (n = number of columns) reactive values.
nms <- vector('list', ncol(my_data())) %>% set_names(names(my_data()))
ccol <<- exec("reactiveValues", !!!nms)
#pre-allocate all the columns that we're going to use.
my_data(map_dfc(names(ccol), ~transmute(my_data(), 'orange_{.x}' := 0)) %>% {bind_cols(my_data(), .)})
Now, each time a column is modified somewhere, the corresponding orange_colname will contain a boolean indicated if a modification took place.
ccol[[input$col]] <- str_detect(dat[[input$col]], input$old)
my_data(my_data() %>%
mutate('orange_{input$col}' := ccol[[input$col]]))
finally, we render the table using datatable()'s option argument to hide the extra cols, and then use a for loop to add the colors in each column. I need to use a loop here because the app can import any table really as long it is a data frame.
Dtable <-
datatable(my_data(),
options = list(columnDefs = list(list(visible = FALSE, targets = (ncol(my_data())):((ncol(my_data()) / 2) + 1) ))))
walk(names(ccol), ~ { Dtable <<- Dtable %>% formatStyle(..1, str_glue("orange_{.x}"),
backgroundColor = styleEqual(c(1), c("orange"))) })
Dtable
App:
library(shiny)
library(DT)
library(stringr)
library(tidyverse)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File", accept = ".csv"),
checkboxInput("header", "Header", TRUE),
selectInput("col", "Column to search:", NULL),
textInput("old", "Replace:"),
textInput("new", "By:"),
actionButton("replace", "Replace!"),
),
mainPanel(
DTOutput("table1")
)
)
)
server <- function(input, output, session) {
my_data <- reactiveVal(NULL)
last_coloured <- reactiveVal(NULL)
observeEvent(input$file1, {
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
# validate(need(ext == "csv", "Please upload a csv file"))
my_data(read_csv(file$datapath))
updateSelectInput(session, "col", choices = names(my_data()))
#create (n = number of columns) reactive values.
nms <- vector('list', ncol(my_data())) %>% set_names(names(my_data()))
ccol <<- exec("reactiveValues", !!!nms)
#pre-allocate all the columns that we're going to use.
my_data(map_dfc(names(ccol), ~transmute(my_data(), 'orange_{.x}' := 0)) %>% {bind_cols(my_data(), .)})
})
observeEvent(input$replace, {
req(input$col)
dat <- req(my_data())
traf <- if (is.numeric(dat[[input$col]])) as.numeric else identity
my_data(dat %>%
mutate(!!rlang::sym(input$col) :=
stringr::str_replace_all(
!!rlang::sym(input$col),
input$old,
input$new
) %>%
traf()))
# also i would like to know which rows are modified
ccol[[input$col]] <- str_detect(dat[[input$col]], input$old)
my_data(my_data() %>%
mutate('orange_{input$col}' := ccol[[input$col]]))
})
output$table1 <- renderDT({
req(my_data())
Dtable <-
datatable(my_data(),
options = list(columnDefs = list(list(visible = FALSE, targets = (ncol(my_data())):((ncol(my_data()) / 2) + 1) ))))
walk(names(ccol), ~ { Dtable <<- Dtable %>% formatStyle(..1, str_glue("orange_{.x}"),
backgroundColor = styleEqual(c(1), c("orange"))) })
Dtable
})
}
shinyApp(ui, server)
I used the parameter selection from renderDT(). After changing my_data(), you can compare which positions were changed in relation with dat (where you stored the unchanged data.frame) and then pass them as coordinates to the selection parameter
server <- function(input, output, session) {
my_data <- reactiveVal(NULL)
positions <- reactiveVal(NULL) ## here we'll save positions of changed cells
observeEvent(input$file1, {
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
# validate(need(ext == "csv", "Please upload a csv file"))
my_data(read.csv2(file$datapath, sep = ",", header = input$header))
updateSelectInput(session, "col", choices = names(my_data()))
})
observeEvent(input$replace, {
req(input$col, my_data())
dat<- my_data()
traf <- if (is.numeric(dat[[input$col]])) as.numeric else identity
my_data(dat %>%
mutate(!!rlang::sym(input$col) :=
stringr::str_replace_all(!!rlang::sym(input$col),
input$old,
input$new) %>%
traf()))
positions(which(dat != my_data(), arr.ind = T)) # this is where new
# values positions are stored
})
output$table1 <- renderDT({
req(my_data())
}, selection=list(mode="single",##### this argument let you select a cell
target="cell",
selected = positions()))
}
If you set input$old to "030", all cells will be selected, since "030" is present in all 3 cells. But if you do it with "066", you'll see only the first cell of "values" will be highlighted

How to use the Undo button in R shiny to undo earlier operations and recover them

I am working on a R shiny app that reads CSV and produces a dataTable. I am looking for a way to undo prior actions one by one whenever I clik the Undo button (like CTRL+ Z in Windows), however, the code below restores all previous actions once I press the Undo button.
Could someone please assist me in resolving this problem?
csv data
ID Type Range
21 A1 B1 100
22 C1 D1 200
23 E1 F1 300
app.R
library(shiny)
library(reshape2)
library(DT)
library(tibble)
###function for deleting the rows
splitColumn <- function(data, column_name) {
newColNames <- c("Unmerged_type1", "Unmerged_type2")
newCols <- colsplit(data[[column_name]], " ", newColNames)
after_merge <- cbind(data, newCols)
after_merge[[column_name]] <- NULL
after_merge
}
###_______________________________________________
### function for inserting a new column
fillvalues <- function(data, values, columName){
df_fill <- data
vec <- strsplit(values, ",")[[1]]
df_fill <- tibble::add_column(df_fill, newcolumn = vec, .after = columName)
df_fill
}
##function for removing the colum
removecolumn <- function(df, nameofthecolumn){
df[ , -which(names(df) %in% nameofthecolumn)]
}
### use a_splitme.csv for testing this program
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File", accept = ".csv"),
checkboxInput("header", "Header", TRUE),
actionButton("Splitcolumn", "SplitColumn"),
uiOutput("selectUI"),
actionButton("deleteRows", "Delete Rows"),
textInput("textbox", label="Input the value to replace:"),
actionButton("replacevalues", label = 'Replace values'),
actionButton("removecolumn", "Remove Column"),
actionButton("Undo", 'Undo')
),
mainPanel(
DTOutput("table1")
)
)
)
server <- function(session, input, output) {
rv <- reactiveValues(data = NULL, orig=NULL)
observeEvent(input$file1, {
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
validate(need(ext == "csv", "Please upload a csv file"))
rv$orig <- read.csv(file$datapath, header = input$header)
rv$data <- rv$orig
})
output$selectUI<-renderUI({
req(rv$data)
selectInput(inputId='selectcolumn', label='select column', choices = names(rv$data))
})
observeEvent(input$Splitcolumn, {
rv$data <- splitColumn(rv$data, input$selectcolumn)
})
observeEvent(input$deleteRows,{
if (!is.null(input$table1_rows_selected)) {
rv$data <- rv$data[-as.numeric(input$table1_rows_selected),]
}
})
output$table1 <- renderDT({
rv$data
})
observeEvent(input$replacevalues, {
rv$data <- fillvalues(rv$data, input$textbox, input$selectcolumn)
})
observeEvent(input$removecolumn, {
rv$data <- removecolumn(rv$data,input$selectcolumn)
})
observeEvent(input$Undo, {
rv$data <- rv$orig
})
}
We can create a list to host every instance of the table to recover multiple undo's. Note that if the .csv is very big this approach will become inefficient very quick. We can mitigate this infefficiency by implementing a button that clears the undo list up to a point or implementing an append function that saves only the part modified of the table rather than the whole table.
Please, fill free to modify the answer or use it for another answer.
library(shiny)
library(reshape2)
library(DT)
library(tibble)
###function for deleting the rows
splitColumn <- function(data, column_name) {
newColNames <- c("Unmerged_type1", "Unmerged_type2")
newCols <- colsplit(data[[column_name]], " ", newColNames)
after_merge <- cbind(data, newCols)
after_merge[[column_name]] <- NULL
after_merge
}
###_______________________________________________
### function for inserting a new column
fillvalues <- function(data, values, columName){
df_fill <- data
vec <- strsplit(values, ",")[[1]]
tibble::add_column(df_fill, newcolumn = vec, .after = columName)
}
##function for removing the colum
removecolumn <- function(df, nameofthecolumn){
df[ , -which(names(df) %in% nameofthecolumn)]
}
# APP ---------------------------------------------------------------------
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File", accept = ".csv"),
checkboxInput("header", "Header", TRUE),
actionButton("Splitcolumn", "SplitColumn"),
uiOutput("selectUI"),
actionButton("deleteRows", "Delete Rows"),
textInput("textbox", label = "Input the value to replace:"),
actionButton("replacevalues", label = 'Replace values'),
actionButton("removecolumn", "Remove Column"),
actionButton("Undo", 'Undo')
),
mainPanel(
DTOutput("table1")
)
)
)
server <- function(session, input, output) {
#added undo (a list) and counter to accumulate more than one undo
rv <- reactiveValues(data = NULL, orig=NULL, undo = list(), counter = 1)
# csv file ----------------------------------------------------------------
observeEvent(input$file1, {
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
validate(need(ext == "csv", "Please upload a csv file"))
rv$orig <- read.csv(file$datapath, header = input$header)
rv$data <- rv$orig
})
output$selectUI <- renderUI({
req(rv$data)
selectInput(inputId='selectcolumn', label='select column', choices = names(rv$data))
})
# rest of the app ---------------------------------------------------------
observeEvent(input$Splitcolumn, {
rv$undo[[rv$counter]] <- rv$data
rv$counter <- rv$counter + 1
rv$data <- splitColumn(rv$data, input$selectcolumn)
})
observeEvent(input$deleteRows,{
if (!is.null(input$table1_rows_selected)) {
rv$undo[[rv$counter]] <- rv$data
rv$counter <- rv$counter + 1
rv$data <- rv$data[-as.numeric(input$table1_rows_selected),]
}
})
output$table1 <- renderDT({
rv$data
})
observeEvent(input$replacevalues, {
rv$undo[[rv$counter]] <- rv$data
rv$counter <- rv$counter + 1
rv$data <- fillvalues(rv$data, input$textbox, input$selectcolumn)
})
observeEvent(input$removecolumn, {
rv$undo[[rv$counter]] <- rv$data
rv$counter <- rv$counter + 1
rv$data <- removecolumn(rv$data,input$selectcolumn)
})
observeEvent(input$Undo, {
if (rv$counter > 1) {
rv$data <- rv$undo[[rv$counter - 1]]
#index must be more than 1
rv$counter <- rv$counter - 1
}
})
}
shinyApp(ui, server)

Shiny: subsetting a table from a textInput with multiple values

I have a simple Shiny app. The user enters a code eg: a1, b1, c1 etc in the textInput.
When only one code is listed it works great, but if the user writes two or more codes separated by a comma it doesn't.
How can the user input as many codes as they like?
library(shiny)
ui <- fluidPage(
titlePanel(""),
sidebarLayout(
sidebarPanel(
textInput(inputId = "textBox",
label = "Code Search",
placeholder = "Enter codes here seperated by a comma"),
actionButton("textSearchButton", "Generate the Table")
),
fluidRow(
tableOutput("dtOut")
)
)
)
server <- function(input, output) {
df <- data.frame(Code = paste0(letters, 1),
Description = "Something here",
Value = "Some value")
outputFunc <- function(code, df){
# # Dummy data
# code <- c('a1', 'b1', 'c1')
outTbl <- df[df$Code %in% code,]
return(list(outTbl))
}
textSearch <- eventReactive(input$textSearchButton, {
outputFunc(input$textBox, df)
})
output$dtOut <- renderTable({
textSearch()[[1]]
})
}
shinyApp(ui, server)
I simplified your code a bit:
library(shiny)
ui <- fluidPage(
titlePanel(""),
sidebarLayout(
sidebarPanel(
textInput(inputId = "textBox",
label = "Code Search",
placeholder = "Enter codes here seperated by a comma"),
actionButton("textSearchButton", "Generate the Table")
),
fluidRow(
tableOutput("dtOut")
)
)
)
server <- function(input, output) {
df <- eventReactive(input$textSearchButton, {
# outputFunc(input$textBox, df)
req(input$textBox)
codes <- unlist(strsplit(input$textBox, ", "))
return(data.frame(Code = codes,
Description = "Something here",
Value = "Some value"))
})
output$dtOut <- renderTable({
df()
})
}
shinyApp(ui, server)
Does it respond to your need ?

shiny: add/remove time-series to dygraphs upon input values

I'm building a shiny app that would display in dygraphs a basic dataset and then offer an option to add new time series upon selecting the checkbox input. However, as I coded it now, I'm 'stuck' at the original dataset and unable to add/remove new content. Any hints how to solve this are very welcome, thanks.
library(shinydashboard)
library(dygraphs)
library(dplyr)
ui <-dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
useShinyjs(),
checkboxGroupInput(inputId = 'options',
label = 'Choose your plot(s)',
choices = list("mdeaths" = 1,
"ldeaths" = 2)
),
uiOutput("Ui1")
)
)
server <- function(input, output, session) {
output$Ui1 <- renderUI({
output$plot1 <- renderDygraph({
final_ts <- ldeaths
p <- dygraph(final_ts, main = 'Main plot') %>%
dygraphs::dyRangeSelector()
if(1 %in% input$options) {
final_ts <- cbind(final_ts, mdeaths)
p <- p %>%
dySeries('mdeaths', 'Male Deaths')
} else if(2 %in% input$options) {
final_ts <- cbind(final_ts, fdeaths)
p <- p %>%
dySeries('fdeaths', 'Female Deaths')
}
p
})
dygraphOutput('plot1')
})
}
shinyApp(ui, server)
I'd suggest to dynamically filter the data based on the user selection instead of dynamically adding/removing traces from the plot:
library(shinydashboard)
library(shinyjs)
library(dygraphs)
library(dplyr)
lungDeaths <- cbind(ldeaths, mdeaths, fdeaths)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
useShinyjs(),
selectizeInput(
inputId = "options",
label = "Choose your trace(s)",
choices = colnames(lungDeaths),
selected = colnames(lungDeaths)[1],
multiple = TRUE,
options = list('plugins' = list('remove_button'))
),
uiOutput("Ui1")
)
)
server <- function(input, output, session) {
output$Ui1 <- renderUI({
filteredLungDeaths <- reactive({
lungDeaths[, input$options]
})
output$plot1 <- renderDygraph({
p <- dygraph(filteredLungDeaths(), main = 'Main plot') %>%
dygraphs::dyRangeSelector()
if('mdeaths' %in% colnames(filteredLungDeaths())){
p <- dySeries(p, 'mdeaths', 'Male Deaths')
}
if('fdeaths' %in% colnames(filteredLungDeaths())){
p <- dySeries(p, 'fdeaths', 'Female Deaths')
}
p
})
dygraphOutput('plot1')
})
}
shinyApp(ui, server)