You might know r
from data analysis. But, r
can do much more than that for you.
Faculty of Humanities, Education and Social Sciences (FHSE), University of Luxembourg
A brief reminder.
With quarto
we can easily create website and books. Quarto website
and Quarto book
projects create default files.
Preview
Render
Publish
Applications in the common sense - Spotify, Instagram or the like.
Applications serving a supportive role for your research, your skills or expertise.
Applications wrapped around parameterized reports and/or repetitive code.
Engage your audience interactively and dynamically.
We need the package shiny
. If you haven’t done so yet, please install now.
We might have code or custom functions saved in a separate r
script file. This can be sourced into the app.
It helps with keep the workenvironment structured!
In Day 1 - The universe, we wrote a basic function to add a constant to all numeric columns to a data frame.
We can copy this function into an .R
script so that we have access to it in writing our first shiny app
.
Source code
Other strategies in sourcing code include for example installing all packages from a separate r
script file, or importing the data through a separate r
script file.
Note that data importing is different than data loading!
Before we start working on the shiny app
let us save the data (Stanciu et al., 2017) into an .Rdata
format.
This makes it somewhat easier to import the dataframe in the shiny app code, as it is already in an r
format.
Once created, we can load the .Rdata
object as follows:
Save as .Rdata or .rds
Saving a dataframe into one of the r
data formats makes it easier to write code for the app.
Use one of these file formats: .Rdata
and .rds
.
One for the user, one for the server.
### calls r script files
# source("functions.R")
### loads data
load("data/sample.Rdata")
### imports libraries
library(shiny)
r <- getOption("repos")
r["CRAN"] <-"https://cloud.r-project.org/"
options(repos=r)
# install.packages("pacman")
# pacman::p_load(tidyverse,readxl,haven,sjlabelled,kable,kableExtra)
#### shiny app starts here ###
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Illustrative example"),
# Sidebar with a slider input for number of bins
sidebarLayout(position = "left",
sidebarPanel(
# actor input
selectInput("actor",
label="Choose an actor:",
c("Keanu Reeves",
"Alec Baldwin",
"Arnold Schwarzenegger",
"Timothee Chalamet",
"Anamaria Marinca"),
multiple = TRUE),
# stereotype input
selectInput("stereotype",
label="Stereotype dimension:",
c("Warmth women" = "wom_warm",
"Competence women" = "wom_comp",
"Warmth men" = "men_warm",
"Competence men" = "men_comp"))
),
# Show a plot of the generated distribution
mainPanel(
h3("Output displayed here",),
# start tabset Panel
tabsetPanel(
# tab 1
tabPanel("Actors",
tags$p(HTML(paste("A table is generated based on the actors chosen on the side panel..", sep = "")) ),
DT::dataTableOutput("act") ),
# tab 2
tabPanel("Stereotypes",
tags$p(HTML(paste("A plote is generated based on the variable chosen on the side panel..", sep = "")) ),
plotOutput("st") )
) # close tabset Panel
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
####### -- imports and prepares data from here
# reactive object
# data from Stanciu et al. 2017
tempdf <- reactive({
choice=input$stereotype
dfex %>%
sjlabelled::remove_all_labels() %>%
pivot_longer(contains("warm") | contains("comp")) %>%
filter(name %in% choice)
})
# reactive object
# meta data movies
movietmp<- reactive({
dfmv<-readxl::read_excel("mat/movies.xlsx",1) %>%
filter(Actor %in% input$actor)
})
#### -- generates output objects from here
# generate ggplot
plottmp<- reactive({
## ggplot code
(input$plot_type == "ggplot2")
ggplot(tempdf(), aes(x=factor(gen),y=value)) +
labs(title=paste0("Evaluation based on ", input$stereotype),
x="Gender",
y=paste0("Stereotype of ", input$stereotype)) +
geom_boxplot() +
theme_light()
})
##### -- code for output from here
# render plot for user
output$st <- renderPlot({
plottmp()
})
output$act <- DT::renderDataTable({
movietmp()
})
}
# Run the application
shinyApp(ui = ui, server = server)
Shiny apps have a user interface (UI) that is wrapped around code that runs in the background on a server.
When programming a shiny app
therefore we need to program both the design (UI) and the code that runs on the server (server).
The UI part makes a shiny app
attractive to the audience and, if programmed right, can engage the audience in an interactive and dynamic manner.
Programming the UI part requires a bit of orientation toward the audience for which the app is designed.
Pre-work
Before coding the app itself, think of these and similar questions:
What are the minimum skills required by your audience to operate the app?
What theoretical and practical expertise is expected from the audience to intuitively navigate the app?
In the UI part, we need refer to objects from the server part.
If we do not call objects from the server in the UI part properly, the app might still work but the audience will not have access to it.
Commas and brackets!
Make sure that you always use commas and close the brackets appropriately. Otherwise, the design might not look as intended or the entire code might break even.
Take some time to decide what do you want to include in the app and what do you need for your audience.
For example, do you want the audience to view plots or tables, and if yes, do you want these to be interactive?
What code do you need to write on the server part and what is the final r
object that you’d want to be displayed for the audience via the UI?
Forth-back coding
Writing a shiny app
is a bit of a forth and back between the UI and server code.
sidebarPanel()
wrapped inside the sidebarLayout()
because it is just one element of several that can be placed on the sidebarLayout
of the app.
The attributes defined here are fed into the code on the server, so make sure you chose the appropriate user input type.
mainPanel()
contains the output, be it plain text, live text, tables or figures.
If in the sidebarPanel()
you define the user input attributes, in the mainPanel()
you simply call the objects computed on the server and programm how exactly will they be displayed.
mainPanel(
h3("Output displayed here",),
# start tabset Panel
tabsetPanel(
# tab 1
tabPanel("Actors",
tags$p(HTML(paste("A table is generated based on the actors chosen on the side panel..", sep = "")) ),
DT::dataTableOutput("act") ),
# tab 2
tabPanel("Stereotypes",
tags$p(HTML(paste("A plote is generated based on the variable chosen on the side panel..", sep = "")) ),
plotOutput("st") )
...
There might be situations where you’d want to create a conditional user interface.
The UI experience can, at some pre-defined parts, be conditional on user input.
There can be situations where it is helpful to organize output into separate panels – similar logic to having several tabs open on your web browser.
The server part makes a shiny app
, well, work.
Here is where code is written to import, clean, manipulate and analyse data, metadata and all sorts of other things.
One way that I find helpful to think of the server part is to see it as the old-school R coding on my local machine.
What does the server do?!
This distinction is less intuitive when we run the shiny app
on the local machine. But, this distinction between UI and server becomes crucial when we deploy the app on online repositories, as we will see shortly.
Through structuring the app code in an UI and server part, we tell the respective online servers how to read and render the code.
The code for the server is a custom function, a very large and complex one but, still a custom function.
Custom function
Remember from Day 1 - The universe, custom functions look like function(){}
.
The server function takes two arguments:
input
signals what comes from the UI interface. That is, what the user of the app is inputing via the UI.
output
signals what goes from the server to the UI. That is, what the user views as a result of interacting with the app.
Plain R code wrapped inside an object that the server needs to compute.
Reactive, because the server has to first compute it before performing any tasks that call on it.
Objects created on the server are reactive to code that requires them.
Mind the brackets
Remember to always call it as such. It is an r
object all right, but it looks like a function: reactiveobject()
.
Needs to be written inside ({YOUR SERVER CODE HERE})
.
It is a specific code chunk for the server.
# data from Stanciu et al. 2017
tempdf <- reactive({
# define the input object for data manipulation
choice=input$stereotype
# takes the loaded data (a step not shown here)
# then, manipulates the data
# note that the output was not assigned to an object in the code
# the output is assigned to the reactive object "tempdf()"
dfex %>%
sjlabelled::remove_all_labels() %>%
pivot_longer(contains("warm") | contains("comp")) %>%
filter(name %in% choice)
})
Reactive objects that take user input to generate output through code on the server.
# data from Stanciu et al. 2017
tempdf <- reactive({
# define the input object for data manipulation
choice=input$stereotype
# takes the loaded data (a step not shown here)
# then, manipulates the data
# note that the output was not assigned to an object in the code
# the output is assigned to the reactive object "tempdf()"
dfex %>%
sjlabelled::remove_all_labels() %>%
pivot_longer(contains("warm") | contains("comp")) %>%
filter(name %in% choice)
})
Output objects are the output that we want to be displayed on the user interface.
This means that we have to call it as such and indicate the position where we want it displayed.
Ideally, we have already coded the display position and display characteristics in the UI code.
Server code
# generate ggplot
plottmp<- reactive({
## ggplot code
(input$plot_type == "ggplot2")
ggplot(tempdf(), aes(x=factor(gen),y=value)) +
labs(title=paste0("Evaluation based on ", input$stereotype),
x="Gender",
y=paste0("Stereotype of ", input$stereotype)) +
geom_boxplot() +
theme_light()
})
# render plot for user
output$st <- renderPlot({
plottmp()
})
When coding output objects, these need to be wrapped inside designated code chunks – for plots or tables or text.
See this official cheetsheet https://rstudio.github.io/cheatsheets/html/shiny.html.
Running the app locally is as simple as pressing the button Run App
.
This is in fact calling a function written inside the app.R
script.
This function is shinyApp(ui = ui,server = server)
.
Run your app
The shinyApp()
function takes two arguments ui
and server
that are defined separately.
Running the app, a new window will open. You might note two things in the console:
We would need a dedicated server and, of course, an access account on that server.
One efficient and smooth way to deploy a shiny app
online is to use the dedicated server https://www.shinyapps.io/.
Select files for deployment
Make sure you select for deployment only the files the app actually needs to function properly.
One further thing we might want to do is to push the script files to GitHub
. This, if we are working collaboratively or if we want the code to be publically available.
Shiny apps can be programmed in quarto
as well.
Follow the steps here https://quarto.org/docs/interactive/shiny/.
Coding a shiny app inside a quarto
document makes your work environment more structured.
Write a shiny app for your project.
shiny app
.
Figure 3 (b): Corresponding code in the UI.
Inside a quarto document
From inside an .R
script file