Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing user specifications as arguments to dplyr within Shiny

Tags:

r

dplyr

shiny

I'm writing a new Shiny app, and I would like to stay within the Hadleyverse by using dplyr commands for data manipulation. I want Shiny to print a table showing only the top 3 observations according to a variable that can be chosen by the user. For example, one user may want to see the top 3 employees in terms of number of awards received, while another will want to see the top 3 in terms of number of award points earned.

In my latest attempt, I have this for ui.R:

library(shiny)
shinyUI(fluidPage(
verticalLayout(
tableOutput("tbl"),
selectInput("top3", label = h4("Quantity"), 
  choices = list("Number of awards" = "NumberOfAwards", 
  "Total points awarded" = "TotalAwarded")), 
tableOutput("t3")
)))

and this for server.R:

library(dplyr)
library(shiny)
shinyServer(function(input, output) {
employeestats <- read.table(header=TRUE, text='
Employee  NumberOfAwards TotalAwarded
Al        3              10
Betty     6              20
Chuck     2              5
Donna     4              15
Ed        0              0
')  
output$tbl <- renderTable({ 
employeestats
},include.rownames=TRUE) 
datasetInput <- reactive({employeestats})
output$t3 <- renderTable({ 
head(datasetInput() %>% arrange(desc(input$top3)),n=3)  
},include.rownames=TRUE)
})

Outside of Shiny, the command

head(employeestats %>% arrange(desc(NumberOfAwards)),n=3)

gives the answer that I want for the top 3 award winners. In Shiny, the full table and the selection box are printed without the Top 3 table, and I get the message "Error in eval(substitute(expr), envir, enclos) : cannot arrange column of class 'NULL'". I know that this has something to do with both Shiny and dplyr using nonstandard functions, and that it could make a difference if R sees NumberOfAwards or the character string "NumberOfAwards". I've tried things like deparse(substitute()), using numbers to indicate the columns to arrange by, etc. without success. This doesn't have to be elaborate; for example, I don't care about ties for 3rd place.

Any ideas? Thanks for the help.

like image 246
JMH Avatar asked Aug 08 '14 20:08

JMH


2 Answers

The problem is that arrange() function expects your argument as a symbol. However, your input$top3 is a string.

The trick is:

output$t3 <- renderTable({ 
    sorted_stats <- eval(substitute(employeestats %>% arrange(desc(col)), 
                         list(col=as.symbol(input$top3))))
    head(sorted_stats,n=3)  
  },include.rownames=TRUE)

You can use ?substitute to see how it works.

Short version, it parse the expression:

employeestats %>% arrange(desc(col))

into a parse tree consisting of call's (functions) and name's (symbols, constants, objects), allowing us to substitute the components to form a new expression.

To this point, the expression is not evaluated (meaning, the employeestats is not arranged yet).

Here col doesn't really mean anything. It simply serves as a placeholder. And by passing list(col=as.symbol(input$top3)) to substitute, we replace the dummy symbol col by the actual symbol we would like to arrange by, as.symbol(input$top3). Depending on the current input, this would either be TotalAwarded or NumberOfAwards (symbols, not strings anymore).

Finally, the eval() function evaluated the expression (with col replaced by the actual symbol), and returns the sorted data.frame.

like image 159
Xin Yin Avatar answered Nov 15 '22 05:11

Xin Yin


You may also want to look at arrange_ (see vignette). Really nice for in Shiny apps.

The 1st option does what you want. The 2nd is cleaner but not exactly what you are looking for. I guess the 3rd option would be ideal but desc_ is not a function. Might be a nice addition :)

input <- list()
input$top3 <- "mpg"

mtcars %>%
  arrange_(paste0("desc(",input$top3,")"))  %>%
  head(n=3)

mtcars %>%
  arrange_(input$top3)  %>%
  tail(n=3)

# desc_ is not a function (yet)
mtcars %>%
  arrange(desc_(input$top3)) %>%
  head(n=3)
like image 23
Vincent Avatar answered Nov 15 '22 05:11

Vincent