knitr::opts_chunk$set( collapse = TRUE, comment = "ws#>", fig.align = "center" ) # directory to Lab dirdl <- system.file("shiny",package = "Intro2R") # create rmd link library(Intro2R)
The shiny server allows the data scientist to take an otherwise static application and make it dynamic. This is done through the use of a concept called "reactivity".
In this document we will begin from the basic structure of the server application and then introduce the detail of the parts which make the programming easy.
This is "magic" with some plans and rules.
A very good resource can be found here: https://mastering-shiny.org/index.html
The above book has a lot of detail which we can use and implement. In order to jump start the process of learning we will ask and answer a question.
How can we make a shiny table for any data set? We will attempt to answer this question and then after viewing our solution ask more questions answer these and repeat. In so doing we will come to grasp the essentials of "reactive programming".
The basic idea is to carry out the following:
Input data
Input header TRUE/FALSE
Tabulate data as output
The way shiny does this is by using various widgets. The first widget we will use is the shiny function fileInput()
fileInput( inputId, label, multiple = FALSE, accept = NULL, width = NULL, buttonLabel = "Browse...", placeholder = "No file selected" )
The function has 7 options, the first is:
inputId
this is needed for every widget and enables us to find the particular widget and the user values set. Best to use an id whose name points us to the particular function of the widget.
label
is the label that will be placed with the widget which the user will read and understand what he/she needs to input.
multiple
is by default FALSE
and is used to indicate whether multiple files should be read in at once.
accept
is a character vector of file extensions to help the browser show only those types to choose from.
The rest we will leave for now but you can read more from the documentation.
Notice that the widget does not actually read in the data, the widget will provide the name of the file - is this all the information required to read in the data? No! We need to know whether the file has a header or not.
To get this information we need another widget, checkboxInput().
checkboxGroupInput( inputId, label, choices = NULL, selected = NULL, inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL )
As with the previous widget there is the input id:
inputId
an id that uniquely identifies this widget
label
a label that will be intelligible to the user.
choices
The choices that the user can select
selected
What is the default choice
The rest are easy and we will leave them for now. In our case we need to ask the user to select whether the data have a header or not. We will assign one choice "TRUE" and then leave selected to "NULL".
A possible user input for a shiny app would go like the following:
ui <- fluidPage( sidebarLayout( sidebarPanel( fileInput("fileName", "Choose a CSV File", accept = ".csv"), checkboxInput("header", "Has a header!", TRUE) ), mainPanel( tableOutput("fileContents") ) ) )
Notice that a lot of the above code is simply setting up formatting for the html page that will hold the various parts:
A place on the left for the side panel which will hold the two widgets and
A place in the middle called "mainPanel" for the output table using the function tableOutput().
The ui functions to place input and output on the html page and also to supply the server with information concerning the file (name, header = TRUE/FALSE)
The picture below shows you some of what the above UI does.
Now that we have made the ui functional part of the app we shall go to the second functional part called server.
In all shiny apps there are two lists that are automatically made by the shiny package:
input
output
To get the value selected by the user when using the widgets the technique is to use the input$id,
list and id name.
The server function uses two inputs which are the two lists named above. To populate the output area defined in the ui we simply fill the output list appropriately. We may have defined many different places for output in which case we would populate the output
list using the appropriate ids. In our case we have one area of output and it is a table.
We therefore use output$fileContents <- renderTable()
Notice that the function is "renderTable". All shiny server functions that populate the output will need to be "render" functions.
The server function is given below:
server <- function(input, output) { output$fileContents <- renderTable({ newfile <- input$fileName ext <- tools::file_ext(newfile$datapath) req(newfile) validate(need(ext == "csv", "Please upload a csv file")) read.csv(newfile$datapath, header = input$header) }) }
Note that the read.csv()
is called last after req()
and validate()
functions are invoked.
Why?
We need to make sure that a file exists - when the app is first started there will not be a file selected by the user. The renderTable
will then be incapable of reading a file because there will be no valid input$fileName
to use in the read.csv()
function. What to do? Well we should simply not execute the render function until there is a file selected. This is what req()
does.
Secondly we need to validate that a csv file was chosen -- if the file extension is not a csv then give message "Please upload a csv file" and stop the function.
We could make do with only the validate()
function, however the req()
is good practice.
The following shows how the app will run after a wrong file is selected:
The above picture shows the message but read.csv is not called on the wrong file due to validate().
If both the req() and validate() are removed this is what you would see when the app is run:
The header is determined by the input$header
component of the input list.
Notice that the app placed in the r chunk below cannot be run when the RMD is knitted so we use the condition on interactive()
- when knit, the R chunk will give a FALSE
interactive
result. Hence the app will not be run. You can run it by selecting the app in the RMD document.
## Only run examples in interactive R sessions interactive() if (interactive()) { ui <- fluidPage( sidebarLayout( sidebarPanel( fileInput("fileName", "Choose a CSV File", accept = ".csv"), checkboxInput("header", "Has a header", TRUE) ), mainPanel( tableOutput("fileContents") ) ) ) server <- function(input, output) { output$fileContents <- renderTable({ newfile <- input$fileName ext <- tools::file_ext(newfile$datapath) req(newfile) validate(need(ext == "csv", "Please upload a csv file")) read.csv(newfile$datapath, header = input$header) }) } shinyApp(ui, server) }
The following is the output when data file is selected:
Can we make an app that will read in data and allow the user to make selection of variables to create a suitable plot?
To answer this question we can modify the above code. There seems to be a problem though. How can we make a widget to identify the file to read and then have a widget that selects variables when that can only happen if the file is read. That is the second widget must display variables which depends on the existence of a file in the workspace.
library(ggplot2) ui <- fluidPage( titlePanel("Plotting data"), sidebarLayout( sidebarPanel( fileInput("file", "Select csv file:", accept = ".csv"), checkboxInput("header", "The file has a header!", TRUE), br(), varSelectInput("var1", "Variable 1:", data()), varSelectInput("var2", "Variable 2:", data()), checkboxInput("scatter", "Scatter plot", TRUE) ), mainPanel( plotOutput("plot1") ) ) ) server <- function(input, output, session) { # read input file and update choices for variable selection widget data <- reactive({ req(input$file) read.csv(input$file$datapath, header = input$header) }) # create first plot given data and selected variables output$plot1 <- renderPlot({ req(input$file, input$var1, input$var2) # store selected variables in df df <- data()[,c(input$var1, input$var2)] # create ggplot g <- ggplot(df, aes(x = df[,1], y = df[,2])) + labs(title = paste("Plot of", input$var1, "vs.", input$var2), x = input$var1, y = input$var2) # add regression line and/or marginal plots given user input if (input$scatter) { g <- g + geom_smooth(method = "lm") } else { g <- g + geom_boxplot() } print(g) }) } # Run the application shinyApp(ui = ui, server = server)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.