In the previous section, we demonstrated the use of R Markdown that is designed for creating dynamic documents. In this section, we will take a quick tour of creating interactive apps where we use a graphical user interface to interact with the data.
R itself is a great environment for data analysis and visualization. However, it is not usual to deliver R and some analytic scripts to the customers to run by themselves. The outcome of data analysis can be presented not only in a HTML page, PDF document, or a Word document, but also in an interactive app that allows readers to interact with the data by modifying some parameters and see what happens with the outcome.
A powerful package, shiny
(http://shiny.rstudio.com/), developed by RStudio, is designed exactly for this purpose. A shiny app is different from the interactive graphics we demonstrated previously. It works in a web browser and the developer has all the say about what appears in the web page and how users can interact with it. To achieve this, a shiny app basically consists of two important parts: An HTTP server that interacts with the web browser, and an R session that interacts with the HTTP server.
The following is a minimal shiny app. We write an R script to define its user interface (ui
) and server
logic. The user interface is a boostrapPage
which contains a numericInput
to take an integer of sample size and a textOutput
to show the mean of the randomly generated sample. The logic behind server
is to simply generate random numbers according to the sample size (n
) in the input
and put the mean of the random sample to the output
:
library(shiny) ui <- bootstrapPage( numericInput("n", label = "Sample size", value = 10, min = 10, max = 100), textOutput("mean") ) server <- function(input, output) { output$mean <- renderText(mean(rnorm(input$n))) } app <- shinyApp(ui, server) runApp(app)
The definition is now complete and we can source the code in RStudio to play with this minimal shiny app, as shown in the following screenshot:
Each time we change the number of the sample size, the HTTP server will ask the R backend to rerun the server logic and refresh the output mean.
Although the preceding example is not useful, it at least demonstrates the basic components of a shiny app. Now we look at a more complicated but useful example.
The following example is a visualizer of many paths generated by geometric Brownian motion which is often used to model stock prices. As we know, a geometric Brownian motion is characterized by starting value, expected growth rate (r
), volatility (sigma
), duration (T
) and the number of periods
. Expect for T = 1
, we allow users to modify all other parameters.
Now we can define the user interface of the shiny app according to the parameters we want to expose to users. The shiny
package provides a rich set of input controls listed as follows:
shiny_vars <- ls(getNamespace("shiny")) shiny_vars[grep("Input$", shiny_vars)] ## [1] "checkboxGroupInput" "checkboxInput" ## [3] "dateInput" "dateRangeInput" ## [5] "fileInput" "numericInput" ## [7] "passwordInput" "selectInput" ## [9] "selectizeInput" "sliderInput" ## [11] "textInput" "updateCheckboxGroupInput" ## [13] "updateCheckboxInput" "updateDateInput" ## [15] "updateDateRangeInput" "updateNumericInput" ## [17] "updateSelectInput" "updateSelectizeInput" ## [19] "updateSliderInput" "updateTextInput"
To control the randomness of the generated paths, we allow users to specify the random seed (seed
) so that the same seed produces the same paths. In the following code where ui
is defined, we use numericInput
for seed
and sliderInput
for other parameters. The sliderInput
control has a certain range and step so that we can force a parameter to take reasonable values.
The user interface not only defines the input part but also the output part, that is, where to show what. The following is all output types shiny provides:
shiny_vars[grep("Output$", shiny_vars)] ## [1] "dataTableOutput" "htmlOutput" ## [3] "imageOutput" "plotOutput" ## [5] "tableOutput" "textOutput" ## [7] "uiOutput" "verbatimTextOutput"
In this example, the shiny app only shows a plot of all paths put together to indicate different possibilities with the same set of parameters:
library(shiny) ui <- fluidPage( titlePanel("Random walk"), sidebarLayout( sidebarPanel( numericInput("seed", "Random seed", 123), sliderInput("paths", "Paths", 1, 100, 1), sliderInput("start", "Starting value", 1, 10, 1, 1), sliderInput("r", "Expected return", -0.1, 0.1, 0, 0.001), sliderInput("sigma", "Sigma", 0.001, 1, 0.01, 0.001), sliderInput("periods", "Periods", 10, 1000, 200, 10)), mainPanel( plotOutput("plot", width = "100%", height = "600px") )) )
Once the user interface is defined, we need to implement the server logic which is basically about generating random paths according to user-specified parameters and put them together in the same plot.
The following code is a simple implementation of the server logic. First we set the random seed. Then we iteratively call sde::GBM
to generate random paths from geometric Brownian motion. To install the package, run install.packages("sde")
before calling GBM
:
The GBM
package is responsible for generating one path while sapply
is used to combine all generated paths into a matrix (mat
) where each column represents a path. Finally, we use matplot
to plot each path in different colors together in one chart.
The calculation is done in render*
functions no matter whether it is a text, image, or a table. The following lists all the render functions shiny provides:
shiny_vars[grep("^render", shiny_vars)] ## [1] "renderDataTable" "renderImage" "renderPage" ## [4] "renderPlot" "renderPrint" "renderReactLog" ## [7] "renderTable" "renderText" "renderUI"
In this example, we only need renderPlot()
and to put the plotting code in it. The output$plot
function will go to plotOutput("plot")
in the user interface when the input is modified:
server <- function(input, output) { output$plot <- renderPlot({ set.seed(input$seed) mat <- sapply(seq_len(input$paths), function(i) { sde::GBM(input$start, input$r, input$sigma, 1, input$periods) }) matplot(mat, type = "l", lty = 1, main = "Geometric Brownian motions") }) }
Now both user interface and server logic are ready. We can combine them together to create a shiny app and run it in the web browser.
app <- shinyApp(ui, server) runApp(app)
When the parameters are modified, the plot will be refreshed automatically:
If we set a significantly positive annualized expected return, the generated paths will tend to grow more than decline:
In addition to the functions shiny
provides, RStudio also develops shinydashboard
(http://rstudio.github.io/shinydashboard/) which is specialized in presenting data for overview or monitoring purposes.
The following example demonstrates how easy it is to create a simple dashboard to show the most popular R packages on CRAN with the most downloads in weekly and monthly time scale.
The data source is provided by cranlogs
(http://cranlogs.r-pkg.org). First run the following code to install the packages we need:
install_packages(c("shinydashboard", "cranlogs"))
Then we take a quick view of the data source of CRAN downloads:
library(cranlogs) cran_top_downloads() ## No encoding supplied: defaulting to UTF-8. ## rank package count from to ## 1 1 Rcpp 9682 2016-08-18 2016-08-18 ## 2 2 digest 8937 2016-08-18 2016-08-18 ## 3 3 ggplot2 8269 2016-08-18 2016-08-18 ## 4 4 plyr 7816 2016-08-18 2016-08-18 ## 5 5 stringi 7471 2016-08-18 2016-08-18 ## 6 6 stringr 7242 2016-08-18 2016-08-18 ## 7 7 jsonlite 7100 2016-08-18 2016-08-18 ## 8 8 magrittr 6824 2016-08-18 2016-08-18 ## 9 9 scales 6397 2016-08-18 2016-08-18 ## 10 10 curl 6383 2016-08-18 2016-08-18 cran_top_downloads("last-week") ## No encoding supplied: defaulting to UTF-8. ## rank package count from to ## 1 1 Rcpp 50505 2016-08-12 2016-08-18 ## 2 2 digest 46086 2016-08-12 2016-08-18 ## 3 3 ggplot2 39808 2016-08-12 2016-08-18 ## 4 4 plyr 38593 2016-08-12 2016-08-18 ## 5 5 jsonlite 36984 2016-08-12 2016-08-18 ## 6 6 stringi 36271 2016-08-12 2016-08-18 ## 7 7 stringr 34800 2016-08-12 2016-08-18 ## 8 8 curl 33739 2016-08-12 2016-08-18 ## 9 9 DBI 33595 2016-08-12 2016-08-18 ## 10 10 magrittr 32880 2016-08-12 2016-08-18
After getting familiar with the form of data we want to present in the dashboard, we can now think about constructing the dashboard in exactly the same way as constructing a typical shiny app. To make the most of shinydashboard
, it is better to go through http://rstudio.github.io/shinydashboard/structure.html to get a general idea of the nice components it provides.
Similarly to shiny app, we start by creating the user interface. This time, we use dashboardPage
, dashboardSidebar
and dashboardBody
. In the dashboard, we want to present the package download dynamics and tables of the most popular packages with top downloads in both monthly and weekly scales.
We put the menu of monthly and weekly in the side bar so users can choose which to see. In each tab page, we can put plots and tables together. In this example, we use formattable
to add color bars on the download column to make the numbers more comparable and straightforward.
library(shiny) library(shinydashboard) library(formattable) library(cranlogs) ui <- dashboardPage( dashboardHeader(title = "CRAN Downloads"), dashboardSidebar(sidebarMenu( menuItem("Last week", tabName = "last_week", icon = icon("list")), menuItem("Last month", tabName = "last_month", icon = icon("list")) )), dashboardBody(tabItems( tabItem(tabName = "last_week", fluidRow(tabBox(title = "Total downloads", tabPanel("Total", formattableOutput("last_week_table"))), tabBox(title = "Top downloads", tabPanel("Top", formattableOutput("last_week_top_table"))))), tabItem(tabName = "last_month", fluidRow(tabBox(title = "Total downloads", tabPanel("Total", plotOutput("last_month_barplot"))), tabBox(title = "Top downloads", tabPanel("Top", formattableOutput("last_month_top_table"))))) )) )
Note that plotOutput
is provided by shiny
while formattableOutput
is provided by formattable
package. In fact, developers can create all kinds of HTML widgets that can be embedded into a shiny app as long as the package properly defines the render*
function and *Output
function to produce the correct HTML code.
Then we define the server logic. Since the output relies purely on the data source, we download the data before calling formattable
and plot
.
server <- function(input, output) { output$last_week_table <- renderFormattable({ data <- cran_downloads(when = "last-week") formattable(data, list(count = color_bar("lightblue"))) }) output$last_week_top_table <- renderFormattable({ data <- cran_top_downloads("last-week") formattable(data, list(count = color_bar("lightblue"), package = formatter("span", style = "font-family: monospace;"))) }) output$last_month_barplot <- renderPlot({ data <- subset(cran_downloads(when = "last-month"), count > 0) with(data, barplot(count, names.arg = date), main = "Last month downloads") }) output$last_month_top_table <- renderFormattable({ data <- cran_top_downloads("last-month") formattable(data, list(count = color_bar("lightblue"), package = formatter("span", style = "font-family: monospace;"))) }) }
In fact, if the data is updating, we can create a dynamic dashboard where the tables and charts periodically refresh. Using ?reactiveTimer
and ?reactive
will be the key to achieve this. Read the documentation for more information.
Both the user interface and the server logic are ready, so we can run the app now:
runApp(shinyApp(ui, server))
By default, the shiny app shows the first page at the first visit. The following is a screenshot of the Last week tab page which consists of two tab panels of formattable
data frames:
The following screenshot shows the Last month tab page which consists of a histogram and a formattable
data frame:
To see more examples and the code behind them, visit http://rstudio.github.io/shinydashboard/examples.html.