All posts

Building a Simple Shiny Web App

06 May 2019
Hello! Today is the first in a series of posts where we will be looking at R Shiny. Shiny is an R package which allows us to build interactive web apps.

Introduction and Motivation


For this post, we’ll focus on building a simple interactive web app: we will build the Shiny app here, which visualises how the populations of world cities have changed over time.



In later posts we will build two more web apps, each getting more advanced.

But, before we dive into the details of Shiny, why do we need to do this? Historically, Applied Statistics was often about building a model, then writing a paper or a report and presenting results. Modern Data Science, especially commercial Data Science, requires:


  1. embedding models directly into products or machines (AI centric products)

  2. interactively consuming analysis and insights



To deliver this, as Data Scientists, we need to work with different teams (back-end developers and front-end developers). To embed models into products Data Scientists will need to enhance skills in areas such as systems integration and back-end development. To allow users to interactively consume analysis and insights Data Scientists will need to enhance their UX and UI skills. Future Data Scientists will need a broad technological skillset, beyond only analysis and modelling.

Shiny is particularly appealing because: Shiny allows us to quickly develop a lightweight web app, reducing the distance between analysis by the Data Scientists and the consumption of the model/analysis by the end user. It also gives the Data Scientist more control over how models, analysis and plots are presented. In the midst of building a Shiny web app your skill set in HTML, CSS and Javascript can also be enhanced (especially when we produce more advanced Shiny apps).

Whilst Shiny will never replace UI development, it is an opportunity for the two distinct disciplines of Data Science and UI development to come closer, which needs to happen. The skillset overlap between Data Scientists and front-end developers will continue to grow.

But now, into the code!

Getting The Data


We’ll be using time series city population data from the UN. However, this post focuses on building the Shiny app, so we will not look at the data preparation or R steps at all. We assume a good knowledge of R and tidyverse. The entire code is available at GitHub.

What We Will Cover


We will build a simple Shiny web app exploring the basics of Shiny. In particular will cover:

  1. the structure of a Shiny app.

  2. how to create a simple UI.

  3. how to allow the user to use the UI (through drop downs and selections) to change the data they see, through reactive expressions.

  4. how we can begin to add extra customisation using HTML.

  5. how we can make building a Shiny app easier.

  6. how to deploy a Shiny app.



The Structure of a Shiny App


Shiny is a fantastic package with integrations with a lot of things within the R universe. However, it can be a tad fiddly. There are three building blocks to a Shiny app:


  1. the ui, which contains the code used to create the user interface.

  2. the server server <- function(input, output) { }, which contains code which creates the functionality of the app, such as displaying the data, maps and plots.

  3. putting the ui and the server together to create the Shiny app objects shinyApp(ui = ui, server = server).



In conclusion, the server controls what will be shown, whilst the ui controls how it will be shown and the shinyApp() function puts these together to build the app. Putting this together:

ui <- fluidPage(--- we'll explain fluidPage below
--- code to build the ui
)

server <- function(input, output) {
--- code which controls the data to be shown
}

--- build the app
shinyApp(ui = ui, server = server)


And that is the basic structure of a Shiny app!

Creating a Simple UI


The UI possibilities in Shiny are endless. At one end of the spectrum we can create a really simple UI; at the other end of the spectrum are UIs with a full array of HTML, CSS and JavaScript customisation. So, let’s build a simple UI.

We start by using fluidPage(). A fluidPage() creates a page with rows and columns, but most importantly, rescales components to fit the size of the browser window. Within fluidPage() we will use some high level functions to create a page which has a sidebar panel, where we will put some controls, and a main panel, which we will organise into tabs, where we will show the data and plots. In addition we have a title of the app using titlePanel().

library(shiny)

ui <- fluidPage(
titlePanel(“World Populations"),
sidebarLayout(
sidebarPanel(
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("City Populations",
),
tabPanel(“Map”,
),
tabPanel(“About”,
)
)
)
)
)


