-
Notifications
You must be signed in to change notification settings - Fork 5
/
shiny_datacamp.Rmd
1310 lines (1155 loc) · 44.4 KB
/
shiny_datacamp.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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
title: "Building Web Applications with Shiny in R DataCamp"
output: html_notebook
---
## Get Started with Shiny
### "Hello, World" app input (UI)
The "Hello, World!!!" app, while enthusiastic, could be more fun. Extend the app and have it wish a hello to a specific person. A user will enter a name and the app will update to wish that name "Hello".
For users to enter text, you'll have to use a text-specific shiny input function to capture it. Recall that shiny makes available a number of input functions depending on what kind of input you'd like to capture from your users.
```{r}
ui <- fluidPage(
# CODE BELOW: Add a text input "name"
textInput("name", "What is your name?")
)
server <- function(input, output) {
}
shinyApp(ui = ui, server = server)
```
### "Hello, World" app output (UI/Server)
To finish up your "Hello, world" app, you'll have to actually display the text that's input.
Recall this is how you construct an output from an input:
# Render output y using input x
```
output$y <- renderText({
input$x
})
```
```{r}
ui <- fluidPage(
textInput("name", "What is your name?"),
# CODE BELOW: Display the text output, greeting
# Make sure to add a comma after textInput()
textOutput("greeting")
)
server <- function(input, output) {
# CODE BELOW: Render a text output, greeting
output$greeting <- renderText({
paste("Hello,", input$name)
})
}
shinyApp(ui = ui, server = server)
```
### Add input (UI)
This app will allow users to enter a baby name and visualize the popularity of that name over time.
The first step is to add a text input to the UI that will allow a user to enter their (or any other) name. Try using the optional default argument this time around.
### Add output (UI/Server)
The next step in building your app is to add an empty plot as a placeholder. Recall that in order to add a plot p assigned to an object named x to a Shiny app, you need to:
Render the plot object using renderPlot({p}).
Assign the rendered plot to output$x.
Display the plot in the UI using plotOutput("x").
### Update layout (UI)
You can use layout functions provided by Shiny to arrange the UI elements. In this case, we want to use a sidebarLayout(), where the input is placed inside a sidebarPanel() and the output is placed inside the mainPanel(). You can use this template to update the layout of your app.
```
sidebarLayout(
sidebarPanel(p("This goes into the sidebar on the left")),
mainPanel(p("This goes into the panel on the right"))
)
```
We have pre-loaded the shiny and ggplot2 packages for you. Note that p('hello') returns an HTML paragraph with the text "hello".
```{r}
ui <- fluidPage(
titlePanel("Baby Name Explorer"),
# CODE BELOW: Add a sidebarLayout, sidebarPanel, and mainPanel
sidebarLayout(
sidebarPanel(
textInput('name', 'Enter Name', 'David'),
),
mainPanel(
plotOutput('trend'),
)
)
)
server <- function(input, output, session) {
output$trend <- renderPlot({
ggplot()
})
}
shinyApp(ui = ui, server = server)
```
### Update output (server)
You are almost there! The final step is to update the plot output to display a line plot of prop vs. year, colored by sex, for the name that was input by the user. You can use this plot template to create your plot:
ggplot(subset(babynames, name == "David")) +
geom_line(aes(x = year, y = prop, color = sex))
Recall that a user input named foo can be accessed as input$foo in the server. We have already pre-loaded the shiny and ggplot2 packages, as well as the babynames dataset.
```{r}
ui <- fluidPage(
titlePanel("Baby Name Explorer"),
sidebarLayout(
sidebarPanel(textInput('name', 'Enter Name', 'David')),
mainPanel(plotOutput('trend'))
)
)
server <- function(input, output, session) {
output$trend <- renderPlot({
# CODE BELOW: Update to display a line plot of the input name
ggplot(subset(babynames, name == input$name)) +
geom_line(
aes(x = year, y = prop, color = sex))
})
}
shinyApp(ui = ui, server = server)
```
## Inputs, Outputs, and Layouts
### Add a select input
Adding an input to a shiny app is a two step process, where you first add an ___Input(“x”) function to the UI and then access its value in the server using input$x.
For example, if you want users to choose an animal from a list, you can use a selectInput, and refer to the chosen value as input$animal:
```
selectInput(
'animal',
'Select Animal',
selected = 'Cat',
choices = c('Dog', 'Cat')
)
```
In this exercise, you will build a Shiny app that lets users visualize the top 10 most popular names by sex by adding an input to let them choose the sex.
```{r}
ui <- fluidPage(
titlePanel("What's in a Name?"),
# CODE BELOW: Add select input named "sex" to choose between "M" and "F"
selectInput(
'sex',
'Select Sex',
selected = 'F',
choices = c('M', 'F')
),
# Add plot output to display top 10 most popular names
plotOutput('plot_top_10_names')
)
server <- function(input, output, session){
# Render plot of top 10 most popular names
output$plot_top_10_names <- renderPlot({
# Get top 10 names by sex and year
top_10_names <- babynames %>%
# MODIFY CODE BELOW: Filter for the selected sex
filter(sex == input$sex) %>%
filter(year == 1900) %>%
top_n(10, prop)
# Plot top 10 names by sex and year
ggplot(top_10_names, aes(x = name, y = prop)) +
geom_col(fill = "#263e63")
})
}
shinyApp(ui = ui, server = server)
```
### Add a slider input to select year
Slider inputs are great for numeric inputs, both when you'd like users to choose from a range of values and also when they should choose a static value from a set of options, but you want to be more creative than using a selectInput().
Adjust your app displaying top 10 names for a year by adding a slider to select a specific year available in babynames.
```{r}
ui <- fluidPage(
titlePanel("What's in a Name?"),
# Add select input named "sex" to choose between "M" and "F"
selectInput('sex', 'Select Sex', choices = c("F", "M")),
# CODE BELOW: Add slider input named 'year' to select years (1900 - 2010)
sliderInput("year", "Year", value=1900, min=1900,max=2010),
# Add plot output to display top 10 most popular names
plotOutput('plot_top_10_names')
)
server <- function(input, output, session){
# Render plot of top 10 most popular names
output$plot_top_10_names <- renderPlot({
# Get top 10 names by sex and year
top_10_names <- babynames %>%
filter(sex == input$sex) %>%
# MODIFY CODE BELOW: Filter for the selected year
filter(year == input$year) %>%
top_n(10, prop)
# Plot top 10 names by sex and year
ggplot(top_10_names, aes(x = name, y = prop)) +
geom_col(fill = "#263e63")
})
}
shinyApp(ui = ui, server = server)
```
### Add a table output
In order to add any output to a Shiny app, you need to:
Create the output (plot, table, text, etc.).
Render the output object using the appropriate render___ function.
Assign the rendered object to output$x.
Add the output to the UI using the appropriate ___Output function.
In this exercise, you will add a table output to the baby names explorer app you created earlier. Don't forget that code inside a render___ function needs to be wrapped inside curly braces (e.g. renderPlot({...})).
```{r}
ui <- fluidPage(
titlePanel("What's in a Name?"),
# Add select input named "sex" to choose between "M" and "F"
selectInput('sex', 'Select Sex', choices = c("F", "M")),
# Add slider input named "year" to select year between 1900 and 2010
sliderInput('year', 'Select Year', min = 1900, max = 2010, value = 1900),
# CODE BELOW: Add table output named "table_top_10_names"
tableOutput('table_top_10_names')
)
server <- function(input, output, session){
# Function to create a data frame of top 10 names by sex and year
top_10_names <- function(){
top_10_names <- babynames %>%
filter(sex == input$sex) %>%
filter(year == input$year) %>%
top_n(10, prop)
}
# CODE BELOW: Render a table output named "table_top_10_names"
output$table_top_10_names <- renderTable({
top_10_names()
})
}
shinyApp(ui = ui, server = server)
```
### Add an interactive table output
There are multiple htmlwidgets packages like DT, leaflet, plotly, etc. that provide highly interactive outputs and can be easily integrated into Shiny apps using almost the same pattern. For example, you can turn a static table in a Shiny app into an interactive table using the DT package:
Create an interactive table using DT::datatable().
Render it using DT::renderDT().
Display it using DT::DTOutput().
In this exercise, you will update the app created previously, replacing the static table with an interactive table.
```{r}
ui <- fluidPage(
titlePanel("What's in a Name?"),
# Add select input named "sex" to choose between "M" and "F"
selectInput('sex', 'Select Sex', choices = c("M", "F")),
# Add slider input named "year" to select year between 1900 and 2010
sliderInput('year', 'Select Year', min = 1900, max = 2010, value = 1900),
# Add plot output to display top 10 most popular names
DT::DTOutput('table_top_10_names')
)
server <- function(input, output, session){
top_10_names <- function(){
babynames %>%
filter(sex == input$sex) %>%
filter(year == input$year) %>%
top_n(10, prop)
}
# MODIFY CODE BELOW: Render a DT output named "table_top_10_names"
output$table_top_10_names <- DT::renderDT({
top_10_names()
})
}
shinyApp(ui = ui, server = server)
```
### Add interactive plot output
Similar to creating interactive tables, you can easily turn a static plot created using ggplot2 into an interactive plot using the plotly package. To render an interactive plot, use plotly::renderPlotly(), and display it using plotly::plotlyOutput().
Remember that just like with other render functions, the code inside plotly::renderPlotly() should be wrapped in curly braces {}!
```{r}
ui <- fluidPage(
selectInput('name', 'Select Name', top_trendy_names$name),
# CODE BELOW: Add a plotly output named 'plot_trendy_names'
plotly::plotlyOutput('plot_trendy_names')
)
server <- function(input, output, session){
# Function to plot trends in a name
plot_trends <- function(){
babynames %>%
filter(name == input$name) %>%
ggplot(aes(x = year, y = n)) +
geom_col()
}
# CODE BELOW: Render a plotly output named 'plot_trendy_names'
output$plot_trendy_names <- plotly::renderPlotly({
plot_trends()
})
}
shinyApp(ui = ui, server = server)
```
### Sidebar layouts
Layout functions allow inputs and outputs to be visually arranged in the UI. A well-chosen layout makes a Shiny app aesthetically more appealing, and also improves the user experience.
In this exercise, you will modify the layout of a Shiny app that lets users explore the popularity of trendy names.
```{r}
ui <- fluidPage(
# MODIFY CODE BLOCK BELOW: Wrap in a sidebarLayout
sidebarLayout(
# MODIFY CODE BELOW: Wrap in a sidebarPanel
sidebarPanel(
selectInput('name', 'Select Name', top_trendy_names$name)
),
# MODIFY CODE BELOW: Wrap in a mainPanel
mainPanel(
plotly::plotlyOutput('plot_trendy_names'),
DT::DTOutput('table_trendy_names')
)
)
)
server <- function(input, output, session){
# Function to plot trends in a name
plot_trends <- function(){
babynames %>%
filter(name == input$name) %>%
ggplot(aes(x = year, y = n)) +
geom_col()
}
output$plot_trendy_names <- plotly::renderPlotly({
plot_trends()
})
output$table_trendy_names <- DT::renderDT({
babynames %>%
filter(name == input$name)
})
}
shinyApp(ui = ui, server = server)
```
### Tab layouts
Displaying several tables and plots on the same page can lead to visual clutter and distract users of the app. In such cases, the tab layout comes in handy, as it allows different outputs to be displayed as tabs.
In this exercise, we will start with the Shiny app using the sidebar layout from the last exercise and modify it to use tabs. This exercise should also make it very clear that Shiny makes it really easy to switch app layouts with only a few modifications to the code.
```{r}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput('name', 'Select Name', top_trendy_names$name)
),
mainPanel(
# MODIFY CODE BLOCK BELOW: Wrap in a tabsetPanel
tabsetPanel(
# MODIFY CODE BELOW: Wrap in a tabPanel providing an appropriate label
tabPanel('Plot', plotly::plotlyOutput('plot_trendy_names')),
# MODIFY CODE BELOW: Wrap in a tabPanel providing an appropriate label
tabPanel('Table', DT::DTOutput('table_trendy_names'))
)
)
)
)
server <- function(input, output, session){
# Function to plot trends in a name
plot_trends <- function(){
babynames %>%
filter(name == input$name) %>%
ggplot(aes(x = year, y = n)) +
geom_col()
}
output$plot_trendy_names <- plotly::renderPlotly({
plot_trends()
})
output$table_trendy_names <- DT::renderDT({
babynames %>%
filter(name == input$name)
})
}
shinyApp(ui = ui, server = server)
```
### Themes
Shiny makes it easy to customize the theme of an app. The UI functions in Shiny make use of Twitter Bootstrap, a popular framework for building web applications. Bootswatch extends Bootstrap by making it really easy to skin an application with minimal code changes.
In this exercise, you will add a title panel to your app, use the theme selector to explore different themes, and apply then a theme of your choice.
```{r}
ui <- fluidPage(
# CODE BELOW: Add a titlePanel with an appropriate title
titlePanel("Theme Selector"),
# REPLACE CODE BELOW: with theme = shinythemes::shinytheme("<your theme>")
#shinythemes::themeSelector(),
theme = shinytheme("united"),
sidebarLayout(
sidebarPanel(
selectInput('name', 'Select Name', top_trendy_names$name)
),
mainPanel(
tabsetPanel(
tabPanel('Plot', plotly::plotlyOutput('plot_trendy_names')),
tabPanel('Table', DT::DTOutput('table_trendy_names'))
)
)
)
)
server <- function(input, output, session){
# Function to plot trends in a name
plot_trends <- function(){
babynames %>%
filter(name == input$name) %>%
ggplot(aes(x = year, y = n)) +
geom_col()
}
output$plot_trendy_names <- plotly::renderPlotly({
plot_trends()
})
output$table_trendy_names <- DT::renderDT({
babynames %>%
filter(name == input$name)
})
}
shinyApp(ui = ui, server = server)
```
### App 1: Multilingual Greeting
Congratulations! You are now ready to start building your own apps. The best way to learn Shiny is by deconstructing an existing app and rebuilding it from scratch.
In this exercise, you are going to build a Shiny app that allows you to enter your name and select a greeting (Hello/Bonjour), and returns "Hello, Kaelen", when the user is Kaelen.
```{r}
ui <- fluidPage(
selectInput('greeting_type', 'Select greeting', c("Hello", "Bonjour")),
textInput('name', 'Enter your name'),
textOutput('greeting')
)
server <- function(input, output, session) {
output$greeting <- renderText({
paste(input$greeting_type, input$name, sep = ", ")
})
}
shinyApp(ui = ui, server = server)
```
### App 2: Popular Baby Names
Building a Shiny app is a modular process. You start with the UI, then you work on the server code, building outputs based on the user inputs. The more you practice this approach deliberately, the easier it will become to build good apps.
You will now build a Shiny app that lets a user choose sex and year, and will display the top 10 most popular names in that year as a column plot of proportion of births (prop) by name (name).
```{r}
ui <- fluidPage(
titlePanel("Most Popular Names"),
sidebarLayout(
sidebarPanel(
selectInput('sex', 'Select Sex', c("M", "F")),
sliderInput('year', 'Select Year', 1880, 2013, 1900)
),
mainPanel(
plotOutput('plot')
)
)
)
server <- function(input, output, session) {
output$plot <- renderPlot({
top_names_by_sex_year <- get_top_names(input$year, input$sex)
ggplot(top_names_by_sex_year, aes(x = name, y = prop)) +
geom_col()
})
}
shinyApp(ui = ui, server = server)
```
### App 3: Popular Baby Names Redux
Great! Hope you enjoyed building that app displaying popular baby names as a column plot. Let us wrap this chapter up by enhancing the app we built earlier by adding a table showing the top 10 baby names as a tab.
```{r}
ui <- fluidPage(
titlePanel("Most Popular Names"),
sidebarLayout(
sidebarPanel(
selectInput('sex', 'Select Sex', c("M", "F")),
sliderInput('year', 'Select Year', 1880, 2013, 1900)
),
mainPanel(
tabsetPanel(
tabPanel('Plot', plotOutput('plot')),
tabPanel('Table', tableOutput('table'))
)
)
)
)
server <- function(input, output, session) {
output$plot <- renderPlot({
d <- get_top_names(input$year, input$sex)
qplot(name, prop, data = d, geom = 'col')
})
output$table <- renderTable({
get_top_names(input$year, input$sex)
})
}
shinyApp(ui = ui, server = server)
```
## Reactive Programming
### Add a reactive expression
A reactive expression is an R expression that uses widget input and returns a value. The reactive expression will update this value whenever the original widget changes. Reactive expressions are lazy and cached.
In this exercise, you will encapsulate a repeated computation as a reactive expression.
```{r}
server <- function(input, output, session) {
# CODE BELOW: Add a reactive expression rval_bmi to calculate BMI
rval_bmi <- reactive({
input$weight/(input$height^2)
})
output$bmi <- renderText({
# MODIFY CODE BELOW: Replace right-hand-side with reactive expression
bmi <- rval_bmi()
paste("Your BMI is", round(bmi, 1))
})
output$bmi_range <- renderText({
# MODIFY CODE BELOW: Replace right-hand-side with reactive expression
bmi <- rval_bmi()
bmi_status <- cut(bmi,
breaks = c(0, 18.5, 24.9, 29.9, 40),
labels = c('underweight', 'healthy', 'overweight', 'obese')
)
paste("You are", bmi_status)
})
}
ui <- fluidPage(
titlePanel('BMI Calculator'),
sidebarLayout(
sidebarPanel(
numericInput('height', 'Enter your height in meters', 1.5, 1, 2),
numericInput('weight', 'Enter your weight in Kilograms', 60, 45, 120)
),
mainPanel(
textOutput("bmi"),
textOutput("bmi_range")
)
)
)
shinyApp(ui = ui, server = server)
```
### Observers vs reactives
- reactive() is for calculating values, without side effects
- observe() is for calculating values, with side effects
- reactive expressions return values, but observers do not
### Add another reactive expression
A reactive expression can call other reactive expressions. This allows you to modularize computations and ensure that they are NOT executed repeatedly. Mastering the use of reactive expressions is key to building performant Shiny applications.
In this exercise, you will use a reactive expression to calculate the health status based on the BMI.
```{r}
server <- function(input, output, session) {
rval_bmi <- reactive({
input$weight/(input$height^2)
})
# CODE BELOW: Add a reactive expression rval_bmi_status to
# return health status as underweight etc. based on inputs
rval_bmi_status <- reactive({
cut(rval_bmi(),
breaks = c(0, 18.5, 24.9, 29.9, 40),
labels = c('underweight', 'healthy', 'overweight', 'obese')
)
})
output$bmi <- renderText({
bmi <- rval_bmi()
paste("Your BMI is", round(bmi, 1))
})
output$bmi_status <- renderText({
# MODIFY CODE BELOW: Replace right-hand-side with
# reactive expression rval_bmi_status
bmi_status <- rval_bmi_status()
paste("You are", bmi_status)
})
}
ui <- fluidPage(
titlePanel('BMI Calculator'),
sidebarLayout(
sidebarPanel(
numericInput('height', 'Enter your height in meters', 1.5, 1, 2),
numericInput('weight', 'Enter your weight in Kilograms', 60, 45, 120)
),
mainPanel(
textOutput("bmi"),
textOutput("bmi_status")
)
)
)
shinyApp(ui = ui, server = server)
```
### Add an observer to display notifications
Recall that an observer is used for side effects, like displaying a plot, table, or text in the browser. By default an observer triggers an action, whenever one of its underlying dependencies change.
In this exercise, you will use an observer to display a notification in the browser, using observe() and showNotification(). As we are triggering an action using an observer, we do NOT need to use a render***() function or assign the results to an output.
```{r}
ui <- fluidPage(
textInput('name', 'Enter your name')
)
server <- function(input, output, session) {
# CODE BELOW: Add an observer to display a notification
# 'You have entered the name xxxx' where xxxx is the name
observe({
showNotification(
paste('You have entered the name', input$name)
)
})
}
shinyApp(ui = ui, server = server)
```
### Stop reactions with isolate()
Ordinarily, the simple act of reading a reactive value is sufficient to set up a relationship, where a change to the reactive value will cause the calling expression to re-execute. The isolate() function allows an expression to read a reactive value without triggering re-execution when its value changes.
In this exercise, you will use the isolate() function to stop reactive flow.
```{r}
server <- function(input, output, session) {
rval_bmi <- reactive({
input$weight/(input$height^2)
})
output$bmi <- renderText({
bmi <- rval_bmi()
# MODIFY CODE BELOW:
# Use isolate to stop output from updating when name changes.
paste("Hi", isolate({input$name}), ". Your BMI is", round(bmi, 1))
})
}
ui <- fluidPage(
titlePanel('BMI Calculator'),
sidebarLayout(
sidebarPanel(
textInput('name', 'Enter your name'),
numericInput('height', 'Enter your height (in m)', 1.5, 1, 2, step = 0.1),
numericInput('weight', 'Enter your weight (in Kg)', 60, 45, 120)
),
mainPanel(
textOutput("bmi")
)
)
)
shinyApp(ui = ui, server = server)
```
### Delay reactions with eventReactive()
Shiny's reactive programming framework is designed such that any changes to inputs automatically updates the outputs that depend on it. In some situations, we might want more explicitly control the trigger that causes the update.
The function eventReactive() is used to compute a reactive value that only updates in response to a specific event.
```{r}
server <- function(input, output, session) {
# MODIFY CODE BELOW: Use eventReactive to delay the execution of the
# calculation until the user clicks on the show_bmi button (Show BMI)
rval_bmi <- eventReactive(input$show_bmi, {
input$weight/(input$height^2)
})
output$bmi <- renderText({
bmi <- rval_bmi()
paste("Hi", input$name, ". Your BMI is", round(bmi, 1))
})
}
ui <- fluidPage(
titlePanel('BMI Calculator'),
sidebarLayout(
sidebarPanel(
textInput('name', 'Enter your name'),
numericInput('height', 'Enter height (in m)', 1.5, 1, 2, step = 0.1),
numericInput('weight', 'Enter weight (in Kg)', 60, 45, 120),
actionButton("show_bmi", "Show BMI")
),
mainPanel(
textOutput("bmi")
)
)
)
shinyApp(ui = ui, server = server)
```
### Trigger reactions with observeEvent()
There are times when you want to perform an action in response to an event. For example, you might want to let the app user download a table as a CSV file, when they click on a "Download" button. Or, you might want to display a notification or modal dialog, in response to a click.
The observeEvent() function allows you to achieve this. It accepts two arguments:
The event you want to respond to.
The function that should be called whenever the event occurs.
In this exercise, you will use observeEvent() to display a modal dialog with help text, when the user clicks on a button labelled "Help". The help text has already been assigned to the variable bmi_help_text.
```{r}
server <- function(input, output, session) {
# MODIFY CODE BELOW: Wrap in observeEvent() so the help text
# is displayed when a user clicks on the Help button.
observeEvent(input$show_help, {
# Display a modal dialog with bmi_help_text
# MODIFY CODE BELOW: Uncomment code
showModal(modalDialog(bmi_help_text))
})
rv_bmi <- eventReactive(input$show_bmi, {
input$weight/(input$height^2)
})
output$bmi <- renderText({
bmi <- rv_bmi()
paste("Hi", input$name, ". Your BMI is", round(bmi, 1))
})
}
ui <- fluidPage(
titlePanel('BMI Calculator'),
sidebarLayout(
sidebarPanel(
textInput('name', 'Enter your name'),
numericInput('height', 'Enter your height in meters', 1.5, 1, 2),
numericInput('weight', 'Enter your weight in Kilograms', 60, 45, 120),
actionButton("show_bmi", "Show BMI"),
# CODE BELOW: Add an action button named "show_help"
actionButton("show_help", "Help")
),
mainPanel(
textOutput("bmi")
)
)
)
shinyApp(ui = ui, server = server)
```
### Convert height from inches to centimeters
Earlier in the chapter, we practiced stopping, delaying, and triggering apps. This is a very common pattern of programming in Shiny that enables your apps to be optimized for speed (and only re-run when something is updated and your user would like to re-run the app.)
In this exercise, you'll practice some of those concepts again, just to make sure you truly understand them. Instead of calculating BMI, this app converts height in inches to centimeters.
```{r}
server <- function(input, output, session) {
# MODIFY CODE BELOW: Delay the height calculation until
# the show button is pressed
rval_height_cm <- eventReactive(input$show_height_cm, {
input$height * 2.54
})
output$height_cm <- renderText({
height_cm <- rval_height_cm()
paste("Your height in centimeters is", height_cm, "cm")
})
}
ui <- fluidPage(
titlePanel("Inches to Centimeters Conversion"),
sidebarLayout(
sidebarPanel(
numericInput("height", "Height (in)", 60),
actionButton("show_height_cm", "Show height in cm")
),
mainPanel(
textOutput("height_cm")
)
)
)
shinyApp(ui = ui, server = server)
```
## Build Shiny Apps
### Alien sightings: add inputs
The National UFO Reporting Center (NUFORC) has collected sightings data throughout the last century. This app is going to allow users to select a U.S. state and a time period in which the sightings occurred.
```{r}
ui <- fluidPage(
# CODE BELOW: Add a title
titlePanel("UFO Sightings"),
sidebarLayout(
sidebarPanel(
# CODE BELOW: One input to select a U.S. state
# And one input to select a range of dates
selectInput("state",
"Choose a U.S. state:",
choices = unique(usa_ufo_sightings$state)),
dateRangeInput("dates", "Choose a date range:",
start = "1920-01-01",
end = "1950-01-01"
)
),
mainPanel()
)
)
server <- function(input, output) {
}
shinyApp(ui, server)
```
### Alien sightings: add outputs
Now that the dashboard has inputs, you should build your outputs to actually see information about the reported UFO sightings.
Recall there will be two, a plot and a table. The plot should show the number sighted, by shape, for the selected state and time period. The table should show, for the selected state and time period, the number sighted, plus the average, median, minimum, and maximum duration (duration_sec) of the sightings. This will require using dplyr, or a method of your choosing, to manipulate the usa_ufo_sightings data.
```{r}
server <- function(input, output) {
# CODE BELOW: Create a plot output name 'shapes', of sightings by shape,
# For the selected inputs
output$shapes <- renderPlot({
usa_ufo_sightings %>%
filter(state == input$state,
date_sighted >= input$dates[1],
date_sighted <= input$dates[2]) %>%
ggplot(aes(shape)) +
geom_bar() +
labs(x = "Shape", y = "# Sighted")
})
# CODE BELOW: Create a table output named 'duration_table', by shape,
# of # sighted, plus mean, median, max, and min duration of sightings
# for the selected inputs
output$duration_table <- renderTable({
usa_ufo_sightings %>%
filter(
state == input$state,
date_sighted >= input$dates[1],
date_sighted <= input$dates[2]
) %>%
group_by(shape) %>%
summarize(
nb_sighted = n(),
avg_duration = mean(duration_sec),
median_duration = median(duration_sec),
min_duration = min(duration_sec),
max_duration = max(duration_sec)
)
})
}
ui <- fluidPage(
titlePanel("UFO Sightings"),
sidebarLayout(
sidebarPanel(
selectInput("state", "Choose a U.S. state:", choices = unique(usa_ufo_sightings$state)),
dateRangeInput("dates", "Choose a date range:",
start = "1920-01-01",
end = "1950-01-01")
),
mainPanel(
# Add plot output named 'shapes'
plotOutput("shapes"),
# Add table output named 'duration_table'
tableOutput("duration_table")
)
)
)
shinyApp(ui, server)
```
### Alien sightings: tab layout
As-is, the app is sort of busy with the graph on top of the table. Given that this is a dashboard, it might be nice to instead separate the two outputs.
The last step in building your dashboard is to take the plotOutput() and tableOutput() you've built and add the tab layout.
```{r}
ui <- fluidPage(
titlePanel("UFO Sightings"),
sidebarPanel(
selectInput("state", "Choose a U.S. state:", choices = unique(usa_ufo_sightings$state)),
dateRangeInput("dates", "Choose a date range:",
start = "1920-01-01",
end = "1950-01-01"
)
),
# MODIFY CODE BELOW: Create a tab layout for the dashboard
mainPanel(
tabsetPanel(
tabPanel("Number sighted", plotOutput("shapes")),
tabPanel("Duration table", tableOutput("duration_table"))
)
)
)
server <- function(input, output) {
output$shapes <- renderPlot({
usa_ufo_sightings %>%
filter(
state == input$state,
date_sighted >= input$dates[1],
date_sighted <= input$dates[2]
) %>%
ggplot(aes(shape)) +
geom_bar() +
labs(
x = "Shape",
y = "# Sighted"
)
})
output$duration_table <- renderTable({
usa_ufo_sightings %>%
filter(
state == input$state,
date_sighted >= input$dates[1],
date_sighted <= input$dates[2]
) %>%
group_by(shape) %>%
summarize(
nb_sighted = n(),
avg_duration_min = mean(duration_sec) / 60,
median_duration_min = median(duration_sec) / 60,
min_duration_min = min(duration_sec) / 60,
max_duration_min = max(duration_sec) / 60
)
})
}
shinyApp(ui, server)
```
### Explore the Mental Health in Tech 2014 Survey
Don't be intimidated, but in this exercise, you're going to build the entirety of this app (minus the custom error message) in one go!
For this app, you'll be using the questions "Do you think that discussing a mental health issue with your employer would have negative consequences?" (the mental_health_consequence variable) and "Do you feel that your employer takes mental health as seriously as physical health?" (mental_vs_physical) as multi-selector inputs, then displaying a histogram of the Age of respondents. To see the choices for these variables, count() them in the console.
```{r}
ui <- fluidPage(
# CODE BELOW: Add an appropriate title
titlePanel("2014 Mental Health in Tech Survey"),
sidebarPanel(
# CODE BELOW: Add a checkboxGroupInput
checkboxGroupInput(
inputId = "mental_health_consequence",
label = "Do you think that discussing a mental health issue with your employer would have negative consequences?",
choices = c("Maybe", "Yes", "No"),
selected = "Maybe"
),
# CODE BELOW: Add a pickerInput
pickerInput(
inputId = "mental_vs_physical",
label = "Do you feel that your employer takes mental health as seriously as physical health?",
choices = c("Don't Know", "No", "Yes"),
multiple = TRUE
)
),
mainPanel(
# CODE BELOW: Display the output
plotOutput("age")
)
)
server <- function(input, output, session) {
# CODE BELOW: Build a histogram of the age of respondents
# Filtered by the two inputs
output$age <- renderPlot({
mental_health_survey %>%
filter(
mental_health_consequence %in% input$mental_health_consequence,
mental_vs_physical %in% input$mental_vs_physical
) %>%
ggplot(aes(Age)) +
geom_histogram()
})
}
shinyApp(ui, server)
```
### Validate that a user made a selection
Recall from the video that though it is often good practice to select a default value for your selector inputs, if one should be excluded, you can throw a custom error message to your users that clues them in on what they need to do for the app to run successfully.
We saw in the last exercise that, without a default value for the pickerInput(), the plot is simply blank. Instead of a blank plot, in this exercise you'll show users a custom error message telling them to make the correct selection needed to get the app working.
```{r}
server <- function(input, output, session) {
output$age <- renderPlot({
# MODIFY CODE BELOW: Add validation that user selected a 3rd input
validate(
need(
input$mental_vs_physical != "",
"Make a selection for mental vs. physical health."
)