Chapter 5 Create Shiny App from scratch
We’ll build a Shiny App that takes a csv file containing data for 6 samples and the raw counts for 1000 genes. Each sample is a column and each gene is a row. One sample will be plotted on the x-axis and another sample on the y-axis. We’ll also want to be able to display a table of the selected columns but that will be for the challenge.
## sample1 sample2 sample3 sample4 sample5 sample6
## gene_1 89 4664 274 126 1267 143
## gene_2 568 3456 201 9 139 504
## gene_3 275 4307 449 187 7909 188
## gene_4 3913 5912 4082 4318 8892 3851
## gene_5 3382 155 2897 2626 2069 3456
## gene_6 4167 5194 4404 4757 4765 4156
## gene_7 4831 227 5009 4554 479 4713
## gene_8 2532 3395 3000 3441 4575 2460
## gene_9 3274 1658 3074 3189 2293 3277
## gene_10 3272 4169 3454 3192 7403 3344
Top ten rows of the raw data used to build the shiny app
5.1 Setting up a shiny app
Create one blank R script (Click File
> New File
> R Script
). Save it inside the demo
folder with the name app.R
. We’ve now seen how to create a blank app with the ui and the server. We should be able to make out the layout of the app now. The top of the window is where the title panel is located and beneath it are two panels, laid out with the sidebar to the left and the larger main panel to the right.
5.2 Using widgets
We are going to add two widgets to the ui. These will control which samples gets plotted to the x and y axis of the plot and will be located in the sidebar panel. We will use selectInput
to create these widgets but these could easily be swapped out for other widgets such radio-buttons. To see what widgets are available, look at the Shiny widget gallery or consult the Shiny cheatsheet.
To start off with, let’s add just one widget to the sidebar.
<- fluidPage(
ui titlePanel("My first app"),
sidebarLayout(
sidebarPanel("This is a sidebar panel",
selectInput(inputId = "x_select", label = "Select x-axis",
choices = list("sample1",
"sample2",
"sample3",
"sample4",
"sample5",
"sample6"),
selected = "sample1")
),mainPanel("And this is the main panel")
) )
Let’s break down the selectInput
function. inputId
is the name of widget, in this case x_select
is the name that’ll be used in the server object to access the current value of the widget. The labels
argument takes a string that will be used to label above the widget.
choices
takes a list of values and can be a bit confusing to understand. For simplicity’s sake, we have given an unnamed list, which will directly display the values themselves. "sample1"
happens to be a column name in the data the app will be using, so this widget can be used to select which column gets plotted. However, if the names of the choices needs to be different from the values, a named list can be used.
For example:
= list("Sample 1" = "sample1",
choices "Sample 2" = "sample2",
"Sample 3" = "sample3",
"Sample 4" = "sample4",
"Sample 5" = "sample5",
"Sample 6" = "sample6")
In "Sample 1" = "sample1"
the string "Sample 1"
is the text that will appear in the widget, whereas "sample1"
is the value that will be returned from the widget. Using this would make a much neater looking widget. Think of the name as purely cosmetic and what you’ll see while the value is what’s important and will be used behind the scenes for calculations. You could change the names to whatever you wanted without having on effect the app.
Finally, selected
chooses which value the widget will start on when the app is run.
Now to add the second widget. To show the difference between an unnamed and a named list, the second widget will use a named list for choices
.
<- fluidPage(
ui titlePanel("My first app"),
sidebarLayout(
sidebarPanel("This is a sidebar panel",
selectInput(inputId = "x_select", label = "Select x-axis",
choices = list("sample1",
"sample2",
"sample3",
"sample4",
"sample5",
"sample6"),
selected = "sample1"),
selectInput(inputId = "y_select", label = "Select y-axis",
choices = list("Sample 1" = "sample1",
"Sample 2" = "sample2",
"Sample 3" = "sample3",
"Sample 4" = "sample4",
"Sample 5" = "sample5",
"Sample 6" = "sample6"),
selected = "sample2")
),mainPanel("And this is the main panel")
) )
This widget is nearly identical to the first, all that has changed is the name of the widget, the use of a named list and which sample has been pre-selected. This widget has been called y_select
and it will start on the value sample2
so that the columns selected for the x and y axis will be different.
5.2.0.1 Adding some outputs
There’s one last thing to do before moving onto the server object. The app has inputs now, but it doesn’t have any outputs! For this app, we will use three outputs. Two are going to be purely diagnostic so we can check that our selectInputs
are working. They will not be included in our final app. The one output that will remain in our final app is plotOutput
, as we want to generate a plot and it will be located in the main panel. There are numerous types of outputs Shiny can generate - to find out what other outputs Shiny is capable of, consult the Shiny cheatsheet or the Shiny website.
<- fluidPage(
ui titlePanel("My first app"),
sidebarLayout(
sidebarPanel("This is a sidebar panel",
selectInput(inputId = "x_select", label = "Select x-axis",
choices = list("sample1",
"sample2",
"sample3",
"sample4",
"sample5",
"sample6"),
selected = "sample1"),
selectInput(inputId = "y_select", label = "Select y-axis",
choices = list("Sample 1" = "sample1",
"Sample 2" = "sample2",
"Sample 3" = "sample3",
"Sample 4" = "sample4",
"Sample 5" = "sample5",
"Sample 6" = "sample6"),
selected = "sample2")
),mainPanel("And this is the main panel",
textOutput(outputId = "check_x_select"),
textOutput(outputId = "check_y_select"),
plotOutput(outputId = "my_plot")
)
) )
my_plot
is the ID of the plotoutput
and it tells the server object file where to direct data. Same goes for the two textOutputs
. Run the app and see what you’ll get. Does it look any different?
At the moment, the app will display nothing because we haven’t told it do any calculations and generate a plot or display text. To do that, we need to move on to the server side.
5.3 Data processing on the server side
The app now has a full UI but nothing is happening on the backend. To make the app interactive, that’s where the server object comes into play. Think of it as the object that contains an R analysis. The structure of this object can vary immensely depending on the complexity of the app itself.
First, we need to check on our two selectInput
widgets, see if they are working correctly, before we move on to plotting. When building a Shiny app, it’s useful to have create outputs to check to see if parts of the app are doing what they should be, for both the ui and the server code. This step can be omitted, but we’ll go through it to demonstrate what the selectInputs
are doing.
<- function(input, output) {
server
$check_x_select <- renderText({
outputprint(input$x_select)
})
$check_y_select <- renderText({
outputprint(input$y_select)
}) }
Now run the app.
There are a number of render
functions in Shiny, renderTable
, renderText
, renderPrint
, etc, depending on what type of data needs to be output. If you want to check what render options exist, got the Shiny website reference page.
Whenever a render
function is called, it needs to be directed to an output that exists in the ui object. In the case of output$check_x_select
, we have pointed the renderText
function to check_x_select
, which was the id of a textOutput
in the UI. This means that the result of renderText
is sent to the check_x_select
output.
Inside the renderText
function, we print
the value of the x_select
. To access a value of a input widget, we use input$input_Id
. Once the app is running, every time the value of the input changes, a render function that depends on it will be re-run. So if we changed the value of the x_select
, only the first renderText
statement is re-calculated but the second one, which doesn’t depend on x_select
will not be re-calculated.
Notice that despite the difference between the two selectInputs
(one uses a named list and the other doesn’t), that the values returned are the same. Once again, using a named list changes the appearance of the ui but not the actual values of the widgets.
Now that we’ve seen that our widgets are working correctly, we can remove the diagnostic lines of code from both the ui and server. You can either comment them out or delete them. Your app should currently look like this:
library(shiny)
<- fluidPage(
ui titlePanel("My first app"),
sidebarLayout(
sidebarPanel("This is a sidebar panel",
selectInput(inputId = "x_select", label = "Select x-axis",
choices = list("sample1",
"sample2",
"sample3",
"sample4",
"sample5",
"sample6"),
selected = "sample1"),
selectInput(inputId = "y_select", label = "Select y-axis",
choices = list("Sample 1" = "sample1",
"Sample 2" = "sample2",
"Sample 3" = "sample3",
"Sample 4" = "sample4",
"Sample 5" = "sample5",
"Sample 6" = "sample6"),
selected = "sample2")
),mainPanel("And this is the main panel",
plotOutput(outputId = "my_plot")
)
)
)
<- function(input, output) {
server
}
shinyApp(ui = ui, server = server)
The final app will be rather simple. It will take one render
functions and send the output to the UI. To get going, let’s create an empty renderPlot
.
<- function(input, output) {
server
$my_plot <- renderPlot({
output
}) }
Now let’s give the render
function some data to display, the first two columns from the csv shiny_sample_data.csv
.
<- function(input, output) {
server
$my_plot <- renderPlot({
output
#Read in the sample csv
<- read.csv("data-files/shiny_sample_data.csv", row.names = 1)
dat
#Pull out the first and second columns in dat
<- dat[, "sample1"]
x_axis <- dat[, "sample2"]
y_axis
#Create a basic dot plot
plot(x = x_axis, y_axis)
}) }
The complete working code looks like:
titlePanel("My first app")
#Changed up the input and output IDs so that they don't conflict with the earlier app.
sidebarLayout(
sidebarPanel("App input control panel",
selectInput("x_select1", label = h3("Select x-axis"),
choices = list("sample1",
"sample2",
"sample3",
"sample4",
"sample5",
"sample6"),
selected = "sample1"),
selectInput(inputId = "y_select1", label = "Select y-axis",
choices = list("Sample 1" = "sample1",
"Sample 2" = "sample2",
"Sample 3" = "sample3",
"Sample 4" = "sample4",
"Sample 5" = "sample5",
"Sample 6" = "sample6"),
selected = "sample2")
),mainPanel("App output panel",
plotOutput("plot1", width = 600, height = 600)
)
)
$plot1 <- renderPlot({
output<- read.csv("data-files/shiny_sample_data.csv", row.names = 1)
dat
#Pull out the first and second columns in dat
<- dat[, "sample1"]
x_axis <- dat[, "sample2"]
y_axis
plot(x = x_axis, y = y_axis)
})
The app has a plot now…but it isn’t interactive, if we change the widget values, it has no effect on what is plotted on the figure. That’s because the first two columns were selected directly in the renderPlot
function. However, we could change to call the widget values instead using input
.
<- function(input, output) {
server
$my_plot <- renderPlot({
output
#Read in the sample csv
<- read.csv("data-files/shiny_sample_data.csv", row.names = 1)
dat
#Pull out a column for the x and y axis each
<- dat[, input$x_select]
x_axis <- dat[, input$y_select]
y_axis
#Create a basic dot plot
plot(x = x_axis, y_axis)
}) }
The complete working app code looks like:
library(shiny)
library(utils)
<- fluidPage(
ui titlePanel("My first app"),
sidebarLayout(
sidebarPanel("This is a sidebar panel",
selectInput(inputId = "x_select", label = "Select x-axis",
choices = list("sample1",
"sample2",
"sample3",
"sample4",
"sample5",
"sample6"),
selected = "sample1"),
selectInput(inputId = "y_select", label = "Select y-axis",
choices = list("Sample 1" = "sample1",
"Sample 2" = "sample2",
"Sample 3" = "sample3",
"Sample 4" = "sample4",
"Sample 5" = "sample5",
"Sample 6" = "sample6"),
selected = "sample2")
),mainPanel("And this is the main panel",
textOutput(outputId = "check_x_select"),
textOutput(outputId = "check_y_select"),
plotOutput(outputId = "my_plot", width = 600, height = 600)
)
)
)
<- function(input, output) {
server $check_x_select <- renderText({
outputprint(input$x_select)
})
$check_y_select <- renderText({
outputprint(input$y_select)
})
$my_plot <- renderPlot({
output
#Read in the sample csv
<- read.csv("data-files/shiny_sample_data.csv", row.names = 1)
dat
#Pull out a column for the x and y axis each
<- dat[, input$x_select]
x_axis <- dat[, input$y_select]
y_axis
#Create a basic dot plot
plot(x = x_axis, y_axis)
})
}
shinyApp(ui = ui, server = server)
Activity 4: Modily x- and y-axes labels
Can you figure out how to change the axis titles to the name of the selected sample. One of selectInput
widgets also need to be changed to a named list. Other than that, you’re good to go.
Hint:
plot(x = blah, y = plah, xlab = “Label for the x-axis”, ylab = “Label for the y-axis”)
Now imagine you had an experiment where you had many conditions and you needed to plot each one against each other. Instead of plotting each one individually, with a Shiny app, you could easily visualise the different conditions without having to write code for each one of them and then download them.
Challenge 6: Modify the app (breakout).
Our collaborator saw the app we have created to show the relationship between two samples. They want to view the expression of individual genes in the samples in a tabular format. Modify your app to display the tabular data in the final output.
library(ggplot2)
# 1. Add ui
titlePanel("My slightly more complicated app")
sidebarLayout(
sidebarPanel("App input control panel",
selectInput("x_select3", label = h3("Select x-axis"),
choices = list("sample1",
"sample2",
"sample3",
"sample4",
"sample5",
"sample6"),
selected = "sample1"),
selectInput("y_select3", label = h3("Select y-axis"),
choices = list("sample1",
"sample2",
"sample3",
"sample4",
"sample5",
"sample6"),
selected = "sample2")
# 2. Add sliderInput
),mainPanel("App output panel",
plotOutput("plot3", width = 600, height = 600)
# 3. Add tableOutput
)
)
# 4. Add server logic
# 5. Redirect output to a variable to be called in the mainPanel
The first step is to sort out the ui.
We want to print the number of rows in the selected data-set as decided by a slider. Create a slider input widget.
Hint: The code for a slider looks something like this: sliderInput(“inputIDyouwant”, label = “Slider”, min = , max = , value = 50)
To find out more about how a slider works check the Shiny widget gallery
- Create a new output called a
tableOutput
to print the data in tabular format.
Hint: The code for it looks like: tableOutput(outputId = “table1”)
Define the server function.
You’ll need to direct the result of a
renderTable
function to the new output.
Hint: The table that is displayed is a subset of the full csv. It shows only the two currently selected columns. Remember that a dataframe can be subsetted to show only certain columns using
x[, c(column_a, column_c, column_z)]
Inside the
renderTable
call, you’ll need a function that shows you the top of a table. Thehead
function is ideal for this. If you aren’t familiar with how head works, check the help manual forhead
with?head
to see the arguements it takes.
An important thing to note is that only one thing is returned from inside a render function (usually the last line inside that function). Therefore a variable, such as dat
created inside the renderPlot
will not exist outside that renderPlot
function. You’ll need to read the csv of data back in again in the renderTable
function. For an ever further challenge, create a reactive function and pass the reactive variable to the two render
functions. Read the csv file only once in the application