9  Uploads and downloads

Table of content for chapter 09

Chapter section list

9.1 File upload

9.1.1 UI

R Code 9.1 : File upload UI

Loading...





Try in interactive mode adding / changing the arguments label, width, buttonLabel and placeholder to see how it affects the UI appearance.

The UI needed to support file uploads is simple: just add shiny::fileInput() to your UI.

Like most other UI components, there are only two required arguments: id and label. The width, buttonLabel and placeholder arguments allow you to tweak the appearance in other ways. I won’t discuss them here, but you can read more about them in File Upload Control — fileInput.

9.1.2 Server

Handling fileInput() on the server is a little more complicated than other inputs. Most inputs return simple vectors, but fileInput() returns a data frame with four columns:

  • name: the original file name on the user’s computer.
  • size: the file size, in bytes. By default, the user can only upload files up to 5 MB. You can increase this limit by setting the shiny.maxRequestSize option prior to starting Shiny. For example, to allow up to 10 MB run options(shiny.maxRequestSize = 10 * 1024^2).
  • type: the MIME type of the file. This is a formal specification of the file type that is usually derived from the extension and is rarely needed in Shiny apps.
  • datapath: the path to where the data has been uploaded on the server. Treat this path as ephemeral: if the user uploads more files, this file may be deleted. The data is always saved to a temporary directory and given a temporary name.

R Code 9.2 : File upload server

Loading...




Watch out! 9.1

fileInput() does not show multiple uploaded files. One can see only the last one.

This issue was on May 2, 2021 opened at GitHub, but it is still not closed. I do not know with my rudimentary knowledge at the moment (2025-06-08) how to solve this problem.

9.1.3 Uploading data

If the user is uploading a dataset, there are two details that you need to be aware of:

  • input$upload is initialized to NULL on page load, so you’ll need req(input$upload) to make sure your code waits until the first file is uploaded.
  • The accept argument allows you to limit the possible inputs. The easiest way is to supply a character vector of file extensions, like accept = ".csv". But the accept argument is only a suggestion to the browser, and is not always enforced, so it’s good practice to also validate it (e.g. ) yourself. The easiest way to get the file extension in R is tools::file_ext(), just be aware it removes the leading . from the extension.

Putting all these ideas together gives us the following app where you can upload a .csv or .tsv file and see the first n rows. See it in action in https://hadley.shinyapps.io/ms-upload-validate.

R Code 9.3 : Uploading data

Loading...




Note 9.1

Note that since multiple = FALSE (the default), input$file will be a single row data frame, and input$file$name and input$file$datapath will be a length-1 character vector.

9.2 Downloads

9.2.1 Basics

  • Again, the UI is straightforward: use either downloadButton(id) or downloadLink(id) to give the user something to click to download a file. You can customize their appearance using the same class and icon arguments as for actionButtons(), as described in .
  • Unlike other outputs, downloadButton() is not paired with a render function. Instead, you use downloadHandler().

downloadHandler() has two arguments, both functions:

  • filename should be a function with no arguments that returns a file name (as a string). The job of this function is to create the name that will be shown to the user in the download dialog box.
  • content should be a function with one argument, file, which is the path to save the file. The job of this function is to save the file in a place that Shiny knows about, so it can then send it to the user. This is an unusual interface, but it allows Shiny to control where the file should be saved (so it can be placed in a secure location) while you still control the contents of that file.

Next we’ll put these pieces together to show how to transfer data files or reports to the user.

9.2.2 Downloading data

The following app shows off the basics of data download by allowing you to download any dataset in the datasets package as a tab separated file.

Tip 9.1: Use .tsv instead of csv

I recommend using .tsv (tab separated value) instead of .csv (comma separated values) because many European countries use commas to separate the whole and fractional parts of a number (e.g. 1,23 vs 1.23). This means they can’t use commas to separate fields and instead use semi-colons in so-called “c”sv files! You can avoid this complexity by using tab separated files, which work the same way everywhere.

