Reproducible data science with webR and Shinylive

George Stagg

Posit, PBC

The Open Science movement

A fundamental principle of the scientific method is peer preview and verification.

  • Researchers increasingly publishing data, infrastructure, and software.

  • In some areas, becoming an institutional or regulatory requirement.

  • Taking lessons and inspiration from free and open source software.

  • Reproducible software allows the same conclusions to be equally available to all researchers.

Pillars of the Open Science. From: Understanding Open Science, UNESCO, 2022

Reproducibility in practice

A large-scale study on research code quality and execution

We find that 74% of R files failed to complete without error in the initial execution.

What do we mean by β€œreproducible” software?

  1. The ability for anyone to easily re-run and verify the result of some computational procedure.
  1. The ability to modify or extend the procedure (software and/or data) to gain new insights.


There is some level of subjectivity here: β€œeasily”


There are levels of reproducibility, some are easier than others.

The first level of reproducibility

Recognise there is no guarantee your script will run successfully anywhere else.

It works on my machine Β―\(ツ)/Β―


Some simple things to watch out for:

  • Hard-coded paths, setwd(), project organisation.

  • Ensure all source files and datasets can be loaded.

  • Workflow management, order of execution.

  • Avoidable programming errors.

A small effort here, even using automated tools, makes a big difference.

56% failed when code cleaning was applied, showing that many errors can be prevented with good coding practices.

At the language level

  • Source code management, git, GitHub.

  • Defensive programming, handling error conditions.

  • Organising software into modules or packages.

  • Lifecycle management, avoid deprecated functionality.

  • Documentation and tests.

Hex logos: pkgdown, testthat, usethis, rstudio, quarto, roxygen2, shinytest2

No magic bullet: Good software engineering leads to lasting projects.

Research Software Engineering: https://society-rse.org, https://us-rse.org

Computing environments

Many different computational environments exist, all with the potential to affect your analysis.

Language and package management

  • Versions of interpreter software:
    • R 3.6.3, 4.1.3
    • Python 3.8, 3.12
    • Node 16, 20
  • Versions of packages

Tools: rig, pyenv, p3m, renv, venv.

System and library management

  • System libraries: GSL, NLopt, BLAS/LAPACK
  • Operating system: Windows, macOS, Linux

Tools: Virtual Machines, Docker, Nix.


  • Very slow to reproduce environment in full, especially without caching!
  • Difficult to use, very steep learning curve.

Binary-level differences

Software source code is compiled into a machine-language binary. But the same software can give different binaries depending on the type of hardware (ARM vs x86_64 vs RISC)

solve(matrix(c(  1,   3,   11,   0,   -11,  -15, 18,  55,  209,   15, -198, -277,-23, -33,  144,  532,  259,   82,  9,  55,  405,  437, -100, -285,  3,  -4, -111, -180,   39,  219,-13,  -9,  202,  346,  401,  253), nrow=6, byrow = TRUE))[[1]]

options(digits = 22)
(0.1 + 0.2) + 0.3
[1] 0.6000000000000000888178
options(digits = 22)
0.1 + (0.2 + 0.3)
[1] 0.5999999999999999777955

WebAssembly

  • A portable binary code format
  • Enables high-performance applications on web pages
  • Near-native execution speed
  • Supported by most modern browsers
  • Interactive through JavaScript integration

Also provides benefits for security in the form of containerisation and sandboxing.

R for WebAssembly: webR

The webR logo

The webR project is a version of the R interpreter built for WebAssembly.

Execute R code directly in a web browser, without a supporting R server. Alternatively, run an R process server-side using Node.js


Available on GitHub and NPM as a JavaScript & TypeScript library.

The webR Application πŸ”— https://webr.r-wasm.org/v0.3.2/

Live and interactive code

Shinylive for R and Python

Run Shiny applications entirely in a web browser, without the need for a computational server.

Traditional Shiny App

Hosting a Shiny app

Bundle and distribute app source?

  • Transfer source and data to some other machine.
  • Run the app with a local Shiny server.
  • Useful method for e.g. archival, app submission and review.


Requires a reproducible workflow and software installation:

  • R/Python interpreter, of the correct version.
  • Software and environment control: Docker, rix, renv, venv, etc.
  • Software development tools: RStudio, VS Code.
  • Knowledge and experience: Shiny runApp(), debugging.


Wouldn’t it be great if we could run Shiny apps locally, without installing any extra software?

Shinylive App πŸ”— https://shinylive.io/r/

Shinylive in Quarto

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

