14 min read

In this article by Chris Beeley, author of the book, Web Application Development with R using Shiny – Second Edition, we are going to extend our toolkit by learning about advanced Shiny functions. These allow you to take control of the fine details of your application, including the interface, reactivity, data, and graphics. We will cover the following topics:

  • Learn how to show and hide parts of the interface
  • Change the interface reactively
  • Finely control reactivity, so functions and outputs run at the appropriate time
  • Use URLs and reactive Shiny functions to populate and alter the selections within an interface
  • Upload and download data to and from a Shiny application
  • Produce beautiful tables with the DataTables jQuery library

(For more resources related to this topic, see here.)

Summary of the application

We’re going to add a lot of new functionality to the application, and it won’t be possible to explain every piece of code before we encounter it. Several of the new functions depend on at least one other function, which means that you will see some of the functions for the first time, whereas a different function is being introduced.

It’s important, therefore, that you concentrate on whichever function is being explained and wait until later in the article to understand the whole piece of code. In order to help you understand what the code does as you go along it is worth quickly reviewing the actual functionality of the application now. In terms of the functionality, which has been added to the application, it is now possible to select not only the network domain from which browser hits originate but also the country of origin. The draw map function now features a button in the UI, which prevents the application from updating the map each time new data is selected, the map is redrawn only when the button is pushed. This is to prevent minor updates to the data from wasting processor time before the user has finished making their final data selection.

A Download report button has been added, which sends some of the output as a static file to a new webpage for the user to print or download. An animated graph of trend has been added; this will be explained in detail in the relevant section. Finally, a table of data has been added, which summarizes mean values of each of the selectable data summaries across the different countries of origin.

Downloading data from RGoogleAnalytics

The code is given and briefly summarized to give you a feeling for how to use it in the following section.

Note that my username and password have been replaced with XXXX; you can get your own user details from the Google Analytics website. Also, note that this code is not included on the GitHub because it requires the username and password to be present in order for it to work:

library(RGoogleAnalytics)
### Generate the oauth_token object
oauth_token <- Auth(client.id = "xxxx",
                   client.secret = "xxxx")
# Save the token object for future sessions
save(oauth_token, file = "oauth_token")

Once you have your client.id and client.secret from the Google Analytics website, the preceding code will direct you to a browser to authenticate the application and save the authorization within oauth_token. This can be loaded in future sessions to save from reauthenticating each time as follows:

# Load the token object and validate for new run
load("oauth_token")
ValidateToken(oauth_token)

The preceding code will load the token in subsequent sessions. The validateToken() function is necessary each time because the authorization will expire after a time this function will renew the authentication:

## list of metrics and dimensions
query.list <- Init(start.date = "2013-01-01",
end.date = as.character(Sys.Date()),
dimensions = "ga:country,ga:latitude,ga:longitude,
ga:networkDomain,ga:date",
metrics = "ga:users,ga:newUsers,ga:sessions,
ga:bounceRate,ga:sessionDuration",
max.results = 10000,
table.id = "ga:71364313")
gadf = GetReportData(QueryBuilder(query.list),
token = oauth_token, paginate_query = FALSE)

Finally, the metrics and dimensions of interest (for more on metrics and dimensions, see the documentation of the Google Analytics API online) are placed within a list and downloaded with the GetReportData() function as follows:

...[data tidying functions]...
save(gadf, file = "gadf.Rdata")

The data tidying that is carried out at the end is omitted here for brevity, as you can see at the end the data is saved as gadf.Rdata ready to load within the application.

Animation

Animation is surprisingly easy. The sliderInput() function, which gives an HTML widget that allows the selection of a number along a line, has an optional animation function that will increment a variable by a set amount every time a specified unit of time elapses. This allows you to very easily produce a graphic that animates.

In the following example, we are going to look at the monthly graph and plot a linear trend line through the first 20% of the data (0–20% of the data). Then, we are going to increment the percentage value that selects the portion of the data by 5% and plot a linear through that portion of data (5–25% of the data). Then, increment again from 10% to 30% and plot another line and so on.

There is a static image in the following screenshot:

The slider input is set up as follows, with an ID, label, minimum value, maximum value, initial value, step between values, and the animation options, giving the delay in milliseconds and whether the animation should loop:

sliderInput("animation", "Trend over time",
min = 0, max = 80, value = 0, step = 5,
animate = animationOptions(interval = 1000, loop = TRUE)
)