R Code 9.4 : Downloading data

Watch out! 9.2: Workaround for Chromium Issue

To properly download the file in shinylive you need a workaround. Put the following function outside server():

Code
downloadButton <- function(...) {
  tag <- shiny::downloadButton(...)
  tag$attribs$download <- NULL
  tag
}

This workaround is not necessary in an app.R file.

Note the use of validate() to only allow the user to download datasets that are data frames. A better approach would be to pre-filter the list, but this lets you see another application of validate().

9.2.3 Downloading reports

9.2.3.1 Standard

As well as downloading data, you may want the users of your app to download a report that summarizes the result of interactive exploration in the Shiny app. This is quite a lot of work, because you also need to display the same information in a different format, but it is very useful for high-stakes apps.

One powerful way to generate such a report is with a parameterised RMarkdown document. A parameterised RMarkdown file has a params field in the YAML metadata:


title: My Document
output: html_document
params:
  year: 2018
  region: Europe
  printcode: TRUE
  data: file.csv

Inside the document, you can refer to these values using params$year, params$region etc. The values in the YAML metadata are defaults; you’ll generally override them by providing the params argument in a call to rmarkdown::render(). This makes it easy to generate many different reports from the same .Rmd.

Here’s a simple example adapted from https://shiny.rstudio.com/articles/generating-reports.html, which describes this technique in more detail. The key idea is to call rmarkdown::render() from the content argument of downloadHander(). If you want to produce other output formats, just change the output format in the .Rmd, and make sure to update the extension (e.g. to .pdf). See it in action at https://hadley.shinyapps.io/ms-download-rmd.

R Code 9.5 : Downloading reports

Watch out! 9.3: Downloading reports is not working

The code chunk does not produce an error. But whenever you click the button “Generate report” the following message is created in a new browser window:

An error has occurred! pandoc version 1.12.3 or higher is required and was not found (see the help page ?rmarkdown::pandoc_available).

I tried to solve the problem with hints from StackOverflow, especially the answer edited by Yihui Xie.

Code
Sys.getenv("RSTUDIO_PANDOC")
#> [1] "/Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64"

But to add Sys.setenv("RSTUDIO_PANDOC"=Sys.getenv("RSTUDIO_PANDOC")) did not work.

I tried it also with another pandoc version installed at my macOS with homebrew:

Code
rmarkdown::pandoc_exec()
#> [1] "/opt/homebrew/bin/pandoc"

I assume there are path problems with shinylive.

9.2.3.2 Using {callr}

It’ll generally take at least a few seconds to render a .Rmd, so this is a good place to use a notification from .

