Search code examples
rshinycode-duplication

How to Avoid Duplication of Code in Shiny apps and helpers


End of post has working Shiny code

My code takes user inputs and produces two charts.

Each chart has their own renderPlot section in Server which saves the same variables twice, i.e.

    what_races <- input$race
    what_ages<- c(input$age[1],input$age[2])

and uses the same if statement to call a different function in helpers.R, i.e.

if ((length(what_races) > 0 ) & !is.null(what_ages))

And the two functions in helpers.R use the same code repeatedly.

How do I simplify the coding. I have searched Shiny samples, but lot of data is from pre-packaged libraries, so one cannot see under the hood.

Any guidance is greatly appreciated.

app.R

# Load packages ----
library(shiny)
library(ggplot2)
library(dplyr)
library(scales)
library(treemapify)
library(RColorBrewer)
library(forcats)


# Source helpers ----
source("helpers.R")

# Load data ----
data(Marriage, package="mosaicData")


# User interface ----
ui <- fluidPage(
  fluidRow(
           titlePanel(
             h4("Marriage records from the Mobile County, Alabama, probate court.",
                style='color:black;padding-left: 15px'))
  ),

  br(),

  fluidRow(
    column(2,
      checkboxGroupInput("race","Races to show",
                                c("White", "Black","American Indian", "Hispanic")),
      sliderInput("age", "Age Range",min = as.integer(min(Marriage$age)), max = as.integer(max(Marriage$age)),value = c(min,max))
      ),
    column(5,
           plotOutput("tree"), style='height:100px'),
    column(5,
           plotOutput("chart"), style='height:100px')
  )

)

server <- function(input, output) {


  output$tree <- renderPlot({
    what_races <- input$race
    what_ages<- c(input$age[1],input$age[2])
    if ((length(what_races) > 0 ) & !is.null(what_ages))  {
      plot_tree(what_races,what_ages)
    }
  }
  )

  output$chart <- renderPlot({
    what_races <- input$race
    what_ages<- c(input$age[1],input$age[2])
    if ((length(what_races) > 0 ) & !is.null(what_ages))  {
      plot_bar(what_races,what_ages)
    }
  }
  )
}

# Run the app
shinyApp(ui, server)

helpers.R

plot_tree <- function(what_races,what_ages) {



  plotdata <- dplyr::filter(Marriage, race %in% what_races, age >= what_ages[1], age <= what_ages[2]) %>%
    count(officialTitle)

  plotdata <- na.omit(plotdata)

  if (nrow(plotdata) > 0) {
    ggplot(plotdata, 
           aes(fill = officialTitle, 
               area = n,
               label = officialTitle)) +
      geom_treemap() + 
      geom_treemap_text(colour = "white", 
                        place = "centre") +
      labs(title = "Marriages by officiate") +
      theme(plot.title = element_text(color="black", size=14, face="bold"),legend.position = "none")
  } else { }

}


plot_bar <- function(what_races,what_ages) {

  plotdata <- dplyr::filter(Marriage, race %in% what_races, age >= what_ages[1], age <= what_ages[2])
  plotdata$prevconc <- as.character(plotdata$prevconc)
  plotdata$prevconc[is.na(plotdata$prevconc)] <- "Never Married"
  plotdata <- na.omit(plotdata)

  if (nrow(plotdata) > 0) {
    ggplot(plotdata, 
           aes(x = sign, 
               fill = prevconc)) + 
      geom_bar(position = "stack") +
      labs("Race per Astrological Sign") + 
      theme(legend.position = "top") +
      coord_flip()
  } else {}

}

Solution

  • A function is the way to go. They are useful for avoiding repeated code; making your code shorter and easier to maintain. You've got them in action already in creating your plots.

    func_check_inputs <- function() {
    
        what_races <<- input$race
        what_ages  <<- c(input$age[1], input$age[2])
    
        if (length(what_races) > 0 & !is.null(what_ages))  {return(TRUE)} else {return(FALSE)}
    
    }
    

    As you use what_races and what_ages later, outside of the function, we'll make them global variables by using the <<- operator.

    Here is that function in your full app:

    # Load packages ----
    library(shiny)
    library(ggplot2)
    library(dplyr)
    library(scales)
    library(treemapify)
    library(RColorBrewer)
    library(forcats)
    library(mosaicData)
    
    # Source helpers ----
    source("helpers.R")
    
    # Load data ----
    data(Marriage, package="mosaicData")
    
    # User interface ----
    ui <- fluidPage(
    
        fluidRow(
            titlePanel(
                h4("Marriage records from the Mobile County, Alabama, probate court.", style='color:black;padding-left: 15px')
            )
        ),
    
        br(),
    
        fluidRow(
            column(2,
                checkboxGroupInput("race", "Races to show", c("White", "Black", "American Indian", "Hispanic")),
                sliderInput("age", "Age Range", min = as.integer(min(Marriage$age)), max = as.integer(max(Marriage$age)),value = c(min, max))
            ),
            column(5,
                plotOutput("tree"), style='height:100px'
            ),
            column(5,
                plotOutput("chart"), style='height:100px'
            )
        )
    
    )
    
    server <- function(input, output) {
    
        #Function to check if inputs are valid
        func_check_inputs <- function() {
    
            #Make what_races and what_ages global variables
            what_races <<- input$race
            what_ages  <<- c(input$age[1], input$age[2])
    
            if (length(what_races) > 0 & !is.null(what_ages))  {return(TRUE)} else {return(FALSE)}
    
        }
    
        output$tree <- renderPlot({
    
            if (func_check_inputs() == TRUE) {plot_tree(what_races, what_ages)}
    
        })
    
        output$chart <- renderPlot({
    
            if (func_check_inputs() == TRUE) {plot_tree(what_races, what_ages)}
    
        })
    
    }
    
    # Run the app
    shinyApp(ui, server)