Chapter 4 Reactivity
4.1 Overview of Reactivity
In Section 3, we mentioned that the Shiny server logic is based on reactive programming. The server takes input from the UI, runs the code chunks defined within, generates output and sends it to the UI. When you change the input, the server re-runs the code chunks and sends the updated output to the UI.
Consider the following example. The UI takes a single text input i.e. name of a person. The server concatenates the input string with “Hello” at the start and, “!” at the end, followed by another string “Have a nice day!”, and returns the concatenated string to the UI and prints as output.
<- fluidPage(
ui textInput("name", "What's your name?"),
textOutput("greeting")
)
<- function(input, output, session) {
server $greeting <- renderText({
outputpaste0("Hello ", input$name, "!", "\n", "Have a nice day!")
}) }
Challenge 4: Modify greeting.R
to print the uppercase letters (breakout)
Open ShinyApps/reactive/greeting.R
and edit the file to print the input name in uppercase letters. The new app should:
Ask your name in lowercase letters, example: “batman”.
And outputs- “Your name in uppercase letters is: BATMAN”.
Hint: Get help on toupper()
4.2 R expressions vs Reactive expressions
Most R code typically deals with static data. What makes Shiny apps magical is the fact that they are interactive which is made possible due to reactivity. There are two important aspects to reactivity:
Reactive expressions produce the latest computation
Reactive expressions save re-computation.
Consider the following example. We assign a value of 5
to the variable a
. Using R expressions, we add 10
to a
, which prints the result as 15
. Then, we update the value of a
to 10
. Even though, the new value of a
is now 10
, nothing happens! This is because, we haven’t called the expression a + 10
. In order to see the updated output of our expression, we need to explicitly re-run the expression. This is how the R expressions work.
<- 5
a <- a + 10) (sum
## [1] 15
<- 10
a sum
## [1] 15
However, this problem could be solved by using a function. Here we create a function to add 10
to a number.
<- function(num){
add10 <- num + 10
result return(result)
}add10(a)
## [1] 20
Using a function gives us the latest compute with the latest input value. However, it doesn’t save computation. The function will perform the computation even if the input value remains unchanged. That’s just how functions work.
add10(num=10)
## [1] 20
When working with trivial examples or data sets, computation isn’t an issue. But with increasing complexity, computation can be really expensive. We can save re-computation and get the latest computation with the magic of Reactive programming. Now, let’s take a look at some reactive expressions.
The input of a reactive expression is called a reactive input. When we change the reactive input value input$a
, the expression itself changes and re-executes itself, only if necessary.
Example of a reactive expression
library(shiny)
<- reactive(input$a + 1) y
You can write the same expression in different ways:
# or
<- reactive({
y $a + 1
input
})
# or
<- reactive({
y return(input$a + 1)
})
Advanced: Workings of reactive expressions
This is an optional section that explains the workings of reactive expressions in a little more detail.
In the following example, we have a reactive input input$a
, where a =1
. We create a reactive expression, expr1
which is just a function.
<- reactiveValues(a=1)
input
<- reactive({
expr1 $a + 3)*4}) (input
isolate()
helps us view the values of the reactive expression in the R console.
isolate(expr1())
Now, we change the value of our reactive input and update the expression. Remember, we’re not computing anything, we’re just updating an expression.
<- reactiveValues(a=5)
input
<- reactive({
expr1 $a + 3)*4}) (input
isolate(expr1())
4.3 Accessing Reactivity
There are two types of functions that we can use to access reactivity. We can use render
functions or the reactive
function. In most simple examples of Shiny applications, we are able to directly connect/link an input widget and an output object with a renderX
function (e.g renderTable
, renderText
, renderPlot
). These renderX
functions are activated/re-calculated whenever the value of input$someInputId
provided to that renderX
function is updated/changed.
In such situations, the result is directed to an output on the UI. But what if you wanted to use the calculated result for multiple outputs? Since the render
function directs its result into an output, this means that this value is not freely available to be used by other render
functions. One solution could be to run the calculation multiple times with each render
function. This isn’t a very efficient solution but it might be one that would do the job. However consider if the calculation was a very large one, rerunning this calculation multiple times might slow the app down a lot to the point of it becoming inoperable.
This is a situation where a reactive
function would be useful. The output of a reactive function can be assigned to a variable name and this variable can be called within different render
functions. Like a render
function, the reactive
function is recalculated any time the value of input$anInputId
inside the reactive function is updated or changed.
Therefore for a more efficient solution, the calculation could performed in the reactive function once and then in multiple render
functions, the value of the reactive
function could be called.
Here is an example without a reactive
function, somewhat contrived:
library(shiny)
data("airquality")
<- fluidPage(
ui sliderInput(inputId = "tempModifier", label = "this slider will multiply the temperature column of the `airquality` dataset by the selected value", min = 0,
max = 100, value = 50),
textOutput(outputId = "airResult"),
plotOutput(outputId = "plotResult")
)
<- function(input, output, session) {
server $airResult <- renderText({
output<- airquality$Temp * input$tempModifier
new_temp paste0("the average of the modified temperature is:", mean(new_temp))
})
$plotResult <- renderPlot({
output<- airquality$Temp * input$tempModifier # we are performing the same calculation twice
new_temp plot(x=airquality$Temp, y = new_temp, ylim = c(0, 8000))
})
}
shinyApp(ui, server)
Here an example with a reactive
function:
library(shiny)
data("airquality")
<- fluidPage(
ui sliderInput(inputId = "tempModifier", label = "this slider will multiply the temperature column of the `airquality` dataset by the selected value", min = 0,
max = 100, value = 50),
textOutput(outputId = "airResult"),
plotOutput(outputId = "plotResult")
)
<- function(input, output, session) {
server # we've used a reactive function to create a reactive variable which can be used by both render functions
<- reactive({
new_temp $Temp * input$tempModifier
airquality
})
$airResult <- renderText({
outputpaste0("the average of the modified temperature is:", mean(new_temp()))
})
$plotResult <- renderPlot({
outputplot(x=airquality$Temp, y = new_temp(), ylim = c(0, 8000))
})
}
shinyApp(ui, server)
Challenge 5: Create a function that uses Pythagoras theorm to calculate hypotenuse, if height and base variables are given (breakout).
In this challenge, you have 2 input sliders for a
and b
in the UI. Create a server function to compute Pythagoras theorm and calculate hypotenuse.
<- fluidPage(
ui_pythagoras titlePanel("Hypotenuse app"),
sliderInput("a", "Length a", 0, 10, 5),
sliderInput("b", "Length b", 0, 10, 5)
)