-
Notifications
You must be signed in to change notification settings - Fork 41
/
18-functions.Rmd
319 lines (232 loc) · 8.27 KB
/
18-functions.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
# Functions
Like in R analyses, functions can be useful in Shiny applications to improve the functionality/optimization and make your code cleaner for quicker debugging.
**Learning objectives:**
+ Learn how to use functional programming techniques to make many controls at once
+ Understand how to use functions with reactives in the server
+ Walk through some use cases where functions may be appropriate
------------------------------------------------------
Outline
1 - File Organization
2 - UI functions
3 - Server functions
----------------------------------------------------------
## File Organization
+ *Functions* allow you to spread your code across multiple files
+ this is where organization is `r emo::ji("key")`
+ Make sure to have an `R/` directory and in it, place large functions into their own script - `R/myFunc.R`
+ Collections of smaller functions can go into 1 script, i.e. `R/utils.R`
+ Shiny uses similar storing conventions as R packages
--------------------------------------------------------------
## UI Functions
**Functions are useful in reducing duplication**
CASE 1: converting multiple sliders to a function
```{r slider1, eval=FALSE}
ui <- fluidRow(
sliderInput("alpha", "alpha", min = 0, max = 1, value = 0.5, step = 0.1),
sliderInput("beta", "beta", min = 0, max = 1, value = 0.5, step = 0.1),
sliderInput("gamma", "gamma", min = 0, max = 1, value = 0.5, step = 0.1),
sliderInput("delta", "delta", min = 0, max = 1, value = 0.5, step = 0.1)
)
```
TO:
```{r slider2, eval=FALSE}
sliderInput01 <- function(id) {
sliderInput(id, label = id, min = 0, max = 1, value = 0.5, step = 0.1)
}
ui <- fluidRow(
sliderInput01("alpha"),
sliderInput01("beta"),
sliderInput01("gamma"),
sliderInput01("delta")
)
```
Like functions in R analyses, functions in apps makes you app more readable and efficient if we need to change the behavior, we would only do it one place.
CASE 2: customized `dateInput` with `...`
```{r date-input, eval=FALSE}
usWeekDateInput <- function(inputId, ...) {
dateInput(inputId, ..., format = "dd M, yy", daysofweekdisabled = c(0, 6))
}
```
Just a reminder on `dateInput`:
```
dateInput(
inputId,
label,
value = NULL,
min = NULL,
max = NULL,
format = "yyyy-mm-dd",
startview = "month",
weekstart = 0,
language = "en",
width = NULL,
autoclose = TRUE,
datesdisabled = NULL,
daysofweekdisabled = NULL
)
```
CASE 3: radio buttons to make
```{r}
iconRadioButtons <- function(inputId, label, choices, selected = NULL) {
names <- lapply(choices, icon)
values <- if (is.null(names(choices))) names(choices) else choices
radioButtons(inputId,
label = label,
choiceNames = names, choiceValues = values, selected = selected
)
}
```
### Functional Programming
+ Using functions like `map()` may help reduce code further
```{r map-slider, eval=FALSE}
library(purrr)
# pass the variables for the slider to the built function sliderInput01
vars <- c("alpha", "beta", "gamma", "delta")
# returns a list of sliders
sliders <- map(vars, sliderInput01)
# fluidRow unpacks the list to become children of the container
ui <- fluidRow(sliders)
```
### UI as data
**Turning a UI structure into a data structure to have more varitey in inputs**
```{r ui-data, eval=FALSE}
vars <- tibble::tribble(
~ id, ~ min, ~ max,
"alpha", 0, 1,
"beta", 0, 10,
"gamma", -1, 1,
"delta", 0, 1,
)
# function where arg names match the col names
mySliderInput <- function(id, label = id, min = 0, max = 1) {
sliderInput(id, label, min = min, max = max, value = 0.5, step = 0.1)
}
# pmap to call mySliderInput over vars
sliders <- pmap(vars, mySliderInput)
```
---------------------------------------
## Server Functions
+ Long reactives should be put into a non-reactive functions outside the server
+ easier to debug
+ easier to tell what inputs were passed to a function vs a reactive expression
CASE 1: Reading uploaded data
```{r data, eval=FALSE}
server <- function(input, output, session) {
data <- reactive({
req(input$file)
ext <- tools::file_ext(input$file$name)
switch(ext,
csv = vroom::vroom(input$file$datapath, delim = ","),
tsv = vroom::vroom(input$file$datapath, delim = "\t"),
validate("Invalid file; Please upload a .csv or .tsv file")
)
})
output$head <- renderTable({
head(data(), input$n)
})
}
```
To a function that extracts:
```{r data-extract, eval=FALSE}
# IN ITS OWN FILE
load_file <- function(name, path) {
ext <- tools::file_ext(name)
switch(ext,
csv = vroom::vroom(path, delim = ","),
tsv = vroom::vroom(path, delim = "\t"),
# validate works similarly to stop() outside of shiny
validate("Invalid file; Please upload a .csv or .tsv file")
)
}
#----------------------------------------------
server <- function(input, output, session) {
data <- reactive({
req(input$file)
load_file(input$file$name, input$file$datapath)
})
output$head <- renderTable({
head(data(), input$n)
})
}
```
Note: generally better to keep reactive and non-reactive parts of app as separate as possible
### Internal functions
If the function needs to use input, output or session, it makes more sense to write the function directly in the server
```{r internal-func, eval=FALSE}
server <- function(input, output, session) {
switch_page <- function(i) {
updateTabsetPanel(input = "wizard", selected = paste0("page_", i))
}
observeEvent(input$page_12, switch_page(2))
observeEvent(input$page_21, switch_page(1))
observeEvent(input$page_23, switch_page(3))
observeEvent(input$page_32, switch_page(2))
}
```
-------------------------------------------
## Meeting Videos
### Cohort 1
`r knitr::include_url("https://www.youtube.com/embed/g927N1YRjAA")`
<details>
<summary> Meeting chat log </summary>
```
00:02:16 Federica Gazzelloni: hello
00:02:32 russ: Hi everyone
00:06:20 russ: A couple of the regulars apologised that they couldn't make it this week
00:30:10 Federica: https://shiny.rstudio.com/reference/shiny/1.6.0/radioButtons.html
00:34:44 Federica: 2.2.5 Limited choices
There are two different approaches to allow the user to choose from a prespecified set of options: selectInput() and radioButtons().
```
</details>
### Cohort 2
`r knitr::include_url("https://www.youtube.com/embed/aQHyWOEvJAI")`
<details>
<summary> Meeting chat log </summary>
```
00:27:24 Kevin Gilds: https://www.rostrum.blog/2021/04/14/gha-readme/
00:27:42 Kevin Gilds: Update README with Github actions
00:51:26 collinberke: https://www.youtube.com/watch?v=BEyiFVWaTtc&list=PL3x6DOfs2NGjtn1_4BSX99R5wrLjK7XvY&index=19
00:51:35 collinberke: https://www.youtube.com/watch?v=tWEa6JwlJ3c&list=PL3x6DOfs2NGjtn1_4BSX99R5wrLjK7XvY&index=21
```
</details>
`r knitr::include_url("https://www.youtube.com/embed/5YmZiyVgUeg")`
### Cohort 3
`r knitr::include_url("https://www.youtube.com/embed/U2vtBWwWUU8")`
<details>
<summary>Meeting chat log</summary>
```
00:15:32 FG: https://engineering-shiny.org/
00:29:21 Oluwafemi Oyedele: ObserveEvent Respond to "event-like" reactive inputs, values, and expressions.
00:32:12 Brendan Lam: This was great, thanks!
```
</details>
### Cohort 4
`r knitr::include_url("https://www.youtube.com/embed/7b5UQvzgPxo")`
<details>
<summary>Meeting chat log</summary>
```
00:22:45 Trevin Flickinger: Hi everyone 👋
00:22:58 Lydia Gibson: Hello
00:28:45 Trevin Flickinger: No questions
00:28:48 Lucio Cornejo: all good so far
00:32:54 Trevin Flickinger: yes
00:36:03 Trevin Flickinger: I can see it now
00:36:11 Trevin Flickinger: Sorry I can only type today
00:44:31 Matthew Efoli: https://dcl-prog.stanford.edu/purrr-parallel.html#:~:text=The%20pmap()%20functions%20work,to%20supply%20to%20your%20function.
00:59:07 Lucio Cornejo: session = getReactiveDomain()
01:00:04 Lucio Cornejo: session = getDefaultReactiveDomain()
01:01:04 Trevin Flickinger: We’ll get more into sessions next week with modules
01:01:55 Lucio Cornejo: same over here
01:03:16 Trevin Flickinger: Thanks Matthew! Nothing from me
01:03:41 Trevin Flickinger: See everyone next week 😄
01:03:41 Lucio Cornejo: Also excited for next week. Thank you, see you next everyone
```
</details>
### Cohort 5
`r knitr::include_url("https://www.youtube.com/embed/URL")`
<details>
<summary>Meeting chat log</summary>
```
LOG
```
</details>