R Shiny tableOutput() and renderTable() does not generate a summary table output

shiny
tidyverse
rstudio

#1

###Description
I am trying to build a Shiny app for the education sector but I am wrestling with renderTable(). The output I wish to see in ShinyApp I see in RStudio. The code fails to generate a 2 by 2 table in Shiny.

An example of the code view successfully in RStudio is: info %>% group_by(campus) %>% summarise(funded = n()), where 'campus' is 1 of the variables. In RStudio a summary table (2 by 2) is generated with 2 named columns and 2 rows.

In Shiny the user must be able to choose any of the variables contained in names(info). I achieve this by using the function `selectInput()'.

For the code below, renderTable() does not output a summary table. Instead it is a total count - a count of the total rows, not a summary of the totals of each unique element in a variable.

I think the error is in my renderTable() function code.
Please take a look if you have a moment or direct me to a route that will take me toward a solution.

Thank you.

My sessionInfo appears after the code below.

    library(shiny)
    library(tidyverse)

    info <- check_data <- structure(list(campus = c("athlone", "athlone", "athlone", "athlone","pinelands", "pinelands", "pinelands", "pinelands", "pinelands","pinelands"),
              block = c("l0", "l0", "l0", "l0", "l0", "l0", "l0","ys", "ys", "ys"),
              qualification = c("l2: automotive repair & maintenace nqf","l2: automotive repair
              & maintenace nqf", "l2: automotive repair & maintenace nqf","l2: automotive repair
              & maintenace nqf", "l2: electrical engineering nqf","l2: electrical engineering
              nqf", "l2: electrical engineering nqf","ncv3: electrical infrastructure cnstr l3", "ncv3: electrical infrastructure cnstr l3","ncv3: electrical infrastructure cnstr l3"),
              type = c("f", "f","f", "f", "f", "f", "f", "e", "e", "e"),
              employer = c("null","null", "null", "null", "null", "null", "null", "null", "null","null")), row.names = c(NA, 10L), class = "data.frame")
              
              
    info <- info %>% select(c(2, 3, 4, 8, 10))

    ui <- fluidPage(
  
      selectInput(inputId = "choice",
              label = "Select group",
              choices = names(info),
              selected = c("qualification")),
  
      tableOutput(outputId = "fund")
    )

    server <- function(input, output) {
      headcount <- reactive({
        req(input$choice)
        info %>% group_by(input$choice) %>% summarise(funded = n())
      })
  
    # Headcount
    output$fund <- renderTable({
        headcount()
     })
    }

    shinyApp(ui = ui, server = server)

My sessionInfo() is below:

>R version 3.5.1 (2018-07-02)
>Platform: x86_64-apple-darwin15.6.0 (64-bit)
>Running under: macOS High Sierra 10.13.6

>Matrix products: default
>BLAS: 

>/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecL>ib.framework/Versions/A/libBLAS.dylib
>LAPACK: >/Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dyli>b

>locale:

>[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8

>attached base packages:

>[1] stats, graphics, grDevices utils, datasets, methods, base     

>other attached packages:

 >[1] bindrcpp_0.2.2, forcats_0.3.0, stringr_1.3.1, dplyr_0.7.6    
 >[5] purrr_0.2.5, readr_1.1.1, tidyr_0.8.1, tibble_1.4.2   
 >[9] ggplot2_3.0.0, tidyverse_1.2.1, shiny_1.1.0    

>loaded via a namespace (and not attached):
 >[1] tidyselect_0.2.4, haven_1.1.2, lattice_0.20-35, colorspace_1.3-2
 >[5] htmltools_0.3.6, yaml_2.2.0, utf8_1.1.4, rlang_0.2.1     
 >[9] later_0.7.3, pillar_1.3.0, withr_2.1.2, glue_1.3.0      
>[13] modelr_0.1.2, readxl_1.1.0, bindr_0.1.1, plyr_1.8.4      
>[17] munsell_0.5.0, gtable_0.2.0, cellranger_1.1.0, rvest_0.3.2     
>[21] httpuv_1.4.5, fansi_0.2.3, broom_0.5.0, Rcpp_0.12.18    
>[25] xtable_1.8-2, promises_1.0.1, scales_0.5.0, backports_1.1.2 
>[29] jsonlite_1.5, mime_0.5, hms_0.4.2, digest_0.6.15   
>[33] stringi_1.2.4, grid_3.5.1, cli_1.0.0, tools_3.5.1     
>[37] magrittr_1.5, lazyeval_0.2.1, crayon_1.3.4, pkgconfig_2.0.1 
>[41] Matrix_1.2-14, xml2_1.2.0, lubridate_1.7.4, assertthat_0.2.0
>[45] httr_1.3.1, rstudioapi_0.7, R6_2.2.2, nlme_3.1-137    
>[49] compiler_3.5.1  

#2

Hi @JoeMark! Welcome!

Thanks very much for writing up a well-put-together question with a great example :grin:.

So you've run smack into one of the trade-offs that comes with dplyr's use of non-standard evaluation. That's the trick that lets you write group_by(campus) in your pipeline without having to explain to R where in the world campus is supposed to come from — compare to other situations where you'd have to write info$campus. The trade-off is that group_by() expects to receive a bare symbol as an argument — something that's easy to supply by typing directly, but a little tricky to pass around inside other variables.

The other half of the problem is that input$choice supplies a character string, not a bare symbol. It's evaluating to "campus", not campus, and these turn out to be very different things from R's perspective. If you try running:

info %>% group_by("campus") %>% summarise(funded = n())

you'll see that you get the same unhelpful result as your shiny app is giving you. To understand why, look at the result before summarizing:

info %>% group_by("campus")
#> # A tibble: 10 x 6
#> # Groups:   "campus" [1]
#>    campus   block qualification                  type  employer `"campus"`
#>    <chr>    <chr> <chr>                          <chr> <chr>    <chr>     
#>  1 athlone  l0    "l2: automotive repair &\n  m… f     null     campus    
#>  2 athlone  l0    l2: automotive repair & maint… f     null     campus    
#>  3 athlone  l0    "l2: automotive\n  repair & m… f     null     campus    
#>  4 athlone  l0    l2: automotive repair & maint… f     null     campus    

Confronted with a character string, group_by() assumed you wanted to create and then group on a new variable named "campus" (quotes included!) whose values are all the same — therefore there's only one group.

Here's a version of your shiny app that works the way you want:

library(shiny)
library(tidyverse)

info <- structure(list(campus = c("athlone", "athlone", "athlone",
  "athlone","pinelands", "pinelands", "pinelands", "pinelands",
  "pinelands","pinelands"), block = c("l0", "l0", "l0", "l0", "l0", "l0",
  "l0","ys", "ys", "ys"), qualification = c("l2: automotive repair &
  maintenace nqf","l2: automotive repair & maintenace nqf", "l2: automotive
  repair & maintenace nqf","l2: automotive repair & maintenace nqf", "l2:
  electrical engineering nqf","l2: electrical engineering nqf", "l2:
  electrical engineering nqf","ncv3: electrical infrastructure cnstr l3",
  "ncv3: electrical infrastructure cnstr l3","ncv3: electrical infrastructure
  cnstr l3"), type = c("f", "f","f", "f", "f", "f", "f", "e", "e", "e"),
  employer = c("null","null", "null", "null", "null", "null", "null", "null",
  "null","null")), row.names = c(NA, 10L), class = "data.frame")

ui <- fluidPage(
  
  selectInput(inputId = "choice",
              label = "Select group",
              choices = names(info),
              selected = c("qualification")),
  
  tableOutput(outputId = "fund")
)

server <- function(input, output) {
  headcount <- reactive({
    req(input$choice)
    info %>% group_by(!! sym(input$choice)) %>% summarise(funded = n())
  })
  
  # Headcount
  output$fund <- renderTable({
    headcount()
  })
}

shinyApp(ui = ui, server = server)

(I simplified it a bit — the select() step was trying to select non-existent columns of the data frame)

This code does two things:

  1. It converts the string supplied by input$choice into a symbol using the sym() function
  2. It uses the !! (bang-bang) operator to tell group_by() "OK, now evaluate that expression I gave you" (sym(input$choice), which evaluates to the selected variable as a bare symbol).

Both steps are necessary:

info %>% group_by(sym(input$choice)) %>% summarise(funded = n())
#> Error in mutate_impl(.data, dots): Column `sym(input$choice)` is of unsupported type symbol

Just like when we passed in a string, group_by() is trying to make a new column and group on it, but this time it fails because dplyr doesn't support a column full of bare symbols.

The bigger picture

The system for working with non-standard evaluation that dplyr is using is called tidy evaluation. It's a relatively new thing that's gradually rolling out to the whole tidyverse, so it's worth wrapping your head around.

Some starting points:

The vignette on programming with dplyr.

A short list of places to get help with tidy eval in ggplot2 (videos!)

Good luck with your app! :grin:

An aside on testing shiny code...

You are definitely on the right track with the general pattern of working on your code logic outside of shiny! Here's a little trick that can help you catch problems like these sooner.

When you're testing code outside of shiny, try defining a list called input with elements that mimic the structure of input inside of shiny. Then pass input$thing_name into your code where the input values belong. This will make you more aware of when your code is expecting a directly-typed symbol and getting a string or a number from an input, instead.

For this example, the mocked-up input might look like:

input <- list(
  choice = names(info)[1]
)

#3

Dear JCBlum

Thank you for nudging me in the right direction. I shall certainly look closely at tidy evaluation, especially with the objective of understanding the solution you have kindly submitted.
Sincerely
JoeMark