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.

ui <- fluidPage(
  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:

choices = list("Sample 1" = "sample1", 
               "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.

ui <- fluidPage(
  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.

ui <- fluidPage(
  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.

server <- function(input, output) {
  
  output$check_x_select <- renderText({
    print(input$x_select)
  })
  
  output$check_y_select <- renderText({
    print(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)
ui <- fluidPage(
  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")
    )
  )
)

server <- function(input, output) {
  
}

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.

server <- function(input, output) {
  
  output$my_plot <- renderPlot({
    
  })
}

Now let’s give the render function some data to display, the first two columns from the csv shiny_sample_data.csv.

server <- function(input, output) {
  
  output$my_plot <- renderPlot({
    
    #Read in the sample csv
    dat <- read.csv("data-files/shiny_sample_data.csv", row.names = 1)
    
    #Pull out the first and second columns in dat
    x_axis <- dat[, "sample1"]
    y_axis <- dat[, "sample2"]
    
    #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)
  )
)

output$plot1 <- renderPlot({
    dat <- read.csv("data-files/shiny_sample_data.csv", row.names = 1)
    
    #Pull out the first and second columns in dat
    x_axis <- dat[, "sample1"]
    y_axis <- dat[, "sample2"]
    
    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.

server <- function(input, output) {
  
  output$my_plot <- renderPlot({
    
    #Read in the sample csv
    dat <- read.csv("data-files/shiny_sample_data.csv", row.names = 1)
    
    #Pull out a column for the x and y axis each
    x_axis <- dat[, input$x_select]
    y_axis <- dat[, input$y_select]
    
    #Create a basic dot plot
    plot(x = x_axis, y_axis)
  })
}

The complete working app code looks like:

library(shiny)
library(utils)


ui <- fluidPage(
    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)
        )
    )
)

server <- function(input, output) {
    output$check_x_select <- renderText({
        print(input$x_select)
    })
    
    output$check_y_select <- renderText({
        print(input$y_select)
    })
    
    output$my_plot <- renderPlot({
        
        #Read in the sample csv
        dat <- read.csv("data-files/shiny_sample_data.csv", row.names = 1)
        
        #Pull out a column for the x and y axis each
        x_axis <- dat[, input$x_select]
        y_axis <- dat[, input$y_select]
        
        
        #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
  1. The first step is to sort out the ui.

  2. 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

  1. Create a new output called a tableOutput to print the data in tabular format.

Hint: The code for it looks like: tableOutput(outputId = “table1”)

  1. Define the server function.

  2. 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. The head function is ideal for this. If you aren’t familiar with how head works, check the help manual for head 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