-
Notifications
You must be signed in to change notification settings - Fork 1
/
06-specificCoding.Rmd
executable file
·322 lines (237 loc) · 23 KB
/
06-specificCoding.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# Shiny (BOAST) Specific Coding Practices {#specificCoding}
One of the most important aspects of Shiny apps is that they are interactive. We want users to interact with the app by changing the values of various inputs and observing what changes. While there are a variety of ways in which we can allow users to manipulate different aspects, there are two important elements to ensure that we (and the users) make the most of Shiny's reactive environment. We need to plan/design for such interaction and we need to code in a way that makes the most of the interaction.
While planning/designing ostensibly comes before coding, there is a useful back-and-forth that helps use design (and code) better apps. One place where coding can inform planning/designing is understanding *how* Shiny apps handle user interaction
## Planning Interactions
There are two aspects to every interaction in a Shiny app: there is some triggering event that causes something to happen and there are the results of that trigger event (i.e., the effects). To stick with the function metaphor, we call the triggers *inputs* and the results, *outputs*.
### Inputs
As you start thinking about your app, you'll want to thinking about ways in which you want the user to interact with your app. Think beyond the basic interactions of reading text and clicking to a different page. Do you want the user to make choices (more than one at a time?), move a slider, enter a number, enter text, upload a file, click a button, etc.? Each action that you design for a user to take becomes an element in the `input` object that exists for every app. The `input` object is a special type of list in `R` that embraces the reactivity of the Shiny apps.
There are a wide variety of different kinds of inputs that you can plan to use in your apps, including but not limited to
+ Buttons (via `bsButton`)
+ Checkboxes (via `checkboxInput` and `checkboxGroupInput`)
+ Dates (via `dateInput` and `dateRangeInput`)
+ Numbers (via `numericInput`)
+ Radio buttons (via `radioButtons` and `radioGroupButtons`)
+ Drop down lists (via `selectInput`)
+ Sliders (via `sliderInput`)
+ Text (short text via `textInput`; large paragraphs via `textAreaInput`)
With the exception of `bsButton`, all of the above functions are from the `shiny` package and create different kinds of inputs. There are additional types and styles of inputs generated by functions from other packages such as `shinyWidgets` and `shinyMatrix` that we also use. While all of these functions will create different kinds of inputs, they all share vitally important argument which you must provide a value to: `inputId`. The `inputId` argument gives your input a name which allows you to access the user's entry elsewhere. What is vitally important is that __every__ input must have a __unique__ value for `inputId`. Missing values for `inputId` can cause your app to not run at all and duplicated values can cause your app to crash or behave in unexpected ways. Given their importance, we want to be sure that each input's name (value for `inputId`) is an [informative name](#naming).
Since we design the inputs to be something that users interact with, the above input creation functions (`*Input`) will appear in the UI portion of your app code. To make use of the user's values in the server portion, you'll need to call the values using the format `input$objName`. As an example, if we had a drop-down menu where the user could pick the difficulty level for a game, we might use `selectInput(inputId = "difficultyLevel",...)` in the UI to create the input object. Then in the server, we would access the user's choice with `input$difficultyLevel`. Using informative names will help you keep track of what inputs provide what information. We also recommend keeping a list of input names as part of your design process.
### Outputs
The effect or result side of interactivity appears with the notion of outputs in Shiny apps. Just as there is a special list object called `input`, there's a matching list object called `output`. The big question to ask yourself here is what do you want your user to observe as a result of their actions? Outputs generally come in a much smaller set of flavors:
+ Plots
+ Images
+ Tables
+ Text
+ User Interface elements
Outputs require the use of two partner functions: a `render*` function in the server and a matching `*Output` function in the UI. Just like with inputs, all output functions have a vital argument called `outputId`. The value of `outputId` becomes the name of the object and the way that you can access that object throughout your app. Again, we want [informative names](#naming).
Later on in this chapter (see [below](#renderOutput)), we'll look at an example of working with output objects.
## Reactive vs. Observe
As mentioned, the `input` and `output` objects are special types of lists in `R`. They get their special status through the fact that they contain *reactive* elements. This brings up another area where knowledge of what is happening in your app can help you with design.
Within Shiny apps, there are two classes of objects, `reactive` and `observe`, that impart special attributes to objects. In general, a `reactive` object anticipates changing over time and functioning as an input to other elements of your app. An `observe` object will call upon `reactive` elements, perform some task, and return an output. You can think about the `*Input` functions as creating `reactive` objects while the `render*` functions as `observe` objects.
There are a couple of key differences between `reactive` and `observe`:
1. `reactive` elements can be used as inputs in other `reactive` elements as well as `observe` elements. `observe` elements cannot be used as inputs to other `reactive` (or `observe`) elements.
2. `observe` elements are *eager*--the moment they detect any one of their inputs changing, they update themselves as soon as possible. On the other hand, `reactive` elements are *lazy*--while they see when their inputs change, they don't update until they are called on by another element.
To help highlight this second difference, consider your picking up a cake from a bakery. If you were to act like an `observe` object, the moment the bakery called to say that the cake was ready, you'd drive to them and pick it up. If you were to act like a `reactive` element, you'd thank them for the call and make a note. When the party host asks you for the cake, that is when you head to the bakery to pick it up.
You will work with both `reactive` and `observe` elements (in particular with values and events) in your apps. While there are various ways in which you can do this, we detail the ways that we use (and want you to copy) them in BOAST.
If you want to learn more about the Shiny's Reactivity, please check out their [Understanding Reactivity page](https://shiny.rstudio.com/articles/understanding-reactivity.html).
## Working with Reactive Values
Reactive values are exactly what you might think: values which react to changes in your app. The only catch is that in order to use them, you must be in an a reactive environment. If you don't, you'll see the following error:
<div style = "color: red;">
>Error in .getReactiveEnvironment()$currentContext() : Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive function.)
</div>
We will detail how to avoid these errors below.
There are three common ways to work with reactive elements.
### `input`
Anything to which you assign an `inputId` is automatically created as a `reactive` element. Whether you're making a button, a slider, a drop-down menu, etc., your app will store the value of that object which you can then use elsewhere in the code. As mentioned before to call (make use of) any input values you simply type `input$fieldName` where `fieldName` is whatever meaningful name you assigned.
The most common cause of the above error is putting `input$fieldName` in the wrong place. To avoid this error, you must ensure that you are inside an `eventReactive` or `observeEvent` call.
### `reactiveVal`
The `reactiveVal` function allows you to create a single value (much like a variable in R) which will have `reactive` properties. One key usage of `reactiveVal` is that it allows you store values which you might want to update while someone uses the app, but not be an input. For example, suppose you want to keep track of a player's score while they are playing a game. This is a great place to use `reactiveVal`. We would code this in the following way:
```{r reactiveVal1, echo=TRUE, eval=FALSE}
server <- function(input, output, session){
score <- reactiveVal(0)
observeEvent(
eventExpr = input$submit,
handlerExpr = {
if (correct){
score(score() + 1)
} else {
score(score() - 1)
}
output$scoreDisplay <- renderUI({
paste("Your current score is", score())
})
}
)
}
```
In the above code, we've created `score` as a reactive element that starts with the initial value of 0. To call `score` and get its current value we need to use `score()`. To update `score`'s value we put the new value (or an expression) as an argument (e.g., `score() + 1`). Thus, the code `score(score() + 1)` says "take the current value of `score`, add 1, and then save as the new value of `score`".
Unlike `inputs`, app users don't directly change the values of a `reactiveVal`. Rather there's an intermediate step. Further, `reactiveVal` may be called outside fo `eventReactive` and `observeEvent`...provided that you are acting on global objects or constants. That is to say, you can't write `submitCounter <- reactiveVal(input$submit)` without getting the above error message. In order to use `input$fieldName` inside of `reactiveVal`, you must be inside of an `eventReactive` or `observeEvent` chunk. However, you can write `submitCounter <- reactiveVal(0)` and `submitCounter(1)` anywhere.
### `reactiveValues`
One limitation of `reactiveVal` is that it is only a single object (one value, one vector, one data frame). This works fine if you just want that. However, let's say you want to keep track of multiple related values; for example, not only the user's score, but their number of attempts and how many times they used a hint. You could define three separate `reactiveVal` objects or you could use a single `reactiveValues` call. In essence, `reactiveValues` creates a list of `reactiveVals` for you.
```{r reactiveValues1, echo=TRUE, eval=FALSE}
server <- function(input, output, session){
playerStats <- reactiveValues(
score = 0,
attempts = 0,
hints = 0
)
observeEvent(
eventExpr = input$submit,
handlerExpr = {
playerStats$attempts <- playerStats$attempts + 1
if (correct) {
playerStats$score <- playerStats$score + 1,
} else {
playerStats$score <- playerStats$score - 1
}
}
)
observeEvent(
eventExpr = input$hint,
handlerExpr = {
playerStats$hints <- playerStats$hints + 1
}
)
}
```
A couple things to notice is that unlike `reactiveVal` you don't need to append `()` to the name to get the value and you don't need to place the new value as an argument. Rather you can use the assignment operator, `<-`, just as you would in regular R. By using `reactiveValues` to create a list, `playerStats`, we can create a clear conceptual link between `score`, `attempts`, and `hints` as well as have them travel together throughout the app. Outside of their nature of being a list of values, `reactiveValues` still acts like `reactiveVal`.
## Events
There are two major types of events that we use within the `server`: `eventReactive` and `observeEvent`. Just as you might expect from their names `eventReactive` is a `reactive` element and can be stored as an output while `observeEvent` is an `observe` element and can't be stored.
### `eventReactive`
One way to think about `eventReactive` is that it takes `reactiveVal`/`reactiveValues` and goes beyond in a couple of ways. First, you can store more complicated objects such as incorporating logic controls. Second, you define a specific trigger to cause an update (i.e., the `eventExpr`). Here is an example `eventReactive` that will switch between various data sets, depending upon the user's choice (via `input$selectedData`):
```{r eventReactive1, echo=TRUE, eval=FALSE}
dataSet <- eventReactive(
eventExpr = input$selectedData,
valueExpr = {
switch(
EXPR = input$selectedData,
"Iris" = iris,
"Cars" = mtcars,
"Penguins" = palmerpenguins::penguins
)
},
ignoreNULL = TRUE,
ignoreInit = FALSE
)
output$dataTable <- DT::renderDataTable(
expr = dataSet()
)
```
In this example, any time the user changes the input field called "selectedData" (i.e., they change which data set they want to look at a.k.a. the "event"), the `dataSet` object will update. We can then use `dataSet()` to call the current data set and display that for the user in the output `dataTable`. Notice by using the `eventReactive` in this way, we don't have to write multiple instances of the `output$dataTable` to handle which data set is currently selected. Thus, `eventReactive` is a great way to keep our code DRY.
### `observeEvent`
As a contrast, `observeEvent` allows you to make use of `reactive` elements and cause things to happen BUT you can't save/store those things to be used elsewhere. We would use `observeEvent` if we want to update a plot, text, or other aspect of the UI for the user. As an example suppose that we want to create a box plot based upon the sample size and mean a user sets:
```{r observeEvent1, echo=TRUE, eval=FALSE}
observeEvent(
eventExpr = c(input$sampleSize, input$mean),
handlerExpr = {
localData <- data.frame(
x = rnorm(
n = input$sampleSize,
mean = input$mean,
sd = 1
)
)
output$dataPlot <- renderPlot(
normBox <- ggplot(
data = localData,
mapping = aes(x = x)
) +
geom_boxplot()
normBox
)
},
ignoreNULL = TRUE,
ignoreInit = FALSE
)
```
Just as in `eventReactive`, we have the `eventExpr` argument. This is the trigger(s) that the code looks for changes to. In this case, we've tied the observer to two triggers--the sample size and the mean input fields. If the user changes either one of these, the app will immediately run the code that is in the `handlerExpr` (notice the use of curly braces, `{}`, to form a code block/chunk). Notice that we create `localData` and `normBox` as part of the `handlerExpr`. Both of these objects only exist inside this particular `observeEvent`. We cannot access them anywhere else given our current coding. Remember, `observeEvent` (and other `observe` elements) do not create stored and accessible objects like `reactive` elements.
### `ignoreNULL` and `ignoreInit`
Both `eventReactive` and `observeEvent` take the arguments `ignoreNULL` and `ignoreInit`. These are two *extremely* useful arguments to consider as you code your app. Both relate to what you are using as the `eventExpr`.
When you're working with input fields or reactive elements, you can run into cases where the input will have the value `NULL`. Suppose you've created a drop down list for a user to select things from and they have yet to make a selection. In this event, the value of `input$userSelection` could be `NULL` (unless you've provided a default value). By setting `ignoreNULL = TRUE`, you instruct the `eventReactive`/`observeEvent` to not be triggered when `eventExpr` ends up with a value of `NULL`. To put this another way, setting `ignoreNULL = TRUE` will tell your app to wait for the user to do something before carrying out the event while `ignoreNULL = FALSE` will tell your app to immediately carry out the event.
When you launch a Shiny app two things happen before the user even sees the interface: R reads all of the code and generates the user interface, and R activates (initializes) any `eventReactive` and `observeEvent` you've created. If you don't want the app to activate an event on initialization, you need to set `ignoreInit = TRUE`. If you want the app immediately carry out the event when the app loads, set `ignoreInit = FALSE`. Leaving `ignoreInit = FALSE` (the default) does carry some risks. If any code that is part of the `valueExpr` or the `handlrExpr` (not just `eventExpr`) has a value that is either `NULL` or needs the user to set to something valid, you can run the risk of your app encountering a fatal error and crashing when you try to run it. The following table provides a handy guide to getting the desired behavior out of `ignoreNULL` and `ignoreInit`:
| Desired Effect | Set `ignoreNULL` to | Set `ignoreInit` to |
|---------------------------------------------------------------------|:-----------------------------:|:-----------------------------:|
| Run no matter what<br>(can cause fatal errors) | `FALSE` | `FALSE` |
| Run every time except App Initialization | `FALSE` | `TRUE` |
| Run whenever `eventExpr` isn't `NULL`<br>(the default) | `TRUE` | `FALSE` |
| Run only when the user explicitly does something<br>(provided `eventExpr` isn't `NULL`) | `TRUE` | `TRUE` |
## Validation
In addition to `ignoreNULL` and `ignoreInit`, the `shiny` package provides an additional tool that you can use in any of `render*` functions (e.g., `renderPlot`, `renderText`, `DT::renderDataTable`, etc.): validation. Validation will check that any needed inputs are available AND are valid.
The are two components that you'll need to set up validation are the `validate` call and at least one `need` call. You only need one instance of `validate` inside of each `render*` function. You will use as many `need` calls as is necessary to fully set up all of the validation checks you want in place. This will be the first thing that you write inside the `expr` argument of the `render*` function. For example, the following code ensure that the sample size set by the user is at least two before generating/displaying the scatter plot.
```{r validate1, echo=TRUE, eval=FALSE}
output$scatterPlot <- renderPlot(
expr = {
validate(
errorClass = "leftParagraphError",
need(
expr = input$sampleSize >= 2,
message = "Sample size must be greater than 2."
)
)
ggplot(
data = data.frame(
# Rest of code omitted
)
)
}
)
```
There is one named argument for `validate` and that is `errorClass`. This would be where you put extra text to fully specify which CSS class this error message should belong to. For more details on this, see the section on [CSS](#css). In most cases, you can omit this argument thereby just using the default.
Within the `need` function, you must specify two arguments: `expr` which is where you will define each validation check and `message` which is the text you want displayed for the user. For the `message` you should put something that will help the user fix the error.
## Connecting the User Interface and the Server {#renderOutput}
If you go back and look at some of the examples in the preceding sections you'll notice a reoccurring pattern when referencing outputs (e.g., `output$scatterPlot`, `output$dataPlot`, `output$dataTable`):
+ They all start with `output$` and immediately followed by a (semi-)descriptive name,
+ They all have the assignment operator, `<-`, and
+ They all are followed by a function whose name begins with `render*`
While accessing inputs from the UI is straightforward when you're in a reactive environment (e.g., `input$nameOfInput`), working the other direction (i.e., accessing the outputs) takes a bit more work. This is where the `render*` functions come into play. These functions will take whatever you place in them and prepare those objects so that they be placed into your UI.
In your UI you'll need to create a placeholder object in your code. For example, you'll need to have a line of code such as `plotOutput("fliperLengthHist")`. This creates the space in your app for the `render*` functions to send the output. For each `render*` function there is a companion `*Output` function. Here is an example for displaying a histogram:
```{r renderExample1, echo=TRUE, eval=FALSE}
freedmanDiaconis <- function(x){ifelse(IQR(x) == 0, 0.1, 2 * IQR(x) / (length(x)^(1/3)))}
ui <- list(
#..code omitted..
plotOutput(outputId = "fliperLengthHist")
)
server <- function(input, output, session) {
#..code omitted..
output$fliperLengthHist <- renderPlot(
expr = {
ggplot(
data = penguins,
mapping = aes(x = flipper_length_mm)
) +
geom_histogram(
binwidth = freedmanDiaconis,
closed = "left",
na.rm = TRUE,
fill = "blue",
color = "black"
) +
theme_bw() +
xlab("Flipper length (mm)") +
scale_y_continuous(expand = expansion(add = c(0, 5)))
},
alt = "Penguin flipper lengths appear bimodal with one group centered
around 190 mm and the second centered around 215 mm"
)
}
```
There are a few things to notice in this code example:
+ We needed to include `plotOutput` in the UI so that R/Shiny knows where to send the histogram.
+ Since we are creating a static plot (i.e., the histogram doesn't change), we did not wrap the `renderPlot` inside of an `observeEvent`.
+ Within the `renderPlot` we have two __REQUIRED__ elements:
- The `expr` is where you'll write the code to make the histogram
- The `alt` provides alt text for your plot.
- Not all `render*` functions will have the `alt` argument; `renderPlot` does and you are required to fill this argument in with meaningful text; see [Section \@ref(altText2)](#altText2) for more information.
This second required element, the `alt` argument, is how you apply alternative text ("alt text") to a plot. This text should be descriptive for what appears in plot as this is what screen readers will read out loud to low-/no- vision users. __You must have alt text on all visualizations.__
You might have also noticed some of the coding that is part of the `ggplot` calls such as the `theme_bw` and `scale_y_continuous`. Check out [Chapter \@ref(graphics)](#graphics) for more information.
### Table of `render*` and `*Output` Companions
The following table contains the main pairings of functions that you'll need to use, from most to least common:
| Type of Object | In the UI | In the Server |
|:---------------|:----------:|:------------:|
| Plots | `plotOutput` <br /> *alt required | `renderPlot` |
| Tables <br /> Requires the `DT` package| `DT::dataTableOutput` | `DT::renderDataTable` |
| Grading Icons <br /> Requires the `boastUtils` package | `uiOutput` | `boastUtils::renderIcon`|
| User Interface Elements | `uiOutput` | `renderUI` |
| Text | `textOutput` | `renderText` |
| Raw Output | `verbatimTextOutput` | `renderPrint` |
There is some flexibility between the last three items that will depend upon what exactly you're trying to do. We'll handle those on a case-by-case basis. You may see `htmlOutput` instead of `uiOutput`; we would like to use `uiOutput` as your primary choice.