0

i am trying to plot graphs based on user input from checkboxes. it all works fine until i uncheck the first checkbox and an error pops up saying "no applicable method for 'ggplotly' applied to an object of class "NULL"". Even though other checkbox/es are checked, it gives anerror. for my codde to work, the first checkbox has to be mandatorily always checked. How do i resolve my code such that the graph is plotted based on user input and doesn't depend on the first checkbox only? my sample data has 3 columns, namely "distributor_name", "outlet_type" and "total_sales". it is a csv file and here, i am showing how my data looks like. EDIT- for these 8 rows, i get no errors, when number of rows increase, i get the following error.

library(ggplot2)

mydata <-structure(list(State_Name =  c("ANDAMAN AND NICOBAR ISLANDS","ANDAMAN AND NICOBAR ISLANDS","ANDAMAN AND NICOBAR ISLANDS","ANDAMAN AND NICOBAR ISLANDS","ANDAMAN AND NICOBAR ISLANDS","ANDAMAN AND NICOBAR ISLANDS","ANDAMAN AND NICOBAR ISLANDS","ANDAMAN AND NICOBAR ISLANDS"), 
                     District_Name   = c("ANDAMANS","ANDAMANS","ANDAMANS","ANDAMANS","ANDAMANS","ANDAMANS","ANDAMANS","ANDAMANS"),               
                     Place_Name= c("PORT BLAIR", "PORT BLAIR", "PORT BLAIR", "PORT BLAIR", "PORT BLAIR", "PORT BLAIR", "PORT BLAIR", "PORT BLAIR"), 
                     Distributor_Name = c("M.A. MOHMAD & SONS(S1145)","M.A.MOHMAD & SONS(S1145)","M.A.MOHMAD & SONS(S1145)","M.A.MOHMAD & SONS(S1145)", "M.A.MOHMAD & SONS(S1145)","M.A.MOHMAD & SONS(S1145)","M.A.MOHMAD & SONS(S1145)","M.A. MOHMAD & SONS(S1145)"), 
                     Product_Code= c("ALHF", "ARFM", "ARTT", "BNEF", "BNPP", "BNSS", "BNTI","COFM"), 
                     Product_Value=c(8839.2, 39777.3, 19092.96, 254577.61, 63640.8, 10608, 28284.8, 21214.57),
                     Qty =c(80,90,72,720,720,240,320,48),
                     Tto= c(8662.42, 38981.76, 18711.1, 249486.05, 62367.99, 10395.84, 27719.1, 20790.28)), 
.Names = c("State_Name", "District_Name","Place_Name","Distributor_Name","Product_Code","Product_Value","Qty", "Tto"), row.names = c(NA,-8L), class = "data.frame")



print(mydata)

mydata <- head(mydata,n=20)
dput(mydata)
depvar <- mydata$Tto

avail_wise <- setdiff(colnames(mydata), depvar)
avail_wise <- setNames(avail_wise,
                   paste0(avail_wise, "-wise"))
set.seed(20180307)


 # random fill/color assignments
colors <- data.frame(
  field = avail_wise,
  fill = sample(palette(), length(avail_wise), replace=TRUE),
  color = sample(palette(), length(avail_wise), replace=TRUE)
)
str(colors)
# de-magic-constant something later in the code
checkboxes_max_levels <- 10 # an arbitrary number, seems reasonable
ui <- fluidPage(
  theme = "bootstrap.css",
  titlePanel("Hello User"),
  fluidRow(
    column(3, wellPanel(
      selectInput("input_type", "Input type",
                  choices = avail_wise, selected = avail_wise[1] )
    ) ),
    column(9, wellPanel( uiOutput("ui") ))
  ),
  fluidRow(
    column(12, plotOutput("dynamic_value") )
  )
)

