R shiny error when using reactive to plot - r

I have this data set:
Area <- c("Mexico", "USA", "USA", "Canada").
Type_of_participants <- c("Doctor", "Doctor", "Engineer", "Dancer".
Salary <- c("4000", "6000", "8000", "5000").
and I am trying to plot the salary base on the user input of Area(level1) and Type_of_participants(level2), but nothing appears. I modified aes to aes_string as I looked up here. Please help me find the error
My Code
`ui <- fluidPage(
titlePanel("Survey Results"),
sidebarLayout(
sidebarPanel(strong("Overview Plot"),
br(),
###1a.Area input
selectInput("selection","Var",
choices = c("Area","Type_of_participants"),
selected = "Area"),
uiOutput("choice_selection")
),
mainPanel(
plotOutput("Overview"))
`server <- function(input, output) {
output$choice_selection <- renderUI({
checkboxGroupInput("baseinput","Detail",
unique(df[,input$selection])
)`
})
dt1 <- reactive({
df %>%
group_by(input$selection,Type) %>%
filter (input$selection %in% input$baseinput) %>%
summarise(avg_salary_by_area = mean(Salary, na.rm = TRUE)) %>%
select(input$selection, Type, avg_Salary_by_area)
})
output$Overview <- renderPlot({
ggplot(data= dt1())+
aes(fill = Type)+
geom_bar(x=input$selection, y = avg_salary_by_area,stat="identity",
position = position_dodge())
The result is I can select the input but can not visualize the plot. The error "unknown column Area or unknow Type of participants
Please help me find the mistake
Thank you
*** Update
Thanks to Mr Flick, I have fixed my code but it still informs error "Object area not found". Please help to advise. Thank you so much
`dt1 <- reactive({
df[df[,input$selection] %in% input$baseinput,] %>%
group_by(input$selection,Type) %>%
summarise(avg_score_by_area = mean(Score, na.rm = TRUE))
})
output$Overview <- renderPlot({
ggplot(data= dt1(),aes_string(x= input$selection,
y = "avg_score_by_area",fill = "Type"))+
geom_bar(stat="identity",
position = position_dodge())`

#Suzie - as mentioned above, it would help if you edited your question with your complete code as you currently have it.
A few things that would help:
Salary should be numeric in your df (or be converted with as.numeric before trying to take the mean
Your reactive expression can use !!as.symbol with input$selection to filter by the string name from df
The plot can use aes_string for the variable names.
Edit:
For further explanation of !!as.symbol, first consider what the result of input$selection. If you use browser() in your shiny code, and inspect what input$selection returns, you will see something like "Area" (it returns a string). But a string would not be appropriate in your filter - it is expecting a symbol that represents a column in your data frame. (A symbol is the name of an object like df or mtcars, etc.)
First, you want to convert a string to a symbol. You can do that either by using as.symbol() or rlang::sym(). You can try this out in your console. If you do as.symbol("df") it would return the symbol df. If you entered eval(as.symbol("df")) it would be the same as just entering df itself (and it would show the contents of your data frame).
The other issue is that tidyverse functions evaluate code expressions in a special context (searching for names within a data frame, for example). In this case dplyr knows that the name Area is in the context of df (one of the column names). This is a complicating factor since arguments are quoted. To address this, you need to unquote (replace a name with its value) with the bang-bang !! operator.
Putting both together you get !!as.symbol().
Of note, varSelectInput is a newer shiny alternative to selectInput that can be considered for use in situations like these.
For more information:
shinymeta special topics
advanced R
library(tidyverse)
library(shiny)
Area <- c("Mexico", "USA", "USA", "Canada")
Type_of_participants <- c("Doctor", "Doctor", "Engineer", "Dancer")
Salary <- c(4000, 6000, 8000, 5000)
df <- data.frame(Area, Type_of_participants, Salary)
ui <- fluidPage(
titlePanel("Survey Results"),
sidebarLayout(
sidebarPanel(strong("Overview Plot"),
br(),
###1a.Area input
selectInput("selection","Var",
choices = c("Area","Type_of_participants"),
selected = "Area"),
uiOutput("choice_selection")
),
mainPanel(
plotOutput("Overview")
)
)
)
server <- function(input, output) {
output$choice_selection <- renderUI({
checkboxGroupInput("baseinput", "Detail", unique(df[,input$selection]))
})
dt1 <- reactive({
df %>%
group_by(Area, Type_of_participants) %>%
filter(!!as.symbol(input$selection) %in% input$baseinput) %>%
summarise(avg_salary_by_area = mean(Salary, na.rm = TRUE))
})
output$Overview <- renderPlot({
ggplot(data = dt1(), aes_string(x = input$selection, y = "avg_salary_by_area", fill = "Type_of_participants")) +
geom_bar(stat="identity", position = position_dodge())
})
}
shinyApp(ui, server)

Related

Subset a data frame in R/Shiny to generate a ggplot2 sf object

A complete ggplot2/Shiny beginner here. I have been searching on Stack and Google for days and could not come up with a decent solution.
Task: to create an interactive leaflet map showing a user-selected column in a long data format (Covid vaccine doses - first, second, and third dose; need shiny to feed this into ggplot2's "data"), which are pre-filtered based on additional user choices (month of the year, age group, type of vaccine administered; these cannot be fed into ggplot2 directly so I need to filter out the data). I am therefore interested in subsetting selected columns (time, age_group, vaccine) based on the values the users select in the input.
I am importing a data frame in .csv which needs to be merged with a sf object later on to match the data with the sf coordinates (supplied by RCzechia).
# Load packages
library(shiny)
library(here)
library(tidyverse)
library(ggplot2)
library(RCzechia)
library(sf)
# Load data
df <- read.csv("data", encoding = "UTF-8")
# load geo-spatial sf data for ggplot
czrep <- republika()
regions <- kraje(resolution = "low")
# Defining UI for the ggplot application
ui <- fluidPage(
titlePanel(),
# Sidebar
sidebarLayout(
sidebarPanel(width = 3,
selectInput("box_time", label = "Month & Year",
choices = sort(unique(df$time)), selected = "",
width = "100%", selectize=FALSE),
selectInput("box_age", label = "Age group",
choices = sort(unique(df$age_group)), selected = "",
width = "100%", selectize=FALSE),
selectInput("box_vax", label = "Type of vaccine",
choices = sort(unique(df$vaccine)), selected = "",
width = "100%", selectize=FALSE),
radioButtons("button_dose", label = "Vaccine dose",
choices = c("First dose" = "first_dose",
"Second dose" = "second_dose",
"Booster" = "booster"))
),
# Displaying the user-defined ggplot
mainPanel(
plotOutput("map")
)))
# Server
server <- function(input, output) {
# select column for ggplot
r_button_dose <- reactive({input$button_dose})
### Subset based on user choices - this is where I tried to create a new data frame (new_df) as a result of subsetting by - see below. ###
# merge the df with the sf object
new_df <- merge(regions, new_df, by.x = "region_id", by.y="region_id")
# transform data set into an sf object (readable by ggplot)
new_df <- st_as_sf(new_df)
})
# Generating the plot based on user choices
output$map <- renderPlot({
ggplot(data = new_df) +
geom_sf(aes_string(fill = r_button_dose(), colour = NA, lwd = 2)) +
geom_sf(data = czrep, color = "grey27", fill = NA) +
scale_fill_viridis_c(trans = "log", labels = scales::comma) +
labs(fill = "log scale") +
theme_bw() +
theme(legend.text.align = 1,
legend.title.align = 0.5)
})
}
# Starting the Shiny application
shinyApp(ui = ui, server = server)
I cannot figure out how to subset the data - I have tried many different things that I found here and on the RStudio community forms.
Here are a couple of things I have already tried:
# used both filter() and subset(); also tried both '==' and '%in%'
new_df %>%
filter(time %in% box_time() &
age_group %in% input$box_age() &
vaccine %in% input$box_vax())
})
#OR#
new_df <- reactive({
df <- df %>%
filter(time %in% box_time() &
age_group %in% input$box_age() &
vaccine %in% input$box_vax())
})
#OR#
new_df <- df
new_df$time <- df[df$time==box_time(),]
new_df$age_group <- df[df$age_group==input$box_age(),]
new_df$vaccine <- df[df$vaccine ==input$box_vax(),]
# I also tried passing them the same way as this example:
r_button_dose <- reactive({input$button_dose})
#OR EVEN#
new_df <- reactive({
new_df <- df
new_df$time <- df[df$X.U.FEFF.year_mo==box_time(),]
new_df$age_group <- df[df$age_group==input$box_age(),]
new_df$vaccine <- df[df$vaccine ==input$box_vax(),]
})
With the latest option, I get the following error - even though they are similar:
Listening on http://127.0.0.1:4092
Warning: Error in $: object of type 'closure' is not subsettable
1: runApp
Warning: Error in $: object of type 'closure' is not subsettable
1: runApp
Warning: Error in as.data.frame.default: cannot coerce class ‘c("reactiveExpr", "reactive", "function")’ to a data.frame
176: stop
175: as.data.frame.default
172: merge.data.frame
168: renderPlot [C:/Users/xyz/Documents/R/example/gg_app.R#78]
166: func
126: drawPlot
112: <reactive:plotObj>
96: drawReactive
83: renderFunc
82: output$map
1: runApp
I don't know what to do - looking for more examples online has not worked. I know that I cannot pass a reactive value directly (even though I am not sure if it is because it returns a logical value). I would be extremely grateful for any tips regarding how to resolve this - thank you!
You can define your reactive dataframe as a reactiveVal:
df_filtered <- reactiveVal(df) ## df being your initial static dataframe
The tricky bit is to treat your reactive dataframe as a function, not an static object:
## works:
df_filtered(df %>% filter(age_group == input$box_age))
renderDataTable(df_filtered()) ## note the parentheses
instead of:
## won't work:
df_filtered <- df %>% filter(age_group %in% input$box_age)
renderDataTable(df_filtered)
finally, wrap it into a reactive expression:
observe({df_filtered(df %>% filter(age_group == input$box_age))
## note: function argument, not assignment operator
output$map <- renderPlot({
df_filtered() %>% ## again: note function (parentheses)
ggplot() # etc.
})
}) %>% bindEvent(input$box_age, input$some_other_picker)
I think you are almost there, slight syntax issue. Note I return the new_df as part of reactive block (essentially a function), and, in renderPlot, I tell 'data' is in essence invocation result of function r_button_dose. You need to modify the fill attribute as I'm not sure what you want it to be filled with
# select column for ggplot
r_button_dose <- reactive({input$button_dose})
### Subset based on user choices - this is where I tried to create a new data frame (new_df) as a result of subsetting by - see below. ###
# merge the df with the sf object
new_df <- merge(regions, new_df, by.x = "region_id", by.y="region_id")
# transform data set into an sf object (readable by ggplot)
new_df <- st_as_sf(new_df)
new_df
})
# Generating the plot based on user choices
output$map <- renderPlot({
ggplot(data = r_button_dose()) +
geom_sf(aes_string(fill = r_button_dose()$region_id, colour = NA, lwd = 2)) +
geom_sf(data = czrep, color = "grey27", fill = NA) +
scale_fill_viridis_c(trans = "log", labels = scales::comma) +
labs(fill = "log scale") +
theme_bw() +
theme(legend.text.align = 1,
legend.title.align = 0.5)
})

Select a variable based on two inputs in Rshiny

I have a dataset with variables for the ID of patients, different tests (MMT), and the treatment.
ID
MMT_II_week15_change
MMT_II_Week20_change
MMT_Tot_week15_change
MMT_Tot_Week20_change
Treatment
As you can see, we have two different tests (MMT_II_change and MMT_Tot_change), for two different timepoints (week15, week20).
What I want is the user to be able to select, first, the test, and then, the timepoint.
In reality, he would be picking just one of the variables, but in two different steps.
Something like:
**Select test:**
MMT_II
MMT_III
**Select timepoint:**
Week15
Week20
And after this, the variable selected would be:
e.g: MMT_II_Week20_change
I though of using regex for this, but it seems quite complicated and coulnd't find of a way of doing it.
Any help really appreciated, as I've been stuck with this for a while.
Would something like this work?
VAR = paste0(test,"_",timepoint,"_change")
...
# then later to use the variable...
.data[[VAR]]
You can wrap the checking of changess occured in a single reactive function in the server section of your code.
uptodateChoice <- reactive({
paste0(input$firstcontrol, "_", input$secondcontrol, "_change")
})
This function will be called once any of the two controls state change.
You can also add any validate(need(...)) checks inside the function if required or simply return() if some conditions are not satisfied.
You can access the string value calling uptodateChoice().
I'm thinking about pivoting the data to longer format, filter it and then pivot again to wider. This way we can filter using filter function directly.
library(tidyverse)
library(shiny)
# create some data
df <- tibble(
ID = 1:5, MMT_II_week15_change = sample(seq(0.01, 0.2, 0.01), 5), MMT_II_week20_change = sample(seq(0.01, 0.2, 0.01), 5),
MMT_Tot_week15_change = sample(seq(0.01, 0.2, 0.01), 5), MMT_Tot_week20_change = sample(seq(0.01, 0.2, 0.01), 5)
)
# pivot wider capturing MMT_* for the first column and the number of week in the second.
df_pivot <- pivot_longer(df, -ID, names_to = c("test", "week"), values_to = "change", names_pattern = "(MMT_.*)_week(\\d+)_change$")
## APP
library(shiny)
ui <- fluidPage(
selectInput("test", "Select Test", choices = unique(df_pivot$test)),
selectInput("timepoint", "Select Timepoint", choices = NULL),
tableOutput("table")
)
server <- function(input, output, session) {
table <- reactiveVal(NULL)
observeEvent(input$test, {
choices <- filter(.data = df_pivot, test == input$test) %>%
{
unique(.$week)
}
updateSelectInput(inputId = "timepoint", choices = choices)
})
# this could also be a reactive.
observe({
table(filter(df_pivot, test == input$test, week == input$timepoint) %>%
pivot_wider(names_from = "test", values_from = "change"))
})
output$table <- renderTable({
table()
})
}
shinyApp(ui, server)

"Error in match: 'match' requires vector arguments in R Shiny

I am trying to create a dashboard using R Shiny from NYC Tree Census 2015. The dashboard should look something like in the picture here > Dashboard in Shiny Picture
My code is mentioned below:
library(shiny)
library(tidyverse)
library(ggplot2)
my_data <- read.csv("/Users/abhikpaul/Documents/Documents/Github/Fiverr/2015_Street_Tree_Census_-_Tree_Data.csv")
ui <- fluidPage(
titlePanel("The Dashboard of Tree Distribution in New York City"),
sidebarLayout(
sidebarPanel(
# Description ----
helpText("In this page you can get information about the tree distribution, status, health conditions, and species rank in New York City. Please choose the borough that you want to check. It may take 10 seconds for the graphics to load. Thank you for your patience!"),
#Input: Check boxes for Boroughs ----
checkboxGroupInput("checkboxInput",
label = "Borough",
choices = list("Bronx",
"Brooklyn",
"Manhattan",
"Queens",
"Staten Island"),
selected = "Bronx"),
),
# Main panel for displaying outputs ----
mainPanel(
# Tabs panel for displaying outputs ----
tabsetPanel(type = "tabs",
#Output: About ----
tabPanel("About",
h3("About this dataset", align = "left"),
p("The dataset displays the information of trees (including health, status, species, etc.) within the five boroughs in New York City. The dataset is organized by NYC parks & Recreation and partner organizations."),
h3("How to make NYC an urban forest?", align = "left"),
p("As a group, we are concerned about planting tree and green environments. Therefore, we will focus on identifying the locations that require more taking care of trees, the top species that have the most number of trees in each borough, the health conditions of those species, and the distribution of trees in each borough."),
HTML("<p>For more information, visit: <a href='https://data.cityofnewyork.us/Environment/2015-Street-Tree-Census-Tree-Data/uvpi-gqnh'>2015 NYC Tree Census</a></p>")
),
#Output: Status ----
tabPanel("Status", plotOutput(outputId = "statusplot")),
)
)
)
)
)
server <- function(input, output) {
my_data <- as_tibble(my_data)
my_data <- my_data[my_data$borough %in% checkboxInput,]
my_data <- data.frame(table(my_data$borough,my_data$status))
my_data <- my_data[apply(my_data!=0, 1, all),]
my_data <- my_data %>%
group_by(Var1) %>%
mutate(Percent = (Freq/sum(Freq) * 100))
output$statusplot <- renderPlot({
ggplot(my_data, aes(fill = Var2, y = Percent, x = Var1)) +
geom_bar(position = "dodge", stat = "identity")
})
}
shinyApp(ui = ui, server = server)
However, while running the app, I am getting an error as mentioned below
Warning: Error in match: 'match' requires vector arguments 50: %in% 47: server [/Users/abhikpaul/Documents/Documents/GitHub/Fiverr/my_app.R#90]Error in match(x, table, nomatch = 0L) : 'match' requires vector arguments
Can someone help me fix this issue as I am a newbie in R Shiny?
Try this
server <- function(input, output) {
output$statusplot <- renderPlot({
my_data <- as_tibble(my_data)
my_data <- my_data[my_data$borough %in% input$checkboxInput,]
my_data <- data.frame(table(my_data$borough,my_data$status))
my_data <- my_data[apply(my_data!=0, 1, all),]
my_data <- my_data %>%
group_by(Var1) %>%
mutate(Percent = (Freq/sum(Freq) * 100))
ggplot(my_data, aes(fill = Var2, y = Percent, x = Var1)) +
geom_bar(position = "dodge", stat = "identity")
})
}

How to make a bar graph in Shiny that relies on checkboxGroupInput to graph specific values

I am trying to make a shiny app that displays different variables of the "Cut" variable (Fair, good, very good...) in a bar graph. I am not totally sure how to do it using the checkboxGroupInput function. I pretty much want it so that if the user selects fair, good, and very good, the bar graph will display only those values. This is my first week with R, any help would be much appreciated.
library(shiny)
library(datasets)
library(tidyverse)
library(ggplot2)
jewl <- diamonds
# User interface ----
ui <- fluidPage(
titlePanel("Diamonds Information"),
sidebarLayout(
sidebarPanel(
helpText("Choose a Cut to Examine"),
checkboxGroupInput("vars", "What cuts would you like to display?", choices = c("Fair",
"Good",
"Very Good",
"Premium",
"Ideal", "All"),
selected = "All"
)
),
mainPanel(plotOutput("plot")
)
)
)
server <- function(input, output) {
output$plot <- renderPlot({
if (input$vars == "All"){
newdata <- group_by(diamonds, cut)
newdata2 <- summarize(newdata, avg = mean(price))
ggplot(data = newdata2) +
geom_col(mapping = aes(x = cut, y = avg))}
#WANT TO ADD SOMETHING HERE
})
}
# Run the app
shinyApp(ui, server)
You can subset your newdata2 to get only those variables selected like this:
# 'which' function does a comparison and returns the indexes
# which meets the conditions (check the subset_ids to see that is a vector of integers (the indexes)
subset_ids <- which(newdata2$cut %in% c('Fair', 'Premium'))
# now, you use your ids to subset the data.frame
# a data.frame can be subsetted like this
# data.frame[rows, columns] (you can leave blank for no subsetting)
newdata3 <- newdata2[subset_ids, ]
# OBS: Columns can be selected by numbers or its name
So in your code you can do like this
output$plot <- renderPlot({
if (input$vars == "All"){
newdata <- group_by(diamonds, cut)
newdata2 <- summarize(newdata, avg = mean(price))
ggplot(data = newdata2) +
geom_col(mapping = aes(x = cut, y = avg))
}
else {
newdata <- group_by(diamonds, cut)
newdata2 <- summarize(newdata, avg = mean(price))
ggplot(data = newdata2[which(newdata2$cut %in% input$vars),]) +
geom_col(mapping = aes(x = cut, y = avg))
}
})
}
Which will cause some warnings, but solves your problem.

Is it possible to achieve Shiny plot interaction with multiple categorical axes?

I'm in the process of creating my first Shiny app that returns a data table when a user interacts with a ggplot object (plot) with a mouse event. Using this example from RStudio, I've been able to produce something which filters and returns a data table (diamonds) based upon the position on the x-axis (cut). Its almost there... However, I have two outstanding issues that I have been unable to solve:
Is it possible to return a data table based upon a mouse event that is filtered by the y-axis (color) as well as the x-axis (cut)?
Following from (1), can the data table then be further filtered so that it returns only information from that facet (type)?
This is where I've got up to using reproducible code:
library(shiny)
library(dplyr)
library(ggplot2)
ui <- fluidPage(
fluidRow(
plotOutput("plot1", click = "plot1_click")),
fluidRow(column(width = 10, dataTableOutput("selected_rows"))))
server <- function(input, output) {
is.even <- function(x) x %% 2 == 0
plot <- diamonds %>%
mutate(cut = as.factor(cut)) %>%
mutate(colour = as.factor(color)) %>%
mutate(type = is.even(price)) %>%
group_by(type, color, cut) %>%
count()
output$plot1 <- renderPlot({
ggplot(plot, aes(x = cut, y = color, colour = type)) +
geom_point(aes(size = n)) +
facet_grid(~type) +
theme(legend.position = "none")
})
output$selected_rows <- renderDataTable({
if (is.null(input$plot1_click$x)) return()
keeprows <- round(input$plot1_click$x) == as.numeric(diamonds$cut)
diamonds[keeprows, ]
})
}
shinyApp(ui, server)
Any help would be much appreciated. Thanks in advance.
I believe this is possible if you do a bit more logic within output$selected_rows. To filter by the y variable, simply add a reference to input$plot1_click$y. For the facet (or panels), you'll want to use input$plot1_click$panelvar1:
keeprows_x <- round(input$plot1_click$x) == as.numeric(diamonds$cut)
keeprows_y <- round(input$plot1_click$y) == as.numeric(diamonds$color)
keeprows_panel <- input$plot1_click$panelvar1 == is.even(diamonds$price)
diamonds[keeprows_x & keeprows_y & keeprows_panel, ]
Note: I'm mimicing the logic for type with is.even(diamonds$price). You may want to see this github issue for further discussion and solutions.

Resources