This Shiny tutorial was made for the R Ladies NYC meetup on May 8, 2018. Thanks to the New York NBA office for hosting!!
Shiny is R Studio’s framework for building interactive plots and web applications in R
. By the end of this tutorial you should have some basic understanding of how Shiny works, and will make and deploy a Shiny app using NBA shots data. Thanks to Todd Schneider’s ballR app inspiration. I highly recommend checking it out! He also has a cool comparison of Citi Bike vs. Taxi commuting times in NYC.
Before we begin, make sure you have the shiny
, plotly
, tidyverse
, and rsconnect
packages installed. I have created a template for our app and completed version, please download and unzip these folders as well.
install.packages(c("shiny", "plotly", "tidyverse", "rsconnect"))
Run an example of a simple shiny app to ensure the package is installed properly.
library(shiny)
runExample("01_hello")
A slider bar the allows you to change the number bins in the histogram shown.
Each Shiny app has a ui
and a server
file, both of which we have to define. The ui
defines a webpage that the user interacts with, it controls layout and appearance. The server
file is a set of instructions your computer needs to build the app. R
code is executed in the background, and output depends on the user input and this R
code.
image from https://deanattali.com/blog/building-shiny-apps-tutorial/
All Shiny apps follow the same overall structure. fluidPage()
controls the page layout for the UI. The server
is a function with arguments input
and output
.
library(shiny)
ui <- fluidPage()
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
This template itself is a minimal Shiny app, try running the code. Copy this template into a new file called app.R
, and save it in a new folder. After saving the file, you should see a Run App button at the top, indicating R Studio has recognized the file as a Shiny app.
There are two ways to create Shiny apps:
app.R
, ideal for simple apps. If you are using a single file, the file must be called app.R
for the app to run.ui.R
and server.R
files, ideal for more complicated apps. These must be named ui.R
and server.R
. Our NBA app uses this approach.You can initialize a Shiny app using either approach right from R Studio:
Select Shiny Web App… and the following will pop up:
Select Multiple File to generate an app with separate ui.R
and server.R
files. The app initialized above will be stored in a folder called “new_app” on the Desktop. To run the app you can do either of the following:
server.R
or ui.R
file and click the Run App button.shiny::runApp("~/Desktop/new_app/")
in your R consoleThe input and output arguments to the server function are actually lists of objects defined in the UI. These input options, output options, and server side code called render statements are the main components of most Shiny apps, and are defined below.
Input options (usually) go in the ui.R
file. Input is defined through input functions called widgets. These are text elements a user can interact with, like scroll bars or buttons. Three widget options and the code used to generate them are shown below.
All input functions have inputId
and label
as the first two arguments. The inputId
is a string that the server side will use to access the value of the user input. For example, if inputId = "slider_widget"
then on the server side you would use input$slider_widget
to use its value.label
is the title for the widget that will show up in the UI.
The hr()
, br()
, and h2()
in the example code above are wrappers for the html tags <hr>
<br>
, and <h2>
. There are a bunch of awesome html wrapper functions to help you customize your user interface.
Output options (also usually) go in the ui.R
file. They define things like plots and tables and instruct Shiny where in the UI to place these items. Examples include plotOutput()
, textOutput()
, tableOutput()
.
Debugging tip: Make sure you have a comma between each input call and output call! Commas are needed between elements for the UI but not for the server, which operates more like regular R
code.
Render statements go in the server.R
file. They take user input from the widgets and build reactive output to display in the UI. Examples including renderTable()
to make tables, renderText()
for text, and renderPlot()
for certain plots.
Input, output, and render statements are the simplest examples of the reactive programming paradigm that Shiny uses. We will cover reactivity in more detail later.
Download and unzip the shiny_nba folder provided. Notice what’s in the folder:
shiny_nba.Rproj
nba_shots.RData
data on shots taken by LeBron James, Kevin Durant, Russell Westbrook, Stephen Curry, and Carmelo Anthonyhelper.R
file with extra R
code our app usesui.R
fileserver.R
fileDouble click the shiny_nba.Rproj to open up R Studio. This automatically sets your working directory to the shiny_nba
folder, which makes it easier to load the data and source the helper functions. This is one of many reasons I always use R projects as part of my workflow.
The nba_shots
data contains 81,383 basketball shots taken by five star NBA players.
library(tidyverse)
load("nba_shots.RData")
nba_shots %>%
group_by(player_name) %>%
summarize(n())
## # A tibble: 5 x 2
## player_name `n()`
## <fct> <int>
## 1 LeBron James 22381
## 2 Kevin Durant 14476
## 3 Russell Westbrook 13778
## 4 Stephen Curry 10508
## 5 Carmelo Anthony 20240
The data has 19 variables, including information on shot distance, accuracy, season, and location on the court.
The helper.R
code is adapted from Todd Schneider’s ballR app. While the code looks complicated, it is just used to draw the lines of a basketball court. The empty basketball court (below) is a ggplot object you can put other ggplot layers on top of.
source("helpers.R")
gg_court = make_court()
gg_court
This code was stored in a separate file in order to make the code within the Shiny app more readable and because this part of the code does not change with user input. However, it doesn’t have to be included as a separate file - all of the code could be placed within the server.R
file instead. Where to put static code like this is a choice to be made when building a Shiny app.
Next let’s add a plot. We are going to plot the locations of the attempted shots made by the selected NBA star in a given season, like LeBron James in his rookie 2003-2004 season (below).
player_data = filter(nba_shots, player_name == "LeBron James", season == "2003-04")
gg_court + geom_point(data = player_data, alpha = 0.75, size = 2.5,
aes(loc_x, loc_y, color = shot_made_flag)) +
scale_color_manual("", values = c(made = "blue", missed = "orange"))
Some of the plots for our app use Plotly, which is a framework for creating interative graphics that has a variety of implementations, including the plotly
library in R
. Plotly has some nice benefits:
We can make Plotly box plot of shooting distances for our NBA players with just two lines of code. Notice that you can hover, zoom, unclick players, and download the image.
library(plotly)
plot_ly(data = nba_shots, y = ~shot_distance, color = ~player_name, type = "box") %>%
layout(legend = list(x = 0.2, y = 1.0))
Note: There is also a Plotly wrapper, ggplotly
, for ggplot2 objects. The code below makes a box plot using ggplot()
then translates it to Plotly. This can be useful for making quick plots if you want Plotly functionality but you’re more used to ggplot2 syntax. From my experience the plot_ly()
function works better than the ggplotly()
function, so I would usually recommend using the plot_ly
function or just sticking with ggplot()
if you don’t need the extra interactivity. I do use ggplotly()
to quickly identify outliers when doing exploratory analysis on a new dataset.
nba_boxplot = nba_shots %>%
filter(shot_made_flag == "made") %>%
ggplot(aes(player_name, shot_distance, fill = player_name)) + geom_boxplot() +
theme(legend.position = "none")
ggplotly(nba_boxplot)
This time I also filtered out missed shots because I was curious how many of the shots taken really far away were actually made. Steph Curry makes the most far shots on average (turns out this is one of his trademarks, which I didn’t know being a basketball neophyte) – but the outliers belong to LeBron!
In 2007 LeBron made an 82 foot shot in a game against the Celtics, as the buzzer indicated the end of the 3rd quarter.
I refer back to what’s in the shiny_nba
folder. The ui.R
and server.R
files define the actual Shiny app.
ui.R
fileserver.R
fileshiny_nba.Rproj
nba_shots.RData
datahelper.R
fileRun the app by opening the shiny_nba.Rproj
then typing runApp()
into your console or opening the ui.R
or server.R
file and clicking the Run App button. You should see a simple app with only the title “NBA Shot Attempts”, because code for widgets and plots has been commented out.
Let’s add the plot that shows the spatial distribution of shots on the court. We need a plotOutput
statement in the ui.R
file to tell Shiny where the plot should appear in the layout of the app, and a renderPlot
statement in the server.R
file that constructs the plot.
## uncomment in ui.R
plotOutput("court_shots") #, uncomment comma when adding next plot
## uncomment in server.R
output$court_shots <- renderPlot({
# subset data by selected player and season(s)
player_data = filter(nba_shots, player_name == input$player_choice,
season %in% input$season_choice)
# create plot
gg_court + geom_point(data = player_data, alpha = 0.75, size = 2.5,
aes(loc_x, loc_y, color = shot_made_flag, shape = season)) +
scale_color_manual("", values = c(made = "blue", missed = "orange"))
})
Debugging tip: if the non-Shiny version of your plot isn’t working, your Shiny version won’t either! Make sure to test your code before placing it in the Shiny framework.
All of the server code that changes based on user input goes within the renderPlot
statement. We allow the plot to change based on choices of player or season, which are stored in input$player_choice
and input$season_choice
.
Exercise: try editing the app so that the court shots plot also changes based on the radio button input.
To add Plotly plots to Shiny apps you need to use the functions plotlyOutput()
and renderPlotly()
rather than plotOutput()
and renderPlot()
. Add the Plotly box plot of shooting distances to the shiny_nba
app by uncommenting the code below. We allow the user to filter on whether shots were made or missed by accessing the input$shots_made
UI input from the radioButtons
widget.
## uncomment in ui.R
plotlyOutput("shot_distances")
## uncomment in server.R
output$shot_distances <- renderPlotly({
nba_shots %>%
filter(if(input$shots_made != 'all') (shot_made_flag == input$shots_made) else TRUE) %>%
plot_ly(y = ~shot_distance, color = ~player_name, type = "box") %>%
layout(showlegend = FALSE)
})
Make sure to uncomment the ,
between output calls in the ui.R
file. No commmas are needed between code chunks in the server file.
The Plotly Shiny gallery contains many more examples of what’s possible when using Plotly and Shiny together.
Now you have a cool Shiny app! I’ve included an expanded version of the app shiny_nba app to show off more stuff Shiny can do. Download it here, then open the shiny_nba_complete.Rproj
and run the app. The app has a few updates:
server.R
for more efficient codeShiny uses reactive programming, which is what allows output to update based on user input. There are three types of reactive objects in Shiny’s reactive programming paradigm: reactive sources, reactive conducters, and reactive endpoints.
In what we have done so far, input$
statements are the reactive sources and output$
statements are the reactive endpoints. We haven’t used any reactive conducters. From our simple shiny_nba
app:
However, sometimes Shiny apps require slow computation, and if one source has multiple endpoints then these computations will need to be done several times. Reactive conducters can speed this up. Reactive expressions are an implementation of reactive conducters that take an input$
value, do some operation, and cache the results. The code our_expression = reactive({})
creates a reactive expression called our_expression
. Since reactive expressions are actually functions, we call the reactive expression using parenthesis: our_expression()
.
The fancier shiny_nba_complete
app uses a reactive expression to store a data set that has been filtered on the current value of input$player_name
. In the code below, from server.R
of this app, a dataframe called player_data
is defined using a reactive expression, then accessed by the reactive endpoint output$court_shots
by calling player_data()
.
# subset data by selected player using reactive expression
player_data = reactive({
filter(nba_shots, player_name == input$player_choice)
})
# create court_shots plot
output$court_shots <- renderPlot({
gg_court + geom_point(data = filter(player_data(), season %in% input$season_choice),
alpha = 0.75, size = 2.5,
aes(loc_x, loc_y, color = shot_made_flag, shape = season)) +
scale_color_manual("", values = c(made = "blue", missed = "orange"))
})
# create court_position plot
output$court_position <- renderPlot({
# subset data by selected player and season(s)
nba_subset = player_data() %>%
Since both output$court_shots
and output$court_position
use this data, we save ourselves from doing the computation twice. The reactive diagram for this is:
When using Plotly and Shiny together you can use coupled mouse events to create new user-driven plots. Coupled events allow you (for example) to click or select points on a plot and have information based on those clicks or selections show up in another plot. The “Time remaining” tab of the complete app uses coupled events - in the top plot the user can select data points, and the subset of shots that corresponds to the user selection will show up on the plot below.
All code needed to add coupled events to a set of plots goes in the server.R
file. For our coupled event, the first plot is made using Plotly. The boxed parts of the code below are needed to turn on mouse event coupling for this plot. In the app, the code for this plot is much longer, but the rest of the code is just for aesthetics and doesn’t have anything to do with coupling.
The source = "time_plot"
gives your coupled event an id (which is useful if you want multiple plots to have coupling), and key = ~shot_id
identifies a variable in the dataset that can you used to access the mouse event data.
The second plot is made using ggplot2, but only data that the user selects on the plot above will show up in this plot.
We access selected data using "plotly_selected"
. We then subset the nba_shots
data based on the user selection.
To access hover or click data use "plotly_hover"
or "plotly_click"
, respectively.
We’ve already run our app locally, but hosting it publicly can be more tricky. You can’t just host it on GitHub like you would an R Markdown or blogdown website because R
needs to be running in the background. However, you can publicly host shiny apps at Shinyapps.io.
The rsconnect
package deploys Shiny apps to shinyapps.io. Load this package and create your own account at shinyapps.io. Once you have created a shinyapps.io account and configured the rsconnect
package to your account (follow these instructions, you only have to do it once) you are ready to host your app. All you have to do is navigate to the folder where your app is held (so easy if you’re using an R Project!) and run to following code:
library(rsconnect)
deployApp()
Congrats, you have a published your Shiny app! Mine is hosted here.
A few other things about deployment:
deployApp()
again. It should be faster after the first timeui.R
and server.R
files. This is why I put the nba_shots.RData
in the same folder as these other files