Hi Gabriella! I've tested the app and looked at the code behind it. I see what you mean with the repetition, I have some ideas that might help with some of the copy-paste and repeated code! I'll focus on the first boxplot/dataset, as the suggestions should work for all.
Also, these are not really shiny-related tips: to be honest I'm not expert at all in shiny, and there might be better ways to do the same thigs.
First of all, looking at the render_plot() call for the boxplot I don't think that you are using anything from the model fit in there, so you could remove those lines. This might also help with performance, as you are computing the model fit twice every time you select a new marker in the dropdown.
A lot of the code to compute and fit the model stays the same, so it's a good candidate to be converted into a custom built function.
Here is your code:
olink_plot_data <- olink_data %>%
select(timepoint, ID, !!sym(column_name)) %>%
filter(!is.na(.[[column_name]]) & is.finite(.[[column_name]])) # Filter the data, removing rows with NA or infinite values
# Perform linear mixed-effects regression and calculate ANOVA
lmer_model <- lmer(formula = as.formula(paste(column_name, "~ timepoint + (1 | ID)")), data = olink_plot_data)
anova_result <- anova(lmer_model, type = "III")
p_value <- formatC(anova_result[["Pr(>F)"]][1], format = "f", digits = 5) # Format the p-value
p_value_display <- ifelse(as.numeric(p_value) < 0.05, paste0(p_value, "*"), p_value) # Add asterisk if p-value is significant
paste("Repeated Measures ANOVA p-value:", p_value_display)
And here is a function that takes as inputs the dataframe of interest and the column_name that you want to use as DV as a string:
compute_model <- function(df, column_name) {
filtered_df <- df %>%
select(timepoint, ID, !!sym(column_name)) %>%
filter(!is.na(.[[column_name]]) & is.finite(.[[column_name]])) # Filter the data, removing rows with NA or infinite values
# Perform linear mixed-effects regression and calculate ANOVA
lmer_model <- lmer(formula = as.formula(paste(column_name, "~ timepoint + (1 | ID)")), data = filtered_df)
anova_result <- anova(lmer_model, type = "III")
p_value <- formatC(anova_result[["Pr(>F)"]][1], format = "f", digits = 5) # Format the p-value
p_value_display <- ifelse(as.numeric(p_value) < 0.05, paste0(p_value, "*"), p_value) # Add asterisk if p-value is significant
textual_result <- paste("Repeated Measures ANOVA p-value:", p_value_display)
## this put all the things in a list in case you might need it,
# but if you only need the textual label, you can simply
# return textual_result
results <- list(
lmer_model = lmer_model,
anova_result = anova_result,
p_value = p_value,
p_value_display = p_value_display,
textual_result = textual_result
)
return(results)
}
This way you can reduce the amount of code that you see inside the textual rendering function to a compact
The same applies to the plotting code, that could become
plot_boxplot <- function(df, column_name) {
ggplot(data = df, aes(x = timepoint, y = .data[[column_name]], color = timepoint)) +
geom_boxplot() +
labs(x = "timepoint", y = column_name) +
theme_classic()
}
A more shiny related suggestion is to look into reactivity and reactives, which might help with the preprocessing steps that you take, for example the column selection and the filtering of missing data. Reactives are executed only when the input changes, and than you can use them in multiple places.
Hi Gabriella! I've tested the app and looked at the code behind it. I see what you mean with the repetition, I have some ideas that might help with some of the copy-paste and repeated code! I'll focus on the first boxplot/dataset, as the suggestions should work for all. Also, these are not really shiny-related tips: to be honest I'm not expert at all in shiny, and there might be better ways to do the same thigs.
First of all, looking at the
render_plot()
call for the boxplot I don't think that you are using anything from the model fit in there, so you could remove those lines. This might also help with performance, as you are computing the model fit twice every time you select a new marker in the dropdown.A lot of the code to compute and fit the model stays the same, so it's a good candidate to be converted into a custom built function.
Here is your code:
And here is a function that takes as inputs the dataframe of interest and the column_name that you want to use as DV as a string:
This way you can reduce the amount of code that you see inside the textual rendering function to a compact
The same applies to the plotting code, that could become
A more shiny related suggestion is to look into reactivity and reactives, which might help with the preprocessing steps that you take, for example the column selection and the filtering of missing data. Reactives are executed only when the input changes, and than you can use them in multiple places.