library(shiny)
shiny is a package that has routines to create interactive web applications that run in a browser.
As an example consider the app Taylor Polynomials.
Every app has two parts:
Let’s begin by creating a page that does the following: it generates a data set with n observations from a standard normal distribution and then draws the histogram. The user can choose n:
ui <- fluidPage(
numericInput(inputId = "n", # name of variable
label = "Sample Size", # text on page
value = 1000), # starting value
plotOutput("plot1")
)
server <- function(input, output) {
output$plot1 <- renderPlot({
x <- rnorm(input$n)
bw <- diff(range(x))/50
ggplot(data.frame(x=x), aes(x)) +
geom_histogram(color = "black",
fill = "white",
binwidth = bw) +
labs(x = "x",
y = "Counts")
})
}
shinyApp(ui, server)
If you are on a computer that has R running with shiny installed you can run this and all the apps here with
runGitHub(repo = "Computing-with-R",
username = "WolfgangRolke",
subdir = "shiny/example1")
you can also do this: File > New File > Shiny Web App, copy-paste commands into file, click Run App
Exercise 1
write a shiny app that does the same but using base R histograms.
Next we will improve the app in three ways:
ui <- fluidPage(
titlePanel("Histogram App"),
sidebarLayout(
sidebarPanel( # Input widgets on left side of page
numericInput(inputId = "n",
label = "Sample Size",
value = 1000,
width = "40%"), # size of box
textInput(inputId = "nbins",
label = "Number of bins",
value = "50",
width = "20%") # size of box
),
mainPanel(
plotOutput("plot1")
)
))
server <- function(input, output) {
output$plot1 <- renderPlot({
x <- rnorm(input$n)
bw <- diff(range(x))/as.numeric(input$nbins)
ggplot(data.frame(x=x), aes(x)) +
geom_histogram(color = "black",
fill = "white",
binwidth = bw) +
labs(x = "x",
y = "Counts")
})
}
shinyApp(ui, server)
notice also that instead of a numericInput I used a textInput, although nbins is meant to be a number. I often do this because I find the appearance of text inputs more pleasing. I can of course always turn it into a number with as.numeric.
Exercise 2
add the possibility to change the number of bins to your solution of the previous example.
Next we will use one of the most useful commands in shiny: conditionalPanel. With this we can control what happens in the app depending on the users choices.
Let’s add the possibility to show a boxplot instead of the histogram. Notice that there are no bins in a boxplot, so we want to show the nbins input only when a histogram is done:
ui <- fluidPage(
titlePanel("Histogram or Boxplot App"),
sidebarLayout(
sidebarPanel(
numericInput("n", "Sample Size", 1000),
selectInput("whichgraph", "What Graph?",
choices = c("Histogram", "Boxplot"),
selected = "Boxplot"),
conditionalPanel(
condition = "input.whichgraph=='Histogram'",
textInput("nbins",
"Number of bins",
"50",
width = "20%")
)
),
mainPanel(
conditionalPanel(
condition="input.whichgraph=='Histogram'",
plotOutput("plot1")
),
conditionalPanel(
condition="input.whichgraph=='Boxplot'",
plotOutput("plot2")
)
)
))
server <- function(input, output) {
output$plot1 <- renderPlot({
x <- rnorm(input$n)
bw <- diff(range(x))/as.numeric(input$nbins)
ggplot(data.frame(x=x), aes(x)) +
geom_histogram(color = "black",
fill = "white",
binwidth = bw) +
labs(x = "x",
y = "Counts")
})
output$plot2 <- renderPlot({
x <- rnorm(input$n)
df <- data.frame(x=x, z=rep(" ", length(x)))
ggplot(df, aes(z, x)) +
geom_boxplot() +
xlab("")
})
}
shinyApp(ui, server)
Exercise 3
add the same option to the previous exercise
Another useful feature is the ability to create tabs. Let’s say we want to show the histogram on one tab and the boxplot on another:
ui <- fluidPage(
titlePanel("Histogram or Boxplot App"),
sidebarLayout(
sidebarPanel(
numericInput("n", "Sample Size", 1000),
conditionalPanel(condition="input.mytabs=='Histogram'",
textInput("nbins", "Number of bins", "50", width = "20%")
)
),
mainPanel(
tabsetPanel(
tabPanel("Histogram", plotOutput("plot1")),
tabPanel("Boxpot", plotOutput("plot2")),
id="mytabs"
)
)
))
Notice again the use of conditionalPanel: now the nbins box appears when the Histogram tab is selected.
Did you notice a slight flaw in the program? When we switch from one graph to the other the data is generated a new, so the graphs are not for the same data set. Let’s fix this.
Also let’s say we want on another tab to show some summary statistics:
ui <- fluidPage(
titlePanel("Histogram or Boxplot App"),
sidebarLayout(
sidebarPanel(
numericInput("n", "Sample Size", 1000),
conditionalPanel(condition="input.mytabs=='Histogram'",
textInput("nbins", "Number of bins", "50", width = "20%")
)
),
mainPanel(
tabsetPanel(
tabPanel("Histogram", plotOutput("plot1")),
tabPanel("Boxpot", plotOutput("plot2")),
tabPanel("Summary Statistics", uiOutput("text1")),
id="mytabs"
)
)
))
server <- function(input, output) {
data <- reactive({
rnorm(input$n)
})
summaries <- reactive({
x <- data()
list(n = length(x),
xbar = round(mean(x), 3),
shat = round(sd(x), 3))
})
output$plot1 <- renderPlot({
bw <- diff(range(data()))/as.numeric(input$nbins)
ggplot(data.frame(x=data()), aes(x)) +
geom_histogram(color = "black",
fill = "white",
binwidth = bw) +
labs(x="x", y="Counts")
})
output$plot2 <- renderPlot({
x <- data()
df <- data.frame(x=x, z=rep(" ", length(x)))
ggplot(df, aes(z, x)) +
geom_boxplot() +
xlab("")
})
output$text1 <- renderText({
lns <- "<table>"
lns[2] <- paste("<tr><th>Sample Size </th><td>", summaries()[1], "</td></tr")
lns[3] <- paste("<tr><th>Mean </th><td>", summaries()[2], "</td></tr")
lns[4] <- paste("<tr><th>Standard Deviation </th><td>", summaries()[3], "</td></tr")
lns[5] <- "</table>"
lns
})
}
shinyApp(ui, server)
This also shows that we can use html syntax in shiny. This is not a surprise because in the end it is a web page!
Exercise 4
change your exercise so that the graphs show the same data set.
Exercise 5
change your exercise so that below the graph we see the mean of the data.
shiny apps are incredible versatile, you can write apps with a lot of stuff in it. You can even make little movies:
ui <- fluidPage(
titlePanel("2-D Random Walk"),
sliderInput("step","Step",
min=1, max=100, value=1, step=1,
animate=animationOptions(interval = 500, loop = FALSE)),
plotOutput("plot", width="500", height="500")
)
server <- function(input, output) {
data <- reactive({
x <- cumsum(c(rep(0, 10), rnorm(1000)))
y <- cumsum(c(rep(0, 10), rnorm(1000)))
data.frame(x=x, y=y)
})
make.plot <- reactive({
xymax <- abs(max(data()))
ggplot(data(), aes(x, y)) +
lims(x=1.2*c(-xymax, xymax), y=1.2*c(-xymax, xymax)) +
labs(x="x", y="y")
})
output$plot <- renderPlot({
for(i in 1:input$step) {
plt <- make.plot() +
geom_point(data=data()[10*i, ],
aes(x,y), size=2, color="red") +
geom_line(data=data()[1:(10*i), ], aes(x,y),
size=0.25, color="blue", alpha=0.5)
}
plt
})
}
shinyApp(ui, server)
There are a number of ways to create, run and distribute shiny apps:
ui.R
shinyUI(fluidPage(
numericInput("n", "Sample Size", 1000),
plotOutput("plot1")
))
server.R
shinyServer(function(input, output) {
output$plot1 <- renderPlot({
x <- rnorm(input$n)
bw <- diff(range(x))/50
ggplot(data.frame(x=x), aes(x)) +
geom_histogram(color = "black", fill = "white", binwidth = bw) +
labs(x="x", y="Counts")
})
})
then in the console run
runApp("PATH/myapp1")
where PATH is the folder path to myapp1.
runGitHub("reponame", "username",
subdir = "shiny/myapp1")
here I assume you have myapp1 in a folder called shiny.
We will talk about Github a lot more soon.
runUrl("http://websiteurl/shiny/myapp1.zip")
Uploading an app to shinyapps should be very simple: run the app and click on the Publish button in the upper right corner. I have however had occasional problems with this method. If it does not work run the following command from the console:
shinyapps::deployApp("C:\\Users\\...\\name_of-app",
account="your_account_name")
For more information on how to get up and running with shinyapps.io read https://shiny.rstudio.com/articles/shinyapps.html