I am creating a shiny machine learning application. I am displaying data in datatables and want to pass the data to machine learning model by selecting the row and clicking the button to get result.
How can it be done in shiny ?
I think I understand what you are trying to do. Hope this minimal example I made will help you. Use DT for table rendering and row selection (here i suppressed the selection of more than one row because I deduced that is what you want). Use button and isolate to run model only if row is selected and button is pressed. I didn't fit a model here, instead I made a plot with highlited row data, but the principle is exactly the same.
library(shiny)
library(DT)
server <- function(input, output, session) {
output$x1 = DT::renderDataTable(mtcars, server = FALSE, selection = "single")
# client-side processing
output$x2 = renderPrint({
s = input$x1_rows_selected
if (length(s)) {
cat('These rows were selected:\n\n')
cat(s, sep = ', ')
}
})
# highlight selected rows in the scatterplot - here you add your model
output$x3 = renderPlot({
input$run_model # button input
s = isolate(input$x1_rows_selected) # use isolate to run model only on button press
par(mar = c(4, 4, 1, .1))
plot(mtcars[, 2:3])
if (length(s)) points(mtcars[s, 2:3, drop = FALSE], pch = 19, cex = 2)
})
}
ui <- fluidPage(
title = 'Select Table Rows',
h1('A Client-side Table'),
fluidRow(
column(9, DT::dataTableOutput('x1')),
column(3, verbatimTextOutput('x2'))
),
hr(),
h1('Model'),
fluidRow(
column(6, actionButton("run_model", "Go")),
column(9, plotOutput('x3', height = 500))
)
)
shinyApp(ui = ui, server = server)
Related
below there is a shiny app that renders a datatable using DT. Rather than have the table start the display at row 1 I'd like to have the table render with a specific top row selected by the user (using input$startRow in this example).
E.g., if the user chose 50 in the slider the first row shown in the table would be row 50 rather than row 1.
Any tips for getting a dynamic starting row appreciated.
Edit for clarity: I do not want to subset the table, I want to display to begin at input$startRow but the user could scroll up and down and still see the entire dataset (e.g., faithful in this example).
Edit 2: It looks like the issue is that the displayStart option is what I want but that there is a known bug as of May 21 with Scroller starting the display mid table.
library(shiny)
library(DT)
data("faithful")
ui <- fluidPage(
h2("Select the first row of the table to show"),
sliderInput(inputId="startRow",
label="Choose a row to start the datatable display",
min = 1,
max = 200,
value = 100,
step=5),
# show a datatable
h2("Show a table"),
dataTableOutput("table1")
)
server <- function(input, output) {
output$table1 <- renderDataTable({
# use input$startRow to begin the table display?
datatable(faithful,
extensions = "Scroller",
options = list(scrollY = 300,
scroller = TRUE))
})
}
# Run the application
shinyApp(ui = ui, server = server)
Success. Following the link in the comment. I was able to use initComplete to start the table on the row from input$startRow. This appears to work.
library(shiny)
library(DT)
data("faithful")
ui <- fluidPage(
h2("Select the first row of the table to show"),
sliderInput(inputId="startRow",
label="Choose a row to start the datatable display",
min = 1,
max = 200,
value = 10,
step=5),
# show a datatable
h2("Show a table"),
dataTableOutput("table1")
)
server <- function(input, output) {
output$table1 <- renderDataTable({
datatable(faithful,
extensions = "Scroller",
options = list(scrollY = 300,
scroller = TRUE,
initComplete = JS('function() {this.api().table().scroller.toPosition(',
input$startRow-1,');}')))})
}
shinyApp(ui = ui, server = server)
Yes, use input$startRow to begin the table display to generate the selected table.
library(shiny)
library(DT)
data("faithful")
ui <- fluidPage(
h2("Select the first row of the table to show"),
sliderInput(inputId="startRow",
label="Choose a row to start the datatable display",
min = 1,
max = 200,
value = 100,
step=5),
# show a datatable
h2("Show a table"),
dataTableOutput("table1")
)
server <- function(input, output) {
topDF <- reactive({
# use input$startRow to begin the table display
topRow <- input$startRow
selectedDf <- faithful[-(1:(topRow-1)), ]
return(selectedDf)
})
output$table1 <- renderDataTable({
datatable(topDF(),
extensions = "Scroller",
options = list(scrollY = 300,
scroller = TRUE))
})
}
# Run the application
shinyApp(ui = ui, server = server)
Probably very basic question - but can't translate similar posts I've found to my exact issue.
Within an R Shiny app, I have a first drop-down menu that is populated by a vector produced on the server - this allows me to make one set of choices.
I want to have a tick box that then introduces a second drop down - but I want that drop down to disappear if I un-tick the tick box.
I've had a go - see MWE below - the graph is just there to keep to the structure of my original code (obviously I'm aware my drop-downs do nothing but that's not the case in the original but wanted the MWE to be as 'M' as possible).
If I remove the removeUI() line then ticking the tick-box does create a new drop down as required - but then un-ticking the tick box fails to remove it.
I'm obviously missing something; any help much appreciated as I totally suck at R Shiny but really want to get better!
library(shiny)
library(shinyMobile)
# define UI elements
ui <- f7Page(
f7SingleLayout(
navbar = f7Navbar(
),
f7Card(htmlOutput("initial_drop_down"), #first drop down
f7checkBox(inputId = "switch", label = "Introduce second choice", FALSE), #tick box for second drop down if required
htmlOutput("reactive_drop_down") #second drop down
),
f7Shadow(
intensity = 16,
f7Card(
plotOutput("distPlot", height = "800px") # plot - originally linked to drop down choices but an arbitrary graph here for simplicity
)
)
)
)
# server calculations
server <- function(input, output) {
library(ggplot2)
# generate first drop down - done on server side since usually choices vector is comprised of information read in from files
output$initial_drop_down = renderUI({
selectInput(inputId = "initial_choice",
label = "First choice:",
choices = c("Choice 1", "Choice 2", "Choice 3"))
})
observeEvent(input$initial_choice, {
# trying to add second drop down based on action in switch - not convinced my use of observeEvent is quite right - issue likely sits in here.
observeEvent(input$switch, {
if(input$switch == T){
output$reactive_drop_down = renderUI({
selectInput(inputId = "second_choice",
label = "Second (dynamic) choice:",
choices = c(1,2,3))
})
}else{
removeUI(selector ="#reactive_drop_down")
}
})
output$distPlot <- renderPlot({
ggplot(data = cars) + geom_line(aes(x=speed, y=dist))
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
Could you use conditionalPanel? Put your htmlOutput for your second input there in your ui. I would avoid using nested observeEvent and output.
library(shiny)
library(shinyMobile)
library(ggplot2)
# define UI elements
ui <- f7Page(
f7SingleLayout(
navbar = f7Navbar(
),
f7Card(htmlOutput("initial_drop_down"), #first drop down
f7checkBox(inputId = "switch", label = "Introduce second choice", FALSE), #tick box for second drop down if required
conditionalPanel(
condition = "input.switch==1",
htmlOutput("reactive_drop_down") #second drop down
)
),
f7Shadow(
intensity = 16,
f7Card(
plotOutput("distPlot", height = "800px") # plot - originally linked to drop down choices but an arbitrary graph here for simplicity
)
)
)
)
# server calculations
server <- function(input, output) {
# generate first drop down - done on server side since usually choices vector is comprised of information read in from files
output$initial_drop_down = renderUI({
selectInput(inputId = "initial_choice",
label = "First choice:",
choices = c("Choice 1", "Choice 2", "Choice 3"))
})
output$reactive_drop_down = renderUI({
selectInput(inputId = "second_choice",
label = "Second (dynamic) choice:",
choices = c(1,2,3))
})
output$distPlot <- renderPlot({
ggplot(data = cars) + geom_line(aes(x=speed, y=dist))
})
}
# Run the application
shinyApp(ui = ui, server = server)
To edit a one-to-many like data structure,
I would like to create a data table (DTOutput), on which I observe cell clicks observeEvent(input$groupingsOut_cell_clicked, {...}).
Then at every cell click, I would like to generate input fields on the UI.
Finally, I would like to listen to changed on those rendered/generated UI.
I can't edit these cells directly in the DTOutput, as it doesn't support vectors within cells. (Hence, one-to-many relationship).
I have managed steps 1 and 2. I can render a DTOutput with corresponding cells. I can observe cell clicks and insert UI (insertUI()) upon cell clicks. I created observeEvent objects to observe those rendered fields. However, those observeEvents are never fired upon editing newly generated fields.
ui = fluidPage(title = titlePanel("Title"),
tags$head(tags$style(HTML("hr {border-top: 1px solid #000000;}"))),
sidebarLayout(
sidebarPanel(),
mainPanel(tabsetPanel(type = "tabs",
tabPanel("Groupings", DTOutput(outputId = "groupingsOut"),
tags$div(id = 'placeholder')),
tabPanel("Test", textOutput(outputId = "statusOut")),
tabPanel("Plot Generator", plotOutput(outputId = "distPlotOut")))
)
)
)
server = function(input, output) {
# Label reactive values
labelRVs = list()
#Example table
groupings = data.frame(names = c("cars", "mbikes", "bikes"),
labels = c("Cars", "Motor Bikes", "Bikes"),
groups = I(list(c("toyota", "vw", "tesla"), c("harley", "kawasaki"), c("somth", "anoth", "bla"))),
groupLabels = I(list(c("Toyota", "VW", "Tesla"), c("Harley Davidson", "Kawasaki"), c("Something", "Another Thing", "Bla bla"))))
#groupings = data.frame()
proxy = dataTableProxy('groupingsOut')
observeEvent(input$groupingsOut_cell_clicked, {
info = input$groupingsOut_cell_clicked
if(!is.null(info$row)){
grouping = groupings[[info$row, 1]]
groupingLabel = groupings[[info$row, 2]]
groups = groupings[[info$row, 3]]
groupLabels = groupings[[info$row, 4]]
# remove previously generated UI
removeUI(selector = paste0('#placeholder input'), multiple = TRUE)
removeUI(selector = paste0('#placeholder label'), multiple = TRUE)
# Generating ID for grouping labels
id = paste0("groupLabel_", i)
# Inserting text input for grouping label
insertUI(selector = '#placeholder', ui = textInput(id, label = "Grouping label:", value = groupingLabel))
labelRVs[[id]] <<- observeEvent(id, {
cat(paste(id, i, "\n")) # THIS LINE ONLY RUNS AT INITIALIZATION :(
})
lapply(1:length(groups), function(i){
index = sprintf("%03d", i)
id = paste0('label_', index)
insertUI(selector = '#placeholder',
ui = textInput(id, label = paste0("Group label for ", groups[i], ":"), value = groupLabels[i]))
labelRVs[[id]] <<- observeEvent(id, {
cat(paste(id, i, "\n")) # ALSO THIS LINE ONLY RUNS AT INITIALIZATION :(
})
})
}
})
output$groupingsOut = renderDT(groupings[, c(1, 3)], rownames = FALSE, editable = TRUE, selection = 'single')
}
shinyApp(ui = ui, server = server)
However, this example Shiny - Can dynamically generated buttons act as trigger for an event runs perfectly fine. In the example instead of insertUI into tags, renderUI to outputUI is used. I adapted my code above to use renderUI, which also failed. At this point, I am suspecting if DTOutput doesn't behave the same way as other input fields.
Beaware of the usage of <<- operator to assign labelRVs to keep observeEvent objects alive. This is indeed necessary, which is shown in the example.
I wonder, if there is any way to observe such fields?
I am currently working on a Shiny app which displays a static HTML table, sourced from another file, because of the size of the HTML code. The table is initialized with an empty data table in order to render the empty table. Above the HTML table are normal selectizeInput fields which filter a data table in the background (via a observe() function). The HTML table should then be populated with the filtered data table.
I am stuck at the process of updating the HTML table with the "new" data table. I tried sourcing the table again in the observe() - no change. I initialized the data table as reactiveValue and wrapped the HTML table with the reactive()-Function - again no change.
Here is a toy example which somewhat resembles my Shiny app:
app.R
library(shiny)
ui <- fluidPage(
fluidRow(
column(width = 6, uiOutput("cars"))
),
fluidRow(
column(width = 6, htmlOutput("html.table"))
)
)
server <- function(input, output) {
filtered_cars <- data.frame(matrix("NA", nrow = 1, ncol = 4, dimnames = list("NA", c("mpg","cyl","disp","hp"))))
source("server_html_table.R", local = TRUE)
output$cars <- renderUI({
selectizeInput(
inputId = "cars",
label = NULL,
choices = rownames(mtcars),
options = list(placeholder = 'Cars')
)
})
output$html.table <- renderUI({
html.table
})
observeEvent(input$cars, {
filtered_cars <- subset(mtcars, rownames(mtcars) %in% input$cars)
#some kind of update for the html table missing
})
}
# Run the application
shinyApp(ui = ui, server = server)
server_html_table.R
html.table <- tags$table(style = "border: 1px solid black; padding: 1%; width: 100%;",
tags$tr(
tags$th("Car Name"),
tags$th("MPG"),
tags$th("CYL"),
tags$th("DISP"),
tags$th("HP")
),
tags$tr(
tags$td(rownames(filtered_cars)),
tags$td(filtered_cars$mpg),
tags$td(filtered_cars$cyl),
tags$td(filtered_cars$disp),
tags$td(filtered_cars$hp)
)
)
As you can see, the table cells do not update. I am aware that there is some kind of update function missing in the observeEvent (like updateSelectizeInput()), but I cannot figure out a way to code it on my own.
I am grateful for any form of ideas or tips!
EDIT #1: Maybe to make the point about the HTML table clearer - I am displaying a Profit and Loss table in my app which needs to be build manually via HTML. Hence, I cannot use the usual dataTableOutput() and renderDataTable() functions. As the table relies heavily on CSS, the usage of basic HTML is much easier than the htmlTable package.
I found a solution to my problem!
The static html table is wraped in a function, which will be sourced once on startup in the server part of the app and then called in the renderUI() function. The render-function will be triggered every time a user changes the menu. Here I filter the dataframe regarding to the input and pass it to the "build_table" function. Each cell of the table is then populated with the needed values from the dataframe via indexes. The function return the full html table back to the renderUI().
This is the toy example from above, adjusted to the working solution:
app.R
library(shiny)
ui <- fluidPage(
fluidRow(
column(width = 6, uiOutput("cars"))
),
fluidRow(
column(width = 6, htmlOutput("html.table"))
)
)
server <- function(input, output) {
source("server_html_table.R", local = TRUE)
output$cars <- renderUI({
selectizeInput(
inputId = "cars",
label = NULL,
choices = rownames(mtcars),
options = list(placeholder = 'Cars')
)
})
output$html.table <- renderUI({
input$cars
isolate({
filtered_cars <- subset(mtcars, rownames(mtcars) %in% input$cars)
build_table(filtered_cars)
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
server_html_table.R
build_table <- function(data){
html.table <- tags$table(style = "border: 1px solid black; padding: 1%; width: 100%;",
tags$tr(
tags$th("Car Name"),
tags$th("MPG"),
tags$th("CYL"),
tags$th("DISP"),
tags$th("HP")
),
tags$tr(
tags$td(rownames(data)),
tags$td(data$mpg),
tags$td(data$cyl),
tags$td(data$disp),
tags$td(data$hp)
)
)
return(html.table)
}
I need to only display a BS modal when a button is pressed and and a condition on a variable is met.
This is a simple app that demonstrates what the challenge is. I need to display a BS modal when num_rows >= 500, and the submit button is fired, not just when the submit button is fired.
I am aware this could be done with a conditionalPanel using input.slider as one of the conditions, but in my real project it is much more complicated than this, and the BS modal/conditional panel needs to depend on both a button (user input) and a variable assigned in the server.
library(shiny)
library(shinyBS)
data = matrix(rnorm(1000*10, 0, 1), nrow = 1000)
ui <- fluidPage(
fluidRow(
column(width = 4,
sliderInput("slider", "Choose Number of Rows to Display", 0, 1000, value = NULL),
submitButton('Submit'),
bsModal("modalExample", "Yes/No", "submit", size = "small", wellPanel(
p(div(HTML("<strong>Warning: </strong> you have chosen to display a large
number of rows. Are you sure you want to proceed?"))),
actionButton("no_button", "Yes"),
actionButton("yes_button", "No")
))
),
column(width = 8,
tableOutput('data')
)
)
)
server <- shinyServer(function(input, output, server){
observe({
num_rows <- input$slider
if(num_rows >= 500){
#
# ACTIVATE MODAL PANEL
#
observeEvent(input$no_button, {
# Do not show table
})
observeEvent(input$yes_button, {
output$table <- renderTable(data)
})
} else{ # Display table normally if number of rows is less than 500
output$table <- renderTable(data)
}
})
})
shinyApp(ui, server)
Have a look at the following code. I disabled the action button if num_rows<500 with the package shinyjs. If num_rows>=500 the action button becomes available to trigger the popup. To update the number of rows selected with the slider you'll have to press the submit button every time. Hope this helps or gets you some ideas. For now I have not implemented your warning message (that did not work for me). Another issue: the slider and display for the pop up only work towards increasing number of rows, not decreasing afterwards. If you find a solution for that, pls share =)
library(shiny)
library(shinyBS)
library(shinyjs)
data = matrix(rnorm(1000*10, 0, 1), nrow = 1000)
data1=data[(1:500),]
head(data)
ui <- fluidPage(
fluidRow(
column(width = 4,
sliderInput("slider", "Choose Number of Rows to Display", 0, 1000, value = NULL),
submitButton('Submit'),
actionButton('Show','Show'),
useShinyjs(),
bsModal("modalExample",'Yes/No','Show', size = "large",tableOutput("tab")
# wellPanel(
# p(div(HTML("<strong>Warning: </strong> you have chosen to display a large
# number of rows. Are you sure you want to proceed?")
# )))
)),
column(width = 8,tableOutput('table'))))
server <- function(input, output)({
observe({
num_rows = input$slider
if(num_rows<500 &num_rows!=0) {
shinyjs::disable('Show')
output$table <- renderTable({
data = data1[(1:num_rows),]
print(head(data1))
data})
}else{
shinyjs::enable('Show')
output$tab = renderTable({
data = data[(1:num_rows),]
data}) }
})
})
shinyApp(ui, server)