There are a couple of other tricks worth knowing about:

  • RMarkdown works in the current working directory, which will fail in many deployment scenarios (e.g. on https:://shinyapps.io). You can work around this by copying the report to a temporary directory when your app starts (i.e. outside of the server function):
Code
report_path <- tempfile(fileext = ".Rmd")
file.copy("report.Rmd", report_path, overwrite = TRUE)

Then replace “report.Rmd” with report_path in the call to rmarkdown::render():

Code
rmarkdown::render(report_path,
  output_file = file,
  params = params,
  envir = new.env(parent = globalenv())
)
  • By default, RMarkdown will render the report in the current process, which means that it will inherit many settings from the Shiny app (like loaded packages, options, etc). For greater robustness, I recommend running render() in a separate R session using the {callr} package:
Code
render_report <- function(input, output, params) {
  rmarkdown::render(input,
    output_file = output,
    params = params,
    envir = new.env(parent = globalenv())
  )
}

server <- function(input, output) {
  output$report <- downloadHandler(
    filename = "report.html",
    content = function(file) {
      params <- list(n = input$slider)
      callr::r(
        render_report,
        list(input = report_path, output = file, params = params)
      )
    }
  )
}

R Code 9.6 : Downloading reports

Watch out! 9.4

The last two code chunks didn’t work.

At first I got the message with a code version I can’t reproduce > Warning: Error in : ! in callr subprocess
> Caused by error in abs_path(input):
> ! The file ’/var/folders/sd/g6yc4rq1731__gh38rw8whvc0000gr/T//RtmplpyWiO/file422772b5bfd1.Rmd’ does not exist.

In the current code version I got an error message whenever I try to include the report.Rmd file.

error: object ‘params’ not found

Resource 9.1 : Downloading reports with shiny and looking for shinylive error messages

  • You can see all these pieces put together in rmarkdown-report/, found inside the Mastering Shiny GitHub repo. It works as Shiny app but with shinylive I got the error message reported in .
  • The {shinymeta} package solves a related problem: sometimes you need to be able to turn the current state of a Shiny app into a reproducible report that can be re-run in the future. (Github Source, Documentation. My latest CRAN version 0.2.0.3 is from November 17, 2021. In the meantime there were 15 commits indicating that a new version is in preparation. preparation. )
  • Learn more about the {shinymeta} package in Joe Cheng’s useR! 2019 keynote, “Shiny’s holy grail: Interactivity with reproducibility”. There is also the code for the example Shiny app available, featuring package download data from CRAN.The code for the example Shiny app is available at GitHub. The sample code is useful in its own. It features package download data from CRAN.
  • Concerning again {shinymeta}: Keep in mind that there were breaking changes in the meantime by replacing bang-bang (!!) with the dot-dot operator (..()).
  • It seems to me that a question on StackOverflow is relevant to my shinylive-r issue. At least I got exactly the same error message as in the question. But even with the different hints to solve the problem, notably the edited answer by Yihui Xie and the following several comments, I could not solve the shinylive-r error message.

9.3 Case study

To finish up, we’ll work through a small case study where we upload a file (with user supplied separator), preview it, perform some optional transformations using the {janitor} package (), and then let the user download it as a .tsv.

To make it easier to understand how to use the app, I’ve used sidebarLayout() to divide the app into three main steps:

  1. Uploading and parsing the file
  2. Cleaning the file
  3. Downloading the file

All three parts get assembled into a single fluidPage() and the server logic follows the same organization.

R Code 9.7 : Upload a file, clean it, and then download the results

9.4 Exercises

9.4.1 Multidimensional noise

9.4.1.1 Task description

Use the {ambient} package () to generate worley noise and download a PNG of it.

9.4.1.2 Solution

Important 9.1

There is a conflict between the two versions. The first one (Shiny) does not work correctly at the beginning. One has to restart the app (clicking the right top arrow in the code window). The drop down menu changes it form and the reactive values work as intended. (The same problem occurs with the {bslib} version if it is the first one. There is no problem if there is only one version of the app.)

Exercise 9.1 : Generate Worley noise and download a PNG of the resulting image

R Code 9.8 : Generate noise with two variable parameters

R Code 9.9 : Generate noise with eight variable parameters

Note 9.2

I was surprised how easy the sidebar layout was created with {bslib}. The only issue I discovered so far: The default layout is much bigger and therefore not so convenient as in the {shiny} layout version. But I assume that there are option to change the overall appearance easily.

See Dashboards, an article that shows using {bslib} to create the user interface (UI) for Shiny dashboards.

In this exercise I had to overcome several difficulties:

  1. Exercise interpretation: First of all I had to interpret the challenge. I don’t now anything about multidimensional noise generator. So I had to use the two provided examples to play around. Soon I learned that there are several parameters that change the resulting noise picture. I concluded that it would be nice to have an environment where you could experiment with the different parameters. This would be a really advantage of a Shiny app in contrast to pages with fixed parameters.

    I learned that Solution to Mastering Shiny and Mastering Shiny Solution did not capture this idea. The first one skipped this exercise, the second one provided a solution without Shiny.

  2. content(file) function: My biggest problem was to download the PNG. I didn’t understand that the argument file for the function content() is only the way of Shiny to save the file internally. So the app author must not assign a value to file.

  3. PNG function: I never used so far the png() function with the dev.off() command at the end of the transaction.

Not knowing about png() with my content(file) misunderstanding it took me several hours to find the solution.

9.4.2 Upload CSV & t-test

9.4.2.1 Task description

Create an app that lets you upload a CSV file, select a variable, and then perform a t.test() on that variable. After the user has uploaded the csv file, you’ll need to use updateSelectInput() to fill in the available variables. See () for details.

Note 9.3

I have extended the task with three more options:

  • Not only CSV but also TSV files are allowed.
  • Because t.test() requires numeric data, load only columns with numeric data into updateSelectInput().
  • Add input for the hypothesized mean, otherwise the one same t test will always use 0.

9.4.2.2 Solution

Exercise 9.2 : Upload file, select a variable and perform a t test

9.4.3 Download histogram

9.4.3.1 Task description

Create an app that lets the user upload a csv file, select one variable, draw a histogram, and then download the histogram. For an additional challenge, allow the user to select from .png, .pdf, and .svg output formats.

I have developed several version of this exercise provided in different tabs:

  1. base R plot: This is the minimum solution using base R plot. It took my a long time to learn and understand how to reference a specific column for the plot. The $ does not work, you have to use [[ like data()[[input$my_column]]. See SO: Shiny R Histogram.

    I used to replicate the code for plotting the histogram in renderPlot() and downloadHandler(). By creating a reactive for the plot code I could prevent duplication. See: SO: How to avoid code duplication in a render() function in Shiny for R.

  2. ggplot: This is the minimal solution with {ggplot2}. The reference to a column with {ggplot2} is even more complicated with .data[[input$my_column]]. The .data part is the tidy eval pronoun. See: SO: What is the difference between . and .data.

    Tidy evaluation is a concept used in the tidyverse, particularly in packages like dplyr and ggplot2, to handle the evaluation of expressions in a way that allows for more flexible and powerful programming. It is especially useful when you want to build functions that can take column names as arguments and use them within dplyr verbs.

    It is a very complex topic and I will not open this Pandora box here, but these two references might provide a good start: Programming with {dplyr} and Evaluate an expression with quosures and pronoun support from the {rlang} package. (The {rlang} package is in this context important as it collects functions for the core {tidyverse} features like tidy evaluation.)

  3. Hist button: In the previous versions the histogram graphics appeared automatically with the first column in the selectInput() menu. In this version I have added a button to start drawing the histogram explicitly.

  4. shinyjs: In all the previous trials was the downloadButton() active, even if there was nothing to download. I learned that I could prevent this easily with {shinyjs}. See SO: shiny app: disable downloadbutton

  5. dynamic-UI: With a dynamic UI there is another solution to present an active downloadButton(). The user only get the button when there is something to download. (It seems to me that this will be covered in the next book chapter on Dynamic UI. See ().) But here I have used SO: Display download button in Shiny R only when output appears in Main Panel

    I do not know what is the better UI alternative. To have an inactive button that changes or appearing a complete new UI element during the processing.

  6. radioButtons: In this exercise the user can decide if (s)he wants the graphic as base R plot or with {ggplot2}.

    I personally prefer the ggplot() version for two reasons: (ggplot2) is the modern plotting variant and has in the meanwhile a huge ecosystem on supporting packages and I am more experienced with {ggplot2} than with base R graphic.

Important 9.2: Provide plot dimensions and set resolution to 96

The first time I selected a tab of I got briefly error: figure margin too large. The problem is that the reserved plotting error is in the beginning too small. To prevent this error message I had to change plotOutput("hist") to plotOutput("hist", width = '600px', height = '400px').

By this occasion I also changed the resolution in renderPlot() from the standard 72 to the better resolution of 96. For instance: renderPlot(my_hist(), res = 96). If there is a function with several lines (and not one liner function my_hist() as in the just mentioned example), then you have to include the res = 96 addition between the ending }) like }, res = 96).