Server <- function(input, output) {
  output$ui <- renderUI({
    req(input$input_type)
    choices <- sort(unique(mydata[[input$input_type]]))
    if (is.factor(choices) || is.character(choices) || length(choices) < checkboxes_max_levels) {
      checkboxGroupInput("dynamic", paste0(input$input_type, "-wise"),
                         choices = choices, selected = choices[1],
                         inline = TRUE)
    } else {
      shiny::sliderInput("dynamic", paste0(input$input_type, "-wise"),
                         min = min(choices), max = max(choices),
                         value = round(quantile(choices, c(0.25,0.75)), 1))
    }
  })
  filtered <- reactive({
    req(input$dynamic)
    col <- filteredcolors()
    it <- isolate(input$input_type)
    if (is.character(input$dynamic)) {
      # checkboxGroupInput
      ind <- mydata[[it]] %in% input$dynamic
    } else {
      # sliderInput
      ind <- input$dynamic[1] <= mydata[[it]] & mydata[[it]] <= input$dynamic[2]
    }
    mydata[ind,,drop=FALSE]
  })
  filteredcolors <- reactive({
    dplyr::filter(colors, field == input$input_type)
  })
  # mydata.ordered <- mydata[order(mydata[,8]),]
  output$dynamic_value <- renderPlot({
    req(filtered())
    col <- filteredcolors()
    ggplot(filtered(), aes_string(depvar)) + 
      geom_histogram(fill=col$fill, col=col$color)
  })
}

shinyApp(ui = ui, server = Server)

for some values of data, i do get graph

for a few, i get some aesthetics error[![][2]]3

SSV
  • 149
  • 2
  • 12
  • `if (input$dynamic == mydata1$outlet_type)` is broken/wrong, the second is a vector, and `if` does not do well with a `logical` *vector* of length over 1 ... please clarify what you intend with this. You should probably use `req(input$input_type)` somewhere instead of `is.null`, it tends to be more robust. Your first `ggplot` is wasted, why are you doing it? – r2evans Mar 08 '18 at 05:36

1 Answers1

3

There are a few issues with your code.

  1. if (input$dynamic == mydata1$distributor_name) is creating a logical vector, yet if requires its comparison to be length 1. This does produce results, but even if it is doing what you ultimately need, it is really bad practice to do this, and will almost certainly fail miserably in the future (when you least expect it). I suggest you accept that if must always be length-1, and move on; there are other conditional methods that take longer vectors.

  2. You are creating a ggplot and discarding it before moving on. This is indicative of iterative build processes, and not a problem per se in the final execution, it is inefficient at best.

  3. Your conditions were checking for equality but your checkbox group allowed for multiple selections; you should be using %in% instead of ==.

Some suggested improvements, though your code is not "bad" in these senses:

  1. Typically this type of app is written with exactly two arguments because it becomes a hard to manage all of the if/thens and state variables. I prefer to start from the beginning with a mindset of "2 or more", meaning a potentially arbitrary number of options. This of course, leads to number 6, ...

  2. Calling checkboxGroupInput twice with barely-different arguments is a bit repetitive, you can reduce your code significantly. This doesn't speed it up, but it makes it much easier to read and maintain.

  3. Based on my choice to allow arbitrary "column-wise" arguments (since my data here has several more), some of them are not discrete, so I'm going to use the dynamic UI in an interesting way: produce either checkboxes or a slider input. This produces some slight problems later, for which I do not have an elegant solution, but I do have a functional one.

Additionally, some shiny recommendations:

  1. Use reactivity smartly. This involves things like the use of req(input$dynamic) to make sure that $dynamic is "truthy" (initialized, non-NULL, etc) before that block executes. It makes transitions when switching other larger items significantly smoother.

  2. Be wary of blocks that refresh crazily. For example, if B depends on A, and C depends on both A and B, it is possible that when A changes, C will refresh because of its dependency on A, and then B will update (due to A), causing C to refresh again. How do you know if there is dependency? Look for the top-level variables (input$...) and reactive variables (e.g., filtered() here). Where this is a problem, use isolate(A) in C. This is why I use isolate(input$input_type).

  3. I added another layer of reactivity, choosing to have a reactive block that does nothing other than filter the data. This does not add much in this constrained example, but most apps use the filtered data in more than one UI element.

