Class Meeting 22 (8) DashR: Part II
This lecture is 95% completed. I need to still explain a bit about quosures and then we’re all set.
22.1 Today’s Agenda
- Announcements: (5 mins)
- Reminder to give anonymous feedback on how things are going with online class
- Please create an issue in our
Discussion
repo if you have questions! - Reminder about milestone04 and assignment04
- Go over teamwork document
- Part 1: Review of Dash app (5 mins)
- Demo from cm107: adding a ggplot object
- Basic Dash anatomy
- Part 2: Organizing your Dash app (20 mins)
- Make Plot
- Assign components to variables
- Part 3: Callbacks in Dash (45 mins)
- Template App
- Interlude:
!!sym()
syntax - Call backs in Dash
- Part 4: Bonus/Optional - Change the scale of the y-axis using a button
22.2 Part 1: Review of Dash app (5 mins)
Let’s make sure everyone is on the same page about the last demo from cm107: a ggplot graph inside a Dash app.
Last time we looked at this schematic as a bare-bones skeleton of a Dash app.
A few things were left out, here they are now:
Roughly corresponding to the above, here is a full template of a DashR app:
# author: YOUR NAME
# date: THE DATE
"This script is the main file that creates a Dash app.
Usage: app.R
"
## Load libraries
## Make plot
## Assign components to variables
## Create Dash instance
app <- Dash$new()
## Specify App layout
app$layout(
htmlDiv(
list(
### Add components here
)
)
)
## App Callbacks
## Update Plot
## Run app
app$run_server(debug=TRUE)
# command to add dash app in Rstudio viewer:
# rstudioapi::viewer("http://127.0.0.1:8050")
I have also created a template on repl.it that you can fork. I will update this template as we learn new things (like callbacks and layouts)
22.3 Part 2: Organizing your Dash app (20 mins)
Today we will look at a complete dash app. But before we get there, we need to do a bit more housekeeping:
The following two subsections should be updated in the app.R
file of cm107!!
22.3.1 (NEW) Make Plot
- To make your app more organized, you may optionally create a function that outputs a ggplot object
- We will call this function
make_plot
to be consistent, but it can be called anything and there can even be multiple functions. - The input arguments of the
make_plot
function will be the features that the user can filter or select for. - The
make_plot
function will filter the data based on the provided input arguments and ouput a plot based on the filtered data.
For example, let’s turn the creation of a plot in last demo from cm107 into a function (make_plot
) that outputs a ggplotly object:
## YOUR SOLUTION HERE
make_plot <- function() {
# add a ggplot
plot <- mtcars %>%
ggplot() +
theme_bw() +
geom_point(aes(x = mpg, y = hp) ) +
labs(x = 'Fuel efficiency (mpg)',
y = 'Horsepower (hp)') +
ggtitle(("Horsepower and Fuel efficiency for "))
ggplotly(plot)
}
22.3.2 (NEW) Assign components to variables
In order to keep app$layout()
relatively clean, tidy, and easy to debug, I recommend you create your components as variables first and then pass those into the list of app$layout()
.
For example:
## YOUR SOLUTION HERE
# Assign components to variables
heading_helloworld = htmlH1('Hello world!! Dash application')
heading_subtitle = htmlH2('This is a subheading')
graph_1 = dccGraph(id='mtcars',figure = make_plot())
# Specify App layout
app$layout(
htmlDiv(
list(
heading_helloworld,
heading_subtitle,
graph_1
)
)
)
22.4 Part 3: Callbacks in Dash (45 mins)
So far, you’ve learnt how to create Dash core components (ex. dropdown menu filter) and plots. Let’s (finally) now try to link those two things, and interactively update our plots based on the user selection.
To do so, we are going to use what are called callbacks
.
22.4.2 Interlude: !!sym()
syntax
22.4.3 Back to regularly scheduled programming: Updating make_plot()
## YOUR SOLUTION HERE
make_plot <- function(yaxis = "gdpPercap"){
# gets the label matching the column value
y_label <- yaxisKey$label[yaxisKey$value==yaxis]
#filter our data based on the year/continent selections
data <- gapminder
# make the plot!
# on converting yaxis string to col reference (quosure) by `!!sym()`
# see: https://github.com/r-lib/rlang/issues/116#issuecomment-298969559
#
# `sym()` turns strings (or list of strings) to symbols (https://www.rdocumentation.org/packages/rlang/versions/0.2.2/topics/sym)
#
# `paste` concatenates vectors after converting to characters (https://www.rdocumentation.org/packages/base/versions/3.6.1/topics/paste)
p <- ggplot(data, aes(x = year, y = !!sym(yaxis), colour = continent,
text = paste('continent: ', continent,
'</br></br></br> Year:', year,
'</br></br> GDP:', gdpPercap))) +
geom_jitter(alpha = 0.6) +
scale_color_manual(name = 'Continent', values = continent_colors) +
scale_x_continuous(breaks = unique(data$year))+
xlab("Year") +
ylab(y_label) +
ggtitle(paste0("Change in ", y_label, " Over Time")) +
theme_bw()
# passing c("text") into tooltip only shows the contents of
ggplotly(p, tooltip = c("text"))
}
Now that we changed the function that creates the graph so that it takes a value of the y-axis as an input, we have to link the two dash components that we have : the graph and the dropdown menu. This is when you create the callback!
22.4.4 Callbacks
Callbacks are chunks of code that you are going to put :
after you created your layout (that contains your plots, your dropdown menus,…)
before you run your application (so before the
app$run_server()
line)
Callbacks usually have the following structure :
app$callback(
#What you want to update
output=list(id = <element_id>, property= <element_type>),
#Based on the following values
params=list(input(id = '<value_1>', property='value'),
input(id = '<value_2>', property='value'),
input(id = '<value_3>', property='value')),
#translate your list of params into function arguments
function(<value_1>, <value_2>, <value_3>) {
my_function(<value_1>, <value_2>, <value_3>)
})
The angle brackets mean that you have to change those names into the ones that corresponds to the elements of your app.
The way callbacks work is the following : Dash is going to use the inputs that are in the params
list as the inputs of the function
in order to change the property of the Dash component that are specified in the output
argument.
I gave you the general structure of a callback so that you can see what it looks like. Let’s now use an example to understand better how to specify callbacks. In our example, we are going to change what variable is used as the y-axis of a graph by using a dropdown menu.
Step 2 : create the callback
Step 2.1 : define the output of the callback
If we look back at the general structure of a callback, we can see that the first thing we have to define is the element we want to update : this is the output of the callback.
Try to fill up the blanks :
output=list(id = <element_id>, property= <element_type>)
## YOUR SOLUTION HERE
output=list(id = 'gap-graph', property= 'figure')
The required parameter id
‘gap-graph’ is the id of the component you want to display (look where graph <-
is defined in the template of the code above).
The optional parameter property
argument refers to which property of your component you want to display.
Step 2.2 : define the input(s) of the callback
Then, we have to define our parameters, which are the values we are going to use as an input of our callback to update our graph.
Try to fill in the blanks :
params=list(input(id = '<value_1>', property='value')),
## YOUR SOLUTION HERE
params=list(input(id = 'y-axis', property='value')),
The way to read this code is the same as before : our input is the “value” property of the component that has the ID “y-axis” (look where yaxisDropdown <-
is defined above in the template).
Step 2.3 : define the function in the callback
Finally, we just have translate our list of params into a function arguments.
Try to fill in the blanks :
function(<value_1>) {
my_function(<value_1>)
}
## YOUR SOLUTION HERE
function(yaxis_value) {
make_plot(yaxis_value)
}
Notice that we have never defined yaxis_value
before, this is just the name that I decided to give to the input of my function. What is important here is that the argument of your function has the same name as the argument you put inside the make_plot()
function.
If we gather all those answers, we obtain the complete callback :
app$callback(
#update figure of gap-graph
output=list(id = 'gap-graph', property='figure'),
#based on values of year, continent, y-axis components
params=list(input(id = 'y-axis', property='value')),
#this translates your list of params into function arguments
function(yaxis_value) {
make_plot(yaxis_value)
})
What is happening here? Well, see that Dash takes the input property (the ‘value’ of the ‘y-axis’ component), uses it as an input argument of the function, and updates the property of the output component (the ‘figure’ of the ‘gap-graph’ component) with whatever was returned by the function! So cool!
Step 3 : Put it all together!
Now, add this chunck of code in your app after your app layout under the section heading ## App Callbacks
.
You should have the following code :
library(dash)
library(dashCoreComponents)
library(dashHtmlComponents)
library(dashTable)
library(tidyverse)
library(plotly)
library(gapminder)
app <- Dash$new(external_stylesheets = "https://codepen.io/chriddyp/pen/bWLwgP.css")
# Storing the labels/values as a tibble means we can use this both
# to create the dropdown and convert colnames -> labels when plotting
yaxisKey <- tibble(label = c("GDP Per Capita", "Life Expectancy", "Population"),
value = c("gdpPercap", "lifeExp", "pop"))
#Create the dropdown
yaxisDropdown <- dccDropdown(
id = "y-axis",
options = map(
1:nrow(yaxisKey), function(i){
list(label=yaxisKey$label[i], value=yaxisKey$value[i])
}),
value = "gdpPercap"
)
# Use a function make_plot() to create the graph
make_plot <- function(yaxis = "gdpPercap"){
# gets the label matching the column value
y_label <- yaxisKey$label[yaxisKey$value==yaxis]
#filter our data based on the year/continent selections
data <- gapminder
# make the plot!
# on converting yaxis string to col reference (quosure) by `!!sym()`
# see: https://github.com/r-lib/rlang/issues/116#issuecomment-298969559
#
# `sym()` turns strings (or list of strings) to symbols (https://www.rdocumentation.org/packages/rlang/versions/0.2.2/topics/sym)
#
# `paste` concatenates vectors after converting to characters (https://www.rdocumentation.org/packages/base/versions/3.6.1/topics/paste)
p <- ggplot(data, aes(x = year, y = !!sym(yaxis), colour = continent,
text = paste('continent: ', continent,
'</br></br></br> Year:', year,
'</br></br></br> GDP:', gdpPercap))) +
geom_jitter(alpha = 0.6) +
scale_color_manual(name = 'Continent', values = continent_colors) +
scale_x_continuous(breaks = unique(data$year))+
xlab("Year") +
ylab(y_label) +
ggtitle(paste0("Change in ", y_label, " Over Time")) +
theme_bw()
# passing c("text") into tooltip only shows the contents of
ggplotly(p, tooltip = c("text"))
}
# Now we define the graph as a dash component using generated figure
graph <- dccGraph(
id = 'gap-graph',
figure=make_plot() # gets initial data using argument defaults
)
app$layout(
htmlDiv(
list(
htmlH1('Gapminder Dash Demo'),
htmlH2('Looking at country data interactively'),
#selection components
htmlLabel('Select y-axis metric:'),
yaxisDropdown,
#graph and table
graph,
htmlIframe(height=20, width=10, style=list(borderWidth = 0)), #space
dccMarkdown("[Data Source](https://cran.r-project.org/web/packages/gapminder/README.html)")
)
)
)
app$callback(
#update figure of gap-graph
output=list(id = 'gap-graph', property='figure'),
#based on values of year, continent, y-axis components
params=list(input(id = 'y-axis', property='value')),
#this translates your list of params into function arguments
function(yaxis_value) {
make_plot(yaxis_value)
})
app$run_server()
Run this app, and try to play with the dropdown menu! You can see that what is displayed by the graph on the y-axis depends on the value you choose on your dropdown menu.
Congratulations, you just did your first callback and a “real” dashboard!!!
22.6 Appendix: Anatomy of a full Dash app
22.6.0.1 Load libraries & documentation
Documentation goes first inside double quotes " " and should include a Usage line of just app.R. Libraries are then loaded.
22.6.0.2 (NEW) Make Plot
- To make your app more organized, you may optionally create a function that outputs a ggplot object
- We will call this function
make_plot
to be consistent, but it can be called anything and there can even be multiple functions. - The input arguments of the
make_plot
function will be the features that the user can filter or select for. - The
make_plot
function will filter the data based on the provided input arguments and ouput a plot based on the filtered data.
For example, let’s turn the creation of a plot in last demo from cm107 into a function (make_plot
) that outputs a ggplotly object:
## YOUR SOLUTION HERE
make_plot <- function() {
# add a ggplot
plot <- mtcars %>%
ggplot() +
theme_bw() +
geom_point(aes(x = mpg, y = hp) ) +
labs(x = 'Fuel efficiency (mpg)',
y = 'Horsepower (hp)') +
ggtitle(("Horsepower and Fuel efficiency for "))
ggplotly(plot)
}
22.6.0.3 Create Dash instance
app <- Dash$new()
creates a new instance of a dash app
22.6.0.4 (NEW) Assign components to variables
In order to keep app$layout()
relatively clean, tidy, and easy to debug, I recommend you create your components as variables first and then pass those into the list of app$layout()
.
For example:
# Assign components to variables
heading_helloworld = htmlH1('Hello world!! Dash application')
heading_subtitle = htmlH2('This is a subheading')
graph_1 = dccGraph(id='mtcars',figure = make_plot())
# Specify App layout
app$layout(
htmlDiv(
list(
heading_helloworld,
heading_subtitle,
graph_1
)
)
)
22.6.0.5 Specify App Layout app$layout()
app$layout()
describes the layout of your app.- An
htmlDiv
is placed inside anapp$layout()
call that allows you to specify where to add “Divs” in your dashboard. For example, create an area for plots, a header for a title, or a sidebar for filters. It also allows you to specify where in your dashboard to place your graphs and filters. We will look at these later in the layouts section ; for now, you will need just one div and specify Dash components using a list - See here for more information on Dash layouts.
22.6.0.6 (NEW) App Callbacks app$callback()
app$callback()
allows you to use Dash components (ex. dropdown menus) to interactively change your plots (or other components).app$callback()
can also be used to filter your data, or do other things that control the data going into your plots.
- See here for more information on Dash callbacks.
22.6.0.7 Run App
app$run_server()
runs your Dash app.- Render your Dash app by running
$ Rscript app.R
in your terminal. - Look at the output of your shell and navigate to the specified address (should be http://127.0.0.1:8050/) in your web browser. You should see your dash app.
- Note: To automatically reload your dashboard when you make changes, add
debug=TRUE
to theapp$run_server
call.