9.4.3.2 Solution

Exercise 9.3 : Upload file, draw histogram and download in different formats

R Code 9.10 : Minimal version with base R plot

R Code 9.11 : Minimal version with {ggplot2}

R Code 9.12 : Using button to start drawing the histogram

R Code 9.13 : Enable download button with {shinyjs}

R Code 9.14 : Dynamic UI: Display download button only when there is something to download

R Code 9.15 : Radio buttons to choose whether base R or ggplot graphic

Resource 9.4 : Used resources to solve exercise 3: Download plot with shiny

I also used two GitHub Gist articles, even if there were not decisive for my solution they provided general background to work with shiny plots:

9.4.4 LEGO bricks

9.4.4.1 Task description

Write an app that allows the user to create a Lego mosaic from any .png file using Ryan Timpe’s {brickr} package. Once you’ve completed the basics, add controls to allow the user to select the size of the mosaic (in bricks), and choose whether to use “universal” or “generic” color palettes.

9.4.4.2 Solution

Watch out! 9.5: Compiled {brickr} package for webR not available

It turned out, that I couldn’t run my solution because the {brickr} was not available for shinylive (webR / WebAssembly). I think the reason is that the current version of the package is neither on CRAN anymore (it was until the previous version 0.3.5) nor is there a GitHub release available.

