Shiny Developer Conference 2016 Recap

February 5, 2016
By

(This article was first published on Getting Genetics Done, and kindly contributed to R-bloggers)

This is a guest post from VP Nagraj, a data scientist embedded within UVA’s Health Sciences Library, who runs our Data Analysis Support Hub (DASH) service.

Last weekend I was fortunate enough to be able to participate in the first ever Shiny Developer Conference hosted by RStudio at Stanford University. I’ve built a handful of apps, and have taught an introductory workshop on Shiny. In spite of that, almost all of the presentations de-mystified at least one aspect of the how, why or so what of the framework. Here’s a recap of what resonated with me, as well as some code and links out to my attempts to put what I digested into practice.

tl;dr

  • reactivity is a beast
  • javascript isn’t cheating
  • there are already a ton of shiny features … and more on the way

reactivity

For me, understanding reactivity has been one of the biggest challenges to using Shiny … or at least to using Shiny well. But after > 3 hours of an extensive (and really clear) presentation by Joe Cheng, I think I’m finally starting to see what I’ve been missing. Here’s something in particular that stuck out to me:
output$plot = renderPlot() is not an imperative to the browser to do a what … it’s a recipe for how the browser should do something.
Shiny ‘render’ functions (e.g. renderPlot(), renderText(), etc) inherently depend on reactivity. What the point above emphasizes is that assignments to a reactive expression are not the same as assignments made in “regular” R programming. Reactive outputs depend on inputs, and subsequently change as those inputs are manipulated.
If you want to watch how those changes happen in your own app, try adding options(shiny.reactlog=TRUE) to the top of your server script. When you run the app in a browser and press COMMAND + F3 (or CTRL + F3 on Windows) you’ll see a force directed network that outlines the connections between inputs and outputs.
Another way to implement reactivity is with the reactive() function.
For my apps, one of the pitfalls has been re-running the same code multiple times. That’s a perfect use-case for reactivity outside of the render functions.
Here’s a trivial example:
library(shiny)

ui = fluidPage(
numericInput("threshold", "mpg threshold", value = 20),
plotOutput("size"),
textOutput("names")
)

server = function(input, output) {

output$size = renderPlot({

dat = subset(mtcars, mpg > input$threshold)
hist(dat$wt)

})

output$names = renderText({

dat = subset(mtcars, mpg > input$threshold)
rownames(dat)

})
}

shinyApp(ui = ui, server = server)
The code above works … but it’s redundant. There’s no need to calculate the “dat” object separately in each render function.
The code below does the same thing but stores “dat” in a reactive that is only calculated once.
library(shiny)

ui = fluidPage(
numericInput("threshold", "mpg threshold", value = 20),
plotOutput("size"),
textOutput("names")
)

server = function(input, output) {

dat = reactive({

subset(mtcars, mpg > input$threshold)

})

output$size = renderPlot({

hist(dat()$wt)

})

output$names = renderText({

rownames(dat())

})
}

shinyApp(ui = ui, server = server)

javascript

For whatever reason I’ve been stuck on the idea that using JavaScript inside a Shiny app would be “cheating”. But Shiny is actually well equipped for extensions with JavaScript libraries. Several of the speakers leaned in on this idea. Yihui Xie presented on the DT package, which is an interface to use features like client-side filtering from the DataTables library. And Dean Attali demonstrated shinyjs, a package that makes it really easy to incorporate JavaScript operations.
Below is code for a masterpiece that that does some hide() and show():
# https://apps.bioconnector.virginia.edu/game
library(shiny)
library(shinyjs)
shinyApp(

ui = fluidPage(
titlePanel(actionButton("start", "start the game")),
useShinyjs(),
hidden(actionButton("restart", "restart the game")),
tags$h3(hidden(textOutput("game_over")))
),

server = function(input, output) {

output$game_over =
renderText({
"game over, man ... game over"
})

observeEvent(input$start, {

show("game_over", anim = TRUE, animType = "fade")
hide("start")
show("restart")
})

observeEvent(input$restart, {
hide("game_over")
hide("restart")
show("start")
})

}
)

everything else

brushing

http://shiny.rstudio.com/articles/plot-interaction.html
Adding a brush argument to plotOutput() let’s you click and drag to select a points on a plot. You can use this for “zooming in” on something like a time series plot. Here’s the code for an app I wrote based on data from the babynames package – in this case the brush let’s you zoom to see name frequency over specific range of years.
# http://apps.bioconnector.virginia.edu/names/
library(shiny)
library(ggplot2)
library(ggthemes)
library(babynames)
library(scales)

options(scipen=999)

ui = fluidPage(titlePanel(title = "names (1880-2012)"),
textInput("name", "enter a name"),
actionButton("go", "search"),
plotOutput("plot1", brush = "plot_brush"),
plotOutput("plot2"),
htmlOutput("info")

)