Since I don't have enough of your data to really do anything meaningful, I'll use mtcars. There are five variables that are "discrete" (cyl, vs, am, gear, and carb), all others are continuous. Because of the two types, I have the two types of delectors: checkboxGroupInput and sliderInput (with two ends).

Some global variables, making other areas of the code just a little clearer. Specifically, colors is a way I updated your if/then blocks to account for per-column coloring. Obviously this random method is silly and simpler ways almost always exist (based on your individual needs), but I went big early.

mydata <- mtcars
depvar <- "mpg"

avail_wise <- setdiff(colnames(mydata), depvar)
avail_wise <- setNames(avail_wise,
                       paste0(avail_wise, "-wise"))
set.seed(20180307)
# random fill/color assignments
colors <- data_frame(
  field = avail_wise,
  fill = sample(palette(), length(avail_wise), replace=TRUE),
  color = sample(palette(), length(avail_wise), replace=TRUE)
)
str(colors)
# de-magic-constant something later in the code
checkboxes_max_levels <- 10 # an arbitrary number, seems reasonable

I opted to move the plot to a second row. This is just aesthetic, and you can play with your layout.

library(shiny)
library(ggplot2)

ui <- fluidPage(
  theme = "bootstrap.css",
  titlePanel("Hello User"),
  fluidRow(
    column(3, wellPanel(
      selectInput("input_type", "Input type",
                  choices = avail_wise, selected = avail_wise[1] )
    ) ),
    column(9, wellPanel( uiOutput("ui") ))
  ),
  fluidRow(
    column(12, plotOutput("dynamic_value") )
  )
)

Lots of liberty taken here. Four big "blocks", for output$ui, filtered() data set, filteredcolors() ancillary dataset (could easily be reduced/improved), and output$dynamic_value (the plot).

Server <- function(input, output) {
  output$ui <- renderUI({
    req(input$input_type)
    choices <- sort(unique(mtcars[[input$input_type]]))
    if (is.factor(choices) || is.character(choices) || length(choices) < checkboxes_max_levels) {
      checkboxGroupInput("dynamic", paste0(input$input_type, "-wise"),
                         choices = choices, selected = choices[1],
                         inline = TRUE)
    } else {
      shiny::sliderInput("dynamic", paste0(input$input_type, "-wise"),
                         min = min(choices), max = max(choices),
                         value = round(quantile(choices, c(0.25,0.75)), 1))
    }
  })
  filtered <- reactive({
    req(input$dynamic)
    col <- filteredcolors()
    it <- isolate(input$input_type)
    if (is.character(input$dynamic)) {
      # checkboxGroupInput
      ind <- mtcars[[it]] %in% input$dynamic
    } else {
      # sliderInput
      ind <- input$dynamic[1] <= mtcars[[it]] & mtcars[[it]] <= input$dynamic[2]
    }
    mtcars[ind,,drop=FALSE]
  })
  filteredcolors <- reactive({
    dplyr::filter(colors, field == input$input_type)
  })
  output$dynamic_value <- renderPlot({
    req(filtered())
    col <- filteredcolors()
    ggplot(filtered(), aes_string(depvar)) +
      geom_histogram(fill=col$fill, col=col$color)
  })
}

shinyApp(ui = ui, server = Server)

simple shiny example with checkboxes

simple shiny example with slideinput

EDIT

This can literally be done with any dataset that has a display-able variable. I've modified it slightly, so grab all of this (I tweaked several small pieces.)

All you have to do is assign mydata and then choose a column name and assign that string to depvar. Such as any of the following:

mydata <- mtcars
depvar <- "mpg"

# doesn't provide discrete variables, but ...
mydata <- iris
depvar <- "Sepal.Length"

# very interesting, histograms are actually meaningful
mydata <- diamonds
depvar <- "price"

