Recap
In the last post we got to the stage where we had the data for each club’s league positions since 1958/59 in a Google sheet.
The next step is to visualise this data in a Shiny app.
Shiny lets you create visualisations that your users can interact with, and they have slightly different code to ordinary R. Here are some examples from RStudio’s gallery.
Here is the app itself:
More about Shiny
Usually a Shiny app has two main components: the underlying data the app manipulates and the code that makes it work.
If you want people on other devices to be able to use your app then both the data and the code need to be hosted somewhere on a server.
One option for hosting the code is Shinyapps.io, which I use.
At the moment you can’t host the data there, so you have to use a separate online storage facility and get R to read the data from there.
This post from RStudio’s Shiny section goes into more detail about the kind of storage options you can use, such as:
- Dropbox
- Google Sheets
- SQL database
- MongoDB
All of these have advantages and disadvantages, but I went with Google Sheets for simplicity and because I know how to get R to read sheets hosted on Google.
More about Shiny
The basic format of Shiny is an input, controlled by the user, and the output.
The user will select an input or inputs and the Shiny code will display or plot the corresponding subset of the overall data as the output.
Shiny code usually comes in two files: server.R and ui.R that work together.
UI stands for ‘user interface’ and controls the ‘front-end’ of the app, i.e. what the user sees and interacts with.
The ui.R file covers:
- the input (in my example, this is the ‘Club’ selector)
- the output (the chart)
- the display (the styling of the header and panels, but not of the chart itself)
The server.R file is the ‘back-end’ of the app, which uses the input from the user to make the app work.
In our case the server.R code filters the league positions data with the input and plots a graph accordingly.
For example if I choose Arsenal as my club the server.R recognises this as the input, selects the data from the Google Sheet with the matching ‘Arsenal’ heading and plots a graph showing this data.
Here is the full R code:
server.R
library(shiny) library(DT) library(dplyr) library(googlesheets) library(ggthemes) library(ggplot2) library(scales) function(input, output) { league_positions <- "https://docs.google.com/spreadsheets/d/1FYYclGnDt7lLXjCBKM1dldUdsglO2xXw1YB0TXf9uRs" league_positions <- gs_url(league_positions, lookup = FALSE, visibility = "public") lp_data <- gs_read(league_positions) output$plot <- renderPlot({ p <- ggplot(data = lp_data, aes(x = lp_data$season)) + geom_line(aes(y = lp_data[,input$x1], group = 1), size = 1.1) + labs(x = "Season", y = "Finishing position") + theme_economist() + theme(axis.title = element_text(size = 20), axis.text = element_text(size = 16, hjust = 0.7), plot.title = element_text(size = 16), legend.title=element_blank(), legend.justification=c(0, 0)) + scale_y_continuous(limits = c(1,100)) + scale_y_reverse(limits = c(100,1), breaks = c(1,20,44,68,92), labels = c(1,20,44,68,92)) + scale_x_continuous(breaks = c(1958, 1968, 1978, 1988, 1998, 2008, 2016)) p }) }
Going back over it a bit at a time:
league_positions <- "https://docs.google.com/spreadsheets/d/1FYYclGnDt7lLXjCBKM1dldUdsglO2xXw1YB0TXf9uRs" league_positions <- gs_url(league_positions, lookup = FALSE, visibility = "public") lp_data <- gs_read(league_positions)
This establishes a connection with our public Google sheet (which must be published to the web) and builds a data frame based on the football data it contains.
output$plot <- renderPlot({ p <- ggplot(data = lp_data, aes(x = lp_data$season)) + geom_line(aes(y = lp_data[,input$x1], group = 1), size = 1.1) + labs(x = "Season", y = "Finishing position") + theme_economist() + theme(axis.title = element_text(size = 20), axis.text = element_text(size = 16, hjust = 0.7), plot.title = element_text(size = 16), legend.title=element_blank(), legend.justification=c(0, 0)) + scale_y_continuous(limits = c(1,100)) + scale_y_reverse(limits = c(100,1), breaks = c(1,20,44,68,92), labels = c(1,20,44,68,92)) + scale_x_continuous(breaks = c(1958, 1968, 1978, 1988, 1998, 2008, 2016)) p }) }
This part plots a ggplot, using the input we choose in ui.R as our line chart. The rest is just formatting, including using the theme_economist() theme from the very useful ggthemes package.
I couldn’t include all the league positions so I chose the ones at the bottom of each division to highlight the differences between the tiers. There are 92 teams in the top four tiers, so if a team dropped lower than this or was otherwise not in the four top four tiers for the entire period since 1958 then the line plot will have a break in it. Remember you can use breaks and labels to select which data appears in your plot. I also reversed the data so that position 1 (champions of England) is at the top.
ui.R
library(shiny) library(shinythemes) library(DT) library(googlesheets) #help: https://stackoverflow.com/questions/24175997/no-default-select league_positions <- "https://docs.google.com/spreadsheets/d/1FYYclGnDt7lLXjCBKM1dldUdsglO2xXw1YB0TXf9uRs" league_positions <- gs_url(league_positions, lookup = FALSE, visibility = "public") lp_data <- gs_read(league_positions)
shinyUI(fluidPage( tags$style(type="text/css", ".shiny-output-error { visibility: hidden; }", ".shiny-output-error:before { visibility: hidden; }", "h1, h3 { text-align:center; }" ), titlePanel(h1("The Football Pyramid",h3("Select a club to see where it has finished in the top four divisions of English football since 1958/59."))), fluidPage(sidebarLayout( sidebarPanel( selectizeInput('x1', 'Club:', choices = c("", colnames(lp_data[3:118])), multiple = FALSE, options = list(maxOptions = 4, maxItems = 1, placeholder = 'Select a team')), hr(), helpText("Data source:", (a("James P. Curley", href="https://github.com/jalapic/engsoccerdata", target="_blank"))) ), mainPanel( plotOutput('plot') ) )) ) )
The first few lines are reading the Google sheet as before.
shinyUI(fluidPage( tags$style(type="text/css", ".shiny-output-error { visibility: hidden; }", ".shiny-output-error:before { visibility: hidden; }", "h1, h3 { text-align:center; }" ),
This section, written as CSS, suppresses the Shiny error message when no input is selected as well as controlling the alignment of the heading and subheading (the h1 and h3 tags).
Then comes the title and subtitle text.
fluidPage(sidebarLayout( sidebarPanel( selectizeInput('x1', 'Club:', choices = c("", colnames(lp_data[3:118])), multiple = FALSE, options = list(maxOptions = 4, maxItems = 1, placeholder = 'Select a team')), hr(), helpText("Data source:", (a("James P. Curley", href="https://github.com/jalapic/engsoccerdata", target="_blank"))) ),
This section creates the dropdown menu for selecting a club. It is using the same data frame as server.R (minus the first two columns which aren’t clubs, hence why it is subsetted beginning on column 3. Using selectize gives you more options with the dropdown, including creating an autocomplete, limiting the number of options it displays and creating a placeholder.
Adding the blank option to the choices means that it starts off with a blank canvas rather than drawing a graph for Accrington, the first alphabetically.
The helpText credits James Curley as the source of the data.
mainPanel( plotOutput('plot') )
Finally, plot the chart.
Uploading your app to Shinyapps.
You have to register with Shinyapps first. If you sign up to the free package then your usage is limited and your app may well break under significant traffic. Mine did after I published my last post.
Clicking on the blue icon next to ‘Run app’ in RStudio brings up this message. Hit Publish and it will upload your work to Shinyapps.
There you have it, your first Shiny app!
Shiny has a bit of a learning curve on top of your basic R code. If you are serious about using Shiny professionally you will probably want to buy one of their paid-for packages (no affiliation) otherwise you simply won’t be able to scale your app properly.