[Disclaimer: This post contains some affiliate links to my Udemy Course]
So, you’ve developed your Data Science model or analysis using R, and now you are, maybe, thinking that you would like to showcase the results in a visual and intuitive way.
You’ve tweaked a bit of your storytelling, added some plots but you feel that your visuals are a bit static, making you repeat code or plots throughout the storytelling process. Also, you are having a hard time hiding your code from the output, something critical, that you know will cause some confusion with business or non-technical users.
Luckily, you have [shiny](https://shiny.rstudio.com/)
! shiny is a great library to build R apps where you can embed R code and results, without having to build your own front end and back end. shiny
features are amazing, enabling you to serve apps instantly, while letting your users interact with results from your models or analysis.
Another cool thing about shiny
is that it can be used directly from R, just like any other library – using R code, we can set up a basic HTML interactive page, that can be used to show plots, DataFrames or other elements.
In this post, we’ll explore a bit of the Shiny
library, with the goal of helping you build your first app. We’ll use several examples, like:
- Building a simple ‘hello world app’, to understand back end and front end flow.
- Building a simple scatter plot app.
- Building an app with three different views, including some results from a decision tree.
At the end of the post, you should be ready to work with this library using R. Although shiny
will take some time to master, what we’ll learn here will, hopefully, be a good kick-off to get you started on the intricacies of using shiny
apps, as well as connecting your dataframes and plots with it.
Let’s get started!
Our first shiny Vanilla App
Let’s start by installing shiny
in our environment:
install.packages('shiny')
After installing the shiny
package, we can load it in our environment using the usuallibrary
function:
library(shiny)
For starters, we’ll declare afluidPage
. This function will be our main way to dictate and interact with the front end of the web app. In its core, fluidPage
is a pretty standard row-column format user interface (it even includes some bootstrap features), and that should be enough for most basic data products. It’s also generally tweakable, being able to incorporate a lot of sections inside the app, as we will see in a couple of minutes.
In a nutshell,fluidPage
can be considered the front-end layer of our shiny
app. This is where we will build everything that the user will see, and how it will interact with the back end.
Our first page is a pretty basic one that just prints "My first shiny page!"
:
sp <- fluidPage(
"My first shiny page!"
)
To run our app, we also need to setup our back end – this can be done by defining a custom function that will be passed to our shinyApp
. For now, our server
function will be completely empty (we are not using anything on the back end, at the moment):
server <- function(input, output) {
}
Using shinyApp
, we can launch our first app by passing the front end (sp
) back end layers (server
):
shinyApp(sp, server)
After running the code above, a new window should pop up! This window is where our shiny app is being served:
Our app is being served locally in our computer on the local host http://127.0.0.1:7388/. You should see your local host address right below theshinyApp
command on the R console:
While you have the app running, you can also type the address on your browser, and that will also take you to your app.
Of course, our app is useless, at the moment – we don’t have any input or output, and it would be useful to be able to interact with our app! Let’s start by building a simple application with a 2D scatter plot, using the mtcars
DataFrame, next!
Building an Interactive Plot App
Of course, we are not only tied with writing a basic text app. For instance, imagine we would like to show, on our app, a plot similar to this one:
I’m using the toy dataset mtcars
to plot a scatter plot of the weight of the car vs. gas consumption (miles-per-gallon):
library(ggplot2)
ggplot(
data = mtcars,
aes(x=mpg, y=wt)
) + geom_point(color='darkgreen')
A cool thing we could do is serve a scatter plot inshiny
app. Additionally, and to make our app a bit more interesting, we would like to have dynamic x
and y
columns, using inputs from the user.
Although it seems pretty hard to do this, we can do it simply by changing some portions of our back and front end layers. Let’s start by making the user select two columns from the list of columns available in mtcars
:
sp <- fluidPage(
selectInput(
inputId = 'var1',
label = 'Column X Axis',
choices = colnames(mtcars)
),
selectInput(
inputId = 'var2',
label = 'Column Y Axis',
choices = colnames(mtcars)
)
)
The selectInput
function creates a dropdown box with all the columns available in mtcars
. selectInput
is one of the many available input methods in shiny
and we can pass colnames(mtcars)
to the choices argument, that will take care of filling our dropdown with all the elements.
The inputId
is extremely important in most shiny
apps – it’s the way we have to send information between the front end and back end. In this case, everytime we point to a var1
or var2
in the back end, R will pick up the value that we have stated in the dropdown box. In certain scenarions, our variables coming from the front end must be included in a reactive
context, something we will see on the third app we will build.
If we just give this new sp
to our app, we have something new on our front end:
shinyApp(sp, server)
Interesting! Our front end now has two dropdown boxes where we can choose the columns for our X
and Y
axis. But.. how can we give these elements to our plot? That’s where the back end layer comes to the rescue:
server <- function(input, output) {
output$scatter <- renderPlot({
ggplot(
data = mtcars,
aes_string(x=input$var1, y=input$var2)
) + geom_point(color='darkgreen')
})
}
Notice that we can use the standard input and output arguments. When we use the server
function in the context of a shinyApp
, input and output will be resolved inside the app. For instance, input$var1
will pick data from the first selectInput
in our page – because this first selectInput
has id var1
.
When we now run our shinyApp
, our app will look a bit different:
shinyApp(sp, server)
We have our plot available in our shiny app! In the current view, the scatter plot does not say much as we are plotting mpg
against mgp
, the same variable. The neat thing is that if we change our columns in the dropdown, our plot will update, automatically:
Cool! So, basically, everytime we switch our selectInput
with id var1
, the stored column is stored on the variable and fed to the input$var1
on our server. The same reasoning goes for input$var2
and var2
. Logically, the ids are the reference we use to connect the our back end and front end layers.
We can also tweak our app a bit further, in terms of front end. For instance, we can split our layout into a side by side app, using sidebarLayout
and wrapping each section in a sidebarPanel
and mainPanel
:
sp <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(
inputId = 'var1',
label = 'Column X Axis',
choices = colnames(mtcars)
),
selectInput(
inputId= 'var2',
label = 'Column Y Axis',
choices = colnames(mtcars)
)
),
mainPanel(
plotOutput("scatter")
)
)
)
Our app will now have the following look:
Our new layout is a two column format plot, that we can manipulate. We’ve chosen a sideBar
layout, but, there are more layouts available at our disposal, available here.
Building an App based on a Model
We’ve built our first usefulshiny
app using a plotting application! The main layers we’ve looked into were:
- A back end we could create using our custom
server
function. - A front end we could design using
fluidPage
This mechanic is repeated for most single page applications we can develop inside shiny
. If you are curious about multiple page applications, you can also check shiny.router.
For our last example, let’s serve a basic decision tree model using shiny
with a three column layout.
First, we’ll train a decision tree on top of our mtcars
data, trying to predict the mpg
based on the wt
, cyl
and hp
:
library(rpart)
dtree <- rpart(data=mtcars, mpg ~ wt + cyl + hp,
control = list(minsplit=1))
This is, of course, a laughable decision tree and model training process, that doesn’t even contain a proper train-test split. The goal here is just to use a basic model in the context of shiny
so let’s ignore those details, for now.
Let’s save this dtree
object into a rds
file:
saveRDS(object=dtree, file='decisiontree.rds')
Imagine we would like to build the following app:
Here, we would need three columns, an excellent use case for a mix of fluidRow
and column
. Let’s start by building a three column layout, starting with our front end:
sp <- fluidPage(
fluidRow(
column(4, 'Column 1!'),
column(4, 'Column 2!'),
column(4, 'Column 3!')
)
)
server <- function(input, output) {
}
shinyApp(sp, server)
Our shinyApp
will have the following look:
Notice that our Column 1!
, Column 2!
and Column 3!
are spaced equally on the app’s front end. The cool thing about fluidRow
and column
is that they behave in a "boostrapy" way, meaning that, if we don’t use full screen, our columns will be stacked, instead of crunching data into something that is not readable.
Starting with the first column, we’ll want to have a list of all the cars available, so that we can select which car we will highlight on the center column – doing this is easy as we’ve already learned selectInput
:
sp <- fluidPage(
fluidRow(
column(4,
selectInput(
inputId = 'selectedcar',
label = 'Car List',
choices = rownames(mtcars)
)
),
column(4, 'Column 2!'),
column(4, 'Column 3!')
)
)
server <- function(input, output) {
}
shinyApp(sp, server)
On the left column, we’ll now be able to select specific cars:
On the second column, we would like to set a plot of our predictions with the selected car highlighted. We need several things to do this, namely interacting with the back end:
- Load our model and predict all cars weight.
- Build a two Dimensional plot of the MPG vs. HP (if you want, you can also add another dropdown that chooses the second variable)
- Highlight the scatter with the selected car.
Here’s an example of how we can build a back end that will match those requirements:
server <- function(input, output) {
model <- readRDS('decisiontree.rds')
predictions <- data.frame(cbind(
mtcars$hp,
predict(object = model, mtcars)
))
colnames(predictions) <- c('hp','pred_mpg')
sc <- reactive({
selected_car <- input$selectedcar
})
output$scatter <- renderPlot({
select_car <- sc()
(
plot_all_cars <- ggplot(
data = predictions,
aes(x=hp, y=pred_mpg)
)
+ geom_point(colour="darkgreen")
+ geom_point(data=predictions[select_car, ], aes(x=hp, y=pred_mpg), colour="red", size=5)
)
})
Let’s detail our back end code step-by-step:
-
First, we load our model using
model<-readRDS('decisiontree.rds')
and use thepredict
function to get the predictions from our model. - Then, we define the
colnames
of ourpredictions object.
- After that, we set a
reactive
object. Areactive
object is an object that will affect any other object (requiring calculations). For instance, in this case, theselect_car
will affect the filter applied to thedataframe
in one of thegeom_point
plots. As this will require some calculation to happen in the background, we need to set a reactive context on this object, defining it to be called with the functionsc()
. - Finally, we are defining
plot_all_cars
with twogeom_point
: one for all cars and one (that will serve as the highlight) for the car we will select. Notice thatselect_car
will contain the returning reactive object fromsc
, the object we will load using the dropdown on the left.
Let’s check the look of our app now:
If we highlight another car, our plot’s highlight changes:
On the front end, we just had to change our second column, feeding it a plotOutput
module:
sp <- fluidPage(
fluidRow(
column(4,
selectInput(
inputId = 'selectedcar',
label = 'Car List',
choices = rownames(mtcars)
)
),
column(4,
plotOutput('scatter')
),
column(4, 'Column 3!')
)
)
Only one thing left! Plotting our decision tree on the right column – let’s do that on our backend, as well but, first, we need to load rattle
library:
library(rattle)
And then, applying some changes to our back end and front end layers:
server <- function(input, output) {
model <- readRDS('decisiontree.rds')
predictions <- data.frame(cbind(
mtcars$hp,
predict(object = model, mtcars)
))
colnames(predictions) <- c('hp','pred_mpg')
sc <- reactive({
selected_car <- input$selectedcar
})
output$scatter <- renderPlot({
select_car <- sc()
(
plot_all_cars <- ggplot(
data = predictions,
aes(x=hp, y=pred_mpg)
)
+ geom_point(colour="darkgreen")
+ geom_point(data=predictions[select_car, ], aes(x=hp, y=pred_mpg), colour="red", size=5)
)
})
output$dtree_plot <- renderPlot({
fancyRpartPlot(model, sub='')
})
}
sp <- fluidPage(
fluidRow(
column(4,
selectInput(
inputId = 'selectedcar',
label = 'Car List',
choices = rownames(mtcars)
)
),
column(4,
plotOutput('scatter')
),
column(4,
plotOutput('dtree_plot')
)
)
)
Super simple! To the back end code above, we added:
output$dtree_plot <- renderPlot({
fancyRpartPlot(model, sub='')
})
After referencing it in our front end, our final app looks like the following:
As you can see, building shiny
apps in R is pretty straightforward. In this post, we’ve checked some important components, such as:
- Building the front end of our application, using
fluidPage
. - Building the back end of our application, using a custom function
server
. - Connecting our front end and back using
ids
andreactive
elements. - Using different UI elements such as
fluidRow
orsidebarPanel
.
I hope this post gave you some ideas on cool shiny
apps you can develop for your projects. As a conclusion:shiny
contains a bunch of tweakable elements, and it’s very difficult to know all of them by heart. One of the more important pages you can save is shiny articles, the official documentation of the library that will guide you on other stuff we didn’t cover throughout this post.
Thank you for taking the time to read this post!
Here’s the full code for the apps we’ve built:
library(shiny)
# First App
sp <- fluidPage(
"My first shiny page!"
)
server <- function(input, output) {
}
shinyApp(sp, server)
# Second App
sp <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(
inputId = 'var1',
label = 'Column X Axis',
choices = colnames(mtcars)
),
selectInput(
inputId= 'var2',
label = 'Column Y Axis',
choices = colnames(mtcars)
)
),
mainPanel(
plotOutput("scatter")
)
)
)
server <- function(input, output) {
output$scatter <- renderPlot({
ggplot(
data = mtcars,
aes_string(x=input$var1, y=input$var2)
) + geom_point(color='darkgreen')
})
}
shinyApp(sp, server)
# Training model
library(rpart)
dtree <- rpart(data=mtcars, mpg ~ wt + cyl + hp,
control = list(minsplit=1))
# Save dtree file
saveRDS(object=dtree, file='decisiontree.rds')
# Third App
sp <- fluidPage(
fluidRow(
column(4,
selectInput(
inputId = 'selectedcar',
label = 'Car List',
choices = rownames(mtcars)
)
),
column(4,
plotOutput('scatter')
),
column(4,
plotOutput('dtree_plot')
)
)
)
server <- function(input, output) {
model <- readRDS('decisiontree.rds')
predictions <- data.frame(cbind(
mtcars$hp,
predict(object = model, mtcars)
))
colnames(predictions) <- c('hp','pred_mpg')
sc <- reactive({
selected_car <- input$selectedcar
})
output$scatter <- renderPlot({
select_car <- sc()
(
plot_all_cars <- ggplot(
data = predictions,
aes(x=hp, y=pred_mpg)
)
+ geom_point(colour="darkgreen")
+ geom_point(data=predictions[select_car, ], aes(x=hp, y=pred_mpg), colour="red", size=5)
)
})
output$dtree_plot <- renderPlot({
fancyRpartPlot(model, sub='')
})
}
shinyApp(sp, server)
If you would like to drop by my R courses, feel free to join here (R Programming for Absolute Beginners) or here (Data Science Bootcamp). My R courses are suitable for beginners/mid-level developers and I would love to have you around!