Having set this up, the animated graph code is pretty simple, looking very much like the monthly graph data except with the linear smooth based on a subset of the data instead of the whole dataset. The graph is set up as before and then a subset of the data is produced on which the linear smooth can be based:

groupByDate <- group_by(passData(), YearMonth, networkDomain) %>%
summarise(meanSession = mean(sessionDuration, na.rm = TRUE),
users = sum(users),
newUsers = sum(newUsers), sessions = sum(sessions))
groupByDate$Date <- as.Date(paste0(groupByDate$YearMonth, "01"),
format = "%Y%m%d")
smoothData <- groupByDate[groupByDate$Date %in%
quantile(groupByDate$Date, input$animation / 100, type = 1):
quantile(groupByDate$Date, (input$animation + 20) / 100, type =
1),
]

We won’t get too distracted by this code, but essentially, it tests to see which of the whole date range falls in a range defined by percentage quantiles based on the sliderInput() values. See ?quantile for more information.

Finally, the linear smooth is drawn with an extra data argument to tell ggplot2 to base the line only on the smaller smoothData object and not the whole range:

ggplot(groupByDate, aes_string(x = "Date",
y = input$outputRequired,
group = "networkDomain",
colour = "networkDomain")
)
+ geom_line() +
geom_smooth(data = smoothData,
method = "lm",
colour = "black"
)

Not bad for a few lines of code. We have both ggplot2 and Shiny to thank for how easy this is.

Streamline the UI by hiding elements

This is a simple function that you are certainly going to need if you build even a moderately complex application. Those of you who have been doing extra credit exercises and/or experimenting with your own applications will probably have already wished for this or, indeed, have already found it.

conditionalPanel() allows you to show/hide UI elements based on other selections within the UI. The function takes a condition (in JavaScript, but the form and syntax will be familiar from many languages) and a UI element and displays the UI only when the condition is true. This has actually used a couple of times in the advanced GA application, and indeed in all the applications, I’ve ever written of even moderate complexity.

We’re going to show the option to smooth the trend graph only when the trend graph tab is displayed, and we’re going to show the controls for the animated graph only when the animated graph tab is displayed.

Naming tabPanel elements

In order to allow testing for which tab is currently selected, we’re going to have to first give the tabs of the tabbed output names. This is done as follows (with the new code in bold):

