Esthetically displaying long data frames to subset from in an R shiny app

Hi,

Crossposting from stackoverflow.

I have some data ( design.df below) which I want to explore with an R shiny app:

set.seed(1)
library(dplyr)
samples <- paste0("s",1:5)
clusters <- paste0("c",1:10)
groups <- paste0("g",1:20)

design.df <- expand.grid(samples,clusters,groups) %>%
    dplyr::rename(sample=Var1,cluster=Var2,group=Var3) %>%
    dplyr::mutate(value=rnorm(nrow(.)))

I want to allow the user to be able to subset the design.df by any of the columns (leave the value column). In this example, these will be sample , cluster , and group , but in reality, this is an app to which different users will load data.frame s with different columns (leave the value column which all will have).

I'm trying to adapt the 10.3.2 Dynamic filtering example to my case but not quite there.

Here's my code:

library(shiny)
library(dplyr)

make_ui <- function(x, var) {
  if (is.numeric(x)) {
    rng <- range(x, na.rm = TRUE)
    sliderInput(var, var, min = rng[1], max = rng[2], value = rng)
  } else if (is.factor(x)) {
    levs <- levels(x)
    selectInput(var, var, choices = levs, selected = levs, multiple = TRUE)
  } else {
    # Not supported
    NULL
  }
}

filter_var <- function(x, val) {
  if (is.numeric(x)) {
    !is.na(x) & x >= val[1] & x <= val[2]
  } else if (is.factor(x)) {
    x %in% val
  } else {
    # No control, so don't filter
    TRUE
  }
}

