Introducing R Shiny web apps

Recently I’ve begun experimenting with the R Shiny package, which is a great way for interactively showcasing and sharing statistical analyses and results performed and generated in R on the web. Here I provide a simple example of how to use the shiny package to create and deploy an app to your browser using a local host. A Shiny app can also be hosted on a web server, but that is beyond the scope of this post.

Building upon a more basic app that appears in the RStudio Shiny tutorial, here we have an app that plots histograms of random samples drawn from any one of a set of common probability distributions. The user can select a distribution, a sample size, whether to overlay a smooth density curve, and only if shown, what bandwidth to use for the curve.

RshinyIntro
You can click the image to go to the app page.

This is the first of a four-part series where I add enhancements to the app in stages. Here are the other posts and corresponding versions of the app:
R sampling app version 2 [app version 2]
R sampling app version 3 [app version 3]
R sampling app version 4 [app version 4]

Apps can become much more complicated of course. The shiny package is a powerful tool for statisticians and data analysts who wish to share their work in an interactive fashion. Statistics is storytelling, and from start to finish – from the formulation of a problem or a question; through exploratory data analysis, looking at tables, charts, graphs and summary statistics; to the selection and application of appropriate advanced inferential methods; and finally the interpretation of results and dissemination of relevant information – a Shiny app can be used to tell the story interactively, in a way that is accessible to managers, clients, scientists, and anyone else who can interact with the app on a webpage but who may not be statisticians or data analysts themselves. It is an excellent web presentation tool that allows you to share your work without being present and it permits clients to focus on what aspects of a project are most interesting and important to them.

An R Shiny app consists of two scripts, ui.R and server.R, the user-interface and server-side scripts, respectively, as well as any data or other files that may be needed by the app, depending on its complexity. In this example, we need no extra files. We will generate our own data as part of the app. Here is the ui.R script:

shinyUI(pageWithSidebar(
	headerPanel("Distributions of Random Variables"),
	sidebarPanel(
		radioButtons("dist","Distribution type:",
			list("Normal"="norm","Uniform"="unif","t"="t","F"="F","Gamma"="gam","Exponential"="exp","Chi-square"="chisq","Log-normal"="lnorm","Beta"="beta")),
		sliderInput("n","Sample size:",1,1000,500),
		uiOutput("dist1"),
		uiOutput("dist2"),
		checkboxInput("density","Show density curve",FALSE),
		conditionalPanel(
			condition="input.density==true",
			numericInput("bw","bandwidth:",1)
		),
		downloadButton('dldat', 'Download Sample')
	),
	mainPanel(
		tabsetPanel(
			tabPanel("Plot",plotOutput("plot")),
			tabPanel("Summary",verbatimTextOutput("summary")),
			tabPanel("Table",tableOutput("table"))
		)
	)
))

The purpose here is not to reproduce tutorials which already exist, but rather just to show an example. If you haven’t explored Shiny before, I recommend you see the tutorial put together by the RStudio team and other online examples to get a better sense for how Shiny works, and the package documentation for a more thorough understanding of the package functions and examples of their varied uses. With at least some nominal exposure to those resources and some experience with R, it should be relatively straightforward to connect the code here to the behavior of the app and what it displays at the app image link above. The ui.R script is usually pretty simple and short even for fairly complex apps. The server.R script is the one that grows in size and complexity more linearly with your project. Here is the server.R script:

library(shiny)
rt2 <- function(n=500,dft=15){ rt(n=n,df=dft) }
formals(rgamma)[1:2] <- c(500,1)
rchisq2 <- function(n=500,dfx=1){ rchisq(n=n,df=dfx) }
formals(rf)[1:3] <- c(500,1,15)
rexp2 <- function(n=500,rate2=1){ rexp(n=n,rate=rate2) }
formals(rbeta)[1:3] <- c(500,2,2)

