Trying to find an easy solution to change the order of x-axis and other parameters in ggplot2 using the user input in a Shiny app.
This is the code for the app
library(shiny)
library(openxlsx)
library(ggplot2)
library(plotly)
library(ggbeeswarm)
library(stringr)
library(dplyr)
library(ggpubr)
# Define UI
ui <- fluidPage(
titlePanel("Upload and Visualize tabular Data"),
sidebarLayout(
sidebarPanel(
width = 3, # Reduce the width of the side panel
tags$h4("Upload input files"),
fluidRow(
column(12, fileInput("data_file", "Choose dataset (.xlsx)")),
),
fluidRow(
column(12, fileInput("annotation_file", "Choose annotation file (.xlsx)")),
),
hr(), # Add another horizontal line as a split
tags$h4("Filter your data"),
fluidRow(
column(12, uiOutput("filter_inputs"))
),
hr(), # Add another horizontal line as a split
tags$h4("Select graph parameters"),
fluidRow(
column(12, selectInput("x_param", "X axis variable", "")),
),
fluidRow(
column(12, selectInput("data_column", "Y axis Variable", "")),
),
fluidRow(
column(12, selectInput("fill_param", "Color boxplots by:", "")),
),
fluidRow(
column(12, selectInput("facet_param", "Choose parameter to create facets:", "")),
),
hr(), # Add another horizontal line as a split
tags$h4("Download data"),
fluidRow(
column(12, downloadButton("download_data", "Download Filtered Data")),
),
fluidRow(
column(12, downloadButton("download_plot", "Download Plot")),
)
),
mainPanel(
plotOutput("graph")
)
)
)
# Define server
server <- function(input, output, session) {
# Read dataset and annotation files
dataset <- reactive({
req(input$data_file)
read.xlsx(input$data_file$datapath, rowNames = TRUE)
})
annotation <- reactive({
req(input$annotation_file)
read.xlsx(input$annotation_file$datapath, rowNames = TRUE)
})
# Update the filter input widgets based on the annotation file
observe({
col_names <- names(annotation())
filter_inputs <- lapply(col_names, function(col) {
#if (is.numeric(annotation()[[col]])) {
# numericInput(inputId = paste0("filter_", col), label = col, value = "")
#} else {
selectizeInput(inputId = paste0("filter_", col), label = col, choices = c("", unique(annotation()[[col]])),
multiple = TRUE, selected = "")
#}
})
output$filter_inputs <- renderUI({ filter_inputs })
# Update the choices for x_param, fill_param, and facet_param based on the annotation
updateSelectInput(session, "x_param", choices = col_names, selected = "")
updateSelectInput(session, "fill_param", choices = c("none", col_names), selected = "none")
updateSelectInput(session, "facet_param", choices = c("none", col_names), selected = "none")
})
# Merge the datasets based on the row names (unique identifier)
merged_data <- reactive({
merge(annotation(), dataset(), by = 'row.names', all = TRUE)
})
# Update the choices for data_column based on the dataset
observe({
req(dataset())
col_names <- names(dataset())
updateSelectInput(session, "data_column", choices = col_names)
})
# Filter the data based on the selected filter columns
filtered_data <- reactive({
data <- merged_data()
for (col in names(annotation())) {
if (!is.null(input[[paste0("filter_", col)]])) {
filter_values <- input[[paste0("filter_", col)]]
if (length(filter_values) > 0) {
col_escaped <- if (grepl("[[:punct:]]", col)) paste0("`", col, "`") else col
if (is.numeric(annotation()[[col]])) {
data <- data[data[[col_escaped]] %in% filter_values, ]
} else {
data <- data[grepl(paste0("^(", paste(filter_values, collapse = "|"), ")$"), data[[col_escaped]]), ]
}
}
}
}
data
})
# Create the graph using ggplot2 and render with plotly
output$graph <- renderPlot({
req(input$x_param, input$fill_param, input$facet_param, input$data_column)
facets <- input$facet_param %>%
str_replace_all(",", "+") %>%
rlang::parse_exprs()
p <- ggplot(filtered_data(), aes_string(x = input$x_param, y = col_escaped(input$data_column)))
if(input$fill_param != "none") {
p = p + geom_boxplot(aes_string(fill=input$fill_param), alpha = 0.25, outlier.shape = NA) +
geom_point(aes_string(fill=input$fill_param), col="black", size = 2, position = position_jitterdodge(jitter.width=0.2))
}
else {
p = p + geom_boxplot(outlier.shape = NA) +
geom_quasirandom(size=2)
}
#geom_boxplot(aes_string(fill=input$fill_param), outlier.shape = NA) +
if(input$facet_param != "none"){
p = p + facet_grid(cols = vars(!!!facets)) +
theme_bw() +
theme(axis.text.x=element_text(size=15), axis.text.y = element_text(size=12),
axis.title.y = element_text(size=18), strip.text.x = element_text(size = 15),
legend.title = element_text(size=15), legend.text = element_text(size=12))+
theme(axis.text.x = element_text(angle = 45, hjust=1))+
xlab("")
}
else{
p = p + theme_bw() +
theme(axis.text.x=element_text(size=15), axis.text.y = element_text(size=12),
axis.title.y = element_text(size=18), strip.text.x = element_text(size = 15),
legend.title = element_text(size=15), legend.text = element_text(size=12))+
theme(axis.text.x = element_text(angle = 45, hjust=1))+
xlab("")
}
print (p)
}, height = 750)
# Function to escape column names with special characters
col_escaped <- function(col) {
if (grepl("[[:punct:]]", col)) {
return(paste0("`", col, "`"))
} else {
return(col)
}
}
# Download the filtered data as a CSV file
output$download_data <- downloadHandler(
filename = function() {
"filtered_data.csv"
},
content = function(file) {
write.csv(filtered_data(), file)
}
)
# Download the plot as a PNG image
output$download_plot <- downloadHandler(
filename = function() {
"plot.png"
},
content = function(file) {
ggsave(file, plot = p, width = 12, height = 8, dpi = 300, device = "png")
}
)
}
# Run the Shiny app
shinyApp(ui, server)
The way the app works is straight forward, users upload an Annotation and a Dataset file where the first column is the sample ID. The app uses the column names of the annotation file to create filters so users can filter their data. Those column names will also be available for 3 parameters: x-axis, color of boxplots and facets. The columns of the dataset file will be used for the Y axis variable.
The app works perfectly but I had some requests to be able to change the order of X-axis and the order of the facets in the plot. I know I should use factor and levels in the ggplot to change the order from alphabetically to the order I want but I do not really know how to do that within the Selectinput choices.
So basically, depending on the order of the choices made in the "Filter your data" section, ggplot would use that order in the x-axis and in the facet.
I can provide mor info if my request is not explicit enough.
Thank you
UPDATE: I managed to do something that could work by adding this piece of code:
# Define a reactive expression to store the selected values for x_param
selected_x_values <- reactive({
req(input$x_param)
unique(filtered_data()[[input$x_param]])
})
I then changed the plot part like this:
# Create the graph using ggplot2 and render the plot
output$graph <- renderPlot({
req(input$x_param, input$fill_param, input$facet_param, input$data_column, selected_x_values())
facets <- input$facet_param %>%
str_replace_all(",", "+") %>%
rlang::parse_exprs()
p <- ggplot(filtered_data(), aes_string(x = factor(filtered_data()[[input$x_param]], levels = selected_x_values()), y = col_escaped(input$data_column)))
(...)
The good news is that I can make a plot. The bad news is that I still cannot have the order right. I know the first part in the update is showing the selected_x_values but somehow the order is still coming alphabetically.
I still need some help here.
UPDATE2:
Answer in the comment below!