mydata <- read.table(text='distributor_name    outlet_type   total_sales
abc                    pooj          120
def                    alkr           345
ghi                    mfjc           266
jkl                    zlwh           595', header=TRUE)
depvar <- "total_sales"

Take any one of these assignment pairs and replace the two lines at the top of this app, and you'll have a different data app.

r2evans
  • 141,215
  • 6
  • 77
  • 149
  • this helps but i am getting confused now. how do i put in my sample data? can you give an example? my data is in an excel file . – SSV Mar 08 '18 at 08:38
  • You had a CSV file before, not an excel file, but ... just use `read.csv` as you had in your code, and then use that variable in place of `mtcars` in my code. My answer is much more of an example for you to adapt, not one you can use verbatim, since there was not enough sample data in your question to form it perfectly. – r2evans Mar 08 '18 at 08:40
  • sorry, it was my mistake, it is a csv file. and i meant, i wanted to provide a sample data set, hence, how to put in the sample data in here. to show you. i will try using the example and build on it. – SSV Mar 08 '18 at 08:45
  • Literally all you should need to do is (a) `mydata <- read.csv(...)`; (b) change all `mtcars` to `mydata` or whatever you want to name it; (c) change the two instances of `mpg` (one a quote, one a literal) to the column name you want displayed in the histogram. That is all. Otherwise, this is completely data-agnostic. (I just tested it with `mtcars <- iris` and changed `mpg` to `Sepal.Length`, so it can be even easier if you want your data named `mtcars`.) – r2evans Mar 08 '18 at 09:36
  • got the graph for 2 of the entities. for the rest of them, it says "factors are not allowed". i am trying to plot graph for total sales (number) v/s distributor (string) – SSV Mar 08 '18 at 10:05
  • the trouble is all because my data set is not familiar. how do i show you my dataset? – SSV Mar 08 '18 at 10:15
  • Good reproducible examples: https://stackoverflow.com/q/5963269/3358272 – r2evans Mar 08 '18 at 16:13
  • It sounds like your data has `factor`s, and there are more than 10 levels in at least one of them. This is a great example of how my "[magic constant](https://stackoverflow.com/a/47902/3358272)" worked fine in my example but not in your dataset. It's been corrected/documented, and the check for `factor` has been added (which should mitigate your latest error). – r2evans Mar 08 '18 at 17:50
  • what does "Aesthetics must be either length 1 or the same as the data (72): x" mean? I am getting an error like that.. – SSV Mar 10 '18 at 05:09
  • It sounds like the variable you are trying to use for one of `ggplot`'s aesthetics (within `aes`, or an argument to a `geom_*` or `stat_*`) is not based on the same data as going towards the plot. Perhaps see https://stackoverflow.com/questions/44403362/ggplot-error-aesthetics-must-be-either-length-1-or-the-same-as-the-data-10, https://github.com/tidyverse/ggplot2/issues/507, or https://github.com/tidyverse/ggplot2/issues/1366. – r2evans Mar 10 '18 at 06:18
  • i saw the above mentioned posts. i tried show.legened= FALSE but no luck. i changed the arguments in aes_string from aes_string(depvar) to aes_string(mydata, depvar) and now an error saying "Unknown input:data.frame" pops. . – SSV Mar 10 '18 at 07:29
  • That error should not be coming from `aes_string(depvar)`. What version of `ggplot2` do you have installed? I did this with 2.2.1. Are you saying you get that error with my code, unaltered? – r2evans Mar 10 '18 at 16:28
  • i too have the version 2.2.1 . with the same code as yours, i get the graphs, with slight changes like data.frame instead of data_frame and including library(ggplot2).. otherwise, when i change "mydata" and "depvar", i get the aesthetics error and unknown data frame error – SSV Mar 12 '18 at 04:14
  • Smr, I don't know exactly what your error is. Even with the exact error string, I don't have your code (unless it's mine above) *and* any data you are using to reproduce the error. I can only reassert that it sounds like a factor problem; have you tried `data.frame(..., stringsAsFactors=FALSE)`? The `factor` can work well (esp with ggplot), but it can have some limitations, too. – r2evans Mar 14 '18 at 06:23
  • i have used the code exactly as yours, except for changing the mydata and depvar. in my code, depvar is a 3 digit number and mydata is a csv file . that csv file 8 columns, 3 columns are numbers and others are characters. also, i tried using stringAsFactors, but doesn't seem to help. i need to improve my basics. any suggestions on that? – SSV Mar 14 '18 at 07:33
  • Ok, now that's a problem. It's a bad idea to have the column name of a data.frame completely as a number; it might work in some areas, but (like here) anything else really should be avoided. Does the frame actually have a 3-digit number as a header? I suggest you change it (using `colnames` or `names`): start with a letter, contain only alphanumeric, perhaps with one of `._`. See [`?make.names`](https://stat.ethz.ch/R-manual/R-devel/library/base/html/make.names.html) for legal names. – r2evans Mar 14 '18 at 15:07
  • r2evans No. the name of the column is a string. depvar in mine is a column named Tto which is a column of numbers – SSV Mar 14 '18 at 15:31
  • Ok, that wasn't clear. If you are able to post some of your data, here's what I suggest: after you read your data, do `mydata <- head(mydata,n=20)`, and then make sure the error remains. Then add the output from `dput(mydata)` to your question. (The reason for you truncating and running it is to make sure that the error you see now remains with the partial data.) – r2evans Mar 14 '18 at 15:37
  • i tried executing the changes, when i take less rows, i am getting proper output for a few drop down options. the error goes off when data is truncated – SSV Mar 15 '18 at 05:48
  • Smr, sorry, I cannot think of a way to help if I cannot see your data. If you can make the csv file available for me to briefly test, I can try to help, but otherwise I cannot reproduce your problem. – r2evans Mar 15 '18 at 16:35
  • It would be better if you used `dput` to post your data, as classes can be hidden within `character` representations. (For example, post the output of `dput(head(x,n=10))`. – r2evans Mar 16 '18 at 15:01
  • this is the output of dput(head(x, n = 7)). – SSV Mar 19 '18 at 11:56
  • this is the output for dput(7) – SSV Mar 21 '18 at 05:39
  • The output from `dput` is rarely usable in comments, and your last two comments only suggest that something is coming but I never see anything. You should still be able to edit your question if you want to add it there. – r2evans Mar 21 '18 at 06:15
  • i have edited my first question. like, initially when i asked the question, in that part, i have displayed the 7 sample inputs of my dataset. sorry for the confusion. – SSV Mar 21 '18 at 08:48
  • I have no idea what you are talking about, this question has no such editing. – r2evans Mar 21 '18 at 16:17
  • i couldn't post a comment, so i have just given "answer your question" and put a snapshot and the output of dput below in a different post – SSV Mar 22 '18 at 04:37
  • Smr, your sample data (which should be *in your question*, not posted as an answer ... since it is not one) doesn not produce any error for me. Are you telling me that the 8 rows of data you provided give an error when you run it with my code above? ***Please***, edit your question above, add the `object1 <- ...` code below, add the exact text of the error you get on the console, and either delete your answer below or, once we've figured out the problem, add that to your "solution". (Answers, self or otherwise, are not meant to expand on the question.) – r2evans Mar 31 '18 at 20:34
  • no, this data doesn't give an error, but as the number of rows increases, the error occurs. i have made the changes now – SSV Apr 02 '18 at 09:27
  • i have edited my question and posted snapshot of the errors i get when i use a huge csv file.. also, when there are less number of rows (like 8 or so), i don't get any errors.. – SSV Apr 05 '18 at 07:04
  • That error can only come from `ggplot` and friends, and I can only reproduce that error when `col$color` is empty. This means something in the `filteredcolors` is wrong, and based on what it is doing, it means there is something else up with your data or code that I have not seen. Do this: change it to `filteredcolors <- reactive({ out <- dplyr::filter(colors, field == input$input_type); if (! isTRUE(nrow(out) == 1)) {browser();1;}; out })`, reproduce the error, and try to figure out why `out` is not one row. – r2evans Apr 05 '18 at 16:39
  • (Actually, I can reproduce that error when `col` is more or less than one row, not just empty. However, looking at `filteredcolors`, I don't see how it could be more than one row unless you've changed how `colors` is created.) – r2evans Apr 05 '18 at 16:51