The aim of this exercise is to allow users to compare two different models based on their inputs. To do this, I have created an action button that asks users to specify their base model, and a reset button that takes the dataset back to before the baseline was added. The "base" logical determines whether the user wishes to include the base or not.
Once the add baseline actionbutton is clicked, the current state of the data.frame is saved and grouping variable is renamed with "baseline" added before it (using paste). Users can select a different model which renders in comparison to this static base.
For some reason, I cannot get the observe event to change the dataset. The observe event creates the baseline dataset fine (tested with print() ), however, the if() function does not alter "data" and therefore stops the base added to the ggplot. The code is written like this for two reasons. 1) by including the if() function after the observe event, any further changes to data only changes "data", it then gets added to the unchanged baseline data. 2) Also allows for the creation of the reset button which simply resets the data.frame to before the rbinding took place.
This small issue has infuriated me and I cannot see where I am going wrong. Cheers in advance for any help people can provide. There are simplier ways to do this (open to suggestions), however, the iris data is only an example of the function, and the actual version is more complex.
library("ggplot2")
if (interactive()) {
ui <- fluidPage(
selectInput("rows", label = h3("Choose your species"),
choices = list("setosa", "versicolor", "virginica")
),
actionButton("base", "Create baseline"),
actionButton("reset", "Reset baseline"),
plotOutput(outputId = "plot")
) # close fluid page
server <- function(input, output) {
output$plot <- renderPlot({ # create plot
base <- "no" # create baseline indicator which we can change once the observeevent below is changed
data <- iris
data <- iris[which(data$Species == input$rows),] # Get datasubset based on user input
observeEvent(input$base, { # If base is Pressed, run code below:
baseline <- data # Make Baseline Data by duplicating the users' specification
baseline$Species <- paste("Baseline",
data$Species, sep = "_") # Rename the grouping variable to add Baseline B4 it
base <- "yes" # Change our indicator of whether a baseline had been made to yes
}) # Close observe Event
observeEvent(input$reset, {
base <- "no" # This is placed before the rbind so that if we want to reset it will stop the merging of the two dataframes before it happens.
})
if (base == "yes") {
data <- rbind(data, baseline) # Run once the observe event has changed baseline to yes.This is kept seperatel that way any subsequent changes to data will not effect
# the final data. This command will simple add the base onto the changed "data" before plotting
}
observeEvent(input$reset, {
base <- "no"
})
ggplot(data, aes(x=Petal.Width, y = as.numeric(Sepal.Width), colour = Species)) + # variable = each dataset selected, value = respective values for that model
labs(x="Hypothetical X", y="Hypothetical X") +
geom_line()
}) # Close Render Plot
} # Close Serve Function
shinyApp(ui, server)
}
EXAMPLE TWO WITH REACTIVE OBJECT
library(shiny)
library(ggplot2)
library("tidyr")
library("dplyr")
library("data.table")
# Lets make a fake dataset called "Data". Has 4 variable options and
the Ages each data point relates to.
Ages <- 1:750
Variable1 <- rnorm(n=750, sd = 2, mean = 0)
Variable2 <- rnorm(n=750, sd = 1, mean = 2)
Variable3 <- rnorm(n=750, sd = 8, mean = 6)
Variable4 <- rnorm(n=750, sd = 3, mean = 3)
Data <- as.data.frame(cbind(Ages, Variable1, Variable2, Variable3,
Variable4) )
### UI
ui <- fluidPage(
checkboxGroupInput(inputId = "columns",
label = h4("Which Variables would you like in your
model?"), # Input Checkbox
choices = c("Variable1", "Variable2", "Variable3",
"Variable4")),
plotOutput(outputId = "plot"),
# Lets have our plot
actionButton("base", "Create baseline"),
# Baseline action
actionButton("reset", "Reset baseline") # Reset Action
) # Close UI
server <- function(input, output) {
output$plot <- renderPlot({
validate(need(!is.null(input$columns), 'Please tick a box to show a
plot.')) # Place a please choose columns for null input
data <- gather(select(Data, "Ages", input$columns), variable, value, -
Ages) ## Just doing a little data manipulation to change from wide to
long form. This allows for calculations down the track and easier
plotting
# Now we can modify the data in some way, for example adding 1. Will
eventually add lots of model modifications here.
data$value <- data$value + 1
rVals <- reactiveValues() # Now we create the reactive
values object
rVals[['data']] <- data # Making a reactive values
function. Place Data as "data".
observeEvent(input$base,{
baseline <- data
baseline$variable <- paste("Baseline",
baseline$variable, sep = "_")
# Rename Variables to Baseline preamble
rVals[['baseline']] <- baseline
# Put the new data into the reactive object under "baseline"
})
observeEvent(input$reset,{ # Reset button will wipe the
data
rVals[['baseline']] <- NULL
})
if(!is.null(rVals[['baseline']])) # if a baseline has been .
created, then
{rVals[['final']] <- bind_rows(rVals[['data']], rVals[['baseline']])
# Here we can simply bind the two datasets together if Baseline exists
} else {rVals[['final']] <- rVals[['data']]}
# Otherwise we can use keep it as it is
## Make our Plot !
ggplot(rVals[['final']], aes(x=Ages, y = as.numeric(value), colour =
variable)) + # variable = each dataset selected, value = respective
values for that model
labs(x="Age", y="value") +
geom_line()
}) ## Close the render plot
} ## Close the server
shinyApp(ui, server)
You have observer inside reactive expression, i have seen this causing problems on number of occasions when i was correcting shiny code. Create reactive expression (your plot function) and observers only to specify which is the baseline value of species (character string) then feed this to filtering data inside the plot function:
library(shiny)
library(ggplot2)
ui <- fluidPage(
selectInput("rows", label = h3("Choose your species"),
choices = list("setosa", "versicolor", "virginica")
),
actionButton("base", "Create baseline"),
actionButton("reset", "Reset baseline"),
plotOutput(outputId = "plot")
) # close fluid page
server <- function(input, output) {
rVals = reactiveValues()
rVals[['data']] = iris
rVals[['baseline']] = NULL
output$plot <- renderPlot({
# here we duplicate table to manipulate it before rendering
# the reason for duplicate is that you dont want to affect your
# base data as it may be used elsewhere
# note that due to R's copy-on-write this may be expensive operation and
# have impact on app performance
# in all cases using data.table package is recommended to mitigate
# some of the CoW implications
render.data = rVals[['data']][rVals[['data']][['Species']] %in% c(rVals[['baseline']],input$rows),]
# here manipulate render.data
# and then continue with plot
ggplot(data=render.data,
aes(x=Petal.Width, y = as.numeric(Sepal.Width), colour = Species,group=Species)
) +
labs(x="Hypothetical X", y="Hypothetical X") +
geom_line()
})
observeEvent(input$base,{
rVals[['baseline']]=input$rows
})
observeEvent(input$reset,{
rVals[['baseline']]=NULL
})
}
shinyApp(ui, server)
Related
I'm struggling to update a reactive variable, that is created with eventReactive(), in an observeEvent() with new data.
The background is following: I have a data.frame df with some variables (x and y) and number of observations depending on the selected city (created randomly for this example).
x and y are initialized with zeros.
Because I need to further process df, I pass df to city_df in an eventReactive().
So far, so good. Next, I want to add new data to city_df. The computation of this new data is dependent on the "compute" actionButton (input$compute), wherefore I update city_df in an observeEvent(). I manage to read the data stored in city_df, but I am struggling to overwrite its content.
Actually, I am a bit unsure if this is possible at all, but I hope that some of you could give me a hint on how to update the reactive variable city_df with the new data in this observeEvent() and have its output evaluated in the app(?).
library(shiny)
# global variables
cities <- c("Nairobi", "Kansas", "Uppsala", "Sangon", "Auckland", "Temuco")
# ui
ui <- fluidPage(
fluidPage(
fluidRow(
column(2,
selectInput("city", "Select city",
choices = cities,
selected = sample(cities,
size = 1)
),
actionButton("compute",
"Compute")),
column(8,
verbatimTextOutput("the_city"))
))
)
# server
server <- function(input, output, session) {
# create variable
city_df <- eventReactive(input$city, {
len <- round(runif(1, 20, 50), 0)
df <- data.frame(city = rep(input$city, len))
# initialize x and y with zeros
df <- cbind(df,
data.frame(x = rep.int(0, len),
y = rep.int(0, len)))
})
output$the_city <- renderText({
paste(city_df())
})
observeEvent(input$compute, {
# grab data
test <- city_df()
# compute new data
test$x <- runif(dim(test)[1], 11, 12)
test$y <- runif(dim(test)[1], 100, 1000)
# and how to send this values back to city_df?
})
}
# run app
shinyApp(ui, server)
The actual app is far more complex--so forgive me if this MWE app seems a bit overly complicated to achieve this usually simple task (I hope I managed to represent the more complex case in the MWE).
Instead of a data.frame, I am parsing layers of a GeoPackage and append some variables initialized with zeros. The selected layer is displayed in a Leaflet map. On pressing the "compute" button, a function computes new data that I wish to add to the layer to then have it displayed on the map.
The alternative solution I have on mind is to write the new data to the GeoPackage and then, reread the layer. However, I would appreciate if I could avoid this detour as loading the layer takes some time...
Many thanks :)
Rather than using an eventReactive, if you use a proper reactiveVal, then you can change the value whenever you like. Here's what that would look like
server <- function(input, output, session) {
# create variable
city_df <- reactiveVal(NULL)
observeEvent(input$city, {
len <- round(runif(1, 20, 50), 0)
df <- data.frame(city = rep(input$city, len))
# initialize x and y with zeros
df <- cbind(df,
data.frame(x = rep.int(0, len),
y = rep.int(0, len)))
city_df(df)
})
output$the_city <- renderText({
paste(city_df())
})
observeEvent(input$compute, {
# grab data
test <- city_df()
test$x <- runif(dim(test)[1], 11, 12)
test$y <- runif(dim(test)[1], 100, 1000)
city_df(test)
})
}
So calling city_df() get the current value and calling city_df(newval) updates the variable with a new value. We just swap out the eventReactive with observeEvent and do the updating ourselves.
I built a shiny dashboard, which takes an input file (as reactive) and creates some plots based on that file. As I did not want to rewrite all the code for barplots, histograms etc again and again, I created different functions for plotting bars, histograms etc.
As an input these functions take processed data. Usually that means that I take my raw data (stored in an reactive variable), manipulate some values and create some kind of cross tabulated dataframe, which is passed to the plotting function.
Everything works fine, except that the plots are not updated, if I change my input data. The reason for that seems to be that I first process my reactive data and then pass it to my function. Apparently one has to use the reactive variable in direct context with/inside the plot function to make the plot reactive too.
Before I start re-writing my dashboard (an option that I really don't like), I wanted to ask if somebody knew an easy workaround to pass processed reactive variables to functions and still re-evaluate these functions, if the reactive value changes?
As my code works, there is no need for a minimal example, but to make it easier to understand my problem, here is some kind of pseudo code
# read selected xlsx file
dat <- shiny::reactive({
readxl::read_xlsx(path=input$selected_file$datapath)
})
# function to plot data
plot_bar <- function(dat,
.x,
.y){
# plot data
plot(data=dat,x=.x,y=.y)
}
# call plot_bar
plot_bar(dat=dat() %>%
dplyr::count(age),
.x=age,
.y=n)
As Ronak Shah mentioned I might have been a bit too lazy not sharing a reproducible example. Sorry for that. I was hoping that plain text would do the trick as it's hard to keep it minimal with dashboards :D
Anyways, here is some reproducible code. I hope this helps to clearify the problem.
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("blupp"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
selectInput(inputId="sel_tibble",
label="select tibble",
choices=c("test1","test2"))
),
# Show a plot of the generated distribution
mainPanel(
column(width=4,
plotOutput(outputId="barplot1")),
column(width=4,
plotOutput(outputId="barplot2")),
column(width=4,
plotOutput(outputId="barplot3"))
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
# some data
dat_list <- list(test1=dplyr::tibble(X=1:10,
Y=10:1,
GRP1=sample(LETTERS[1:2],
size=10,
replace=T),
GRP2=sample(LETTERS[5:6],
size=10,
replace=T)),
test2=dplyr::tibble(X=101:1000,
Y=1000:101,
GRP1=sample(LETTERS[1:2],
size=900,
replace=T),
GRP2=sample(LETTERS[5:6],
size=900,
replace=T)))
# Reactive: change between datasets (should affect plots)
dat <- reactive({
input$sel_tibble
res <- dat_list[[input$sel_tibble]]
return(res)
})
# Functions
# passing processed reactive (plot won't change)
plot_bar1 <- function(dat,
.x,
.y,
id){
# NSE
.x <- rlang::enquo(.x)
.y <- rlang::enquo(.y)
# Plot Date
output[[id]] <- renderPlot({
dat %>%
ggplot2::ggplot(ggplot2::aes(x=!!.x,y=!!.y)) +
ggplot2::geom_col()
})
}
# passing reactive and processing inside function (plot changes)
plot_bar2 <- function(dat,
.x,
id){
# NSE
.x <- rlang::enquo(.x)
# Plot Date
output[[id]] <- renderPlot({
dat() %>%
dplyr::count(!!.x) %>%
ggplot2::ggplot(ggplot2::aes(x=!!.x,y=n)) +
ggplot2::geom_col()
})
}
# Output
plot_bar1(dat=dat() %>%
dplyr::count(GRP1),
.x=GRP1,
.y=n,
id="barplot1")
plot_bar1(dat=dat() %>%
dplyr::count(GRP2),
.x=GRP2,
.y=n,
id="barplot2")
plot_bar2(dat=dat,
.x=GRP1,
id="barplot3")
}
# Run the application
shinyApp(ui = ui, server = server)
I'm not sure your way of program in shiny is wrong, but for me is odd having functions creating output values directly, and specially having functions defined in the server block. Also try to use different names for the data structures you're working with and the reactive functions you create.
I modified your code with my own practices and it works as you expected.
My advise, keep the outputs defined by name nor dynamically named, your functions best declared outside server function, and if you need to add objects dynamically use removeUI and insertUI on your server code.
Working code
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("blupp"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
selectInput(inputId="sel_tibble",
label="select tibble",
choices=c("test1","test2"))
),
# Show a plot of the generated distribution
mainPanel(
column(width=4,
plotOutput(outputId="barplot1")),
column(width=4,
plotOutput(outputId="barplot2")),
column(width=4,
plotOutput(outputId="barplot3"))
)
)
)
dat_list <- list(test1=dplyr::tibble(X=1:10,
Y=10:1,
GRP1=sample(LETTERS[1:2],
size=10,
replace=T),
GRP2=sample(LETTERS[5:6],
size=10,
replace=T)),
test2=dplyr::tibble(X=101:1000,
Y=1000:101,
GRP1=sample(LETTERS[1:2],
size=900,
replace=T),
GRP2=sample(LETTERS[5:6],
size=900,
replace=T)))
# Define server logic required to draw a histogram
plot_bar1 <- function(dat,
.x,
.y,
id){
# NSE
.x <- rlang::enquo(.x)
.y <- rlang::enquo(.y)
# Plot Date
return(
dat %>%
ggplot2::ggplot(ggplot2::aes(x=!!.x,y=!!.y)) +
ggplot2::geom_col()
)
}
plot_bar2 <- function(dat,
.x,
id){
# NSE
.x <- rlang::enquo(.x)
# Plot Date
return(
dat %>%
dplyr::count(!!.x) %>%
ggplot2::ggplot(ggplot2::aes(x=!!.x,y=n)) +
ggplot2::geom_col()
)
}
server <- function(input, output) {
# some data
# Reactive: change between datasets (should affect plots)
dat <- reactive({
#input$sel_tibble
res <- dat_list[[input$sel_tibble]]
print("data updated")
return(res)
})
# Functions
# passing processed reactive (plot won't change)
output$barplot1 <- renderPlot({
plot_bar1(dat=dat() %>%
dplyr::count(GRP1),
.x=GRP1,
.y=n,
id="barplot1") })
output$barplot2 <- renderPlot({
plot_bar1(dat=dat() %>%
dplyr::count(GRP2),
.x=GRP2,
.y=n,
id="barplot2")
})
output$barplot3 <- renderPlot({
plot_bar2(dat=dat(),
.x=GRP1,
id="barplot3")
})
# passing reactive and processing inside function (plot changes)
}
# Output
}
# Run the application
shinyApp(ui = ui, server = server)
I am having an issue with using Plotly on my shiny server. I have a clustering app where I bring in a data file and you are able to perform two variable and three variable clustering. The first tab is the two variable clustering. I use ggplot() to create the plot and then using Plotly's ggplotly() function to make it a plotly object to enable interactivity. This renders fine on the app's page.
The issue here is when plotting the three variable clustering. Instead of ggplot() and then ggploty(), I use Plotly's function plot_ly(). This allows me to pass an x, y, and z variable. It works like a charm on my local machine, but when on the shiny server I get this error Error: An error has occurred. Check your logs or contact the app author for clarification. I open my logs and do a hard refresh of the app, no logs are showing. My package versions are the same for both my shiny server and my local machine. The error is not every telling here and I have tried to google around but I am getting no where.
Here is the code:
server.R
## Needed Lib's
library(shiny)
library(ggplot2)
library(plotly)
library(DT)
library(cluster)
## Initiate the severer
shinyServer(
function(input, output, session) {
## Display the text that describes why to use this app/method for grouping
output$text <- renderText({
## Let the user know which tab they have selected
{paste0("You are viewing the \"", input$ClusterChoice, "\"")}
})
## Display the text that describes why to use this app/method for grouping
output$why_text <- renderText({
## Let the user know which tab they have selected
"This is why"
})
# Variable #1
output$varselect1 <- renderUI({
selectInput("var1", label = "Select first variable for clustering:",
choices = names(dataset()), selected = names(dataset())[1])
})
# Variable #2
output$varselect2 <- renderUI({
selectInput("var2", label = "Select second variable for clustering:",
choices = names(dataset()), selected = names(dataset())[2])
})
## Clustering with third variable
# Variable #1
output$varselect3 <- renderUI({
selectInput("var3", label = "Select third variable for clustering (only works in Multiple Variable Tiering Tab):",
choices = names(dataset()), selected = names(dataset())[3])
})
## Read in the data
dataset <- reactive({
infile <- input$datafile
if (is.null(infile)) {
return(NULL)
}
else {read.csv(infile$datapath)}
})
## Compute the K means algo
compute_kmeans <- reactive({
# Choose between simple K means or a more complex K means with third variable
if (input$ClusterChoice == 'Two Variable Tiering') {
data <- subset(dataset(), select = c(input$var1, input$var2))
# Scale the data. Using "standardized" here. This will center and then scale the data
#data <- scale(data, center = TRUE)
# Change some columns names
colnames(data) <- c('x', 'y')
data <- na.omit(data)
# Set the seed
set.seed(111)
# Cluster
Kclust <- kmeans(data, input$k)
# Save results to a list
kmean.result <- list(kmean.result = data.frame(data, cluster = as.factor(Kclust$cluster)))
return(kmean.result)
}
# K means with third variable
else { (input$ClusterChoice == 'Multiple Variable Tiering')
three_var_data <- subset(dataset(), select = c(input$var1, input$var2, input$var3))
# Scale the data. Using "standardized" here. This will center and then scale the data
#three_var_data <- scale(three_var_data, center = TRUE)
three_var_data <- na.omit(three_var_data)
# Set the seed
set.seed(111)
# Cluster
Kclust <- kmeans(three_var_data, input$k)
kmean.result <- list(kmean.result = data.frame(three_var_data, cluster = as.factor(Kclust$cluster)))
return(kmean.result)
}
})
## Create a dataframe of the K means & K means with third variable results
kmeans_results <- reactive({
# For only two variables
if (input$ClusterChoice == 'Two Variable Tiering') {
# Call the method that computes the k means
data <- compute_kmeans()
# Save the results into a df
results <- data$kmean.result
results_df <- data.frame(results)
colnames(results_df) <- c(input$var1, input$var2, 'Grouping')
return(results_df)
}
# For more than two variables
else { (input$ClusterChoice == 'Multiple Variable Tiering')
# Call the method that computes the k means with third variable
three_var_data <- compute_kmeans()
# Save the results into a df
three_var_results <- three_var_data$kmean.result
three_var_results_df <- data.frame(three_var_results)
colnames(three_var_results_df) <- c(input$var1,
input$var2,
input$var3,
'Grouping')
return(three_var_results_df)
}
})
## Results of each K means & K means with third variable
master_results <- reactive({
# Call the method that stores the raw input data
data <- dataset()
# For only two variables
if(input$ClusterChoice == 'Two Variable Tiering') {
# Call the method that stores the K means results
kmean_results <- kmeans_results()
# Merge the K means results with the raw input data
results <- merge(kmean_results, data)
results <- results[!duplicated(results), ]
return(results)
}
# For more than two variables
else { (input$ClusterChoice == 'Multiple Variable Tiering')
# Call the method that stores the K means with third variable results
kmean_three_var_results <- kmeans_results()
# Merge the K means with third variable results with the raw input data
three_var_resutls <- merge(kmean_three_var_results, data)
three_var_resutls <- three_var_resutls[!duplicated(three_var_resutls), ]
return(three_var_resutls)
}
})
## Plot the K means results
output$plot <- renderPlotly({
graphics.off()
pdf(NULL)
# For only two variables
if (input$ClusterChoice == 'Two Variable Tiering') {
# Call the K means results method
results <- master_results()
# Change the x & y variables so we can call it in the tool-tip
x_axis <- results[[input$var1]]
y_axis <- results[[input$var2]]
# Plot
plot <- ggplot(data = results,
aes(x = x_axis,
y = y_axis,
color = Grouping,
name = Markets)) +
geom_point(size = 2) +
ggtitle("Grouping Results") +
labs(x = input$var1, y = input$var2)
# Use Plotly for interactivity
plotly_plot <- ggplotly(plot)
return(plotly_plot)
}
# For more than two variables
else if (input$ClusterChoice == 'Multiple Variable Tiering')
# Call the K means and third variable
three_var_resutls <- master_results()
# Change x, y, & z variables so we can call it in the tool-tip
x_axis <- three_var_resutls[[input$var1]]
y_axis <- three_var_resutls[[input$var2]]
z_axis <- three_var_resutls[[input$var3]]
# Plot (using Plotly for interactivity)
three_var_plot <- plot_ly(three_var_resutls,
x = x_axis,
y = y_axis,
z = z_axis,
color = factor(three_var_resutls$Grouping),
text = ~paste('Markets:', three_var_resutls$Markets,
'<br>Grouping:', three_var_resutls$Grouping)) %>%
add_markers() %>%
layout(title = 'Grouping Results',
scene = list(xaxis = list(title = input$var1),
yaxis = list(title = input$var2),
zaxis = list(title = input$var3)))
return(three_var_plot)
})
## Render the K means and K means with third variable results to a data table
output$cluster_table <- DT::renderDataTable({
# For only two variables
if (input$ClusterChoice == 'Two Variable Tiering') {
# Call results method
results <- master_results()
# Render data table
datatable(results,
# Get rid of row indexes
rownames = FALSE,
# Enable downloading options
extensions = 'Buttons',
options = list(
dom = "Blfrtip",
buttons =
list("copy", list(
extend = "collection",
buttons = c("csv", "excel", "pdf"),
text = "Download")),
lengthMenu = list(c(10, 20, -1),
c(10, 20, "All")),
pageLength = 10))
}
else if (input$ClusterChoice == 'Multiple Variable Tiering') {
# Call results method
results_three_var <- master_results()
# Render data table
datatable(results_three_var,
# Get rid of row indexes
rownames = FALSE,
# Enable downloading options
extensions = 'Buttons',
options = list(
dom = "Blfrtip",
buttons =
list("copy", list(
extend = "collection",
buttons = c("csv", "excel", "pdf"),
text = "Download")),
lengthMenu = list(c(10, 20, -1),
c(10, 20, "All")),
pageLength = 10))
}
})
}
)
ui.r
## Needed Lib's
library(shiny)
library(plotly)
## Start the UI renderer
shinyUI(
pageWithSidebar(
## The TITLE!
headerPanel("Grouping Data Together"),
## This is where the up-loader and drop downs live
sidebarPanel(
fileInput('datafile',
'Choose CSV file',
accept=c('text/csv', 'text/comma-separated-values,text/plain')),
uiOutput("varselect1"),
uiOutput("varselect2"),
uiOutput("varselect3"),
numericInput('k', 'Number of clusters', value = 3, min = 1, step = 1)),
## The main panel where all the shit happens
mainPanel(
textOutput("text"),
tabsetPanel(id = 'ClusterChoice',
tabPanel("Two Variable Tiering", value = 'Two Variable Tiering'),
tabPanel("Multiple Variable Tiering", value = 'Multiple Variable Tiering'),
tabPanel("Why Do It This Way?", value = "Why Do It This Way?")
),
## Description of this app
h2("Description"),
p("This tool's main functionality is to quickly put your data into groups. The file that is being uploaded
should only be the data that you want to group together. For example, if you have data that is broken out
by Markets (DMA) and you want to group those Markets into similar groupings you should upload all data
that you think is important to those groupings. Then via the drop downs you can select the 2 or 3 variables
you think best represents the groupings."),
## The tabs
h2("The Different Tabs"),
p("Tab #1: This tab is only when you want to use 2 variables in your data to create the groupings."),
p("Tab #2: This tab is only when you want to use 3 variables in your data to create the groupings."),
## Instructions for using this app
h2("Instructions"),
p("Please follow these instructions to create your groupings."),
p("1. Upload a data file (.csv) that will be used for making your groups.
The first row in the .csv file should be the column headers of the data.
The first column in the .csv file should be where your data starts.
THERE SHOULD BE NO 'BUFFERS' AROUND YOUR DATA FILE, i.e. empty rows and columns."),
p("2. Pick the variables you want to create the groupings off of via the drop-downs"),
p("3. Indicate the desired number of groups via the last drop-down."),
## The plot instructions
h2("Visualizing Your Grouped Data"),
p("You can download the plot as a .png file by hovering over the plot and selecting the
camera icon in the upper right hand side of the plot."),
# Plotly function that links the UI to the Server
plotlyOutput('plot', height = 700),
## The grouping data table instructions
h2("The Groupings"),
p("Instructions for downloading data:"),
p("1. To download the data use the drop-down below, labeled 'Show entries', to show 'All' entries."),
p("2. Click the 'Download' button and then select the type of file (PDF, excel, csv)."),
p("3. (Optional) You can copy the data that is shown to your clipboard is paste it into an excel/csv document."),
# JS DataTable function that links the UI to the Server
DT::dataTableOutput("cluster_table"))
)
)
All of this code is adapted from Shiny - dynamic data filters using insertUI.
I am currently using R Shiny code that is supposed to allow for the creation of multiple filters (as many as the Shiny server will allow).
Each filter includes a selection of the variable to filter by, the upper bound, lower bound, and whether the values will be filtered by taking only the values between the upper and lower bound (i.e., lwr < x < upr), or the opposite (i.e., x < lwr ∪ x > upr). I have compiled the relevant code into code that is specifically relevant to this question.
The source code (for the simplified code) is below:
library(shiny)
library(ggplot2)
# Column names of file.
logColumns <- names(read.csv("file.csv"))
ui <- fluidPage(
titlePanel("Testing Filters"),
sidebarLayout(
sidebarPanel(
# Data type to display as Y value in graph.
selectInput("display", label = "Data Type", choice = logColumns),
# Button to activate addFilter actions.
fluidRow(
column(6, actionButton('addFilter', "Add Filter")),
offset=6
),
tags$hr(),
# Area to generate new filters.
tags$div(id='filters'),
width = 4
),
mainPanel(
# Displays plot.
plotOutput("distPlot")
)
)
)
server <- function(input, output, session) {
# File to use.
usefile <- reactive({
# Placeholder code, does basic file reading for now.
# Basic (unedited) file format is time (in milliseconds) in first column
# followed by other columns with different types of data, e.g., voltage.
usefile <- read.csv("file.csv", header=TRUE)
usefile$time <- usefile$time / 1000
usefile
})
# Column names of above file.
logNames <- reactive({
names(usefile())
})
# Turns aggregFilterObserver into a reactive list.
makeReactiveBinding("aggregFilterObserver")
aggregFilterObserver <- list()
observeEvent(input$addFilter, {
# Generates unique IDs for each filter.
add <- input$addFilter
filterId <- paste0('filter', add)
colFilter <- paste0('colFilter', add)
lwrBoundNum <- paste0('lowerBound', add)
uprBoundNum <- paste0('upperBound', add)
removeFilter <- paste0('removeFilter', add)
exclusivity <- paste0('exclusivity', add)
# Dictates which items are in each generated filter,
# and where each new UI element is generated.
insertUI(
selector = '#filters',
ui = tags$div(id = filterId,
actionButton(removeFilter, label = "Remove filter", style = "float: right;"),
selectInput(colFilter, label = paste("Filter", add), choices = logNames()),
numericInput(lwrBoundNum, label = "Lower Bound", value=0, width = 4000),
numericInput(uprBoundNum, label = "Upper Bound", value=0, width = 4000),
checkboxInput(exclusivity, label = "Within Boundaries?", value=TRUE)
)
)
# Generates a filter and updates min/max values.
observeEvent(input[[colFilter]], {
# Selects a data type to filter by.
filteredCol <- usefile()[[input[[colFilter]]]]
# Updates min and max values for lower and upper bounds.
updateNumericInput(session, lwrBoundNum, min=min(filteredCol), max=max(filteredCol))
updateNumericInput(session, uprBoundNum, min=min(filteredCol), max=max(filteredCol))
# Stores data type to filter with in col, and nulls rows.
aggregFilterObserver[[filterId]]$col <<- input[[colFilter]]
aggregFilterObserver[[filterId]]$rows <<- NULL
})
# Creates boolean vector by which to filter data.
observeEvent(c(input[[lwrBoundNum]], input[[uprBoundNum]], input[[colFilter]], input[[exclusivity]]), {
# Takes only data between lower and upper bound (inclusive), or
if (input[[exclusivity]]){
rows <- usefile()[[input[[colFilter]]]] >= input[[lwrBoundNum]]
rows <- "&"(rows, usefile()[[input[[colFilter]]]] <= input[[uprBoundNum]])
}
# Takes only data NOT between lower and upper bounds (inclusive).
else{
rows <- usefile()[[input[[colFilter]]]] < input[[lwrBoundNum]]
rows <- "|"(rows, usefile()[[input[[colFilter]]]] > input[[uprBoundNum]])
}
aggregFilterObserver[[filterId]]$rows <<- rows
})
# Removes filter.
observeEvent(input[[removeFilter]], {
# Deletes UI object...
removeUI(selector = paste0('#', filterId))
# and nulls the respective vectors in aggregFilterObserver.
aggregFilterObserver[[filterId]] <<- NULL
})
})
# Filters data based on boolean vectors contained in aggregFitlerObserver
adjusted <- reactive({
toAdjust <- rep(TRUE,nrow(usefile()))
lapply(aggregFilterObserver, function(filter){
toAdjust <- "&"(toAdjust, filter$rows)
})
subset(usefile(), toAdjust)
})
# Creates plot based on filtered data and selected data type
output$distPlot <- renderPlot({
xData <- adjusted()$time
yData <- adjusted()[[input$display]]
curData <- data.frame(xData, yData)
plot <- ggplot(data=curData, aes(x=xData, y=yData)) + geom_point() + labs(x = "Time (seconds)", y = input$display)
plot
})
}
# Run the application
shinyApp(ui = ui, server = server)
My problem is that subsetting via the boolean vectors does not work - i.e., filters simply have no effect whatsoever.
Also, I'm not too sure about the wording and variable names for how the upper and lower bounds should be applied (i.e., the "Within Boundaries?" button and exclusivity variable). If a better (while still concise) wording could be used, I'd appreciate some help with that as well.
Any input is appreciated.
EDIT: After fixing my code with the current answer, I have realized that the code that [the fixed] adjusted() had is not exactly what I wanted, and that I have misunderstood what lapply actually does. I had been trying to compile multiple logical vectors into one, and this was achieved by doing the following:
adjusted <- reactive({
toAdjust <- rep(TRUE,nrow(usefile()))
for (filter in aggregFilterObserver){
toAdjust <- "&"(toAdjust, filter$rows)
}
if (length(toAdjust) == 0){
usefile()
} else {
subset(usefile(), toAdjust)
}
})
Thanks for the help given!
The problem comes from the fact that you never store the result of the filtering. When you define adjusted, the result of lapply is never stored.
# Filters data based on boolean vectors contained in aggregFitlerObserver
adjusted <- reactive({
toAdjust <- rep(TRUE,nrow(usefile()))
tmp <- lapply(aggregFilterObserver, function(filter){
toAdjust <- "&"(toAdjust, filter$rows)
})
if (length(tmp$filter1) == 0) {
return(usefile())
} else {
subset(usefile(), tmp$filter1)
}
})
The condition length(tmp$filter1) == 0 is here to prevent the filtering of all rows when no filter is present.
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)
]3
There are a few issues with your code.
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.
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.
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:
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, ...
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.
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:
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.
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).
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)
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.