Error in get_github_wasm_assets():
! Can’t find GitHub release for github::ryantimpe/brickr@HEAD
! Ensure a GitHub release exists for the package repository reference: “HEAD”.
ℹ Alternatively, install a CRAN version of this package to use the default Wasm
binary repository.
Caused by error in gh::gh():
! GitHub API error (404): Not Found
✖ URL not found:
https://api.github.com/repos/ryantimpe/brickr/releases/tags/HEAD
ℹ Read more at
https://docs.github.com/rest/releases/releases#get-a-release-by-tag-name

I have therefore only the apps source code provided. To run the apps, copy it form the code chunks below or fork this repo and run the provided apps.

Resource 9.5 : Compiling packages for Webassembly and webR

About providing / compiling R packages there is a daunting amount of literature available. I do not understand the connection of all of these resources and therefore is it extremely difficult for me to compile the {bricksr} package for WebAssembly myself.

Nonetheless I have collected links to the (presumed) most important resources to solve my problem:

rwasm

V8

webR

There is a package {webr}. It is about “Data and Functions for Web-Based Analysis” and has therefore nothing to do with the open-source R interpreter webR compiled for WebAssembly,

YouTube Videos by Geroge Stagg

Procedure 9.1 : Exercise 4 of chapter 9: LEGO bricks

  1. Understanding {brickr}: My first step to solve the exercise was to get an understanding what the {brickr} package does. I copied the demo code from the website Emulate LEGO Bricks in 2D and 3D and experimented with different pictures and parameters. Tab .
  2. Implementing the basics: My second step was to replicate the demo code with fixed values. See: .
  3. Interactive functionality: Third step was to take advantage of Shiny provide an interactive version. (See: )
  4. Improving the UI: A last step was to disable/enable the Process button and to provide a spinning wheel provided by {waiter}. See .

I’ve got several more ideas to improve the app.

  • Display original image and LEGO bricks image side by side.
  • Provide a download button.
  • Another more challenging option would be to suggest height and width of the LEGO output in relation to the uploaded image. This would require to detect the dimension of the original image and to find an optimal conversion rule for the number of LEGO bricks.
  • To output the numbers of LEGO bricks sorted by size and colors.
  • To generate a 3D picture.

Code Collection 9.1 : Build LEGO mosaics from any .png file using {bricksr}