shinyServer(function(input,output){
	dat <- reactive({
		dist <- switch(input$dist,
			norm=rnorm,	unif=runif,	t=rt2, F=rf, gam=rgamma, exp=rexp2,	chisq=rchisq2, lnorm=rlnorm, beta=rbeta)

		def.args <- switch(input$dist,
			norm=c(input$mean,input$sd), unif=c(input$min,input$max), t=c(input$dft), F=c(input$df1,input$df2),
			gam=c(input$shape,input$rate), exp=c(input$rate2), chisq=c(input$dfx), lnorm=c(input$meanlog,input$sdlog), beta=c(input$shape1,input$shape2))
			
		f <- formals(dist);	f <- f[names(f)!="n"]; len <- min(length(f),3-1); f <- f[1:len]
		argList <- list(n=input$n)
		for(i in 1:len) argList[[names(f)[i]]] <- def.args[i]
		return(list(do.call(dist,argList),names(f)))
	})

	output$dist1 <- renderUI({
		lab <- switch(input$dist,
			norm="Mean:", unif="Minimum:", t="Degrees of freedom:", F="Numerator degrees of freedom:", gam="Shape:", exp="Rate:",
			chisq="Degrees of freedom:", lnorm="Mean(log):", beta="Alpha:")
		ini <- switch(input$dist,
			norm=0, unif=0, t=15, F=1, gam=1, exp=1, chisq=1, lnorm=0, beta=2)
		numericInput(dat()[[2]][1],lab,ini)
	})
	
	output$dist2 <- renderUI({
		lab <- switch(input$dist,
			norm="Standard deviation:", unif="Maximum:", F="Denominator degrees of freedom:", gam="Rate:", lnorm="Standard deviation(log)", beta="Beta:")
		ini <- switch(input$dist,
			norm=1, unif=1, F=15, gam=1, lnorm=1, beta=2)
		if(any(input$dist==c("norm","unif","F","gam","lnorm","beta"))) numericInput(dat()[[2]][2],lab,ini)
	})
	
	output$dldat <- downloadHandler(
		filename = function() { paste(input$dist, '.csv', sep='') },
		content = function(file) {
			write.csv(data.frame(x=dat()[[1]]), file)
		}
	)

	output$plot <- renderPlot({
		dist <- input$dist
		n <- input$n
		hist(dat()[[1]],main="",xlab="Observations",col="orange",cex.axis=1.2,cex.lab=1.2,prob=T)
		if(input$density) lines(density(dat()[[1]],adjust=input$bw),lwd=2)
	})
	
	output$summary <- renderPrint({
		summary(dat()[[1]])
	})
	
	output$table <- renderTable({
		data.frame(x=dat()[[1]])
	})
})

In the server.R script, we run any R code that we need to up front. This is the one-time stuff. You run these commands when the app launches. The code may set up certain objects in your workspace. It may load a data set or a workspace (.RData) file. Basically, this is anything that you will only need to do one time in your R session and don’t want to rerun wastefully every time a user changes some options on their screen.

After this, shinyServer is called. Within this function is where we achieve our reactivity. We use reactive expressions of various kinds to update the outputs displayed to the user based on the inputs the user selects. Reactive expressions are only evaluated when a user-initiated change to inputs is detected. These expressions are then evaluated, and immediately update the outputs sent to the browser.

Things not to get caught up on. You may notice some odd commands at the beginning of this script, specifically the use of the formals function, and you may wonder why I appear to be assigning base R functions to new objects with altered default parameters. This is not a “Shiny thing”. Ignore it. But if you must know, it’s because in putting this app together, I found that the Shiny app did not behave well the way I had originally coded it. This turned out to be due to the fact that multiple R functions I was using, e.g. rgamma and rexp, or rt and rchisq, had formal arguments with the same name. Picking parameter values in the app for one distribution, and then toggling over to another distribution in the browser which had a similarly named distribution parameter argument, would cause some annoying bugginess.

I got around this by making calls to my own wrapper functions which did not have this kind of argument name overlap. It’s possible I don’t need to do this any longer and it may have been due to the early version of Shiny, or my own lack of understanding of how Shiny worked at the time I was first experimenting with it. In any case, I got it to work. In more recent apps I have not encountered this kind of problem. Not to say that it does not remain an issue, but I have not been trying to recreate it. But if anyone can tell me what I may have been doing wrong or why this was an issue to begin with, please let me know. Moving along now…

For those of you who have dabbled in R Shiny a bit already, it is perhaps worth mentioning that in my experience I have had more success in terms of achieving complexity of reactivity and generalizability by using uiOutput in the ui.R script rather than specifying sidebar controls directly, in tandem with renderUI in the server.R script. I tend to define user controls for the sidebar in the latter script and have found so far that this style works better for my applications in general.

