class: center, middle, inverse, title-slide # Effective use of Shiny modules ##
rstudio::conf(2019L)
###
Eric Nantz
Eli Lilly and Company
2019/1/18
Slides:
bit.ly/modules2019
@thercast
rpodcast
rpodcast
r-podcast.org
--- layout: true <div class="my-footer"><span>bit.ly/modules2019                              Effective use of Shiny Modules                        rstudio::conf 2019</span></div> --- # My Journey with Shiny .pull-left[ .large[ Early adopter when Shiny was released to CRAN __First efforts__: Small prototypes to showcase possibilities and [learn by doing](https://r-podcast.org/15)
Ascend Joe Cheng's [ladder of enlightment](https://github.com/rstudio/ShinyDeveloperConference/blob/45737f1f3386fb3a9194c40f9a999b58f60971de/Reactivity/slides.Rmd#L233-L240) Present day: Developer of __large and complex__ applications integrating multiple systems ] ] .pull-right[ ![:scale 90%](img/Flynnuser.png) ] ??? * In early days, learning Shiny was a struggle when moving from prototypes to large applications * ShinyDevCon in 2016 --- # My First Attempt .pull-left[ .large[ Empower statisticians with little/no
experience to perform subgroup identification based on [TSDT](https://cran.rstudio.com/web/packages/TSDT/) package 🚧 Non-trivial requirements (persistent sessions, dynamic UI, HPC integration) __Difficulties__ * Duplicated similar(ish) UI widgets * Code not organized well * Extending capabilities was challenging ] ] .pull-right[ ![:scale 120%](img/tsdt_screenshot.png) ] ??? * common theme: Use Shiny to give web UI front-end to sophisicated algorithms so those with no expertise in R can perform the analyses --- name: non-modules background-image: url(img/non_modules_slides.svg) background-position: center background-size: contain class: top, clear, center ??? * Notice how I've duplicated various sub-components (variable select, project settings, visualization, summary table) * Difficult to reason through the flow from one component to another --- background-image: url(img/spaghetti.jpg) background-position: center background-size: contain class: inverse, clear, center, bottom .center[ ## [Reactive Spaghetti (Ian Lyttle & Alex Shum)](https://www.rstudio.com/resources/videos/shiny-modules/) ] --- template: non-modules .center[ .large[ What can solve many of these issues? ] ] --- background-image: url(img/key.gif) background-size: cover class: inverse, clear, center, middle # Modules to the Rescue! --- # What are Modules? -- ## Compose complex apps out of smaller, more understandable pieces ??? * Take a step back: If you have experience with creating R functions and even packages, these are not new concepts to you * We will visit this theme throughout the presentation -- .large[ - Avoid namespace collisions when using same widget across different parts of your app - Allows you to encapsulate distinct app components - Organize code into logical and easy-to-understand components - Facilitates collaboration ] -- ## Sound familiar? -- .large[ -
functions also help avoid collisions in variable names with general R code - Essential for creating non-trivial and extensive workflows ] --- background-image: url(img/modules_slide.svg) background-position: center background-size: contain class: clear, center, middle ??? * Distinct components mimic the application user's workflow * No more (copying-pasting) sub-components like visualization, summary table as they are part of their own namespace in the "big" modules * More logical flow between modules * I did not arrive at this mindset right away. It took me a lot of practice and trial-error --- background-image: url(img/roadsunset17.png) background-size: cover class: inverse, clear, center, top .center[ ## Road to Modules Mastery ] ??? * Most of you in the room are likely at this first stop -- <div class="step1">(1) Creating/using simple modules occassionally</div> -- <div class="step2">(2) Communication with modules</div> <div class="step3">(3) Communication between sibling modules</div> ??? * We will illustrate a few concepts to get you on your way to (2) and (3) --- # All roads lead to these principles: -- .pull-left[ .center[ ## Careful design .large[ What does the module do? What is it trying to accomplish? 🤔 What should I call this module? ] ] ] ??? * If you can't name your module easily, that's a warning sign that it is trying to do too much -- .pull-right[ .center[ ## Inputs & return values .large[ Static or reactive input(s)? Complexity of return values Which outputs serve as inputs for other modules? ] ] ] ??? * All of these help future you and collaborators understand the flow of your application -- .center[ ## Modules built without these principles is not enough! ] ??? * Bold statement, but I can tell you from experience this is true --- background-image: url(img/ames_explorer_arrows_screenshot.svg) background-position: center background-size: contain class: clear, center, bottom [gallery.shinyapps.io/ames-explorer](https://gallery.shinyapps.io/ames-explorer) [github.com/rpodcast/ames_explorer](https://github.com/rpodcast/ames_explorer) ??? Pull up interactive version if time permits * Side-by-side scatterplots with variable choices * Select points via Shiny's interactive [plot brushing](http://shiny.rstudio.com/articles/selecting-rows-of-data.html) * View additional variables inside a [DT](https://rstudio.github.io/DT/) data table * Ability to annotate total sales price for data points in table --- # Document your modules! .left-column-big[ ```r #' Variable selection module server-side processing #' *#' @param input, output, session: standard shiny boilerplate #' *#' @return list with following components #' \describe{ #' \item{xvar}{reactive character string indicating x variable selection} #' \item{yvar}{reactive character string indicating y variable selection} #' } varselect_mod_server <- function(input, output, session) { * return( * list( * xvar = reactive({ input$xvar }), * yvar = reactive({ input$yvar }) * ) * ) } ``` ] .right-column-small[ 🗒 Document a module's input(s) and return value(s) 📝 Constructing a named list articulates the __intent__ of a module ![:scale 50%](img/contract.svg) ] ??? * Example of an __accessor__ module. Simple yet can be very effective * Explicit return with named list --- # To () or not to () .code150[ ``` plot1var <- reactive({ input$xvar }) callModule(mod, "name", `plot1var`) ``` ] .center[ ![:scale 10%](img/arrow_down.png) ] .code120[ ``` mod <- function(input, output, session, plot1var) { foo <- reactive({ # reference plot1var `plot1var()` }) # return key reactive return(`foo`) } ``` ] --- # To () or not to () .pull-left[ ![:scale 100%](img/map.png) .center[ [Advanced R (Second Edition)](https://adv-r.hadley.nz/functionals.html) ] ] ??? * `map` function from `purrr` similar to `lapply` family of functions -- .pull-right[ .large[ `purrr::map()` takes __name__ of function as parameter, but __invokes__ function on each piece of supplied vector ] ## How does this apply to modules? ] --- # To () or not to () .pull-left[ .code120[ ``` plot1var <- reactive({ input$xvar }) callModule(mod, "name", `plot1var`) ``` ] .center[ ![:scale 10%](img/arrow_down.png) ] .code90[ ``` mod <- function(input, output, session, plot1var) { foo <- reactive({ # reference plot1var `plot1var()` }) # return key reactive return(`foo`) } ``` ] ] .pull-right[ .large[ Reactive parameters referenced by __name__: `plot1var` Inside module, __invoke__ reactive parameter as you would any other reactive in Shiny: `plot1var()` Any reactive(s) returned by module should also be referenced by __name__: `foo`, not `foo()` ] ] ??? * `callModule` executes the `mod` module --- # Static or reactive inputs? .left-column-big[ .code75[ ```r #' ... #' @param dataset data frame (`non-reactive`) with variables #' necessary for scatterplot #' ... scatterplot_server_mod <- function(input, output, session, `dataset`, plot1vars,plot2vars) { plot1_obj <- reactive({ p <- scatter_sales(`dataset`, xvar = plot1vars$xvar(), yvar = plot1vars$yvar()) return(p) }) plot2_obj <- reactive({ p <- scatter_sales(`dataset`, xvar = plot2vars$xvar(), yvar = plot2vars$yvar()) return(p) }) # additional code omitted } ``` ] ] .right-column-small[ .large[ Why `dataset` and not `dataset()`? * `dataset` does not change within the application * Only need the __present__ value ] ] --- # Present or future value? .left-column-big[ .code75[ ```r #' ... #' @param plot1_vars list with `reactive` x-variable name (called xvar) #' and y-variable name (called yvar) for plot 1 #' @param plot2_vars list with `reactive` x-variable name (called xvar) #' and y-variable name (called yvar) for plot 2 scatterplot_server_mod <- function(input, output, session, dataset, `plot1vars`,`plot2vars`) { plot1_obj <- reactive({ p <- scatter_sales(dataset, xvar = `plot1vars$xvar()`, yvar = `plot1vars$yvar()`) return(p) }) plot2_obj <- reactive({ p <- scatter_sales(dataset, xvar = `plot2vars$xvar()`, yvar = `plot2vars$yvar()`) return(p) }) # additional code omitted } ``` ] ] .right-column-small[ `xvar` and `yvar`: __Reactive__ based on variable chooser Not just the __present__ value, but the __future__ value too How to correctly reference these inputs? ❌ `plot1vars()$yvar` ✅ `plot1vars$yvar()` ] --- background-image: url(img/sink_background.png) background-size: cover # Traps to avoid .pull-left[ .code75[ ```r # in main app, set up placeholder r_data <- reactiveValues() # pass r_data around as input parameter in all modules module1_mod <- function(input, output, session, r_data) { obj1 <- reactive({ # various processing ... foo <- complicated_function() r_data[['obj1']] <- foo }) # Return something? Nah, I took care of that already! } # imagine many other modules as above ... callModule(module1_mod, "id1", r_data) callModule(module2_mod, "id2", r_data) ``` ] ] ??? * I used this approach in my first attempt at creating modules in a complex app * As app got bigger, more clutter in the sink -- .pull-right[ ## What could possibly go wrong? .large[ * __Hidden State__: Difficult to pinpoint which module(s) impact `r_data` objects * Only certain objects from `r_data` are needed in a given module * Lose sight of the __contracts__ between modules __Perspective__: Passing environments back and forth between typical
functions would be a bad idea too! ] ] --- background-image: url(https://media.giphy.com/media/l41YBHH1A6OPQ5iXS/giphy.gif) background-size: cover class: inverse, clear, center, middle --- # You may already know... .large[ *
is well-suited for interactive workflows in data science * Functions become an essential building-block when moving from prototypes to non-trivial workflows * Define clear function inputs and return values for effective structure ] -- ## Is it any different when creating Shiny apps? No! .large[ Using modules combined with the principles discussed today is __essential__ to bring software engineering best practices to application development * Determine purpose of the module * Documentation for inputs and outputs * Define the relationships between modules ] --- # What's in it for me? -- ## Avoid namespace collisions when re-using widgets across application -- ## Organize application with distinct components .large[ * Facilitates concurrent development within team * Easier to track down bugs in code ] -- ## Best practices for
development translate well with modules --- # Calls to action .large[ Read the new article on [module communication best practices](http://shiny.rstudio.com/articles/communicate-bet-modules.html) at [shiny.rstudio.com](https://shiny.rstudio.com) * 👀 for more articles this year on additional module topics Review [Ames Housing Explorer app](https://gallery.shinyapps.io/ames-explorer) that demonstrates the principles discussed today and more. * Code available on
: [github.com/rpodcast/ames_explorer](https://github.com/rpodcast/ames_explorer) ] -- .pull-left[ ![:scale 90%](https://media.giphy.com/media/l0K4hqqqwgFijgVLa/giphy.gif) ] .pull-right[ ## You may not get it right the first time ... ## Keep on the Modules highway and it will pay off! ] --- # Thank you! .center[ <table style="border-style:none;padding-top:30px;" class=".table"> <br /> <br /> <tr> <th style="padding-right:25px!important" align="center"><a href="https://twitter.com/thercast"> <i class="fab fa-twitter fa-3x"></i> </a></th> <th style="padding-left:25px!important" align="center"><a href="https://github.com/rpodcast"> <i class="fab fa-github fa-3x"></i> </a></th> <th style="padding-left:25px!important" align="center"><a href="https://gitlab.com/rpodcast"> <i class="fab fa-gitlab fa-3x"></i> </a></th> <th style="padding-left:25px!important" align="center"><a href="https://r-podcast.org"> <i class="fa fa-microphone fa-3x"></i> </a></th> </tr> <tr style="background-color:#fafafa"> <th style="padding-right:25px!important"><a href="https://twitter.com/thercast"> @thercast </a></th> <th style="padding-left:25px!important"><a href="https://github.com/rpodcast"> @rpodcast </a></th> <th style="padding-left:25px!important"><a href="https://gitlab.com/rpodcast"> @rpodcast </a></th> <th style="padding-left:25px!important"><a href="https://r-podcast.org"> r-podcast.org </a></th> </tr></table> ] .large[ Other efforts in the
community: * Contributor to [R Weekly](https://rweekly.org/) * [RStudio Community](https://community.rstudio.com/) sustainer * Member of [Rbind](https://support.rbind.io/) administrator team Slides created with the [xaringan](https://slides.yihui.name/xaringan) package available at [bit.ly/modules2019](https://bit.ly/modules2019) ]