```{shinylive-r}
#| standalone: true
library(shiny)

# Create Shiny UI
ui <- [...]

# Create Shiny server function
server <- function(input, output, session) {
  [...]
}

# Build Shiny app
shinyApp(ui = ui, server = server)
```

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit laborum.

Shinylive for R

#| standalone: true
#| viewerHeight: 700

library(shiny)
library(bslib)

theme <- bs_theme(font_scale = 1.5)

# Define UI for app that draws a histogram ----
ui <- page_sidebar(theme = theme,
  sidebar = sidebar(open = "open",
    numericInput("n", "Sample count", 50),
    checkboxInput("pause", "Pause", FALSE),
  ),
  plotOutput("plot", width=1100)
)

server <- function(input, output, session) {
  data <- reactive({
    input$resample
    if (!isTRUE(input$pause)) {
      invalidateLater(1000)
    }
    rnorm(input$n)
  })
  
  output$plot <- renderPlot({
    hist(data(),
      breaks = 30,
      xlim = c(-2, 2),
      ylim = c(0, 1),
      xlab = "value",
      freq = FALSE,
      main = ""
    )
    
    x <- seq(from = -2, to = 2, length.out = 500)
    y <- dnorm(x)
    lines(x, y, lwd=1.5)
    
    lwd <- 5
    abline(v=0, col="red", lwd=lwd, lty=2)
    abline(v=mean(data()), col="blue", lwd=lwd, lty=1)

    legend(legend = c("Normal", "Mean", "Sample mean"),
      col = c("black", "red", "blue"),
      lty = c(1, 2, 1),
      lwd = c(1, lwd, lwd),
      x = 1,
      y = 0.9
    )
  }, res=140)
}

# Create Shiny app ----
shinyApp(ui = ui, server = server)

Convert a Shiny app to Shinylive

Install the Shinylive R package:

install.packages("shinylive")


Convert the app:

shinylive::export("myapp", "site")

Binary bundle ready to transfer to another machine or host on a static web service.


Run the application:

httpuv::runStaticServer("site")

or…

python -m http.server
npx http-server 

WebAssembly R packages πŸ”— https://repo.r-wasm.org

Binary R packages for Wasm are available from a CRAN-like CDN:

  • Over 60% of CRAN packages available for webR

What if R packages change?

  • R packages are always updating and changing.

  • Despite best efforts, changes and deprecations can break older code.


  • With the next version of Shinylive, Wasm R package binaries will be frozen and bundled with an app automatically.

  • Apps will continue to work in the future, without changes, even as the webR CRAN-like repository updates.


  • Wasm binaries will be downloaded from R-Universe personal package repositories.

  • Custom packages built for Wasm using r-wasm/actions will be downloaded from GitHub.

Exporting a Shinylive app

#| standalone: true
#| viewerHeight: 800
library(shiny)
library(dplyr)

ui <- fluidPage(
  titlePanel("Hello Shiny!"),
  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30),
      
      sliderInput(inputId = "filter",
                  label = "Filter:",
                  min = 45,
                  max = 99,
                  value = 90)

    ),
    mainPanel(
      plotOutput(outputId = "distPlot")
    )
  )
)


server <- function(input, output) {

  output$distPlot <- renderPlot({

    x    <- faithful |> dplyr::pull(waiting)
    xf   <- faithful |> dplyr::filter(waiting < input$filter) |> dplyr::pull(waiting)
    bins <- seq(min(x), max(x), length.out = input$bins + 1)

    hist(xf, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")

    })

}

# Create Shiny app ----
shinyApp(ui = ui, server = server)

R Consortium Submission Working Group

Pilot Shiny app submissions to the FDA investigating containers and WebAssembly.

Screenshot of the pilot shiny app submission using webR

πŸ”— Testing Containers and WebAssembly in Submissions to the FDA - pharmaverse.github.io

Future work and current issues

  • Not all R packages work under WebAssembly.

  • Building custom R packages using GitHub Actions is still experimental.

  • Documentation updates and clarifications to follow.

  • There will always be good reasons to use a traditional Shiny deployment.

  • Browser security restrictions: limited networking, no raw socket access.

  • 😱 There are no secrets with a Shinylive app!

  • All code and data is sent to the client, deploy accordingly.

πŸ”— webR demo website

https://webr.r-wasm.org/v0.3.2/

🌎 Shinylive examples

https://shinylive.io/r/

https://shinylive.io/py/

πŸ“™ Documentation

https://docs.r-wasm.org/webr/v0.3.2/

https://github.com/posit-dev/shinylive

https://github.com/quarto-ext/shinylive