server = function(input, output) {

dat = eventReactive(input$go, {

subset(babynames, tolower(name) == tolower(input$name))

})

output$plot1 = renderPlot({

ggplot(dat(), aes(year, prop, col=sex)) +
geom_line() +
xlim(1880,2012) +
theme_minimal() +
# format labels with percent function from scales package
scale_y_continuous(labels = percent) +
labs(list(title ="% of individuals born with name by year and gender",
x = "n click-and-drag over the plot to 'zoom'",
y = ""))

})

output$plot2 = renderPlot({

# need latest version of shiny to use req() function
req(input$plot_brush)
brushed = brushedPoints(dat(), input$plot_brush)

ggplot(brushed, aes(year, prop, col=sex)) +
geom_line() +
theme_minimal() +
# format labels with percent function from scales package
scale_y_continuous(labels = percent) +
labs(list(title ="% of individuals born with name by year and gender",
x = "",
y = ""))

})

output$info = renderText({

"data source: social security administration names from babynames package

"


})

}

shinyApp(ui, server)

gadgets

http://shiny.rstudio.com/articles/gadgets.html
A relatively easy way to leverage Shiny reactivity for visual inspection and interaction with data within RStudio. The main difference here is that you’re using an abbreviated (or ‘mini’) ui. The advantage of this workflow is that you can include it in your script to make your analysis interactive. I modified the example in the documentation and wrote a basic brushing gadget that removes outliers:
library(shiny)
library(miniUI)
library(ggplot2)

outlier_rm = function(data, xvar, yvar) {

ui = miniPage(
gadgetTitleBar("Drag to select points"),
miniContentPanel(
# The brush="brush" argument means we can listen for
# brush events on the plot using input$brush.
plotOutput("plot", height = "100%", brush = "brush")
)
)

server = function(input, output, session) {

# Render the plot
output$plot = renderPlot({
# Plot the data with x/y vars indicated by the caller.
ggplot(data, aes_string(xvar, yvar)) + geom_point()
})

# Handle the Done button being pressed.
observeEvent(input$done, {

# create id for data
data$id = 1:nrow(data)

# Return the brushed points. See ?shiny::brushedPoints.
p = brushedPoints(data, input$brush)

# create vector of ids that match brushed points and data
g = which(p$id %in% data$id)

# return a subset of the original data without brushed points
stopApp(data[-g,])
})
}

runGadget(ui, server)
}

# run to open plot viewer
# click and drag to brush
# press done return a subset of the original data without brushed points
library(gapminder)
outlier_rm(gapminder, "lifeExp", "gdpPercap")

# you can also use the same method above but pass the output into a dplyr pipe syntax
# without the selection what is the mean life expectancy by country?
library(dplyr)
outlier_rm(gapminder, "lifeExp", "gdpPercap") %>%
group_by(country) %>%
summarise(mean(lifeExp))

req()

http://shiny.rstudio.com/reference/shiny/latest/req.html
This solves the issue of requiring an input – I’m definitely going to use this so I don’t have to do the return(NULL) work around:
# no need to do do this any more
#
# inFile = input$file1
#
# if (is.null(inFile))
# return(NULL)

# use req() instead
req(input$file1)

profvis

http://rpubs.com/wch/123888
Super helpful method for digging into the call stack of your R code to see how you might optimize it.
One or two seconds of processing can make a big difference, particularly for a Shiny app …

rstudio connect

https://www.rstudio.com/rstudio-connect-beta/
Jeff Allen from RStudio gave a talk on deployment options for Shiny applications and mentioned this product, which is a “coming soon” platform for hosting apps alongside RMarkdown documents and plots. It’s not available as a full release yet, but there is a beta version for testing.

To leave a comment for the author, please follow the link and comment on their blog: Getting Genetics Done.

R-bloggers.com offers daily e-mail updates about R news and tutorials on topics such as: Data science, Big Data, R jobs, visualization (ggplot2, Boxplots, maps, animation), programming (RStudio, Sweave, LaTeX, SQL, Eclipse, git, hadoop, Web Scraping) statistics (regression, PCA, time series, trading) and more...



If you got this far, why not subscribe for updates from the site? Choose your flavor: e-mail, twitter, RSS, or facebook...

Comments are closed.

Search R-bloggers


Recent popular posts

Most visited articles of the week

  1. How to write the first for loop in R
  2. Installing R packages
  3. Using apply, sapply, lapply in R
  4. R – Sorting a data frame by the contents of a column
  5. How to perform a Logistic Regression in R
  6. How to Make a Histogram with Basic R
  7. Tutorials for learning R
  8. How to Make a Histogram with ggplot2
  9. Creating Slopegraphs with R

Sponsors

RSS Jobs for R users

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)