R code

library("tidyverse"); theme_set(theme_bw())
library("shiny")

Shiny applications are interactive websites dashdboards built using R and produced using an R Server backend. They are much more flexible in what you can do compared to using packages that allow for interactive tables and figures. Due to the backend requirement, they are not as easy to deploy as the approaches we discussed as the interactive tables and figures. My guess is that shiny 2.0 will utilize javascript and avoid the R server backend, but we’ll have to wait and see.

The backend requirement can be avoided by utilizing Posit’s shinyapps.io. The downside to using this deployment platform is that you are limited in the amount of monthly use on your app.

Take a look at some shiny apps on the Shiny Gallery to get an idea of what shiny can do.

Explore

Let’s explore this Radiant app by Vincent Nijs.

User-interface

Input

The user can control the app through a set of control widgets. Here is a demonstration of many of those widgets.

ui <- fluidPage(
  titlePanel("User input options"),
  
  column(width = 3, 
    actionButton("action",
                 label = "actionButton"),
    
    fileInput("file", 
              label = "File input"),
    
    textInput("text", 
              label = "Text")
  ), 
  column(width = 3,
    checkboxInput("checkbox",
                  label = "Checkbox"),
    
    checkboxGroupInput("checkboxGroup",
                       label = "Checkbox Group", 
                       choices = c("A","B","C")),
    
    radioButtons("radioButtons",
                 label = "Radio Buttons", 
                 choices = c("A","B","C")),
    
    selectInput("select", 
                label = "Select", 
                choices = c("A","B","C")),
    
    selectInput("select", 
                label = "Select", 
                choices = c("A","B","C"),
                multiple = TRUE)
  ),
  column(width = 3,
    numericInput("numeric",
                 label = "Numeric", value = 0),
    
    sliderInput("slider", 
                label = "Slider", 
                min = 0, max = 10, value = 5, step = 1),
    
    sliderInput("slider", 
                label = "Slider", 
                min = 0, max = 10, value = c(3,7), step = 1)
  ),
  column(width = 3,
         dateInput("date",
                   label = "Date"),
         
         dateRangeInput("daterange", 
                        label = "Date range"),
         
         helpText(h3("Help text"),
                  "Here is some help text.")
         )
)

shinyApp(ui = ui, server = function(input, output) {} )

Output

Text

ui <- fluidPage(
  titlePanel("Here is text output!"),
  
  textOutput("text")
)

server <- function(input, output) {
  output$text <- renderText({
    "Here's some text"
  })
}

shinyApp(ui = ui, server = server)

Table

ui <- fluidPage(
  titlePanel("Here is table output!"),
  
  tableOutput('table')
)

server <- function(input, output) {
  output$table <- renderTable(warpbreaks)
}

shinyApp(ui = ui, server = server)

DataTable

ui <- fluidPage(
  titlePanel("Here is datatable output!"),
  
  dataTableOutput('table')
)

server <- function(input, output) {
  output$table <- renderDataTable(warpbreaks)
}

shinyApp(ui = ui, server = server )

Plot

ui <- fluidPage(
  titlePanel("Here is plot output!"),
  
  plotOutput("plot")
)

server <- function(input, output) {
  output$plot <- renderPlot({
    hist(rnorm(100))
  })
}

shinyApp(ui = ui, server = server)

Print

ui <- fluidPage(
  titlePanel("Here is print output!"),
  
  verbatimTextOutput("print")
)

server <- function(input, output) {
  output$print <- renderPrint({
    m <- lm(breaks ~ tension + wool, data = warpbreaks)
    summary(m)
  })
}

shinyApp(ui = ui, server = server)

Server

In the output examples above, you have seen that the output table, figure, text, etc. are constructed in the server function, rendered, and then shown to the user using an *Output function in the ui. In that *Output function, we used a text string that corresponded to the list element of the output object. Thus, we had a connection from the server function to the ui through the output object.

Here we will see how to create a connection from the ui to the server using the input object and the inputIds in control widgets.

Simple example

ui <- fluidPage(
  titlePanel("2-D Histogram with Hexes!"),
  
  sidebarLayout(
    sidebarPanel(
      checkboxInput(inputId = "logx", label = "Log Carat"),
      checkboxInput(inputId = "logy", label = "Log Price"),
      
      textInput(inputId = "low",  label = "Low color",  value = "gray"),
      textInput(inputId = "high", label = "High color", value = "blue"),
    ),
    mainPanel(
      plotOutput(outputId = "hexbinPlot")
    )
  )
)
server <- function(input, output) {
  output$hexbinPlot <- renderPlot({
    g <- ggplot(diamonds, aes(x = carat, y = price)) + 
      geom_hex() +
      theme_bw() + 
      labs(
        title = "Diamonds: Price vs Carat",
        x     = "Carat",
        y     = "Price ($)"
      ) +
      scale_fill_gradient(
        low  = input$low,
        high = input$high
      ) 
    
    if (input$logx) 
      g <- g + scale_x_log10()    
    
    if (input$logy) 
      g <- g + scale_y_log10()
    
    g
  })
}
shinyApp(ui = ui, server = server)

pvalue interpretation

Here is an app I created to try to understand the frequency interpretation of a particular p-value. The idea is to understand how often the null hypothesis is true when we observe a particular p-value, say 0.05.

To construct the app, we consider the simple example of \[ Y \sim N(\mu,1). \] with null hypothesis \[ H_0: \mu = 0. \]

The frequency of the null hypothesis being true will depend on

  1. How often the null hypothesis is true before you ran the experiment.
  2. What values for \(\mu\) are possible when the null hypothesis is not true.

To address the first question, the user will choose the proportion of times the null hypothesis is true among experiments run by a scientist. Various possibilities are reasonable and will depend on the scientific field the user is trying to emulate. On one hand, scientists are typically running experiments because they think the null is not true and thus, probably, the null is true in only a small fraction of experiments. On the other hand, scientists who are running high-throughput experiments, e.g. microarray or remote sensing, which are like looking for a needle in a haystack. In these situations, the null is true in a high fraction of experiments.

Once the user determines the proportion of null hypotheses that are true among the scientist’s experiments, the user needs to determine what \(\mu\) is when the null hypothesis is not true. Since we will end up having many experiments where the null is not true, we generally sample \(\mu\) from some distribution. A distribution where values for \(\mu\) are far from zero (relative to the data variance of 1) indicates large effects. In contrast, if the distribution is near zero, then this indicates small effects.

The app the simulates scientists experiments by randomly determining whether the null or alternative are true. When the null is true, then \(\mu\) is set to 0 while when the alternative is true, it samples a new value for \(\mu\). Then it samples an observation \(Y\) according to the value for \(\mu\). Using this value for \(Y\), the app will compute a p-value. If the p-value is within a small window around the chosen p-value, the app will record whether or not the null hypothesis is true.

The app will repeat this process until the desired number of p-values within the small window are observed. It will then report how many times the null (and alternative) were true.

shiny::runGitHub('pvalue','jarad')

The code for this app can be seen at https://github.com/jarad/pvalue.