server <- function(input, output)
{
  data <- reactive({
    get(input$dataset, data.frame(dplyr::select(design.df,-value)))
  })
  
  vars <- reactive(names(data()))
  
  output$filter <- renderUI(
    purrr::map(vars, ~ make_ui(data()[[.x]], .x))
  )
  
  selected <- reactive({
    each_var <- purrr::map(vars, ~ filter_var(data()[[.x]], input[[.x]]))
    purrr::reduce(each_var, `&`)
  })
  
  scatter.plot <- reactive({
    scatter.plot <- NULL
    if(!is.null(data()[selected(),]){
      plot.df <- suppressWarnings(data()[selected(), ])
      scatter.plot <- suppressWarnings(plotly::plot_ly(marker=list(size=3),type='scatter',mode="markers",color=plot.df$sample,x=plot.df$group,y=plot.df$value) %>%
                                         plotly::layout(xaxis=list(title="group",showgrid=F),yaxis=list(title="value",showgrid=F)))
    }
    return(scatter.plot)
  })
    
  output$out.plot <- plotly::renderPlotly({
    scatter.plot()
  })  
}

ui <- fluidPage(
  titlePanel("Data Explorer"),
  sidebarLayout(
    sidebarPanel(
      tags$head(
        tags$style(HTML(".multicol {-webkit-column-count: 3; /* Chrome, Safari, Opera */-moz-column-count: 3; /* Firefox */column-count: 3;}")),
        tags$style(type="text/css", "#loadmessage {position: fixed;top: 0px;left: 0px;width: 100%;padding: 5px 0px 5px 0px;text-align: center;font-weight: bold;font-size: 100%;color: #000000;background-color: #CCFF66;z-index: 105;}"),
        tags$style(type="text/css",".shiny-output-error { visibility: hidden; }",".shiny-output-error:before { visibility: hidden; }")),
      conditionalPanel(condition="$('html').hasClass('shiny-busy')",tags$div("In Progress...",id="loadmessage")),
      selectInput("dataset", label = "Dataset", choices = colnames(dplyr::select(design.df,-value))),
      uiOutput("filter"),
    ),
    mainPanel(
      plotly::plotlyOutput("out.plot")
    )
  )
)

shinyApp(ui = ui, server = server)

Which gives this interface:

It is close to what I want but still has a few issues:

  1. It presents all columns of design.df rather than reacting to the selected one.
  2. It is not displaying the scatter plot, probably because of the condition that I set up in the scatter.plot reactive .

Any idea what's wrong?

Once these are solved I'll also need to update the plotting code in the scatter.plot reactive so that it does not explicitly choose column names from design.df but rather selected ones, but that's not critical at all for this post.

You should mention that this is a crosspost.

Answered in stackoverflow.

This is a sufficient solution for me.

Data:

set.seed(1)
library(dplyr)
samples <- paste0("s",1:5)
clusters <- paste0("c",1:10)
groups <- paste0("g",1:20)

design.df <- expand.grid(samples,clusters,groups) %>%
    dplyr::rename(sample=Var1,cluster=Var2,group=Var3) %>%
    dplyr::mutate(value=rnorm(nrow(.)))

Shiny code:

library(shiny)
library(dplyr)

make_ui <- function(x, var) {
  if (is.numeric(x)) {
    rng <- range(x, na.rm = TRUE)
    sliderInput(var, var, min = rng[1], max = rng[2], value = rng)
  } else if (is.factor(x)) {
    levs <- levels(x)
    selectInput(var, var, choices = levs, selected = levs, multiple = TRUE)
  } else {
    # Not supported
    NULL
  }
}

filter_var <- function(x, val) {
  if (is.numeric(x)) {
    !is.na(x) & x >= val[1] & x <= val[2]
  } else if (is.factor(x)) {
    x %in% val
  } else {
    # No control, so don't filter
    TRUE
  }
}

server <- function(input, output)
{
  output$filter <- renderUI(
    purrr::map(colnames(data.frame(dplyr::select(design.df,-value))), ~ make_ui(data.frame(dplyr::select(design.df,-value))[[.x]], .x))
  )
  
  selected <- reactive({
    each_var <- purrr::map(colnames(data.frame(dplyr::select(design.df,-value))), ~ filter_var(data.frame(dplyr::select(design.df,-value))[[.x]], input[[.x]]))
    purrr::reduce(each_var, `&`)
  })
  
  scatter.plot <- reactive({
    scatter.plot <- NULL
    if(!is.null(data.frame(dplyr::select(design.df,-value))[selected(),])){
      plot.df <- suppressWarnings(data.frame(dplyr::select(design.df,-value))[selected(), ])
      scatter.plot <- suppressWarnings(plotly::plot_ly(marker=list(size=3),type='scatter',mode="markers",color=plot.df$sample,x=plot.df$group,y=plot.df$value) %>%
                                       plotly::layout(xaxis=list(title="group",showgrid=F),yaxis=list(title="value",showgrid=F)))
    }
    return(scatter.plot)
  })
    
  output$out.plot <- plotly::renderPlotly({
    scatter.plot()
  })  
}

ui <- fluidPage(
  titlePanel("Data Explorer"),
  sidebarLayout(
    sidebarPanel(
      tags$head(
        tags$style(HTML(".multicol {-webkit-column-count: 3; /* Chrome, Safari, Opera */-moz-column-count: 3; /* Firefox */column-count: 3;}")),
        tags$style(type="text/css", "#loadmessage {position: fixed;top: 0px;left: 0px;width: 100%;padding: 5px 0px 5px 0px;text-align: center;font-weight: bold;font-size: 100%;color: #000000;background-color: #CCFF66;z-index: 105;}"),
        tags$style(type="text/css",".shiny-output-error { visibility: hidden; }",".shiny-output-error:before { visibility: hidden; }")),
      conditionalPanel(condition="$('html').hasClass('shiny-busy')",tags$div("In Progress...",id="loadmessage")),
      uiOutput("filter"),
    ),
    mainPanel(
      plotly::plotlyOutput("out.plot")
    )
  )
)

shinyApp(ui = ui, server = server)

Which gives:

This topic was automatically closed 54 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.