Now let’s discuss launching an app locally. Just place your ui.R and server.R scripts in a directory together. Launch R. Make sure the shiny package is installed. Load the package. Then all you need to do is enter runApp("C:/path/to/my/directory") for example. R will launch your app in your default web browser. Done!

In my next post, R sampling app version 2 [app], I spiff up the plots a bit by adding probability distribution function (pdf) annotations using plotmath expressions. In total there are four versions of this sampling app, one building on the next, so that you can see various elements added in stages. I didn’t have it all planned out when I began, but that’s sometimes how it goes. I’ll have more posts regarding R Shiny apps I’ve developed for various SNAP projects, to be hosted on our website like the one above. If you are interested in hosting your own apps on a web server as well, where users can interact with your apps on a webpage without having to deal with R or anything else, you’ll have to follow instructions like these.

13 thoughts on “Introducing R Shiny web apps

  1. Greetings,
    I am finding your tutorials quite helpful in my attempts with Shiny. Thank you for the blog posts. I have one question, if you please:
    Why did you not use the simpler conditionalPanel approach here with the radioButtons?

    • Hi,
      I’m glad you find them helpful. Thank you for your feedback. I have used conditional panels often in other apps, but I am not sure how this particular app would benefit from their use. I did make this first app when I was just learning Shiny, so it is possible I overlooked something more ideal than what I did. Could you elaborate on what you mean by a “simpler conditionalPanel approach” regarding the radio buttons? I have a conditionPanel on the density bandwidth, and later in the series I add more wellPanels to the sidebar. I also later (app version four) separate out discrete and continuous distributions conditionally when the list of distributions becomes longer (though I do this on the server side, so not using a conditionalPanel- I tend to prefer this server-side method). But I’m not sure what you are picturing exactly with conditionalPanels in the context of the probability distribution radio buttons.
      Thanks,
      Matt

      • You’re welcome. I think you would be very right in your current approach; actually, I am a new Shiny user, and I was making extensive usage of conditionalPanels, which are mentioned in the tutorials as being the simpler route to making dynamic UI.
        So when I went through your tutorial, I learnt a lot about Shiny, but I felt compelled to ask why you preferred (or seemed to prefer) the server-side method. I thought maybe it was due to some performance issues or something.
        Kind Regards.

      • Hi,
        I did not avoid them in this app for any kind of performance issues. However, in another app I was making I tried to have many conditional panels, perhaps about 10-15, and it created serious performance issues. It made the page very clunky and slow to load. I don’t know if this is a direct consequence of using lots of conditional panels, as I try to use them sparingly in general, or if it was the particular way I used them.

        I had many tab panels in the tabsetPanel in the mainPanel area. So I tried to use conditional panels to keep many of them turned off when not in use to avoid the tabs at the top being very cluttered. It worked very poorly.

        I may not be the best to explain this, but one other reason I prefer to do as much on the server side and as little on the user side, is that with conditional panels, I think it is a case of the browser simply not displaying content, but that content does exist; those objects are still active behind the scenes. When I do conditioning on the server side fewer things are run and generated to begin with, as opposed to simply being masked on the user side. But, I would check the RStudio Shiny tutorial and Shiny Google Group to see if there is someone who probably has explained this better than me.

        Best Regards,
        Matt

      • Greetings,
        Hmm. Thanks for sharing the experience. I hope not to run into similar problems, though I am just a novice in Shiny. Anyway, good to see the tutorials here!

        Kind Regards.

  2. Can you explain how you are using ‘samplingApp_wsPrep.R’? It seems to define functions but I cannot see where you link to or include these functions!

    • Hi,

      Where are you seeing ‘samplingApp_wsPrep.R’? That is not related to this app version 1 post or version 2. It does not occur until version 3. Nevertheless, this script is simply for preparing the workspace file that I mention in post 3, which is sourced by the version 3 app’s server.R script.

      I included the workspace prep script in the app version 3 directory so that people have convenient access to the code and do not have to copy and paste from the code block in the third post. But the script is not used directly by the app. It is just used to create the workspace file that is used by the app. Typically my apps would only contain a workspace of objects for simplicity, but for tutorial purposes it is not very helpful to just say “here’s the workspace with objects in it.” Instead I included the generating script, even though it is not needed.

      Matt

    • Yes, this file is only included in versions 3 and 4 on github, and is shown in posts 3 and 4. It’s included simply so that people can see the code that was used to generate the R workspace file sourced by the app in versions 3 and 4. The app merely sources the workspace file. This script that generates that file is only included for readers’ benefit; it is not called by the app.

      Matt

      • Forgiving me for being dense. This is my first Shiny app. I cannot see where you are ‘sourcing’ this file. Maybe I do not understand what it means to ‘source’.

        In server.r i see:

        library(shiny)
        pkgs <- c("VGAM")
        pkgs <- pkgs[!(pkgs %in% installed.packages()[,"Package"])]
        if(length(pkgs)) install.packages(pkgs,repos="http://cran.cs.wwu.edu/&quot ;)
        library(VGAM)
        load("samplingApp.RData", envir=.GlobalEnv)

        In ui.r I see:

        library(shiny)
        tabPanelAbout <- source("about.r")$value

        What am I missing?

      • I’m sorry for the ambiguity, I should have said ‘load’ instead of ‘source’. All those objects are loaded into the app’s R session with the line:

        load(“samplingApp.RData”, envir=.GlobalEnv)

        Often with my apps I will compile an R workspace file like this in advance. This way I have a file that already contains many R objects that only need to be loaded once when the app first launches, but without the lag generated by creating them in the app itself. It also helps keep the app code clean by removing copious lines or pages of stand-alone code that are needed to make R objects that would be used by an app but which is code that has nothing to do with making the app run.

        Data and objects compiled up front is good practice as well because it cuts down on lag. In this case it would not take long for the app to create everything in the .RData file by way of the supplemental included script, but it’s unnecessary to run that code every time, and for some apps that are more data heavy, it is critical to prep as much in advance as possible to minimize processing by the app.

        Hope that helps,
        Matt

  3. Hi, Thank you for the blog posts.
    I have a question about difference between Shiny server free and shiny server Pro.
    We’re developping a web health project, shiny is very very interresting, a great way for interactively showcasing and sharing statistical analyses.

    This is a research project, which want open source and quazi free (among the last countries on the UNDP classification) with a goal of technology transfer and skills among departments.
    The pro version is really too expensive and does not fit into our prerogatives.

    In the free server version, with all the data and the website hosted on our own server:

    multiple users could connectthem and query the site in the same time?

    It is mentioned, Single R process per application, what it means?

    In summary, what are the main limitations to the use of the free version?

    Thank you for your answers.

    • Thank you for your feedback. Yes, Shiny Server Pro version is expensive. Even qualifying for research/staff (non-teaching) academic pricing at 50% off as shown on their website at the time of this reply, it is too much for us and we will unfortunately have to stick with the limited free version.

      The free version would suit most people’s needs, however. In my view, the biggest limitation in the free version is the restriction to one R process running an app no matter how many users connect to it in their browser. Yes, multiple users can access and interact with an app simultaneously. However, all their interactions with the app will be queued serially and they will have to take turns waiting for other user’s commands to execute in the single R session.

      Best practice is that you only make simple apps with very low processing overhead. I see Shiny as a tool for displaying and interacting with ideally preprocessed data. As long as you code efficiently and avoid trying to do anything with an app that takes more than a second or two, and you don’t generally have a large number of people using the app at once competing for opportunities to run slow code, then you should be fine. I have made apps as formal project deliverables to clients with great success, with the apps hosted with the free version of Shiny Server. For most purposes, unless you are doing something very intensive, the reactivity between server and browser is so fast that users will not notice that they may be in a queue with several other users who are executing similar R commands serially.

      With the Pro version, I personally see the ability to configure up to one unique R process per user as the main benefit, because it opens up the opportunity for making apps that include more intensive processing, where the user can elect to wait for something without forcing other users to wait. I don’t have much use for the other Pro version benefits, though some sound interesting, and they may be of significant use to others.

      I know a lot more about Shiny than I do about Shiny Server. For more information I recommend contacting RStudio. Also, it’s possible that the features of the free version and the Pro version, as well as the pricing, could change without me knowing.

      Matt

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s