Reactive Expressions


At the moment we have the skeleton of the Shiny app, but it doesn’t show anything. Now we’ll add some content. To help with this, we will use reactive expressions. These let you control how parts of your app are updated when a user inputs something, such as selecting an item from a dropdown. Reactive expressions are the core method of introducing user interactivity within a Shiny app. The user makes a selection or inputs something using (input) widgets. The widget is identified by an inputId.

We take this inputId in the reactive function to create a reactive dataset based on user input. We take this reactive dataset and build the form that we want to show the output as (data.table, plot, map), in the server. The output is given an outputId. We then revert back to the ui to display the output using this outputId.



I created this diagram using the DiagrammeR package. The code for this is in the GitHub repo.

On the first tab we want to simply show a table of our results. We want the user to be able to filter the results by year, country and population size.

Let’s first deal with the UI components. There are numerous widgets of the form …Input() which allow the user to select something (generally filtering the data). For selecting the year and country, we will use pickerInput() from the shinyWidgets package, whilst for population we will use sliderInput.

library(shiny)
library(shinyWidgets)

pickerInput(inputId = “year”, label = “Choose Year”, choices = unique(city_pops$year), selected = unique(city_pops$year), multiple = TRUE, options = list(‘actions-box’ = TRUE))

pickerInput(inputId = “country”, label = “Choose Country”, choices = unique(city_pops$country), selected = unique(city_pops$country), multiple = TRUE, options = list(‘actions-box’ = TRUE))

sliderInput(inputId = "population", label = "Population", min = min(city_pops$population), max = max(city_pops$population), value = c(min(city_pops$population), max(city_pops$population)))


We then put these in the sidebar panel:

...
sidebarPanel(
pickerInput( --- year
),
pickerInput( --- country
),
sliderInput( --- population
)
)
...


We now use these inputs to create a dataframe that will respond to user input. This is where we use a reactive expression. We use the reactive function and set a series of filters filtering by the values as defined by input$.... Below we filter depending on the year ((input$year)), country ((input$country)) and the population (input$population[1]) (min) and (input$population[2]) (max).


library(shiny)
library(dplyr)

# create dataframe
df <- reactive({
city_pops %>%
select(-c(date)) %>%
filter(year %in% input$year &
country %in% input$country &
population >= input$population[1] & population <= input$population[2])
})


Reactive functions work by only updating if they need to (i.e., when a user input changes), which means that we are not performing any additional work or using any unnecessary computing power in our Shiny app.

Next, we have to create the output form. That is, how we are going to display the data! On our first tab we show the data in a data.table.

We use renderDataTable to render our output as a data.table. Generalising, we can use render* to render different output forms. The key thing to note here is we call reactive expressions with the name of the expression followed by parentheses (). Without parentheses, our reactive expressions won't work. We can only call reactive expressions either in other reactive expressions or in a render*() call.

library(shiny)
library(DT)

# create Table
output$Table <- renderDataTable({
datatable(df(), options = list(scrollX = TRUE))
})


And finally we need to show our output in the UI. We can use the family of functions *Output() and pass the outputId.

library(shiny)
dataTableOutput(outputId = "Table")


In the Shiny app you will also see another tab, where we have used leaflet to create a map, which lets the user choose which year to view on the map. This uses reactive expressions as we have described above to ensure we show the correct data. In addition, we change the title of the map depending on the year selected by the user:

library(shiny)
library(leaflet)
library(dplyr)