R Code 9.16 : Play with the demo code to understand the core functionality of {bricksr}

Listing / Output 9.1: Demo code to understand the core functionality of {bricksr}
Code
library(brickr)

demo_img = tempfile()
download.file("http://ryantimpe.com/files/mf_unicorn.PNG", demo_img, mode = "wb")

(
    mosaic1 <- png::readPNG(demo_img)  |>
        image_to_mosaic(img_size = 36) |>
        build_mosaic()
)

R Code 9.17 : Replicate {bricksr} demo code with a Shiny app

Listing / Output 9.2: {bricksr} demo code replicated with Shiny
Code
library(shiny)
library(bslib)
library(base64enc)
library(brickr)

ui <- page_sidebar(
    titlePanel("Emulate LEGO Bricks"),
    sidebar = sidebar(
        fileInput("upload", "Load PNG image", accept = c(".png"))
        ),
    uiOutput("image"),
    plotOutput("plot", width = "600px", height = "400px")
    )

server <- function(input, output, session) {
    base64 <- reactive({
        req(input$upload)
        dataURI(file = input$upload$datapath, mime = "image/png")
    })

    output$plot <- renderPlot({
        req(base64())
        mosaic1 <- png::readPNG(input$upload$datapath) |>
            image_to_mosaic(img_size = c(72, 48), color_palette = "generic") |>
            build_mosaic()
        mosaic1
    }
    )

    output$image <- renderUI({
        req(base64())
        tags$div(
            tags$img(src = base64(), width = "100%"),
            style = "width: 400px;"
        )

    })
}


shinyApp(ui, server)

R Code 9.18 : Create interactive functionality to build mosaics with {bricksr}

Listing / Output 9.3: Provided interactivity for emulating LEGO bricks with {bricks}
Code
library(shiny)
library(bslib)
library(base64enc)
library(brickr)

ui <- page_sidebar(
    titlePanel("Emulate LEGO Bricks"),
    sidebar = sidebar(
        fileInput("upload", "Load PNG image", accept = c(".png", ".PNG")),
        numericInput("width", "Width", 36),
        numericInput("height", "Height", 36),
        selectInput("pal", "Color Palette:",
                    c("Generic" = "generic",
                      "Universal" = "universal")),
        actionButton("process", "Process",
                     icon = icon("cog", lib = "glyphicon"),
                     disabled = TRUE)
        ),
    uiOutput("image"),
    plotOutput("plot", width = "600px", height = "400px")
    )

server <- function(input, output, session) {
    base64 <- reactive({
        req(input$upload)
        updateActionButton(
            session, "process",
            disabled = FALSE)
        dataURI(file = input$upload$datapath, mime = "image/png")
    })

    output$plot <- renderPlot({
        req(input$process > 0)
        my_mosaic <-

                png::readPNG(input$upload$datapath) |>
                image_to_mosaic(img_size =
                        c(isolate(input$width), isolate(input$height)),
                        color_palette = isolate(input$pal)) |>
                build_mosaic()
        my_mosaic
    })

    output$image <- renderUI({
        input$upload
        tags$div(
            tags$img(src = base64(), width = "100%"),
            style = "width: 400px;"
        )
    })

}


shinyApp(ui, server)

R Code 9.19 : Improving User Interface of the LEGO bricks shiny app

Listing / Output 9.4: Improving Shiny’s UI for LEGO bricks emulating with {bricksr}
Code
library(shiny)
library(bslib)
library(base64enc)
library(brickr)
library(waiter)


waiting_screen <- tagList(
    spin_flower(),
    h4("Emulating picture with Lego bricks...")
)

