rdataframeshinyradar-chart

Radar plot in R Shiny


I'm really new to R Shiny (starting playing with it today!), but this code isn't working for me... R keeps saying "the data must be given as dataframe." which, as far as I can tell, it is a dataframe (and it says it is when I check with is.data.frame).

# Load packages ----
library(shiny)
library(fmsb)

# Load data ----
industry <- read.csv("data/industry.csv")

# User interface ----
ui <- fluidPage(
  titlePanel("L&D Capabilities 2023"),
  
  sidebarLayout(
    sidebarPanel(
      helpText("Check which L&D capabilities your industry
      has in-house in 2023."),
      
      selectInput("var", 
                  label = "Choose a variable to display",
                  choices = c("Central government", 
                              "Local government",
                              "IT and Telecoms", 
                              "Professional services, law and accountancy", 
                              "Finance, banking and insurance", 
                              "Health", 
                              "Social care/housing association", 
                              "Other charity/voluntary sector", 
                              "Retail", 
                              "Engineering", 
                              "Manufacturing", 
                              "Pharmaceutical", 
                              "Transport", 
                              "Utilities", 
                              "Hospitality", 
                              "Education (HE, FE)", 
                              "Art, media and design", 
                              "Other", 
                              "Consulting"),
                  selected = "Central government"),
    ),
    
    mainPanel(plotOutput("radarPlot"))
  )
)

# Server logic ----
server <- function(input, output) {
  output$radarPlot <- renderPlot({
    data <- switch(input$var, 
                   "Central government" = industry$Centralgov,
                   "Local government" = industry$Localgov,
                   "IT and Telecoms" = industry$IT,
                   "Professional services, law and accountancy" = industry$PS,
                   "Finance, banking and insurance" = industry$Finance,
                   "Health" = industry$Health,
                   "Social care/housing association" = industry$Social,
                   "Other charity/voluntary sector" = industry$Charity,
                   "Retail" = industry$Retail,
                   "Engineering" = industry$Engineering,
                   "Manufacturing" = industry$Manufacturing,
                   "Pharmaceutical" = industry$Pharmaceutical,
                   "Transport" = industry$Transport,
                   "Utilities" = industry$Utilities,
                   "Hospitality" = industry$Hospitality,
                   "Education (HE, FE)" = industry$Education,
                   "Consulting" = industry$Consulting,
                   "Art, media and design" = industry$Art,
                   "Other" = counties$Other)
    
    radarchart(data)
  })
}

# Run app ----
shinyApp(ui, server)

Any ideas what's going on? Or what I'm missing?

Many thanks!

EDIT: here's my data

> dput(industry)
structure(list(Max = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), Min = c(0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), Centralgov = c(0.6, 
0.18, 0.27, 0.27, 0.27, 0.27, 0.36, 0.3, 0.55, 0.45, 0.1, 0, 
0.1, 0.27, 0.64, 0.09, 0.09, 0.18, 0.27, 0, 0.09, 0.18, 0.25, 
0.29, 0.14), Localgov = c(0.36, 0.5, 0.36, 0.5, 0.42, 0.42, 0.09, 
0.27, 0.36, 0.55, 0.3, 0.36, 0.55, 0.45, 0.73, 0.36, 0.18, 0.45, 
0.64, 0.36, 0.27, 0.18, 0.3, 0.2, 0.6), IT = c(0.73, 0.33, 0.47, 
0.51, 0.38, 0.18, 0.34, 0.38, 0.62, 0.41, 0.19, 0.38, 0.49, 0.41, 
0.62, 0.32, 0.22, 0.38, 0.58, 0.51, 0.33, 0.34, 0.41, 0.15, 0.37
), PS = c(0.73, 0.4, 0.56, 0.6, 0.48, 0.48, 0.29, 0.24, 0.63, 
0.56, 0.29, 0.41, 0.27, 0.36, 0.71, 0.28, 0.16, 0.48, 0.4, 0.52, 
0.36, 0.38, 0.29, 0.25, 0.13), Finance = c(0.9, 0.44, 0.66, 0.66, 
0.61, 0.52, 0.44, 0.5, 0.86, 0.62, 0.32, 0.39, 0.48, 0.59, 0.86, 
0.3, 0.27, 0.5, 0.52, 0.52, 0.57, 0.51, 0.56, 0.33, 0.29), Health = c(0.88, 
0.33, 0.47, 0.65, 0.28, 0.37, 0.33, 0.29, 0.78, 0.47, 0.18, 0.13, 
0.47, 0.5, 0.78, 0.26, 0.16, 0.41, 0.58, 0.5, 0.38, 0.39, 0.33, 
0.13, 0.29), Social = c(0.7, 0.25, 0.5, 0.33, 0.3, 0.2, 0.1, 
0.4, 0.5, 0.2, 0, 0.22, 0, 0.2, 0.4, 0.1, 0.3, 0.1, 0.3, 0.3, 
0.33, 0.3, 0.33, 0, 0.11), Charity = c(0.8, 0.55, 0.62, 0.44, 
0.5, 0.31, 0.08, 0.33, 0.58, 0.5, 0.4, 0.36, 0.33, 0.38, 0.82, 
0.15, 0.08, 0.36, 0.22, 0.42, 0.2, 0.42, 0.18, 0.22, 0.11), Retail = c(0.62, 
0.38, 0.46, 0.27, 0.25, 0.09, 0.08, 0.31, 0.82, 0.46, 0.25, 0.27, 
0.25, 0.54, 0.69, 0.08, 0.17, 0.31, 0.67, 0.5, 0.33, 0.5, 0.38, 
0.18, 0.08), Engineering = c(0.6, 0, 0.4, 0.25, 0.17, 0.17, 0, 
0, 0.33, 0.5, 0.25, 0.33, 0.6, 0.17, 0.33, 0, 0, 0.33, 0.33, 
0.17, 0.17, 0.5, 0.2, 0, 0), Manufacturing = c(0.56, 0.22, 0.35, 
0.42, 0.42, 0.4, 0.24, 0.2, 0.56, 0.41, 0.24, 0.11, 0.21, 0.3, 
0.63, 0.1, 0, 0.25, 0.42, 0.58, 0.21, 0.35, 0.25, 0.33, 0.06), 
    Pharmaceutical = c(0.43, 0.25, 0, 0.71, 0.63, 0.25, 0.13, 
    0.13, 0.63, 0.43, 0, 0, 0, 0, 0.38, 0.25, 0.13, 0.38, 0.38, 
    0.5, 0, 0, 0.33, 0, 0.17), Transport = c(0.77, 0.62, 0.79, 
    0.57, 0.71, 0.64, 0.14, 0.5, 0.79, 0.46, 0.38, 0.21, 0.36, 
    0.38, 0.64, 0.43, 0.29, 0.21, 0.57, 0.64, 0.29, 0.54, 0.57, 
    0.36, 0.15), Utilities = c(1, 0.6, 0.4, 0.33, 0.2, 0.2, 0.6, 
    0.6, 0.8, 0.6, 0.25, 0.2, 0.8, 0.4, 1, 0.4, 0.4, 0.6, 0.4, 
    0.6, 0.2, 0.2, 0.2, 0.2, 0), Hospitality = c(0.67, 0, 0.67, 
    0.4, 0.67, 0.33, 0.33, 0.83, 0.83, 0.2, 0.67, 0.17, 0.2, 
    0.33, 0.83, 0.33, 0.33, 0, 0.67, 1, 0.5, 0.33, 0.33, 0.6, 
    0.33), Education = c(0.87, 0.33, 0.47, 0.53, 0.41, 0.38, 
    0.5, 0.47, 0.65, 0.41, 0.2, 0.31, 0.47, 0.65, 0.53, 0.24, 
    0.29, 0.38, 0.56, 0.41, 0.31, 0.19, 0.38, 0.27, 0.35), Consulting = c(0.67, 
    0.5, 0.67, 1, 0.33, 0.33, 0.17, 0.5, 1, 0.6, 0.33, 0.6, 0.4, 
    0.67, 0.5, 0.17, 0.17, 0.4, 0.6, 0.5, 0.5, 0.33, 0.4, 0.25, 
    0.25), Art = c(1, 0.2, 0.6, 0.5, 0.4, 0.2, 0.2, 0.6, 0.6, 
    0.4, 0.2, 0.2, 0.6, 0.5, 0.5, 0.2, 0.2, 0.2, 0.2, 0.6, 0.4, 
    0.4, 0.4, 0.2, 0.25), Other = c(0.67, 0.57, 0.71, 0.29, 0.57, 
    0.43, 0.14, 0.5, 0.67, 0.29, 0.57, 0.29, 0.43, 0.57, 0.71, 
    0.29, 0.43, 0.29, 0.43, 0.57, 0.71, 0.43, 0.5, 0.6, 0.4)), class = "data.frame", row.names = c("In-person classroom delivery", 
"Strategy and governance", "Stakeholder engagement", "Instructional design", 
"Crafting learning journeys / blended solutions", "Supporting ongoing workplace performance", 
"Facilitating social and collaborative learning", "Understanding learner behaviour", 
"Virtual classroom / webinar delivery", "Digital content development", 
"Performance consulting", "Business acumen", "Marketing and communications", 
"Coaching and mentoring", "Learning management / administration", 
"Analytics / data management", "Evaluating impact", "Technology/infrastructure", 
"Project management", "Leveraging L&D expertise", "Knowledge management", 
"Negotiation, persuasion, and influence", "Learning experience design", 
"Community engagement", "Research capabilities"))