tabsetPanel(id = "theTabs", # give tabsetPanel a name
tabPanel("Summary", textOutput("textDisplay"), value =
"summary"),
tabPanel("Trend", plotOutput("trend"), value = "trend"),
tabPanel("Animated", plotOutput("animated"), value =
"animated"),
tabPanel("Map", plotOutput("ggplotMap"), value = "map"),
tabPanel("Table", DT::dataTableOutput("countryTable"), value = "table")

As you can see, the whole panel is given an ID (theTabs) and then each tabPanel is also given a name (summary, trend, animated, map, and table). They are referred to in the server.R file very simply as input$theTabs.

Finally, we can make our changes to ui.R to remove parts of the UI based on tab selection:

conditionalPanel(
condition = "input.theTabs == 'trend'",
checkboxInput("smooth", label = "Add smoother?", # add smoother
value = FALSE)
),

conditionalPanel(
condition = "input.theTabs == 'animated'",
sliderInput("animation", "Trend over time",
 min = 0, max = 80, value = 0, step = 5,
 animate = animationOptions(interval = 1000, loop = TRUE)
)
)

As you can see, the condition appears very R/Shiny-like, except with the . operator familiar to JavaScript users in place of $. This is a very simple but powerful way of making sure that your UI is not cluttered with an irrelevant material.

Beautiful tables with DataTable

The latest version of Shiny has added support to draw tables using the wonderful DataTables jQuery library. This will enable your users to search and sort through large tables very easily. To see DataTable in action, visit the homepage at http://datatables.net/. The version in this application summarizes the values of different variables across the different countries from which browser hits originate and looks as follows:

The package can be installed using install.packages(“DT”) and needs to be loaded in the preamble to the server.R file with library(DT). Once this is done using the package is quite straightforward. There are two functions: one in server.R (renderDataTable) and other in ui.R (dataTableOutput). They are used as following:

### server. R
output$countryTable <- DT::renderDataTable ({
groupCountry <- group_by(passData(), country)
groupByCountry <- summarise(groupCountry,
 meanSession = mean(sessionDuration),
 users = log(sum(users)),
   sessions = log(sum(sessions))
)
datatable(groupByCountry)
})
### ui.R
tabPanel("Table", DT::dataTableOutput("countryTable"), value =
"table")

Anything that returns a dataframe or a matrix can be used within renderDataTable().

Note that as of Shiny V. 0.12, the Shiny functions renderDataTable() and dataTableOutput() functions are deprecated: you should use the DT equivalents of the same name, as in the preceding code adding DT:: before each function name specifies that the function should be drawn from that package.

Reactive user interfaces

Another trick you will definitely want up your sleeve at some point is a reactive user interface. This enables you to change your UI (for example, the number or content of radio buttons) based on reactive functions.

For example, consider an application that I wrote related to survey responses across a broad range of health services in different areas. The services are related to each other in quite a complex hierarchy, and over time, different areas and services respond (or cease to exist, or merge, or change their name), which means that for each time period the user might be interested in, there would be a totally different set of areas and services. The only sensible solution to this problem is to have the user tell you which area and date range they are interested in and then give them back the correct list of services that have survey responses within that area and date range.

The example we’re going to look at is a little simpler than this, just to keep from getting bogged down in too much detail, but the principle is exactly the same, and you should not find this idea too difficult to adapt to your own UI. We are going to allow users to constrain their data by the country of origin of the browser hit. Although we could design the UI by simply taking all the countries that exist in the entire dataset and placing them all in a combo box to be selected, it is a lot cleaner to only allow the user to select from the countries that are actually present within the particular date range they have selected.

This has the added advantage of preventing the user from selecting any countries of origin, which do not have any browser hits within the currently selected dataset. In order to do this, we are going to create a reactive user interface, that is, one that changes based on data values that come about from user input.

Reactive user interface example – server.R

When you are making a reactive user interface, the big difference is that instead of writing your UI definition in your ui.R file, you place it in server.R and wrap it in renderUI(). Then, point to it from your ui.R file.

Let’s have a look at the relevant bit of the server.R file:

output$reactCountries <- renderUI({
countryList = unique(as.character(passData()$country))
selectInput("theCountries", "Choose country", countryList)
})

The first line takes the reactive dataset that contains only the data between the dates selected by the user and gives all the unique values of countries within it. The second line is a widget type we have not used yet, which generates a combo box. The usual id and label arguments are given, followed by the values that the combo box can take. This is taken from the variable defined in the first line.

Reactive user interface example – ui.R

The ui.R file merely needs to point to the reactive definition, as shown in the following line of code (just add it in to the list of widgets within sidebarPanel()):

uiOutput("reactCountries")

You can now point to the value of the widget in the usual way as input$subDomains. Note that you do not use the name as defined in the call to renderUI(), that is, reactCountries, but rather the name as defined within it, that is, theCountries.

Progress bars

It is quite common within Shiny applications and in analytics generally to have computations or data fetches that take a long time.

However, even using all these tools, it will sometimes be necessary for the user to wait some time before their output is returned. In cases like this, it is a good practice to do two things: first, to inform that the server is processing the request and has not simply crashed or otherwise failed, and second to give the user some idea of how much time has elapsed since they requested the output and how much time they have remaining to wait.

This is achieved very simply in Shiny using the withProgress() function. This function defaults to measuring progress on a scale from 0 to 1 and produces a loading bar at the top of the application with the information from the message and detail arguments of the loading function.

You can see in the following code, the withProgress function is used to wrap a function (in this case, the function that draws the map), with message and detail arguments describing what is happened and an initial value of 0 (value = 0, that is, no progress yet):

withProgress(message = 'Please wait',
detail = 'Drawing map...', value = 0, {
 ... function code...
}
)

As the code is stepped through, the value of progress can steadily be increased from 0 to 1 (for example, in a for() loop) using the following code:

incProgress(1/3)

The third time this is called, the value of progress will be 1, which indicates that the function has completed (although other values of progress can be selected where necessary, see ?withProgess()). To summarize, the finished code looks as follows:

withProgress(message = 'Please wait',
detail = 'Drawing map...', value = 0, {
 ... function code...
incProgress(1/3)
.. . function code...
incProgress(1/3)
   ... function code...
 incProgress(1/3)
}
)

It’s very simple. Again, have a look at the application to see it in action.

Summary

In this article, you have now seen most of the functionality within Shiny. It’s a relatively small but powerful toolbox with which you can build a vast array of useful and intuitive applications with comparatively little effort. In this respect, ggplot2 is rather a good companion for Shiny because it too offers you a fairly limited selection of functions with which knowledgeable users can very quickly build many different graphical outputs.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here