ui <- page_sidebar(
    useWaiter(),
    titlePanel("Emulate LEGO Bricks"),
    sidebar = sidebar(
        fileInput("upload", "Load PNG image", accept = c(".png", ".PNG")),
        numericInput("width", "Width", 36),
        numericInput("height", "Height", 36),
        selectInput("pal", "Color Palette:",
                    c("Generic" = "generic",
                      "Universal" = "universal")),
        actionButton("process", "Process",
                     icon = icon("cog", lib = "glyphicon"),
                     disabled = TRUE)
        ),
    uiOutput("image"),
    plotOutput("plot", width = "600px", height = "400px")
    )

server <- function(input, output, session) {
    # create a waiter
    w <- Waiter$new()

    base64 <- reactive({
        req(input$upload)
        updateActionButton(
            session, "process",
            disabled = FALSE)
        dataURI(file = input$upload$datapath, mime = "image/png")
    })

    output$plot <- renderPlot({
        req(input$process > 0)
        # w$show()
        waiter_show(html = waiting_screen, color = "black")
        my_mosaic <-

                png::readPNG(input$upload$datapath) |>
                image_to_mosaic(img_size =
                        c(isolate(input$width), isolate(input$height)),
                        color_palette = isolate(input$pal)) |>
                build_mosaic()
        # w$hide()
        waiter_hide()
        my_mosaic
    })

    output$image <- renderUI({
        input$upload
        tags$div(
            tags$img(src = base64(), width = "100%"),
            style = "width: 400px;"
        )
    })

}


shinyApp(ui, server)

9.4.5 Reorganizing reactive(s)

9.4.5.1 Task description

The app from the case study contains a very large reactive. Break it up into multiple pieces so that (e.g.) janitor::make_clean_names() is not re-run when input$empty changes.

tidied <- reactive({
  out <- raw()
  if (input$snake) {
    names(out) <- janitor::make_clean_names(names(out))
  }
  if (input$empty) {
    out <- janitor::remove_empty(out, "cols")
  }
  if (input$constant) {
    out <- janitor::remove_constant(out)
  }

  out
})

9.4.5.2 Solution

R Code 9.20 : Code reorganizing of the final version of the case study in

Note 9.4

After two days I gave up on this exercise and looked at the solution in Mastering Shiny Solutions.

I knew that the main change has to be happen in the cleaning function. Exactly the part of code that was repeated in the exercise formulation. But I did not know how to collect the results of of the three cleaning actions into on tableRender(). I tried it with reactiveVal() and managed the separate cleaning procedure but only for the first time. I didn’t succeed to develop an app where the user could also take back the cleaning action through by resetting the selection.

I learned two things from the solution:

  1. Each change of an input results is a call to each available reactive function. Thinking now about it, this seems obvious. In contrast to the eventReactive() handler that only is activated by a change of a specific input value a reactive expression is called always when an input value has changed.

  2. But most important I did not know that “if a reactive expression is marked as invalidated, any other reactive expressions that recently called it are also marked as invalidated. In this way, invalidations ripple through the expressions that depend on each other” (`reactive()``` help page).

This “ripple through the expression” is good visible as the result of every previous cleaning action is the start value of the out variable. Important is here the order of the different reactives. The raw data frame (raw()) must be included in the first reactive and is followed by one reactive after another applying or not applying each cleaning function. In this case is the order from top to bottom, because each reactive was invalidated by the previous one. this structure was a surprise for me, as I had a mental model of Shiny where code sequences never matters.

References

Firke, Sam. 2024. “Janitor: Simple Tools for Examining and Cleaning Dirty Data.” https://doi.org/10.32614/CRAN.package.janitor.
Harris, Jenine K. 2020. Statistics With R: Solving Problems Using Real-World Data. Illustrated Edition. Los Angeles London New Delhi Singapore Washington DC Melbourne: SAGE Publications, Inc.
Pedersen, Thomas Lin, and Jordan Peck. 2022. “Ambient: A Generator of Multidimensional Noise.” https://doi.org/10.32614/CRAN.package.ambient.

  1. Attention: The video starts at minute 11:20.↩︎