Solution

  • Update based on Request in Comment

    If you want to allow multiple selections, the easiest manner in which to approach this is likely pivoting the data, then filtering for the selected fields. This makes color assignment as easy as assigning it to the column now housing the industry names (in other words, the column names in your current data frame).

    The UI remains unchanged from my initial answer. I've added several comments as to what the code is doing. If there is anything that's unclear, let me know.

    library(plotly)
    library(shiny)
    library(tidyverse)
    
    ui2 <- fluidPage(
      titlePanel("With Plotly"),
      sidebarLayout(sidebarPanel(
        helpText("help goes here"),
        selectInput(
          "var", label = "Choose a field to plot", 
          multiple = T,                                       # <<- TRUE now 
          choices = setNames(
            names(industry)[3:21], 
            c("Central government", "Local government", "IT and Telecoms",
              "Professional services, law and accountancy", 
              "Finance, banking and insurance", "Health", 
              "Social care/housing association", "Other charity/voluntary sector", 
              "Retail", "Engineering","Manufacturing", "Pharmaceutical", "Transport",
              "Utilities", "Hospitality", "Education (HE, FE)", "Consulting", 
              "Art, media and design", "Other")), selected = "Central government")
      ), mainPanel(plotlyOutput("radarPlot")))
    )
    
    server4 <- function(input, output, session) {
      output$radarPlot <- renderPlotly({ 
        indus2 <- industry %>% rownames_to_column("rn") %>% # rownames to column
          pivot_longer(!rn, names_to = "field")             # pivot all but rownames
        filter(indus2, field %in% input$var) %>%            # filter for selections
          plot_ly(r = ~value, theta = ~rn, color = ~field,  # color by industry
                  fill = "toself", type = "scatterpolar", mode = "markers") %>% 
          layout(polar = list(
            angularaxis = list(showticklabels = F),
            radialaxis = list(range = c(0, 1))
          ))
      })
    }
    # Run app ----
    shinyApp(ui2, server4)
    

    enter image description here

    enter image description here



    Original Answer

    Like I mentioned in my comment, radarchart requires at least three variables.

    In this solution, I set the plot to only render when there are at least three options selected. I've changed the creation of the dropdown to allow multiple selections (so that three can be selected.

    ui <- fluidPage(
      titlePanel("Title"),
      sidebarLayout(sidebarPanel(
        helpText("help goes here"),
        selectInput(
          "var", label = "Choose at least three options to plot", 
          multiple = T, 
          choices = setNames(
              names(industry)[3:21], 
              c("Central government", "Local government", "IT and Telecoms",
                "Professional services, law and accountancy", 
                "Finance, banking and insurance", "Health", 
                "Social care/housing association", "Other charity/voluntary sector", 
                "Retail", "Engineering","Manufacturing", "Pharmaceutical", "Transport", 
                "Utilities", "Hospitality", "Education (HE, FE)", "Consulting", 
                "Art, media and design", "Other")), selected = "Central government")
          ), mainPanel(plotOutput("radarPlot")))
      )
    
    server <- function(input, output, session) {
      output$radarPlot <- renderPlot({
        if(length(input$var) >= 3) {
          radarchart(industry[, input$var])
        }
      })
    }
    
    # Run app ----
    shinyApp(ui, server)
    

    enter image description here

    However, this is not technically accurate since the first row is supposed to be the max value. The second row is supposed to be the min value. (Which is how your data frame is orientated if you transpose it.)

    Here's another version of the server. In this version, I've modified the data so that the first and second rows are the max and min.

    # add max and min to the top of every column
    industry2 <- t(industry) %>% 
      as.data.frame() %>% 
      mutate(max = 1, min = 0) %>% 
      select(max, min, everything()) %>% 
      t() %>% as.data.frame()
    
    server2 <- function(input, output, session) {
      output$radarPlot <- renderPlot({
        if(length(input$var) >= 3) {
          radarchart(industry[, input$var])
        }
      })
    }
    
    # Run app ----
    shinyApp(ui, server2)
    

    This renders an identical plot to what I pictured for the first version (so I am not sure why the radarchart function calls for this information.)


    I'm not sure how attached you are to fmsb::radarchart, but there are a lot of other options: ggplot2, plotly, highcharter, echarts, etc.

    Here's an alternative using the Plotly library. In this option, I've set multiple in selectInput to false (based on what you were originally trying to plot). I've set the range so the graphs are comparable. A few other changes are annotated in the comments in the code.

    library(plotly)
    library(shiny)
    
    ui2 <- fluidPage(
      titlePanel("With Plotly"),
      sidebarLayout(sidebarPanel(
        helpText("help goes here"),
        selectInput(
          "var", label = "Choose a field to plot", 
          multiple = F,      # <<- FALSE now (can remove argument, default is false)
          choices = setNames(
            names(industry)[3:21], 
            c("Central government", "Local government", "IT and Telecoms",
              "Professional services, law and accountancy", 
              "Finance, banking and insurance", "Health", 
              "Social care/housing association", "Other charity/voluntary sector", 
              "Retail", "Engineering","Manufacturing", "Pharmaceutical", "Transport", 
              "Utilities", "Hospitality", "Education (HE, FE)", "Consulting", 
              "Art, media and design", "Other")), selected = "Central government")
      ), mainPanel(plotlyOutput("radarPlot"))) # <<-- plotlyOutput instead of plotOutput
    )
    
    server3 <- function(input, output, session) {
      output$radarPlot <- renderPlotly({ # <- render plotly instead of plot
          plot_ly(r = industry[, input$var], theta = rownames(industry),
                  fill = "toself", type = "scatterpolar", mode = "markers") %>% 
          layout(
            polar = list(
            angularaxis = list(showticklabels = F),
            radialaxis = list(range = c(0, 1))
          ))
      })
    }
    # Run app ----
    shinyApp(ui2, server3)
    

    I hid the labels, because they overlapped. However, when you hover, you can see the row names.

    enter image description here