Creating R and Python webapps for user-friendly research

Workshops on Efficient Research Computing - WERC02

Waldir Leoncio Netto

Oslo Centre for Biostatistics and Epidemiology

2024-12-13

Rules of Engagement

  1. Slides available on GitHub (ocbe-uio/public-slides)
  2. Content is licensed under CC BY-SA
  3. Interruptions are welcome
  4. This is technically a workshop during lunchtime, so feel free to:
    • Eat
    • Code
    • Eat and code (good luck with that!)
  5. Examples are given in R, but Shiny for Python is a thing, I swear!

Motivation

Consider this basic R script

Say you have something to show an advisee.

# loading data
data <- mtcars

# basic analysis
summary(data)
plot(data)

# regression model
with(data, plot(hp, mpg))
fit <- lm(mpg ~ hp, data)
abline(fit, col = 4)

Problem

This is a fixed script which requires programming knowledge to alter.
Also, one needs R (+ dependencies! 💀) to run this.

Solution

A graphical interface full of buttons that runs on any web browser.

Creating a static app of your script

Minimal elements

library(shiny)

server <- function(input, output) {
  output$smry <- renderPrint(summary(mtcars))
}

ui <- fluidPage(
  verbatimTextOutput("smry")
)

shinyApp(ui, server)

See more Shiny components here

Adding more elements

library(shiny)

server <- function(input, output) {
  output$smry <- renderPrint(summary(mtcars))
  output$scatter <- renderPlot(plot(mtcars))
  output$reg <- renderPlot(
    expr = {
      with(mtcars, plot(hp, mpg))
      fit <- lm(mpg ~ hp, mtcars)
      abline(fit, col = 4)
    }
  )
}

ui <- fluidPage(
  verbatimTextOutput("smry"),
  plotOutput("scatter"),
  plotOutput("reg")
)

shinyApp(ui, server)

Adding text

library(shiny)

server <- function(input, output) {
  output$smry <- renderPrint(summary(mtcars))
  output$scatter <- renderPlot(plot(mtcars))
  output$reg <- renderPlot(
    expr = {
      with(mtcars, plot(hp, mpg))
      fit <- lm(mpg ~ hp, mtcars)
      abline(fit, col = 4)
    }
  )
}

ui <- fluidPage(
  titlePanel("Analysis of mtcars"),
  h1("Summary"),
  verbatimTextOutput("smry"),
  h1("Scatterplots"),
  "Two-by-two plots of all variables.",
  plotOutput("scatter"),
  h1("Regression results"),
  plotOutput("reg")
)

shinyApp(ui, server)

Making your app dynamic

Adding sidebars with some (unworking) buttons

library(shiny)

server <- function(input, output) {
  output$smry <- renderPrint(summary(mtcars))
  output$scatter <- renderPlot(plot(mtcars))
  output$reg <- renderPlot(
    expr = {
      with(mtcars, plot(mpg, hp))
      fit <- lm(hp ~ mpg, mtcars)
      abline(fit, col = 4)
    }
  )
}

ui <- fluidPage(
  titlePanel("Analysis of mtcars"),
  sidebarLayout(
    sidebarPanel(
      h1("Settings"),
      h2("Summary"),
      radioButtons("summaryFun", "Summary function", list("summary", "str", "names")),
      h2("Scatterplots"),
      checkboxInput("removeDiscrete", "Remove discrete variables"),
      h2("Regression"),
      selectInput("x", "x-variable", as.list(names(mtcars))),
      sliderInput("y-lims", "y-limits", 0, 500, 400),
      sliderInput("x-lims", "x-limits", 0, 50, c(10, 40)),
      actionButton("randomColor", "Randomize regression color")
    ),
    mainPanel(
      h1("Summary"),
      verbatimTextOutput("smry"),
      h1("Scatterplots"),
      "Two-by-two plots of all variables.",
      plotOutput("scatter"),
      h1("Regression results"),
      plotOutput("reg")
    )
  )
)

shinyApp(ui, server)

Connecting UI buttons

library(shiny)

server <- function(input, output) {
  output$smry <- renderPrint(
    expr = {
      sumfun <- switch(input$summaryFun,
        summary = summary,
        str = str,
        names = names
      )
      sumfun(mtcars)
    }
  )
  output$scatter <- renderPlot(
    expr = {
      if (input$removeDiscrete) {
        nonBinary <- c("mpg", "disp", "hp", "drat", "wt", "qsec")
        plot(mtcars[nonBinary])
        output$howManyVars <- renderText("continuous")
      } else {
        plot(mtcars)
        output$howManyVars <- renderText("all")
      }
    }
  )
  output$reg <- renderPlot(
    expr = {
      xvar <- mtcars[, input$x]
      plot(xvar, mtcars$hp, ylim = c(0, input$ylims), xlim = input$xlims)
      fit <- lm(mtcars$hp ~ xvar, mtcars)
      abline(fit, col = rv$regcol)
    }
  )
  rv <- reactiveValues(regcol = 4)
  observeEvent(input$randomColor, rv$regcol <- sample(1:10, size = 1))
}

ui <- fluidPage(
  titlePanel("Analysis of mtcars"),
  sidebarLayout(
    sidebarPanel(
      h1("Settings"),
      h2("Summary"),
      radioButtons("summaryFun", "Summary function", list("summary", "str", "names")),
      h2("Scatterplots"),
      checkboxInput("removeDiscrete", "Remove discrete variables"),
      h2("Regression"),
      selectInput("x", "x-variable", as.list(names(mtcars))),
      sliderInput("ylims", "y-limits", 0, 500, 400),
      sliderInput("xlims", "x-limits", 0, 50, c(10, 40)),
      actionButton("randomColor", "Randomize regression color")
    ),
    mainPanel(
      h1("Summary"),
      verbatimTextOutput("smry"),
      h1("Scatterplots"),
      "Two-by-two plots of ", textOutput("howManyVars", inline = TRUE), " variables.",
      plotOutput("scatter"),
      h1("Regression results"),
      plotOutput("reg")
    )
  )
)

shinyApp(ui, server)

Notice how rv$regcol was defined after it’s used in abline().

Sharing your app

Using rsconnect to publish on Shinyapps.io

library(rsconnect)
rsconnect::deployApp(
1    appFiles = "app.R",
2    account  = "ocbe",
    appName  = "WERC"
)
1
deployApp() is very finicky about file names (not well documented!)
2
Write me about access to this account (or create your own)

App will be available on https://ocbe.shinyapps.io/WERC/

Extras

Tips

  1. Remember your app script doesn’t run sequentially (like a script): everything is processed at once
    • Learn about reactive environments
    • Debug with reactlog
  2. Can’t afford a paid hosting account? Keep your app simple.
    • remember scalability: how many people are going to use your app simultaneously?
  3. Can’t keep it simple? Distribute it for local usage (clumsier but safer).
  4. In practice, you may want to split your code into two files
    • Frontend: ui.R
    • Backend: server.R

HTML widgets on Rmarkdown files