Previously

A brief reminder.

Knit with parameters

# specify parameter in yaml header
title: "example"
output: html_document
date: "2024-05-09"
params:
  actor:
    label: "Actor"
    value: "Keanu Reeves"
    input: select
    choices: ["Keanu Reeves", "Alec Baldwin","Arnold Schwarzenegger"]
    multiple: yes
# assign parameter to object
actor<-params$actor
# use parameter in code
tmptbl<-dfmv %>% 
  filter(Actor %in% actor)

Self-publish

With quarto we can easily create website and books. Quarto website and Quarto book projects create default files.

Preview

quarto preview

Render

quarto render

Publish

quarto publish quarto-pub

Not about, but about…

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.

Examples

Predicted as observed by Dr. Julian Kohnke. Read paper by Witte et al. (2022).

Quantum social sciences by myself. Read preprint by Witte & Stanciu (2023).

Values in Europe by Dr. Maksim Rudnev.

Social perception during COVID-19 by Jessica A. Herzig. Read paper by Friehs et al. (2022).
Figure 1: Examples of shiny web application. More examples via https://shiny.posit.co/r/gallery/.

The set up

Figure 2: Steps to creating a Shiny Application project

Set up workenvironment

We need the package shiny. If you haven’t done so yet, please install now.

install.packages("shiny")
library(shiny)

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!

source("")

We might have saved data after having manipulated it (cleaned, filtered etc) as an R data object. This can be loaded into the app.

It saves computing time but it is static meaning that you are limited to the saved data object itself. No access to original or raw data.

load("")

Sourcing code

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.

func1<-function(df,n){
  
  tmp <- Filter(is.numeric, df) # we first filter the dataframe for numeric columns
  
  tmp + n # we then add the constant to all the numeric columns
}

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!

Loading R data objects

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.

# imports data in SPSS .sav format
# make sure to actualize the path to the data file so that it matches your directory
dfex<-haven::read_sav("data/sample.sav")

# saves R object dfex into an .Rdata format 
# which we load into the shiny app shortly
save(dfex,file="data/sample.Rdata")

Once created, we can load the .Rdata object as follows:

load("data/sample.Rdata") # be sure to specify the path to your working directory

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.

Shiny apps structure

One for the user, one for the server.

Shiny apps structure

  ### 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 structure

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).

User Interface – UI

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?

UI

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.

UI

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.

Layout

Figure 3: UI code and corresponding shiny outcome.

Layout

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.

    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), 
          
          ...

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") )
            ...

Input types

sliderInput(inputId = "bins",
            label = "Number of bins: ",
            value = 1,
            min = 1,
            max = 100)
selectInput(inputId = = "actor",
            label = "Choose an actor:",
            choices =  c("Keanu Reeves",
                        "Alec Baldwin",
                        "Arnold Schwarzenegger",
                        "Timothee Chalamet",
                        "Anamaria Marinca"),
                      multiple = TRUE)
checkboxInput(inputId =  "p1.ess",
              label = "Distribution", 
              value = F)
numericInput(inputId = "numinp",
             label = "Number of obs",
             value = 100,
             min = 10,
             max = 999,
             step = 5)

Conditional panels

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.

conditionalPanel( 
            condition= "input.type=='Simulations'",
... # the code continues here with input values

Tabset

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.

tabsetPabel(
  tabPanel1("label 1 for display to user", {content 1 here}),
  tabPanel2("label 2 for display to user", {content 2 here})
)

Server

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.

Code for server

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(){}.

Input and output arguments

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.

Reactive objects

Plain R code wrapped inside an object that the server needs to compute.

Reactive objects

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().

Code for the server

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)
  
  })

Input objects

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)
  
  })
 # UI code for stereotype input  
          selectInput("stereotype",
                        label="Stereotype dimension:",
                        c("Warmth women" = "wom_warm",
                                  "Competence women" = "wom_comp",
                                  "Warmth men" = "men_warm",
                                  "Competence men" = "men_comp"))

Output objects

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.

Output objects

User interface code

plotOutput("st")

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()
  })

Output objects

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.

Run the app locally

Running the app locally is as simple as pressing the button Run App.

Run the app locally

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:

  1. The console is busy with the app, as indicated by the STOP symbol.
  1. The text in the console Listening on {an IP address}.

Deploy the app

We would need a dedicated server and, of course, an access account on that server.

Deploy the app

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.

GitHub (optional)

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.

Advanced resources

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.

Advanced resources

Inside a quarto document

Inside a quarto document

From inside an .R script file

From inside an .R script file

Ways to code a shiny application

Write a shiny app

Write a shiny app for your project.

Reference list

Friehs, M. T., Kotzur, P. F., Kraus, C., Schemmerling, M., Stanciu, A., & al, et. (2022). Warmth and competence perceptions of key protagonists are associated with containment measures during the COVID-19 pandemic: Evidence from 35 countries. Scientific Reports, 12, 21277. https://doi.org/10.1038/s41598-022-25228-9
Stanciu, A., Cohrs, C. J., Hanke, K., & Gavreliuc, A. (2017). Within-culture variation in the content of stereotypes: Application and development of the stereotype content model in an eastern european culture. The Journal of Social Psychology, 157(5), 611–628. https://doi.org/10.1080/00224545.2016.1262812
Witte, E. H., & Stanciu, A. (2023). Error theory of mental test scores without and with a measurement instrument. https://doi.org/10.31219/osf.io/9ap6m
Witte, E. H., Stanciu, A., & Zenker, F. (2022). Predicted as observed? How to identify empirically adequate theoretical constructs. Frontiers in Psychology, 13, 980261. https://doi.org/10.3389/fpsyg.2022.980261