selectInput(inputId = "map_year",
label = "Choose Year",
choices = unique(city_pops$year)
),
textOutput(outputId = "MapTitle”),
leafletOutput(outputId = "WorldMap")
….

# create map dataframe
df_map <- reactive({
city_pops %>%
filter(year == input$map_year)
})

….

# create WorldMap
output$WorldMap <- renderLeaflet({
leaflet(options = leafletOptions(minZoom = 2,
maxZoom = 8,
maxBounds = list(list(-70, -160), list(70, 160)),
worldCopyJump = FALSE)) %>%
addTiles() %>%
addCircleMarkers(lng = df_map()$longitude,
lat = df_map()$latitude,
radius = df_map()$population/1000,
color = "#042A58",
label = paste0(df_map()$city, ", ",
df_map()$country, ", ",
df_map()$population))
})

# create map title
output$MapTitle <- renderText({
paste("World City Populations in", input$map_year)
})


Add Extra Customisation Using HTML


We’ve got a great Shiny app! But everything we’ve used is out of the box (there is nothing wrong with this)! But you may want to begin to customise your app, especially the UI. If you work in a company that requires branding for example, this may be a necessity. It's much better to use something like Shiny than an expensive business intelligence software package. We’ll now begin to (very simply) begin customising with HTML.

Shiny contains a number of HTML Builder Functions. These are syntactically identical to their HTML counterparts. For example h1(…) is <h1> and strong(…) is <strong>. These simple functions are really useful for using HTML capability in Shiny. You’ll notice that I use br() in the code base to insert a line break in certain places. You can also use tags$*(…) to get the same capability. Sometimes you’ll need to use tag(). To create a bulleted list, you can use tags$li(). Look at the code for the About tag in our Shiny app and you will see HTML customisation, a couple of examples are given below:

tags$h4("What We've Done")

p("This simple Shiny App is the web app we created in the",
tags$a(href = "https://markrstevenson.com/blog/H4Hi4pEtJmXciDldaQmz",
"Building a Simple Shiny Web App",
target = "_blank"),
"post.")

tags$div(tags$ol(tags$li("the structure of a Shiny app:",
tags$code("ui"),
"and",
tags$code("server"),
".")))


Tips for Building a Shiny App and How We Can Make It Easier


As I said earlier, building a Shiny app can be fiddly and we often require a few different code chunks (as in the case of reactive expressions). For an R programmer, becoming a Shiny developer can be a different experience: code execution is not linear. The R Shiny documentation has a whole section on debugging, but the part to highlight here is the showcase mode. As per the documentation, showcase mode is where “code is shown alongside your application, and your application’s server code flashes yellow when it executes”. This makes it easy to see which code is doing what and when and is great for understanding the code execution path.



How to Deploy a Shiny App


So, now we’ve built the app, we want others to see it! The easiest way to deploy a Shiny App is using shinyapps.io. After you’ve chosen the plan you want, deploying is easy.


  1. You’ll need the rsconnect package: install.packages(“rsconnect”).

  2. You'll need to authorise your account: rsconnect::setAccountInfo(name = "<NAME>", token = "<TOKEN>", secret = "<SECRET>").

  3. Deploy your app: deployApp(‘appdir’).



Your app should open in a new browser window. You’ve now created and deployed your Shiny app. You can see the Shiny app we created here.

Conclusion


We built this simple Shiny web app, exploring the basics of Shiny. We learnt:

  1. the structure of a Shiny app: ui and server.

  2. how to create a simple UI using fluidPage(...), sidebarLayout(...), sidebarPanel(...) and mainPanel(...).

  3. how to create reactive expressions: reactive({ }), selectInput(), pickerInput() and sliderInput().

  4. how we can begin to add extra customisation using HTML.

  5. how to see how the code runs using runApp('appdir', display.mode = 'showcase').

  6. how to deploy a Shiny app using shinyapps.io and deployApp(‘appdir’).



In future posts we will progressively explore more advanced Shiny capability and functionality. You can get further information about how we built the Shiny web app, and an introduction to Shiny at:


  1. GitHub.

  2. Shiny Tutorials and Documentation.



Thanks for reading this post! I hope you found it a useful introduction to Shiny! If you see any errors in the code, please let me know. If you have any comments, then please leave them below, and don't forget that you can find all the code for this post (and more!) at the post GitHub repository. You can also find me at @stevo_marky.

Leave a comment