diff --git a/AssumptionPlotter/AssumptionPlotter.Rproj b/AssumptionPlotter/AssumptionPlotter.Rproj index 270314b..f0d6187 100644 --- a/AssumptionPlotter/AssumptionPlotter.Rproj +++ b/AssumptionPlotter/AssumptionPlotter.Rproj @@ -18,4 +18,4 @@ StripTrailingWhitespace: Yes BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source -PackageRoxygenize: rd,collate,namespace +PackageRoxygenize: rd,collate,namespace,vignette diff --git a/AssumptionPlotter/DESCRIPTION b/AssumptionPlotter/DESCRIPTION index 142095b..d8b4e1f 100644 --- a/AssumptionPlotter/DESCRIPTION +++ b/AssumptionPlotter/DESCRIPTION @@ -1,28 +1,39 @@ Package: AssumptionPlotter Type: Package -Title: Assumption Plotter +Title: AssumptionPlotter Version: 0.1.0 Authors@R: c( person( "Emma", "Akrong", - email = "emma.akrong@student.uva.nl", + email = "emma.akrong@gmail.com", role = c("aut", "cre") ) ) -Description: This package attempt to provide an intuitive visualization of whether - the user's dataset meets the assumptions of statistical models, as well as - whether a statistical model is expected to capture what the user wants - the model to capture. +Description: This package creates an app that allows for visual inspection of + EMA/ESM data. The purpose is to give EMA/ESM researchers a nice overview of + what their data looks like, and possibly guide decisions on what statistical + model to use. It was developed with the datasets from openESM in mind, and + can currently plot built-in datasets, the users own data, and data from + openESM accessed through the link to download the tsv file in its dedicated + Zenodo page. License: Encoding: UTF-8 LazyData: true RoxygenNote: 7.3.2 Config/testthat/edition: 3 Imports: + bslib, + dplyr, + DT, ggplot2, + grDevices, knitr, quarto, + readr, + sass, + shiny, testthat, + tidyr, usethis VignetteBuilder: quarto Depends: diff --git a/AssumptionPlotter/NAMESPACE b/AssumptionPlotter/NAMESPACE index 97a152f..e0be47d 100644 --- a/AssumptionPlotter/NAMESPACE +++ b/AssumptionPlotter/NAMESPACE @@ -1,9 +1,22 @@ # Generated by roxygen2: do not edit by hand -export(data_log_plot) +export(assumption_plot) +export(clean_df) +export(pie_bar_chart) +export(rank_participants) export(run_plotter) -export(simulate_logistic) +import(bslib) +import(dplyr) import(ggplot2) +import(grDevices) +import(knitr) +import(quarto) +import(sass) import(shiny) import(testthat) +import(tidyr) import(usethis) +importFrom(DT,DTOutput) +importFrom(DT,renderDT) +importFrom(readr,read_csv) +importFrom(readr,read_tsv) diff --git a/AssumptionPlotter/R/assumption_plot.R b/AssumptionPlotter/R/assumption_plot.R new file mode 100644 index 0000000..146baa3 --- /dev/null +++ b/AssumptionPlotter/R/assumption_plot.R @@ -0,0 +1,256 @@ +#' AssumptionPlotter Plot +#' +#' @details +#' This function creates the plots in the Plots tab of AssumptionPlotter +#' @param df Selected dataset. +#' @param participant Participant to be plotted. +#' @param variables All variables to be plotted. +#' @param expected_days Expected days of the study +#' @param beeps_per_day Expected beeps per day +#' @param include_day Option to include day labels. +#' @param include_day_line Option to include day lines. +#' @param impute Method for imputing NA's. +#' @param add_trend Include a regression line showing the trend in the data. +#' @param trend_type Type of trend line to be passed to geom_smooth(). +#' @param theme_choice Decide what theme the plot should have. +#' @param palette Chooses either no palette or custom. +#' @param palette_options Palette options. Any of grDevices::hcl.pals(). +#' @param text_font What text font the plot should have. +#' @param axis_text_size Adjust the text size of the plot's axes. +#' @param legend_text_size Adjust the text size of the legend. +#' @return Interactive plot in AssumptionPlotter. +#' @import ggplot2 +#' @import dplyr +#' @import tidyr +#' @import grDevices +#' @export +#' @examples +#' \dontrun{ +#' assumption_plot( +#' df, +#' participant, +#' variables, +#' expected_days, +#' beeps_per_day, +#' include_day = TRUE, +#' include_day_line = TRUE, +#' impute = c("none", "mean", "mode"), +#' add_trend = FALSE, +#' trend_type = c("lm", "loess"), +#' theme_choice = c("classic", "minimal", "bw", "dark"), +#' palette = c("none", "custom"), +#' palette_option = "Reds", +#' text_font = c("sans", "serif", "mono"), +#' axis_text_size = 12, +#' legend_text_size = 12 +#' ) +#' } +#' + +assumption_plot <- function( + df, + participant, + variables, + expected_days, + beeps_per_day, + include_day = TRUE, + include_day_line = TRUE, + impute = "none", + add_trend = FALSE, + trend_type = "lm", + theme_choice = "classic", + palette = "none", + palette_option = "Reds", + text_font = "sans", + axis_text_size = 12, + legend_text_size = 12){ + + # Create error message for when all variables are deselected + + if(length(variables)==0){ + stop("The plot cannot render when no variables are selected") + } + + # Subset data + ## In the future it would be cool to implement the option to plot multiple + ## participants at once (so you can compare them). + df <- df %>% + filter(id == participant) + + + # Keep relevant variables + keep_cols <- c( + "id", + "day", + "beep", + "missing", + variables + ) + + df <- df[,keep_cols] + + + # Specify imputation method (per variable) + if(impute!="none"){ + + for(v in variables){ + + if(impute=="mean"){ + + df[[v]][is.na(df[[v]])] <-mean(df[[v]],na.rm=TRUE) + } + + if(impute == "mode"){ + + mode_val <- names(sort(table(df[[v]]), decreasing = TRUE))[1] + + if(is.numeric(df[[v]])) { + mode_val <- as.numeric(mode_val) + } + + df[[v]][is.na(df[[v]])] <- mode_val + } + + } + + } + + + # Ensure that all variables are numeric + df[variables] <- lapply(df[variables], as.numeric) + + + # Create plotting index + df <- df %>% + mutate( + plot_x = (day - 1) * beeps_per_day + beep + ) %>% + arrange(plot_x) + + + # Make df long format (required for plotting with ggplot) + long_df <- df %>% + pivot_longer( + cols = -c(id, day, beep, missing, plot_x), + names_to = "Variables", # Risky name since name is same as argument + values_to = "value" + ) + + + # Create location of vertical day lines on the x-axis + day_lines <- seq(beeps_per_day+.5, # Make line appear between last and first day + max(long_df$plot_x), + by = beeps_per_day) + + # Create location of days + ## Note that this is not ideal currently. Might make sense to define max days + ## and beeps by participant. Whether to plot predetermined expected days and + ## beeps for all participants or a specific one could be plot argument. + + day_labels <- data.frame( + day = 1:expected_days, + x = (0:(expected_days - 1)) * beeps_per_day + + (beeps_per_day + 1) / 2, + label = paste("Day", 1:expected_days) + ) + + + # Initialize plot + p <- ggplot2::ggplot(long_df, aes(x = plot_x, y = value, color = Variables))+ + geom_line()+ + geom_point()+ + ylab("Value")+ + xlab("Time") + + + # Option to include day labels + if(include_day){ + p <- p + + geom_label( + data = day_labels, + aes(x = x, y = Inf, label = label), + inherit.aes = FALSE, + vjust = 1.5, + size = 4, + alpha = .8 + ) + } + + + # Option to include lines separating days + if(include_day_line){ + p <- p + + geom_vline(xintercept = day_lines, alpha = .15, linetype = "dashed") + } + + # Option to add a trend line + if(add_trend){ + + p <- p + + geom_smooth(method = trend_type, + se = FALSE) + } + + + # Create x-axis tick labels + breaks <- sort(unique(long_df$plot_x)) + + labels <-rep(1:beeps_per_day, length.out = length(breaks)) + + ## Edit x-axis tick labels + p <- p + + scale_x_continuous(breaks = breaks, labels = labels) + + + # Ensure that y-lim is large enough to show all data points of all variables + p <- p + + coord_cartesian(ylim = c( + min(long_df$value, na.rm = TRUE), + max(long_df$value, na.rm = TRUE) + )) + + + # Switch themes + p <- switch(theme_choice, + minimal = p + theme_minimal(base_family = text_font), + classic = p + theme_classic(base_family = text_font), + bw = p + theme_bw(base_family = text_font), + dark = p + theme_dark(base_family = text_font), + p) + + + # Switch the colors of the plot + + ## Get number of variables + nr_var <- length(variables) + + p <- switch(palette, + none = p + scale_color_hue(), + custom = p + scale_color_manual(values = + grDevices::hcl.colors(nr_var, palette_option, + rev=TRUE)), + p) + + + # Edit font sizes + + p <- p + + theme( + text = element_text(size = axis_text_size), + axis.title = element_text(size = axis_text_size+2), + axis.text = element_text(size = axis_text_size), + legend.title = element_text(size = legend_text_size+2), + legend.text = element_text(size = legend_text_size) + ) + + + + # Return plot + return(p) + +} + + + + + diff --git a/AssumptionPlotter/R/clean_df.R b/AssumptionPlotter/R/clean_df.R new file mode 100644 index 0000000..3a68432 --- /dev/null +++ b/AssumptionPlotter/R/clean_df.R @@ -0,0 +1,100 @@ +#' Edit dataset to fit plotting +#' +#' @details +#' This function edits data files (from openESM). Technically, +#' the openesm::get_data function should read the files. However, +#' because the function fails to connect to zenodo, some datasets have been +#' manually saved in the package. To make the plotting smooth, this function +#' cleans the files further for analysis. To load additional datasets, go to the +#' dedicated zenodo webpage from openESM, copy the link for the dataset, and +#' paste it in the app (in the data tab under the option "paste link"). You will +#' also need to include some additional information from the dedicated openESM +#' page. If the format is correct, the analysis should still work. Make sure to +#' NOT pick the raw data. You can also add your own df as long as it is a csv +#' and the columns have the correct names. +#' The function also adds a column indicating whether a beep has been missed. +#' @param df The data frame. +#' @param id_col Provide the name of the id column +#' @param day_col Name of day column. +#' @param exp_day Expected days of the study. +#' @param beep_col Name of beep column. +#' @param exp_beep Expected beeps per day. +#' @param variables A vector containing the names of all columns to be analysed. +#' Each variable must be within quotation marks. +#' @import dplyr +#' @return Data file with all possible days and beeps and with a column indicating +#' whether a beep was missed. +#' @export +#' @examples +#' \dontrun{ +#' +#' "menghini_2023_orig" <- readr::read_tsv("https://zenodo.org/records/17347538/files/0022_menghini_ts.tsv?download=1") +#' names <- colnames(menghini_2023_orig)[c(9:17,22:28)] +#' +#' menghini_2023 <- clean_df( +#' df = menghini_2023_orig, +#' id_col = "id", +#' day_col = "day", +#' exp_day = 3, +#' beep_col = "beep", +#' exp_beep = 7, +#' variables = names +#' ) +#' } + + +clean_df <- function(df, + id_col = "id", + day_col = "day", + exp_day, + beep_col = "beep", + exp_beep, + variables){ + + # Remove any rows where id, day, or beep is missing a value + df <- df %>% + select(any_of(c(id_col, day_col, beep_col, variables))) %>% + filter( + !is.na(.data[[id_col]]), + !is.na(.data[[day_col]]), + !is.na(.data[[beep_col]]) + ) + + # Only select defined variables + df <- df %>% + select(any_of(c(id_col, day_col, beep_col, variables))) + + + # Change variable names incase original had different one + df_std <- df %>% + rename( + id = all_of(id_col), + day = all_of(day_col), + beep = all_of(beep_col) + ) + + + # Impute missing day x beep combinations with NA's + new_df <- df_std %>% + complete( + id, + day = 1:exp_day, + beep = 1:exp_beep + ) %>% + left_join(df_std, by = c("id", "day", "beep", variables)) + + + # create variable that indicates whether a row has NA's + new_df <- new_df %>% + mutate( + missing = if_any(all_of(variables), is.na) + ) + + + # order data according to first all participant then days then beeps + new_df <- new_df %>% + arrange(id, day, beep) %>% + relocate(missing, .after = beep) + + return(new_df) +} diff --git a/AssumptionPlotter/R/data_log_plot.R b/AssumptionPlotter/R/data_log_plot.R deleted file mode 100644 index 443de9f..0000000 --- a/AssumptionPlotter/R/data_log_plot.R +++ /dev/null @@ -1,50 +0,0 @@ -#' Scatter plot of data points -#' -#' @param x (Simulated) data denoting position of data point on the x-axis. -#' @param y (Simulated) data denoting position of data point on the y-axis. -#' @param type What shape your data should be in. -#' @param pch Shape of your data points in the plot. -#' @param col1 Color of your data points in the plot. -#' @param col2 Color of your logistic curve in the plot. -#' @param incl_mean Whether you want the mean of your data points displayed. -#' @return A plot showing how well your data points follow the logistic function. -#' @export -#' @examples -#' data_log_plot(x = rnorm(10,0,1), y = rbinom(10,1,0.4)) - -data_log_plot <- function(x, y, type = "logistic", pch = 16, col1 =rgb(0,0,0,0.6), - col2 = rgb(1,0,0,1), incl_mean = TRUE) { - if(type == "logistic") { - # fit logistic model - fit <- glm(y ~ x, family = binomial) - - # scatter plot - plot(x, y, - pch = pch, - col = col1, - xlab = "x", - ylab = "Outcome") - - # prediction grid - xgrid <- seq(min(x), max(x), length.out = 500) - - # predicted probabilities - pred <- predict(fit, - newdata = data.frame(x = xgrid), - type = "response") - - # draw sigmoid curve - lines(xgrid, pred, col = "red", lwd = 3) - - if(incl_mean) { - abline(v = mean(x), lty = 2, col= "gray40") - text(.1+mean(x), ) - } - - }else { - stop("The function only supports the logistic function at the moment") - } -} - - - diff --git a/AssumptionPlotter/R/edit_datasets.R b/AssumptionPlotter/R/edit_datasets.R deleted file mode 100644 index f6f2ef4..0000000 --- a/AssumptionPlotter/R/edit_datasets.R +++ /dev/null @@ -1,78 +0,0 @@ -#' Edit dataset to fit plotting -#' -#' @details -#' This function edits the data files (from openESM). Technically, the openesm::get_data function should read the files. However, because the function fails to connect to zenodo, some datasets have been manually saved in the package. To make the plotting smooth, this function cleans the files further for analysis. Load additional datasets, go to the dedicated zenodo webpage from openESM, copy the link for the dataset, and add the dataset manually in the app. If the format is correct, the analysis should still work. Make sure to NOT pick the raw data. -#' @param df The data frame. Note that the df should be in wide format. -#' @param id_col Provide the index for which column the participant ID is in. If there is no participant ID (the data frame is for only one participant), write "none". -#' @param day_col Index number for the column containing the days. -#' @param exp_day Expected days of the study. -#' @param beep_col Index number of the column containing the beeps for the day. -#' @param exp_beep Expected beeps per day. -#' @param variables A vector containing the names of all columns to be analysed. Each variable must be within quotation marks. -#' @import dplyr -#' @returns Creates a data file in the data folder of the package. -#' @examples -#' \dontrun{ -#' read_openESM_data("0022_menghini_ts", "https://zenodo.org/records/17347538/files/0022_menghini_ts.tsv?download=1") -#' } - -edit_df <- function(df, id_col, day_col, exp_day, beep_col, exp_beep, variables){ - - missing <- data.frame( - id = c(), - beeps = numeric(), - days = numeric() - ) - - participants <- unique(df[[id_col]]) - - for(i in 1:length(participants)){ - sub_df <- df[df[[id_col]]==participants[i],] - - expected_pairs <- expand.grid( - beeps = 1:exp_beep, - days = 1:exp_day - ) - - actual_pairs <- data.frame( - beeps = sub_df[[beep_col]], - days = sub_df[[day_col]] - ) - miss <- anti_join(expected_pairs, actual_pairs, by = c("beeps", "days")) - - add <- data.frame( - id = rep(participants[i], times=nrow(miss)), - days = miss$days, - beeps = miss$beeps - ) - - missing <- rbind(missing, add) - - } - - - new_df <- data.frame( - ID = df[,id_col], - time = 1:nrow(df), - day = df[,day_col], - beep = df[,beep_col] - ) - - new_var <- paste0("var_", variables) - - for(i in 1:length(new_var)){ - new_df[new_var[i]] <- df[[variables[i]]] - } - - return(missing) - #return(new_df) - #print(missing) - -} - -buu <- data.frame(x=1:10, y=letters[1:10]) -buu[h[1]] <- 36:45 - -buu[[h[1]]] - -mode(lu[,2:3]) diff --git a/AssumptionPlotter/R/external_packages.R b/AssumptionPlotter/R/external_packages.R index 9c0a52f..ffa99aa 100644 --- a/AssumptionPlotter/R/external_packages.R +++ b/AssumptionPlotter/R/external_packages.R @@ -1,4 +1,15 @@ #' @import ggplot2 #' @import usethis #' @import testthat +#' @import knitr +#' @import quarto +#' @import shiny +#' @import bslib +#' @importFrom DT renderDT DTOutput +#' @import sass +#' @importFrom readr read_tsv read_csv +#' @import dplyr +#' @import tidyr +#' @import grDevices + NULL diff --git a/AssumptionPlotter/R/pie_bar_chart.R b/AssumptionPlotter/R/pie_bar_chart.R new file mode 100644 index 0000000..d5203c7 --- /dev/null +++ b/AssumptionPlotter/R/pie_bar_chart.R @@ -0,0 +1,119 @@ +#' Pie or Bar Chart of Missing Data +#' +#' @details +#' This function creates a piechart of all missing data points +#' @param df Selected dataset. +#' @param participant Participant being plotted +#' @param type Whether it should return a pie or bar chart. +#' @param plot_all Whether the function should generate a plot for everyone or only. +#' @param text_font Font style. +#' @param axis_text_size Text sixe of axes. +#' @param legend_text_size Text size of legend. +#' @param theme_choice The bar chart has the same theme as the assumption plot +#' the participant +#' @return A pie or bar chart showing the ratio of missing values for either a +#' participant or the entire dataset. +#' @import ggplot2 +#' @import dplyr +#' @import tidyr +#' @export +#' @examples +#' \dontrun{ +#' pie_bar_chart( +#' df, +#' participant, +#' type = c("pie", "bar"), +#' plot_all = FALSE, +#' text_font = c("sans", "serif", "mono"), +#' axis_text_size = 12, +#' legend_text_size = 12, +#' theme_choice = c("classic", "minimal", "bw", "void") +#' ) +#' } +#' +#' + + +pie_bar_chart <- function(df, + participant, + type = "pie", + plot_all = FALSE, + text_font = "sans", + axis_text_size = 12, + legend_text_size = 12, + theme_choice = "classic"){ + + # Subset data if we are not plotting everyone + if(!plot_all){ + df <- df %>% + filter(id == participant) + }else{ + df <- df + } + + + # Keep relevant variables + missing <- df$missing + + + # Make df + plot_df <- data.frame( + Missing = missing + ) + + plot_df <- df %>% + mutate(Status = ifelse(missing, "Missing", "Included")) %>% + dplyr::count(Status) + + + # Create different types of plots + if(type == "pie"){ + + p <- + ggplot(plot_df, aes(x = "", y = n, fill = Status)) + + geom_bar(stat = "identity", width = 1) + + coord_polar("y", start = 0)+ + scale_fill_manual(values = + c("Missing" = "red", + "Included" = "blue"))+ + theme_void(base_family = text_font)+ + theme( + legend.title = element_text(size = legend_text_size+2), + legend.text = element_text(size = legend_text_size) + ) + } else if(type == "bar"){ + p <- + ggplot(plot_df, aes(x = Status, y = n, fill = Status)) + + geom_col() + + xlab("Data points")+ + ylab("Count")+ + scale_fill_manual(values = + c("Missing" = "red", + "Included" = "blue")) + + + # Change theme + p <- switch(theme_choice, + minimal = p + theme_minimal(base_family = text_font), + classic = p + theme_classic(base_family = text_font), + bw = p + theme_bw(base_family = text_font), + void = p + theme_void(base_family = text_font), + p) + + # Edit font size + p <- p + + theme( + text = element_text(size = axis_text_size), + axis.title = element_text(size = axis_text_size+2), + axis.text = element_text(size = axis_text_size), + legend.title = element_text(size = legend_text_size+2), + legend.text = element_text(size = legend_text_size) + ) + + + } + + return(p) + +} + diff --git a/AssumptionPlotter/R/rank_participants.R b/AssumptionPlotter/R/rank_participants.R new file mode 100644 index 0000000..dd1e978 --- /dev/null +++ b/AssumptionPlotter/R/rank_participants.R @@ -0,0 +1,62 @@ +#' Create table that ranks participants after number of missing values +#' +#' @details +#' This function creates the table in the summary tab. It shows the participants +#' with the most and least missing values and the sum and proportion of these +#' missing values. +#' @param df Data frame that has been cleaned with clean_df(). +#' @param show How many rows the table should have. +#' @return Table showing the participants with most and least missing values. +#' @import dplyr +#' @export +#' @examples +#' \dontrun{ +#' rank_participants(menghini_2013, 5) +#' } + + +rank_participants <- function(df, show = 5){ + + keep_cols <- c("id", "missing") + df <- df[,keep_cols] + + df <- df %>% + group_by(id) %>% + summarise( + missing_sum = sum(missing, na.rm = TRUE), + missing_prop = mean(missing, na.rm = TRUE) + ) %>% + arrange(desc(missing_sum)) + + if(show > nrow(df)){ + stop("Selected rows exceed number of participants") + } + + most <- df %>% + slice_head(n = show) + + least <- df %>% + slice_tail(n = show) %>% + arrange(missing_sum) + + rank_df <- data.frame( + "ID_most" = as.character(most$id), + "sum_most" = most$missing_sum, + "prop_most" = paste0(round(most$missing_prop*100),"%"), + "ID_least" = as.character(least$id), + "sum_least" = least$missing_sum, + "prop_least" = paste0(round(least$missing_prop*100),"%") + ) + + colnames(rank_df) <- c("ID most:", + "Sum", + "Percent", + "ID least:", + "Sum", + "Percent") + + return(rank_df) + +} + + diff --git a/AssumptionPlotter/R/run_app.R b/AssumptionPlotter/R/run_plotter.R similarity index 100% rename from AssumptionPlotter/R/run_app.R rename to AssumptionPlotter/R/run_plotter.R diff --git a/AssumptionPlotter/R/simulate_logistic.R b/AssumptionPlotter/R/simulate_logistic.R deleted file mode 100644 index 02f4f38..0000000 --- a/AssumptionPlotter/R/simulate_logistic.R +++ /dev/null @@ -1,15 +0,0 @@ -#' Simulate data for logistic regression -#' -#' @param n Number of observations -#' @param beta0 Intercept -#' @param beta1 Slope -#' @return A data frame with simulated data -#' @export -#' @examples -#' simulate_logistic(n = 100, beta0 = 0, beta1 = 1) -simulate_logistic <- function(n = 100, beta0 = 0, beta1 = 1) { - x <- rnorm(n) - p <- 1 / (1 + exp(-(beta0 + beta1 * x))) - y <- rbinom(n, 1, p) - data.frame(x = x, y = y) -} diff --git a/AssumptionPlotter/data/contreras_2020.rda b/AssumptionPlotter/data/contreras_2020.rda new file mode 100644 index 0000000..33b2a9b Binary files /dev/null and b/AssumptionPlotter/data/contreras_2020.rda differ diff --git a/AssumptionPlotter/data/geschwind_2013.rda b/AssumptionPlotter/data/geschwind_2013.rda new file mode 100644 index 0000000..97cef1a Binary files /dev/null and b/AssumptionPlotter/data/geschwind_2013.rda differ diff --git a/AssumptionPlotter/data/menghini_2023.rda b/AssumptionPlotter/data/menghini_2023.rda index 1a51b5d..c7ee469 100644 Binary files a/AssumptionPlotter/data/menghini_2023.rda and b/AssumptionPlotter/data/menghini_2023.rda differ diff --git a/AssumptionPlotter/inst/app/app.R b/AssumptionPlotter/inst/app/app.R index 61467de..e238ac2 100644 --- a/AssumptionPlotter/inst/app/app.R +++ b/AssumptionPlotter/inst/app/app.R @@ -1,210 +1,396 @@ -# -# This is a Shiny web application. You can run the application by clicking -# the 'Run App' button above. -# -# Find out more about building applications with Shiny here: -# -# https://shiny.posit.co/ -# - -library(shiny) -library(AssumptionPlotter) -library(datasets) -library(bslib) -library(DT) -library(sass) - -# Define UI for application that draws a histogram -ui <- page_navbar( - # id = "main_page", - # title = HTML(" - # AssumptionPlotter - # "), - id = "main_page", +############################################################################# +# USER INTERFACE # +############################################################################# + +# Using page_navbar because it looks nicer +ui <- bslib::page_navbar( + id = "main_page", title = tags$span( - tags$img(src = "logo.png", height = "30px"), - tags$b("AssumptionPlotter") - ), - bg = "red", - inverse = TRUE, + tags$img(src = "logo.png", height = "30px"), # logo is CC0 + tags$b("AssumptionPlotter")), + bg = "#b22222", fillable = TRUE, - theme = bs_theme( - base_font = font_google("Merriweather") + + # Edit font style + theme = bslib::bs_theme( + base_font = bslib::font_google("Merriweather") ), - nav_panel(title = "Start", - p(HTML(" + + # First tab: Start page + bslib::nav_panel(title = "Start", + + # Welcome message + HTML("

Welcome to AssumptionPlotter!

-

For now, this package aims to give you an intuition for whether - a selected set of statistical models can account for what you want - to observe in your intensive longitudinal data.

+

For now, this package allows you to visually inspect your + intensive longitudinal data.

-

Thus, this package is useful for anyone conducting EMA/ESM - research.

+

Thus, this package is useful for anyone conducting + EMA/ESM research.

-

The package includes the following steps:

+

The package includes the following functions:

    -
  1. Choose your dataset
  2. -
  3. Plot your dataset
  4. -
  5. Check model assumptions
  6. +
  7. Choose your EMA dataset.
  8. +
  9. Plot your EMA dataset per participant.
  10. +
  11. See overview of missing data.
-

Unfortunately for now, you cannot use your own data.

- " +

While the package aims to account for some errors in data + frame formatting, it is still not guaranteed that all data files + will work.

"), + + tags$p("The package was developed with datasets from", + tags$a("openESM", + href = "https://openesmdata.org/datasets/", + target = "_blank"), # Creates a new tab when + # you navigate. But when running app in RStudio, it + # also opens a blank window. Doesn't happen when ran + # from browser. + + "in mind."), + p("App is recommended to be used in full-screen mode as some + features otherwise might be hidden."), + # Button to easily transport you to the data page + actionButton( + inputId= "go_data", + label= "Choose your dataset") + + ), # End start page + + + + # Second tab: Data page + bslib::nav_panel(title = "Data", + # Choose what data you want to use + radioButtons( + inputId = "data_pick", + label = "Dataset (note that naviagting between + options will reset filled in information)", + choices = c( + "Built in datasets from openESM" = "builtin", + "Upload your own data" = "upload", + "Zenodo link of .tsv file" = "zenodo" + ), + selected = "builtin", + width = "100%" + ), + + # In server, UI will change depending on data_pick choice + uiOutput("data_select"), + + # Button to transport to plot tab + actionButton( + inputId = "go_plot", + label = "Plot your data"), + + # Show table of chosen dataset + DT::DTOutput("chosenData") # Defined in server + + ), # End data page + + + # Third tab: Plotting page + bslib::nav_panel(title = "Plot", + + # Create sidebar and main content field + bslib::layout_sidebar( + + # start sidebar (options) + sidebar = bslib::sidebar( + HTML("Options"), + + # Plotting options + ## Participant defined in server + uiOutput("participant_ui"), + + ## Variables defined in server + uiOutput("variable_ui"), + + ### Option to select all variables + actionButton( + inputId = "all_vars", + label = "Select all"), + + ### Option to deselect all variables + actionButton( + inputId = "clear_vars", + label = "Deselect all"), + + ## Trend line: create input for assumption_plot() + checkboxInput( + inputId = "add_trend", + label = "Show trend line", + value = FALSE), # default + + ### If trend line included, what kind + conditionalPanel( + condition = "input.add_trend == true", + selectInput( + inputId = "trend_type", + label = "Trend line type", + choices = c("lm", "loess"), + selected = "lm")), + + ## Only show imputation options if you want to impute + ### (This is made a bit clumsily. Might make sense to + ### change this in both the plotting function and here. + ### Currently an extra step needs to be taken in the server.) + checkboxInput( + inputId = "impute_toggle", + label = "Impute missing data", + value = FALSE), # default + + conditionalPanel( + condition = "input.impute_toggle == true", + + selectInput( + inputId = "impute_method", + label = "Imputation method", + choices = c("mean", "mode"), + selected = "mean")), + + hr(), # add horizontal line + + ## Day label and lines + ### Label + checkboxInput( + inputId = "day_label", + label = "Include day labels", + value = TRUE), # default + + ### Lines + checkboxInput( + inputId = "day_lines", + label = "Include day lines", + value = TRUE), # default + + ## Edit color palettes + checkboxInput( + inputId = "edit_palette", + label = "Edit color palette", + value = FALSE), + + conditionalPanel( + condition = "input.edit_palette == true", + + ### Choose which palette + selectInput( + inputId = "palette_option", + label = "Choose palette", + choices = grDevices::hcl.pals(), + selected = "Zissou 1")), + + ## Choose theme + selectInput( + inputId = "theme_choice", + label = "Theme", + choices = c("classic", "minimal", "bw", "dark"), + selected = "classic" ), + + ## Choose font + selectInput( + inputId = "text_font", + label = "Font family", + choices = c("sans", "serif", "mono"), + selected = "sans"), + + ## Edit font sizes + ### Axis + sliderInput( + inputId = "axis_size", + label = "Axis text size", + min = 1, + max = 30, + value = 14), + + ### Legend + sliderInput( + inputId = "legend_size", + label = "Legend text size", + min = 1, + max = 30, + value = 14) + + ), # End of options (sidebar) + + + # Start plot page main content + + HTML("Assumption Plot"), + plotOutput("assumption_plot") + + ), # end of layout_sidebar + + # Navigate back to data tab + actionButton( + inputId = "go_summary", + label = "See summary of missing values") + + ), # End plot page + + + + # Fourth tab: Summary of missing values tab + bslib::nav_panel(title = "Summary", + + # Create sidebar and main content field + bslib::layout_sidebar( + + # Start sidebar(options for summary page) + sidebar = bslib::sidebar( + HTML("Options"), + + # Plotting options: + ## Participant defined in server + ### Cannot use the same input because it kept on crashing + ### for me. However, after identifying the real bug, + ### (i thought the bug was that it couldn't use the same + ### input$participant for both plots, but in reality I'd + ### just forgotten to close an html tag properly), it + ### might be possible to just use participant_ui here too. + uiOutput("participant_ui_summary"), + + # ## Choose theme + # ### Not included for now + # uiOutput("theme_ui") + + ## Choose text font defined in server because I want the + ## font chosen in the plot page to automatically be + ## applied to the summary plots. + ### same disclaimer about just using text_font as for + ### participant_ui_summary + uiOutput("font_ui"), + + ## Edit summary plots font sizes + ### Axis + sliderInput( + inputId = "axis_size_sum", + label = "Axis text size", + min = 1, + max = 30, + value = 14), - )), - actionButton( - inputId= "go_data", - label= "Choose your dataset" - ) + ### Legend + sliderInput( + inputId = "legend_size_sum", + label = "Legend text size", + min = 1, + max = 30, + value = 14) - ), - nav_panel(title = "Data", - p(""), - radioButtons( - "data_pick", - "Dataset", - - choices = c( - "Datasets from openESM" = "builtin", - "Upload your own data" = "upload" - ) - ), - - uiOutput("data_select"), - actionButton( - inputId= "go_plot", - label= "Plot your data" - ), - DT::DTOutput("chosenData") - ), - nav_panel(title = "Plot", - p("Data visualization"), - layout_sidebar( - sidebar = sidebar(HTML(" - Options -

hello

- ")), - # uiOutput("summary_box"), - navset_card_tab( - nav_panel( - title= "Plot", - card( - card_header("Assumption Plot"), - plotOutput("test_plot") - ) - ), - nav_panel( - title = "Checks", - p(HTML(" -
    -
  1. hello
  2. -
")) - ) - ), - - - ) - ), - nav_spacer(), - nav_menu( - title = "More", - align = "right", - nav_item(tags$a("EMA Datasets", href = "https://openesmdata.org/datasets/", - target = "_blank")), - nav_item(tags$a("Github", href = "https://github.com/Programming-The-Next-Step-2026/Assumption-Plotter", - target = "_blank")) - ) -) -# Define server logic required to draw a histogram -server <- function(input, output, session) { - output$test_plot <- renderPlot({ + ), # End summary options - data <- switch( - input$dataset, - mtcars=mtcars, - iris=iris, - cars=cars - ) - plot(data[,1],data[,2]) - }) - ##### ACTION BUTTONS ##### - observeEvent(input$go_plot, { + # Start layout_sidebar content - updateNavbarPage( - session, - "main_page", - selected = "Plot" - ) - }) + HTML(" + Tips of what to look for in 'Assumption Plot': +
    +
  1. Do the variables show variability?
  2. +
  3. Are the measurement points equidistant?
  4. +
  5. Does the data remain stationary?
  6. +
+ "), - observeEvent(input$go_data, { - updateNavbarPage( - session, - "main_page", - selected = "Data" - ) - }) + p(HTML("Ratio of Missing Data:")), - output$summary_box <- renderUI({ + # Choose whether to plot all + checkboxInput( + inputId = "plot_all", + label = "Show missing values of full dataset", + value = FALSE), - data <- switch( - input$dataset, - mtcars = mtcars, - iris = iris, - cars = cars - ) + # Render Plots + bslib::layout_columns( + plotOutput("pie_chart"), + plotOutput("bar_chart")), - n_cols <- ncol(data) - n_rows <- nrow(data) - n_na <- sum(is.na(data)) + # How many rows to show in the following table + ## Custom style to get label to appear next to input + ## Needed to create more space + div( + style = "display: flex; align-items: center; gap: 10px;", - value_box( - title = paste0("Dataset summary: ", input$dataset), + tags$label("Rows:"), - value = paste0(n_cols, " cols • ", n_rows, " rows"), + numericInput( + inputId = "show_id_table", + label = NULL, + value = 5, + min = 1, + max = 100) + ), - showcase = bsicons::bs_icon("bar-chart"), + # Create card for top and bottom participants table + bslib::card( + max_height = 200, + full_screen = TRUE, + bslib::card_header("Participants with most and least + missing values"), - theme = value_box_theme(bg="black", fg="white"), + # Table in server + tableOutput("rank_table") - footer = paste("Total NA values:", n_na) - ) - }) + ) - ##### DATA PAGE ##### + ), # End layout_sidebar + + # Navigate back to plot tab + actionButton( + inputId = "go_back", + label = "Go back to Assumption Plot") - output$chosenData <- DT::renderDT({ + ), # End summary page - data <- if (input$data_pick == "builtin") { - switch( - input$dataset, - mtcars = mtcars, - iris = iris, - cars = cars - ) + # Navigation bar + bslib::nav_spacer(), # space between tabs and "more" menu - } else { + # Menu bar with aditional sources + bslib::nav_menu( + title = "More", + align = "right", + bslib::nav_item( + tags$a("openESM", + href = "https://openesmdata.org/datasets/", + target = "_blank") # Creates a new tab when you navigate but when + # running app in RStudio, it also opens a blank + # window. Doesn't happen when ran from browser. + ), + bslib::nav_item( + tags$a("Github", + href = "https://github.com/Programming-The-Next-Step-2026/Assumption-Plotter", + target = "_blank") + ) + ) # End navigation menu - req(input$file) +) - read.csv(input$file$datapath) - } - DT::datatable( - head(data), - options = list(pageLength = 5, scrollX = TRUE) +############################################################################# +# SERVER # +############################################################################# + +server <- function(input, output, session){ + # To data tab from start tab + observeEvent(input$go_data, { + + updateNavbarPage( + session, + "main_page", + selected = "Data" ) }) + + # Update UI of data tab depending on selection output$data_select <- renderUI({ if (input$data_pick == "builtin") { @@ -215,38 +401,476 @@ server <- function(input, output, session) { choiceNames = list( - card(card_header(tags$a("mtcars", href="https://openesmdata.org/datasets/", - target = "_blank")), p("Motor Trend cars")), - card(card_header("iris"), p("Fisher flowers")), - card(card_header("cars"), p("Base R cars dataset")) + bslib::card(max_height = 200, + full_screen = TRUE, + bslib::card_header(tags$a("menghini_2023", + href="https://openesmdata.org/datasets/0022_menghini/", + target = "_blank")), + p(HTML("Topic: workplace stress +
Participants: 139 +
Days: 3 +
Beeps: 7 +
Variables (16): well, discontent, +
state, tense, calm, placid, awake, +
energyless, rested, +
too_much, work_fast, multitasking, +
hard_work, +
change_task, decide_task, schedule_task + "))), + bslib::card(max_height = 200, + full_screen = TRUE, + bslib::card_header(tags$a("geschwind_2013", + href="https://openesmdata.org/datasets/0010_geschwind/", + target = "_blank")), + p(HTML("Topic: depression +
Participants: 129 +
Days: 10 (only exam week) +
Beeps: 10 +
Variables (6): cheerful, +
pleasantness, worried, fearful, sad, +
relaxed + "))), + bslib::card(max_height = 200, + full_screen = TRUE, + bslib::card_header(tags$a("contreras_2020", + href="https://openesmdata.org/datasets/0028_contreras/", + target = "_blank")), + p(HTML("Topic: paranoia +
Participants: 23 +
Days: 7 +
Beeps: 10 +
Variables (8): sad, useless, +
manage_well,no_trust, harm, criticism, +
others + "))) ), - choiceValues = c("mtcars", "iris", "cars"), + choiceValues = c("menghini_2023", "geschwind_2013", "contreras_2020"), + selected = "menghini_2023", inline = TRUE ) - } else { + } else if(input$data_pick == "upload"){ tagList( + HTML(" + Please provide the following information first: + "), + + bslib::layout_columns( + + # ID + textInput( + inputId = "upload_id", + label = "Name of participant ID column", + value = "id"), + + # Day + textInput( + inputId = "upload_day", + label = "Name of day column", + value = "day"), + + # Beep + textInput( + inputId = "upload_beep", + label = "Name of beep column", + value = "beep"), + + # Expected days + numericInput( + inputId = "upload_exp_day", + label = "Expected days", + value = 14, + min = 1, + max = 365 + ), + + # Expected beeps + numericInput( + inputId = "upload_exp_beep", + label = "Expected beeps per day", + value = 10, + min = 1, + max = 20 + ) + ), + + # Variables + textInput( + inputId = "upload_vars", + label = "Name of variables (must match column name) to be plotted", + placeholder = "cheerful, pleasantness, worried, fearful, sad, relaxed", + width = "100%"), + + HTML(" + Note that the function that cleans the + data currently only supports wide format"), + fileInput( "file", "You can only upload a CSV file", accept = c(".csv") + ) + ) # end upload tagList + + } else if(input$data_pick == "zenodo"){ + + tagList( + + HTML(" + All information can be found in the datasets dedicated openESM page: + "), + + bslib::layout_columns( + + # ID + textInput( + inputId = "zenodo_id", + label = "Name of participant ID column", + value = "id"), + + # Day + textInput( + inputId = "zenodo_day", + label = "Name of day column", + value = "day"), + + # Beep + textInput( + inputId = "zenodo_beep", + label = "Name of beep column", + value = "beep"), + + # Expected days + numericInput( + inputId = "zenodo_exp_day", + label = "Expected days", + value = 14, + min = 1, + max = 365 + ), + + # Expected beeps + numericInput( + inputId = "zenodo_exp_beep", + label = "Expected beeps per day", + value = 10, + min = 1, + max = 20 + ) ), - p(HTML("

Your file should needs to meet at least the following criteria - for the app to run smoothly:

-
    -
  1. The
  2. + # Variables + textInput( + inputId = "zenodo_vars", + label = "Name of variables (must match column name) to be plotted", + placeholder = "cheerful, pleasantness, worried, fearful, sad, relaxed", + width = "100%"), + + HTML(" + Click 'Zenodo DOI', then right click + the data file, and copy the link address. Do not choose the raw + data file."), + + # Zenodo link + textInput( + inputId = "zenodo_link", + label = "Link to .tsv file in Zenodo", + placeholder = "https://zenodo.org/records/17347538/files/0022_menghini_ts.tsv?download=1", + width = "100%") + + ) # end zenodo tagList -
- first row = column names)")) - ) } }) + + + # Make data reactive to what dataset has been chosen + plot_data_reactive <- reactive({ + + req(input$data_pick) + + df <- + if (input$data_pick == "builtin") { + + req(input$dataset) + switch( + input$dataset, + menghini_2023 = AssumptionPlotter::menghini_2023, + geschwind_2013 = AssumptionPlotter::geschwind_2013, + contreras_2020 = AssumptionPlotter::contreras_2020 + ) + + } else if(input$data_pick == "upload") { + req(input$file) + + upload <- readr::read_csv(input$file$datapath) + + upload_vars <- trimws( + strsplit(input$upload_vars, ",")[[1]] + ) + + AssumptionPlotter::clean_df( + df = upload, + id_col = input$upload_id, + day_col = input$upload_day, + exp_day = input$upload_exp_day, + beep_col = input$upload_beep, + exp_beep = input$upload_exp_beep, + variables = upload_vars + ) + } else if(input$data_pick == "zenodo"){ + + req(input$zenodo_link) + + zenodo <- readr::read_tsv(input$zenodo_link) + + zenodo_vars <- trimws( + strsplit(input$zenodo_vars, ",")[[1]] + ) + + AssumptionPlotter::clean_df( + df = zenodo, + id_col = input$zenodo_id, + day_col = input$zenodo_day, + exp_day = input$zenodo_exp_day, + beep_col = input$zenodo_beep, + exp_beep = input$zenodo_exp_beep, + variables = zenodo_vars + ) + } + + df + + }) + + + # Create table + output$chosenData <- DT::renderDT({ + + req(plot_data_reactive()) + + df <- plot_data_reactive() + + DT::datatable( + head(df,nrow(df)), + options = list(pageLength = 5, scrollX = TRUE) + ) + }) + + + # To plot tab from data tab + observeEvent(input$go_plot, { + + updateNavbarPage( + session, + "main_page", + selected = "Plot" + ) + + }) + + + ## Choose participant + output$participant_ui <- renderUI({ + + req(plot_data_reactive()) + + df <- plot_data_reactive() + + selectInput( + "participant", # selection can be accesses through input$participant + "Participant", + choices = unique(df$id), # shows all participant + selected = unique(df$id)[1] + ) + + }) + + + ## Choose variables + output$variable_ui <- renderUI({ + + req(plot_data_reactive()) + df <- plot_data_reactive() + + vars <- names(df)[!names(df) %in% c("id", "day", "beep", "missing")] + + checkboxGroupInput( # Check off the ones you want to see + "variables", + "Variables", + choices = vars, + selected = vars + ) + + }) + + ### Option to deselect all variables + ### Might cause problems for rendering plot + observeEvent(input$clear_vars, { + updateCheckboxGroupInput( + session, + "variables", + selected = character(0) + ) + }) + + ### Option to select all variables + ### Might cause problems for rendering plot + observeEvent(input$all_vars, { + + # req(plot_data_reactive()) + + df <- plot_data_reactive() + + vars <- names(df)[!names(df) %in% c("id", "day", "beep", "missing")] + + updateCheckboxGroupInput( + session, + "variables", + selected = vars + ) + + }) + + # Create Assumption plot + output$assumption_plot <- renderPlot({ + + req(plot_data_reactive()) + req(input$participant) + req(input$variables) + + df <- plot_data_reactive() + + + assumption_plot( + df = df, + participant = input$participant, + variables = input$variables, + expected_days = max(df$day), + beeps_per_day = max(df$beep), + include_day = input$day_label, + include_day_line = input$day_lines, + impute = if (isTRUE(input$impute_toggle)) input$impute_method else "none", + add_trend = input$add_trend, + trend_type = input$trend_type, + theme_choice = input$theme_choice, + palette = ifelse(!input$edit_palette, "none","custom"), + palette_option = input$palette_option, + text_font = input$text_font, + axis_text_size = input$axis_size, + legend_text_size = input$legend_size + ) + + }) + + # To summary from plot tab + observeEvent(input$go_summary, { + + updateNavbarPage( + session, + "main_page", + selected = "Summary" + ) + }) + + + # Choose variables for pie and bar charts + ## Choose participant for summary charts + ## See note about why input$participant isn't used in the ui + output$participant_ui_summary <- renderUI({ + + req(plot_data_reactive()) + + df <- plot_data_reactive() + + selectInput( + "participant_sum", # selection can be accesses through input$participant + "Participant", + choices = unique(df$id), # shows all participant + selected = input$participant + ) + + }) + + ## Font style + output$font_ui <- renderUI({ + selectInput( + inputId = "text_font_sum", + label = "Font family", + choices = c("sans", "serif", "mono"), + selected = input$text_font) + }) + + output$rank_table <- renderTable({ + req(plot_data_reactive()) + + rank_df <- rank_participants( + df = plot_data_reactive(), + show = input$show_id_table + ) + + }) + + + + + # Create pie chart of missing values + output$pie_chart <- renderPlot({ + + req(plot_data_reactive()) + + df <- plot_data_reactive() + + pie_bar_chart( + df = df, + participant = input$participant_sum, + type = "pie", + plot_all = input$plot_all, + text_font = input$text_font_sum, + axis_text_size = input$axis_size_sum, + legend_text_size = input$legend_size_sum, + theme_choice = "classic" + ) + + }) + + # Create bar chart of missing values + output$bar_chart <- renderPlot({ + + req(plot_data_reactive()) + + df <- plot_data_reactive() + + pie_bar_chart( + df = df, + participant = input$participant_sum, + type = "bar", + plot_all = input$plot_all, + text_font = input$text_font_sum, + axis_text_size = input$axis_size_sum, + legend_text_size = input$legend_size_sum, + theme_choice = "classic" + ) + + }) + + # To plot from summary tab + observeEvent(input$go_back, { + + updateNavbarPage( + session, + "main_page", + selected = "Plot" + ) + }) + + } + # Run the application shinyApp(ui = ui, server = server) diff --git a/AssumptionPlotter/inst/app/app_flop.R b/AssumptionPlotter/inst/app/app_flop.R new file mode 100644 index 0000000..05090e8 --- /dev/null +++ b/AssumptionPlotter/inst/app/app_flop.R @@ -0,0 +1,612 @@ +############################################################################# +# OLD USER INTERFACE # +############################################################################# + + +# Using page_navbar because it looks nicer +ui <- bslib::page_navbar( + id = "main_page", + title = tags$span( + tags$img(src = "logo.png", height = "30px"), # logo is CC0 + tags$b("AssumptionPlotter")), + bg = "#b22222", + fillable = TRUE, + + # Edit font style + theme = bslib::bs_theme( + base_font = bslib::font_google("Merriweather") + ), + + + # First tab: Start page + bslib::nav_panel(title = "Start", # Name of tab + + # Introduction page message + p(HTML(" + +

Welcome to AssumptionPlotter!

+ +

For now, this package aims to give you an intuition for whether + a selected set of statistical models can account for what you want + to observe in your intensive longitudinal data.

+ +

Thus, this package is useful for anyone conducting + EMA/ESM research.

+ +

The package includes the following steps:

+
    +
  1. Choose your dataset
  2. +
  3. Plot your dataset
  4. +
  5. Check model assumptions
  6. +
+ +

Unfortunately for now, you cannot use your own data.

+ " + + )), + + # Button to easily transport you to the data page + actionButton( + inputId= "go_data", + label= "Choose your dataset" + ) + + ), + + + # Second tab: Data page + bslib::nav_panel(title = "Data", + + # Choose what data you want to use + radioButtons( + inputId = "data_pick", + label = "Dataset", + choices = c( + "Datasets from openESM" = "builtin", + "Upload your own data" = "upload" + ), + selected = "builtin" + ), + + # In server, UI will change depending on data_pick choice + uiOutput("data_select"), + + # Button to transport to plot tab + actionButton( + inputId = "go_plot", + label = "Plot your data"), + + # Show table of chosen dataset + DT::DTOutput("chosenData") # Defined in server + ), + + + # Third tab: Plot tab + bslib::nav_panel(title = "Plot", + + # Create a layout where there is a sidebar and main content + bslib::layout_sidebar( + + # Edit the sidebar + sidebar = bslib::sidebar( + HTML("Options"), + + # Plotting options + + # ## Participant defined in server + # uiOutput("participant_ui"), + # + # ## Variables defined in server + # uiOutput("variable_ui"), + # + # ### Option to select all variables + # actionButton( + # inputId = "all_vars", + # label = "Select all"), + # + # ### Option to deselect all variables + # actionButton( + # inputId = "clear_vars", + # label = "Deselect all"), + # + # ## Trend line: create input for assumption_plot() + # checkboxInput( + # inputId = "add_trend", + # label = "Show trend line", + # value = FALSE), # default + + # ### If trend line included, what kind + # conditionalPanel( + # condition = "input.add_trend == true", + # selectInput( + # inputId = "trend_type", + # label = "Trend line type", + # choices = c("lm", "loess"), + # selected = "lm")), + # + # ## Only show imputation options if you want to impute + # ### (This is made a bit clumsily, might make sense to + # ### change this in both the plotting function and here. + # ### Currently an extra step needs to be taken in the server.) + # checkboxInput( + # inputId = "impute_toggle", + # label = "Impute missing data", + # value = FALSE), # default + # + # conditionalPanel( + # condition = "input.impute_toggle == true", + # + # selectInput( + # inputId = "impute_method", + # label = "Imputation method", + # choices = c("mean", "mode"), + # selected = "mean")), + + hr(), # add horizontal line + + # Day label and lines + ## Label + checkboxInput( + inputId = "day_label", + label = "Include day labels", + value = TRUE), # default + + ## Lines + checkboxInput( + inputId = "day_lines", + label = "Include day lines", + value = TRUE), # default + + ## Edit colors? + checkboxInput( + inputId = "edit_palette", + label = "Edit plot colors", + value = FALSE), + + conditionalPanel( + condition = "input.edit_palette == true", + + # Choose which theme + selectInput( + inputId = "palette_option", + label = "Choose palette", + choices = grDevices::hcl.pals(), + selected = "Zissou 1")), + + ## Choose theme + selectInput( + inputId = "theme_choice", + label = "Theme", + choices = c("classic", "minimal", "bw", "void"), + selected = "classic" ), + + # Choose font + selectInput( + inputId = "text_font", + label = "Font family", + choices = c("sans", "serif", "mono"), + selected = "sans"), + + # Edit font sizes + ## Axis + sliderInput( + inputId = "axis_size", + label = "Axis text size", + min = 5, + max = 30, + value = 12), + + ## Legend + sliderInput( + inputId = "legend_size", + label = "Legend text size", + min = 5, + max = 30, + value = 12) + + ), # End of options + + + # Create a card with tabs next to sidebar + bslib::navset_card_tab( + + # Plot tab + bslib::nav_panel(title = "Assumption Plot", + # plotOutput("assumption_plot") + ), + + # Summary tab + bslib::nav_panel(title = "Summary", + HTML(" + Reminders of what to check for in 'Assumption Plot': +
    +
  1. Do the variables show variability?
  2. +
  3. Are the measurement points equidistant?
  4. +
  5. Does the data remain stationary?
  6. +
+ "), + HTML(" + Ratio of Missing Data: + ") + # , + # + # # Choose whether to plot all + # checkboxInput( + # inputId = "plot_all", + # label = "Show missing values of full dataset", + # value = FALSE), + # + # # Render Plots + # bslib::layout_columns( + # plotOutput("pie_chart"), + # plotOutput("bar_chart")) + # + ) + ), + + # Navigate back to data tab + actionButton( + inputId = "go_back", + label = "Back to data") + ) + ), + + # Navigation bar + bslib::nav_spacer(), # space between tabs and "more" menu + + # Menu bar with aditional sources + bslib::nav_menu( + title = "More", + align = "right", + bslib::nav_item( + tags$a("openESM", + href = "https://openesmdata.org/datasets/", + target = "_blank") # Creates a new tab when you navigate + ), + bslib::nav_item( + tags$a("Github", + href = "https://github.com/Programming-The-Next-Step-2026/Assumption-Plotter", + target = "_blank") + ) + ) +) + + + +############################################################################# +# OLD SERVER # +############################################################################# + +server <- function(input, output, session) { + + # To data tab from start tab + observeEvent(input$go_data, { + + updateNavbarPage( + session, + "main_page", + selected = "Data" + ) + }) + + # Update UI of data tab depending on selection + output$data_select <- renderUI({ + + if (input$data_pick == "builtin") { + + radioButtons( + "dataset", + "Choose your dataset", + + choiceNames = list( + + bslib::card(max_height = 200, + full_screen = TRUE, + bslib::card_header(tags$a("menghini_2023", + href="https://openesmdata.org/datasets/0022_menghini/", + target = "_blank")), + p(HTML("Topic: workplace stress +
Participants: 139 +
Days: 3 +
Beeps: 7 +
Variables (16): well, discontent, +
state, tense, calm, placid, awake, +
energyless, rested, +
too_much, work_fast, multitasking, +
hard_work, +
change_task, decide_task, schedule_task + "))), + bslib::card(max_height = 200, + full_screen = TRUE, + bslib::card_header(tags$a("geschwind_2013", + href="https://openesmdata.org/datasets/0010_geschwind/", + target = "_blank")), + p(HTML("Topic: depression +
Participants: 129 +
Days: 10 (only exam week) +
Beeps: 10 +
Variables (6): cheerful, +
pleasantness, worried, fearful, sad, +
relaxed + "))), + bslib::card(max_height = 200, + full_screen = TRUE, + bslib::card_header(tags$a("contreras_2020", + href="https://openesmdata.org/datasets/0028_contreras/", + target = "_blank")), + p(HTML("Topic: paranoia +
Participants: 23 +
Days: 7 +
Beeps: 10 +
Variables (8): sad, useless, +
manage_well,no_trust, harm, criticism, +
others + "))) + ), + + choiceValues = c("menghini_2023", "geschwind_2013", "contreras_2020"), + selected = "menghini_2023", + inline = TRUE + ) + + } else { + + tagList( + + fileInput( + "file", + "You can only upload a CSV file", + accept = c(".csv") + ), + + p(HTML("

Your file should needs to meet at least the following criteria + for the app to run smoothly:

+
    +
  1. Have the exact same column format with variables of interest having the prescript 'var_'
  2. +
  3. The data should include all days and beeps that should have been included (include them as NA's)
  4. + +
+ first row = column names)")) + ) + + } + }) + + # Make data reactive to what dataset has been chosen + plot_data_reactive <- reactive({ + + req(input$data_pick) + + df <- + if (input$data_pick == "builtin") { + + req(input$dataset) + switch( + input$dataset, + menghini_2023 = AssumptionPlotter::menghini_2023, + geschwind_2013 = AssumptionPlotter::geschwind_2013, + contreras_2020 = AssumptionPlotter::contreras_2020 + ) + + } else { + req(input$file) + + readr::read_csv(input$file$datapath) + } + + df + + }) + + + # Create table + output$chosenData <- DT::renderDT({ + + req(plot_data_reactive()) + + df <- plot_data_reactive() + + DT::datatable( + head(df,nrow(df)), + options = list(pageLength = 5, scrollX = TRUE) + ) + }) + + # To plot tab from data tab + observeEvent(input$go_plot, { + + # req(plot_data_reactive()) + + updateNavbarPage( + session, + "main_page", + selected = "Plot" + ) + + }) + + + # Plotting options + ## Choose participant + output$participant_ui <- renderUI({ + + # req(plot_data_reactive()) + + df <- plot_data_reactive() + + selectInput( + "participant", # selection can be accesses through input$participant + "Participant", + choices = unique(df$id), # shows all participant + selected = unique(df$id)[1] + ) + + }) + + + # Copilot suggested code for debugging why option sidebar isn't showing up + # output$participant_ui <- renderUI({ + # df <- tryCatch(plot_data_reactive(), error = function(e) NULL) + # choices <- if (!is.null(df)) unique(df$id) else character(0) + # selectInput( + # "participant", + # "Participant", + # choices = choices, + # selected = if (length(choices)) choices[[1]] else NULL, + # multiple = FALSE + # ) + # }) + + ## Choose variables + output$variable_ui <- renderUI({ + + df <- plot_data_reactive() + + vars <- names(df)[!names(df) %in% c("id", "day", "beep", "missing")] + + checkboxGroupInput( # Check off the ones you want to see + "variables", + "Variables", + choices = vars, + selected = vars + ) + + }) + + # Copilot suggested code for debugging why option sidebar isn't showing up + # output$variable_ui <- renderUI({ + # df <- tryCatch(plot_data_reactive(), error = function(e) NULL) + # vars <- if (!is.null(df)) names(df)[!names(df) %in% c("id", "day", "beep", "missing")] else character(0) + # checkboxGroupInput( + # "variables", + # "Variables", + # choices = vars, + # selected = if (length(vars)) vars else character(0) + # ) + # }) + + ### Option to deselect all variables + ### Might cause problems for rendering plot + observeEvent(input$clear_vars, { + updateCheckboxGroupInput( + session, + "variables", + selected = character(0) + ) + }) + + ### Option to select all variables + ### Might cause problems for rendering plot + observeEvent(input$all_vars, { + + # req(plot_data_reactive()) + + df <- plot_data_reactive() + + vars <- names(df)[!names(df) %in% c("id", "day", "beep", "missing")] + + updateCheckboxGroupInput( + session, + "variables", + selected = vars + ) + + # Copilot suggested code for debugging why option sidebar isn't showing up + # df <- tryCatch(plot_data_reactive(), error = function(e) NULL) + # vars <- if (!is.null(df)) names(df)[!names(df) %in% c("id", "day", "beep", "missing")] else character(0) + # if (length(vars)) { + # updateCheckboxGroupInput(session, "variables", selected = vars) + # } else { + # updateCheckboxGroupInput(session, "variables", selected = character(0)) + # } + }) + + + # Create Assumption plot + # output$assumption_plot <- renderPlot({ + # + # # req(plot_data_reactive()) + # req(input$participant) + # + # df <- plot_data_reactive() + # + # + # assumption_plot( + # df = df, + # participant = input$participant, + # variables = input$variables, + # expected_days = max(df$day), + # beeps_per_day = max(df$beep), + # include_day = input$day_label, + # include_day_line = input$day_lines, + # impute = if (isTRUE(input$impute_toggle)) input$impute_method else "none", + # add_trend = input$add_trend, + # trend_type = input$trend_type, + # theme_choice = input$theme_choice, + # palette = ifelse(!input$edit_palette, "none","custom"), + # palette_option = input$palette_option, + # text_font = input$text_font, + # axis_text_size = input$axis_size, + # legend_text_size = input$legend_size + # ) + # + # }) + + + # # Create pie chart of missing values + # output$pie_chart <- renderPlot({ + # + # # req(plot_data_reactive()) + # req(input$participant) + # + # df <- plot_data_reactive() + # + # pie_bar_chart( + # df = df, + # participant = input$participant, + # type = "pie", + # plot_all = input$plot_all, + # text_font = input$text_font, + # axis_text_size = input$axis_size, + # legend_text_size = input$legend_size, + # theme_choice = input$theme_choice + # ) + # + # }) + # + # + # # Create bar chart of missing values + # output$bar_chart <- renderPlot({ + # + # # req(plot_data_reactive()) + # req(input$participant) + # + # df <- plot_data_reactive() + # + # pie_bar_chart( + # df = df, + # participant = input$participant, + # type = "bar", + # plot_all = input$plot_all, + # text_font = input$text_font, + # axis_text_size = input$axis_size, + # legend_text_size = input$legend_size, + # theme_choice = input$theme_choice + # ) + # + # }) + + # To data from plot tab + observeEvent(input$go_back, { + + updateNavbarPage( + session, + "main_page", + selected = "Data" + ) + }) + +} + +# Run the application +shinyApp(ui = ui, server = server) diff --git a/AssumptionPlotter/inst/figures/logo.png b/AssumptionPlotter/inst/figures/logo.png new file mode 100644 index 0000000..58f5408 Binary files /dev/null and b/AssumptionPlotter/inst/figures/logo.png differ diff --git a/AssumptionPlotter/man/assumption_plot.Rd b/AssumptionPlotter/man/assumption_plot.Rd new file mode 100644 index 0000000..a5850fe --- /dev/null +++ b/AssumptionPlotter/man/assumption_plot.Rd @@ -0,0 +1,90 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/assumption_plot.R +\name{assumption_plot} +\alias{assumption_plot} +\title{AssumptionPlotter Plot} +\usage{ +assumption_plot( + df, + participant, + variables, + expected_days, + beeps_per_day, + include_day = TRUE, + include_day_line = TRUE, + impute = "none", + add_trend = FALSE, + trend_type = "lm", + theme_choice = "classic", + palette = "none", + palette_option = "Reds", + text_font = "sans", + axis_text_size = 12, + legend_text_size = 12 +) +} +\arguments{ +\item{df}{Selected dataset.} + +\item{participant}{Participant to be plotted.} + +\item{variables}{All variables to be plotted.} + +\item{expected_days}{Expected days of the study} + +\item{beeps_per_day}{Expected beeps per day} + +\item{include_day}{Option to include day labels.} + +\item{include_day_line}{Option to include day lines.} + +\item{impute}{Method for imputing NA's.} + +\item{add_trend}{Include a regression line showing the trend in the data.} + +\item{trend_type}{Type of trend line to be passed to geom_smooth().} + +\item{theme_choice}{Decide what theme the plot should have.} + +\item{palette}{Chooses either no palette or custom.} + +\item{text_font}{What text font the plot should have.} + +\item{axis_text_size}{Adjust the text size of the plot's axes.} + +\item{legend_text_size}{Adjust the text size of the legend.} + +\item{palette_options}{Palette options. Any of grDevices::hcl.pals().} +} +\value{ +Interactive plot in AssumptionPlotter. +} +\description{ +AssumptionPlotter Plot +} +\details{ +This function creates the plots in the Plots tab of AssumptionPlotter +} +\examples{ +\dontrun{ +assumption_plot( + df, + participant, + variables, + expected_days, + beeps_per_day, + include_day = TRUE, + include_day_line = TRUE, + impute = c("none", "mean", "mode"), + add_trend = FALSE, + trend_type = c("lm", "loess"), + theme_choice = c("classic", "minimal", "bw", "dark"), + palette = c("none", "custom"), + palette_option = "Reds", + text_font = c("sans", "serif", "mono"), + axis_text_size = 12, + legend_text_size = 12 +) +} + +} diff --git a/AssumptionPlotter/man/clean_df.Rd b/AssumptionPlotter/man/clean_df.Rd new file mode 100644 index 0000000..eb426e3 --- /dev/null +++ b/AssumptionPlotter/man/clean_df.Rd @@ -0,0 +1,70 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/clean_df.R +\name{clean_df} +\alias{clean_df} +\title{Edit dataset to fit plotting} +\usage{ +clean_df( + df, + id_col = "id", + day_col = "day", + exp_day, + beep_col = "beep", + exp_beep, + variables +) +} +\arguments{ +\item{df}{The data frame.} + +\item{id_col}{Provide the name of the id column} + +\item{day_col}{Name of day column.} + +\item{exp_day}{Expected days of the study.} + +\item{beep_col}{Name of beep column.} + +\item{exp_beep}{Expected beeps per day.} + +\item{variables}{A vector containing the names of all columns to be analysed. +Each variable must be within quotation marks.} +} +\value{ +Data file with all possible days and beeps and with a column indicating +whether a beep was missed. +} +\description{ +Edit dataset to fit plotting +} +\details{ +This function edits data files (from openESM). Technically, +the openesm::get_data function should read the files. However, +because the function fails to connect to zenodo, some datasets have been +manually saved in the package. To make the plotting smooth, this function +cleans the files further for analysis. To load additional datasets, go to the +dedicated zenodo webpage from openESM, copy the link for the dataset, and +paste it in the app (in the data tab under the option "paste link"). You will +also need to include some additional information from the dedicated openESM +page. If the format is correct, the analysis should still work. Make sure to +NOT pick the raw data. You can also add your own df as long as it is a csv +and the columns have the correct names. +The function also adds a column indicating whether a beep has been missed. +} +\examples{ +\dontrun{ + +"menghini_2023_orig" <- readr::read_tsv("https://zenodo.org/records/17347538/files/0022_menghini_ts.tsv?download=1") +names <- colnames(menghini_2023_orig)[c(9:17,22:28)] + +menghini_2023 <- clean_df( + df = menghini_2023_orig, + id_col = "id", + day_col = "day", + exp_day = 3, + beep_col = "beep", + exp_beep = 7, + variables = names +) +} +} diff --git a/AssumptionPlotter/man/data_log_plot.Rd b/AssumptionPlotter/man/data_log_plot.Rd deleted file mode 100644 index cca55c5..0000000 --- a/AssumptionPlotter/man/data_log_plot.Rd +++ /dev/null @@ -1,40 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data_log_plot.R -\name{data_log_plot} -\alias{data_log_plot} -\title{Scatter plot of data points} -\usage{ -data_log_plot( - x, - y, - type = "logistic", - pch = 16, - col1 = rgb(0, 0, 0, 0.6), - col2 = rgb(1, 0, 0, 1), - incl_mean = TRUE -) -} -\arguments{ -\item{x}{(Simulated) data denoting position of data point on the x-axis.} - -\item{y}{(Simulated) data denoting position of data point on the y-axis.} - -\item{type}{What shape your data should be in.} - -\item{pch}{Shape of your data points in the plot.} - -\item{col1}{Color of your data points in the plot.} - -\item{col2}{Color of your logistic curve in the plot.} - -\item{incl_mean}{Whether you want the mean of your data points displayed.} -} -\value{ -A plot showing how well your data points follow the logistic function. -} -\description{ -Scatter plot of data points -} -\examples{ -data_log_plot(x = rnorm(10,0,1), y = rbinom(10,1,0.4)) -} diff --git a/AssumptionPlotter/man/pie_bar_chart.Rd b/AssumptionPlotter/man/pie_bar_chart.Rd new file mode 100644 index 0000000..2256f41 --- /dev/null +++ b/AssumptionPlotter/man/pie_bar_chart.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pie_bar_chart.R +\name{pie_bar_chart} +\alias{pie_bar_chart} +\title{Pie or Bar Chart of Missing Data} +\usage{ +pie_bar_chart( + df, + participant, + type = "pie", + plot_all = FALSE, + text_font = "sans", + axis_text_size = 12, + legend_text_size = 12, + theme_choice = "classic" +) +} +\arguments{ +\item{df}{Selected dataset.} + +\item{participant}{Participant being plotted} + +\item{type}{Whether it should return a pie or bar chart.} + +\item{plot_all}{Whether the function should generate a plot for everyone or only.} + +\item{text_font}{Font style.} + +\item{axis_text_size}{Text sixe of axes.} + +\item{legend_text_size}{Text size of legend.} + +\item{theme_choice}{The bar chart has the same theme as the assumption plot +the participant} +} +\value{ +A pie or bar chart showing the ratio of missing values for either a +participant or the entire dataset. +} +\description{ +Pie or Bar Chart of Missing Data +} +\details{ +This function creates a piechart of all missing data points +} +\examples{ +\dontrun{ +pie_bar_chart( + df, + participant, + type = c("pie", "bar"), + plot_all = FALSE, + text_font = c("sans", "serif", "mono"), + axis_text_size = 12, + legend_text_size = 12, + theme_choice = c("classic", "minimal", "bw", "void") + ) +} + + +} diff --git a/AssumptionPlotter/man/rank_participants.Rd b/AssumptionPlotter/man/rank_participants.Rd new file mode 100644 index 0000000..b9d364c --- /dev/null +++ b/AssumptionPlotter/man/rank_participants.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rank_participants.R +\name{rank_participants} +\alias{rank_participants} +\title{Create table that ranks participants after number of missing values} +\usage{ +rank_participants(df, show = 5) +} +\arguments{ +\item{df}{Data frame that has been cleaned with clean_df().} + +\item{show}{How many rows the table should have.} +} +\value{ +Table showing the participants with most and least missing values. +} +\description{ +Create table that ranks participants after number of missing values +} +\details{ +This function creates the table in the summary tab. It shows the participants +with the most and least missing values and the sum and proportion of these +missing values. +} +\examples{ +\dontrun{ +rank_participants(menghini_2013, 5) +} +} diff --git a/AssumptionPlotter/man/run_plotter.Rd b/AssumptionPlotter/man/run_plotter.Rd index 4ca62ae..08eac95 100644 --- a/AssumptionPlotter/man/run_plotter.Rd +++ b/AssumptionPlotter/man/run_plotter.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/run_app.R +% Please edit documentation in R/run_plotter.R \name{run_plotter} \alias{run_plotter} \title{Launch shiny app} diff --git a/AssumptionPlotter/man/simulate_logistic.Rd b/AssumptionPlotter/man/simulate_logistic.Rd deleted file mode 100644 index dfb95c7..0000000 --- a/AssumptionPlotter/man/simulate_logistic.Rd +++ /dev/null @@ -1,24 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/simulate_logistic.R -\name{simulate_logistic} -\alias{simulate_logistic} -\title{Simulate data for logistic regression} -\usage{ -simulate_logistic(n = 100, beta0 = 0, beta1 = 1) -} -\arguments{ -\item{n}{Number of observations} - -\item{beta0}{Intercept} - -\item{beta1}{Slope} -} -\value{ -A data frame with simulated data -} -\description{ -Simulate data for logistic regression -} -\examples{ -simulate_logistic(n = 100, beta0 = 0, beta1 = 1) -} diff --git a/AssumptionPlotter/tests/testthat/Rplots.pdf b/AssumptionPlotter/tests/testthat/Rplots.pdf deleted file mode 100644 index 54a27af..0000000 Binary files a/AssumptionPlotter/tests/testthat/Rplots.pdf and /dev/null differ diff --git a/AssumptionPlotter/tests/testthat/test-data_log_plot.R b/AssumptionPlotter/tests/testthat/test-data_log_plot.R deleted file mode 100644 index 6fba9c6..0000000 --- a/AssumptionPlotter/tests/testthat/test-data_log_plot.R +++ /dev/null @@ -1,8 +0,0 @@ -test_that("data_log_plot gives a plot without error", { - data <- simulate_logistic(50) - - expect_silent( - data_log_plot(data$x, data$y) - ) - -}) diff --git a/AssumptionPlotter/tests/testthat/test-main_functions.R b/AssumptionPlotter/tests/testthat/test-main_functions.R new file mode 100644 index 0000000..7df81f3 --- /dev/null +++ b/AssumptionPlotter/tests/testthat/test-main_functions.R @@ -0,0 +1,185 @@ +# Unit tests for the main functions of AssumptionPlotter + +## clean_df() +test_that("clean_df() returns a data frame with correct structure", { + df <- data.frame( + id = c(1, 1, 1, 2, 2), + day = c(1, 1, 2, 1, 1), + beep = c(1, 2, 1, 1, 2), + mood = c(5, 4, 3, 6, 7), + stress = c(2, 3, 4, 1, 2) + ) + + result <- clean_df(df, + exp_day = 2, + exp_beep = 2, + variables = "stress") + + expect_s3_class(result, "data.frame") + expect_true("missing" %in% names(result)) + expect_true(nrow(result) > nrow(df)) # expands with complete() +}) + +test_that("clean_df() creates missing column", { + df <- data.frame( + id = c(1, 1, 1, 1), + day = c(1, 1, 2, 2), + beep = c(1, 2, 1, 2), + var1 = c(NA, 5, 3, NA) + ) + + result <- clean_df(df, + exp_day = 2, + exp_beep = 2, + variables = "var1") + + expect_true("missing" %in% names(result)) + expect_true(any(result$missing == 1)) # at least one missing value marked +}) + +test_that("clean_df() handles empty data frame", { + df <- data.frame( + id = character(), + day = numeric(), + beep = numeric(), + var1 = numeric() + ) + + expect_error(clean_df(df, + exp_day = 1, + exp_beep = 1, + variables = "var1"), NA) # should not error +}) + + +## assumption_plot() +test_that("assumption_plot() returns a ggplot object", { + df <- menghini_2023 + + p <- assumption_plot( + df = df, + participant = df$id[1], + variables = c("well", "discontent"), + expected_days = 3, + beeps_per_day = 7, + include_day = TRUE, + include_day_line = TRUE, + impute = "none", + add_trend = FALSE, + theme_choice = "classic" + ) + + expect_s3_class(p, "ggplot") +}) + +test_that("assumption_plot() adds trend line when add_trend = TRUE", { + df <- menghini_2023 + + p <- assumption_plot( + df = df, + participant = df$id[1], + variables = c("well"), + expected_days = 3, + beeps_per_day = 7, + add_trend = TRUE, + trend_type = "lm", + theme_choice = "classic" + ) + + # Check if geom_smooth was added (has stat summary layer) + layers <- sapply(p$layers, function(x) class(x$geom)[1]) + expect_true(any(grepl("Smooth", layers))) +}) + +test_that("assumption_plot() handles single variable", { + df <- menghini_2023 + + p <- assumption_plot( + df = df, + participant = df$id[1], + variables = "well", + expected_days = 3, + beeps_per_day = 7, + theme_choice = "classic" + ) + + expect_s3_class(p, "ggplot") +}) + + +## run_plotter() +test_that("run_plotter() finds the app directory", { + + appDir <- system.file("app", package = "AssumptionPlotter") + + expect_true(nzchar(appDir)) +}) + + + + +## pie_bar_chart() +test_that("pie_bar_chart() returns a ggplot object for pie chart", { + df <- menghini_2023 + + p <- pie_bar_chart( + df = df, + participant = df$id[1], + type = "pie", + plot_all = FALSE + ) + + expect_s3_class(p, "ggplot") +}) + +test_that("pie_bar_chart() returns a ggplot object for bar chart", { + df <- menghini_2023 + + p <- pie_bar_chart( + df = df, + participant = df$id[1], + type = "bar", + plot_all = FALSE + ) + + expect_s3_class(p, "ggplot") +}) + +test_that("pie_bar_chart() handles plot_all = TRUE", { + df <- menghini_2023 + + p <- pie_bar_chart( + df = df, + participant = df$id[1], + type = "pie", + plot_all = TRUE + ) + + expect_s3_class(p, "ggplot") +}) + + +## rank_participants() +test_that("rank_participants() returns a data frame", { + df <- menghini_2023 + + result <- rank_participants(df, show = 5) + + expect_s3_class(result, "data.frame") + expect_true("ID most:" %in% names(result)) + expect_true("Percent" %in% names(result) || "well" %in% names(result)) +}) + +test_that("rank_participants() ranks in correct order", { + df <- data.frame( + id = c(1, 1, 2, 2, 3, 3), + missing = c(T, T, F, F, F, T) + ) + + result <- rank_participants(df, 3) + + expect_s3_class(result, "data.frame") + expect_equal(nrow(result), 3) # 3 unique participants +}) + + diff --git a/AssumptionPlotter/tests/testthat/test-simulate_logistic.R b/AssumptionPlotter/tests/testthat/test-simulate_logistic.R deleted file mode 100644 index b9e178a..0000000 --- a/AssumptionPlotter/tests/testthat/test-simulate_logistic.R +++ /dev/null @@ -1,9 +0,0 @@ -test_that("simulate_logistic gives dataframe", { - data <- simulate_logistic(20) - - expect_s3_class(data, "data.frame") - - expect_equal(nrow(data),20) -}) - - diff --git a/AssumptionPlotter/vignettes/AssumptionPlotter.qmd b/AssumptionPlotter/vignettes/AssumptionPlotter.qmd index 14f0c3a..76b69d1 100644 --- a/AssumptionPlotter/vignettes/AssumptionPlotter.qmd +++ b/AssumptionPlotter/vignettes/AssumptionPlotter.qmd @@ -1,7 +1,16 @@ --- -title: "AssumptionPlotter" +title: | + + AssumptionPlotter author: "Emma Akrong" -date: "May 24, 2026" +date: "May 31, 2026" +format: + html: + theme: simplex + css: vignette-style.css + toc: true + toc-depth: 4 + toc-location: left vignette: > %\VignetteIndexEntry{AssumptionPlotter} %\VignetteEngine{quarto::html} @@ -12,68 +21,204 @@ knitr: comment: '#>' --- -```{r} +```{r, eval=F} #| label: setup library(AssumptionPlotter) ``` -This package aims to give a an overview of whether the user's data matches the statistical model they intend to use. +This package was created in `R` code, and therefore requires you to have it installed. It was created in **Rstudio**, so the app is likely most straightforward to run from there. You can find the instructions to download RStudio here. + +For problems with the package, please contact emma.akrong@gmail.com + +Required packages: + +```{r, eval=FALSE} +library("ggplot2") # For plots +library("testthat") # To run unit tests +library("usethis") # To edit package +library("knitr") # To render/create quarto file +library("quarto") # To render/create quarto +library("shiny") # To create app +library("bslib") # To format user interface of app +library("DT") # To create interactive table in app's data page +library("sass") # To edit font +library("readr") # To read csv/tsv files into tibbles +library("dplyr") # To edit data frames for plots/tables +library("tidyr") # To edit data frames for plots/tables +library("grDevices") # Access color palettes for assumption plot +``` + + +## Welcome to AssumptionPlotter! + +At the moment, this package creates an app that allows for visual inspection of EMA/ESM data. + +The package was developed with the datasets from openESM in mind, and can currently plot built-in datasets, the users own data, and data from openESM accessed through the link to download the dataset from its dedicated Zenodo page. However, the last option is not guaranteed to work. + +The purpose is to give EMA/ESM researchers a nice overview of what their data looks like, and possibly guide decisions on what statistical model to use. It plots according to the expected days and beeps of the study, and therefore requires all participants to have the same study lengths. You can currently only plot the variables of one participant at the time. + +While "*PlotESM*" currently might be a more fitting name, ideally some statistical tests will later be implemented. + +The main functions created by this package are: + +```{r, eval=FALSE} +run_plotter() # Launches the shiny app +clean_df() # Cleans data uploaded/linked to by the user +assumption_plot() # Main plot of the app +pie_bar_chart() # Plots summarizing how many missing values +rank_participants() # Table showing who had the most and least missing values +``` + +You can get more information about them by e.g., running `?run_plotter()` in your console (accessing the function's help file), once you've installed the package. + +We will now take a tour of the package. -While it tests whether the data matches meets the statistical assumptions of the models, it also aims to give an intuition of whether the statistical model will capture what the user wants see in the data. -For example, in the case of a study with intensive longitudinal data, the user might expect to see change. This package aims to show how e.g., a VAR model would not be able to reflect this expectation. +## Running AssumptionPlotter +### Install Package -# Running AssumptionPlotter +To install the package, run this code in your console: -The first step after installing the package is to run the function `run.plotter()`. +```{r, eval=FALSE} +remotes::install_github("Programming-The-Next-Step-2026/Assumption-Plotter", + ref = "week-4", + subdir = "AssumptionPlotter", + build_manual = TRUE, + build_vignettes = TRUE) +``` + +It might ask you to update your packages. You can choose not to, but it is not guaranteed that the app will run as intended. + +### Launch App + +The first step after installing the package is to open it in your library and run the function `run_plotter()`. ```{r, eval=F} -run.plotter() +library(AssumptionPlotter) + +run_plotter() ``` +This will launch the app and take you to the start page: + +![](figures/start_page.png) + +It is recommended that you use the app in full-screen mode as some features (e.g., the table in the data page) otherwise might be hidden. Also note that if you're running the app in RStudio, blank screens might pop up when you press a hyperlink. This does not happen when you run it in your browser. + +Press the "*Choose your dataset*" button or navigate to the data tab in the menu bar. You cannot plot anything until a dataset has been chosen. + +Note that you in the menu bar can find the link to the openESM webpage and the github repository (see the green circle in the plot). + +In all following images, pay attention to information outlined in the green or red circles. + + +### Choose Data + +In the "Data" page you have a few options. You can use the built in datasets, upload your own, or use the link to the tsv file in Zenodo. + +#### Built-in data + +To familiarise yourself with the app, you can choose one of the built-in datasets. + +![](figures/data_builtin.png) + +These datasets were chosen at random and are by no means perfect. However, they can for example highlight how messy the data is (e.g., in `geschwind_2013` most participants have no data in the last days, although it's indicated that there should be 10 days). + +You can press the hyperlink to access the data's dedicated openESM page. To preview more of the data, just choose another option in "Show" at the top-left of the table. + +You can then press the "*Plot your data*" button. + + +#### Upload data + +By choosing "*Upload your own data*", you will be instructed to follow a few steps: + +![](figures/data_upload.png) + +`clean_df()` that cleans the data for the plotting functions, requires you to input the name of the id, day, and beep column (the pre-filled names are "id", "day", and "beep"). As well as how many days and beeps per day each participant should have. You also need to manually fill in the names of all the variables you want to plot. + +In the app, it says that only csv files are accepted. This is true, however, you can edit this in the server in the "app.R" file (line 514; e.g.: `accept = c(".csv", ".tsv")`). You can also edit line 610 to use a different read function. The data is currently read using `readr::read_csv()`, which technically also could work for tsv files. + +When you're happy, you can move to the Plot page. + + +#### Zenodo link + +The final option is to upload the data using the dataset's download file, which can be found in Zenodo. An example link is circled in green: + +![](figures/data_zenodo.png) + +To access the link you will have to take a few actions. Start in the data's dedicated openESM page and follow the following steps: + +![](figures/openESM_zenodo.png) + +On this page you can find the expected days and beeps (circled on the right). + +If you scroll down a bit, you will also find the names of the variables (columns). Fill these out in the app. Note that some pages have the names of the id, day, and beep columns at the bottom. + +Fill in all required information in the app and then press the Zenodo DOI link. + +![](figures/openESM_vars.png) + +Pressing the Zenodo DOI link should show you the downloadable files. Make sure to not pick the raw or static files, but the one circled in red. + +![](figures/zenodo_data.png) -# TO ADD +Then access the link by right clicking, copy the link address, and paste the link to the app. -This will all be added before Friday. +![](figures/zenodo_copy_link.png) -::: {layout-ncol=3} +Note that this doesn't work for all data. For example, in the development, I could not get "0011_kuppens" to work. -![1](figures/r1.png) +If the data previews successfully. you can move on to plotting. -![2](figures/r2.png) +### Plot Data -![3](figures/r3.png) +When you move to the plot tab, you should see all the variables plotted per day and beep in the Assumption Plot. -![4](figures/r4.png) +![](figures/plot_start.png) -![5](figures/r5.png) +A connected line indicates that the participant responded to consecutive beeps. If there is only a dot, it means that the beep was not preceded nor followed by a response. There should be data at every beep. If a beep is empty, this indicates missing data. -![6](figures/r6.png) +In the "*Options*" sidebar you will see different plotting options. Scroll down to see all options. Note that you can control its width. The plot is reactive so should change immediately when change options (with some possible rendering delay). In `assumption_plot()` you can specify the following things that also can be controlled in the plot: +1. What variables to show. +1. Whether to include a trend line (either linear or loess). +1. Whether to impute missing data (either the mean or mode of the variable). +1. Whether to show the day labels. +1. Whether to include lines separating the days. +1. Whether to edit the plot's color palette (supports any palette from `grDevices::hcl.pals()`). +1. What ggplot theme the plot should have (options can be edited in "app.R"). +1. What font the plot should have. +1. Font size of the axes. +1. Font size of the legend. -![7](figures/r7.png) +In "app.R", all of these have preset values. These are not necessarily the same as those seen in "examples" in the help file. -![8](figures/r8.png) +You can play around with the different options. Note that the plot will not render if no variables are chosen. It might also take a while for the plot to render, depending on how quickly the app can load the data. -![9](figures/r9.png) +If you're curious about a summary of the missing values, you can press the "*See summary of missing values*" button at the bottom of the screen and move on to the "*Summary*" page. -![10](figures/r10.png) -![11](figures/r11.png) +### Summary Page -![12](figures/r12.png) +The "*Summary*" page starts with some tips for what to look for in the Assumption Plot in the "*Plot*" page. This could be next to the plot, but I wanted to make the plot as big as possible. -![13](figures/r13.png) +![](figures/summary_page.png) -![14](figures/r14.png) +Next, you will see a pie and bar chart showing the ratio of included to missing data for the selected participant. When navigating from the "*Plot*" page to the "*Summary*" page, it will remember what participant and font should be plotted. However, if you change the participant in the "*Summary*" page and then move back to the "*Plot*", you'll have to manually change it again for the Assumption Plot. -![15](figures/r15.png) +You can also choose to plot the missing data in the entire dataset in the pie and bar chart. This way you can compare the participant's adherence to all other participants. +Under the plots you find a table summarising which participants had the most and least missing values. This could e.g., be useful for knowing who to pick for the Assumption Plot when you're interested in trends in the data. You can edit the amount of rows the table should show (note that this cannot exceed the sample size) and then, as seen in the bottom-right of the page when you hover over the table, you can expand the table. -![16](figures/r16.png) +At the bottom of the "*Summary*" page there is button to allow for easy navigation between the "*Summary*" and "*Plot*" pages. -![17](figures/r17.png) +

Hope it works nicely for you!

+

+Kind regards, +
Emma +

-::: diff --git a/AssumptionPlotter/vignettes/figures/data_builtin.png b/AssumptionPlotter/vignettes/figures/data_builtin.png new file mode 100644 index 0000000..f682aaf Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/data_builtin.png differ diff --git a/AssumptionPlotter/vignettes/figures/data_upload.png b/AssumptionPlotter/vignettes/figures/data_upload.png new file mode 100644 index 0000000..5b622c8 Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/data_upload.png differ diff --git a/AssumptionPlotter/vignettes/figures/data_zenodo.png b/AssumptionPlotter/vignettes/figures/data_zenodo.png new file mode 100644 index 0000000..6412914 Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/data_zenodo.png differ diff --git a/AssumptionPlotter/vignettes/figures/logo.png b/AssumptionPlotter/vignettes/figures/logo.png new file mode 100644 index 0000000..58f5408 Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/logo.png differ diff --git a/AssumptionPlotter/vignettes/figures/openESM_vars.png b/AssumptionPlotter/vignettes/figures/openESM_vars.png new file mode 100644 index 0000000..632c1b8 Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/openESM_vars.png differ diff --git a/AssumptionPlotter/vignettes/figures/openESM_zenodo.png b/AssumptionPlotter/vignettes/figures/openESM_zenodo.png new file mode 100644 index 0000000..42f888f Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/openESM_zenodo.png differ diff --git a/AssumptionPlotter/vignettes/figures/plot_start.png b/AssumptionPlotter/vignettes/figures/plot_start.png new file mode 100644 index 0000000..2205ad4 Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/plot_start.png differ diff --git a/AssumptionPlotter/vignettes/figures/r1.png b/AssumptionPlotter/vignettes/figures/r1.png deleted file mode 100644 index 08b43bf..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r1.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r10.png b/AssumptionPlotter/vignettes/figures/r10.png deleted file mode 100644 index b9f35ed..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r10.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r11.png b/AssumptionPlotter/vignettes/figures/r11.png deleted file mode 100644 index ac0adf3..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r11.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r12.png b/AssumptionPlotter/vignettes/figures/r12.png deleted file mode 100644 index ae21a0b..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r12.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r13.png b/AssumptionPlotter/vignettes/figures/r13.png deleted file mode 100644 index 476fa5b..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r13.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r14.png b/AssumptionPlotter/vignettes/figures/r14.png deleted file mode 100644 index 32b529c..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r14.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r15.png b/AssumptionPlotter/vignettes/figures/r15.png deleted file mode 100644 index 3b0c40d..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r15.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r16.png b/AssumptionPlotter/vignettes/figures/r16.png deleted file mode 100644 index 4879280..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r16.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r17.png b/AssumptionPlotter/vignettes/figures/r17.png deleted file mode 100644 index f5e8ba3..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r17.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r2.png b/AssumptionPlotter/vignettes/figures/r2.png deleted file mode 100644 index de9baea..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r2.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r3.png b/AssumptionPlotter/vignettes/figures/r3.png deleted file mode 100644 index 7731c1f..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r3.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r4.png b/AssumptionPlotter/vignettes/figures/r4.png deleted file mode 100644 index de50130..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r4.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r5.png b/AssumptionPlotter/vignettes/figures/r5.png deleted file mode 100644 index aa2925a..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r5.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r6.png b/AssumptionPlotter/vignettes/figures/r6.png deleted file mode 100644 index e6f3269..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r6.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r7.png b/AssumptionPlotter/vignettes/figures/r7.png deleted file mode 100644 index dee7e18..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r7.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r8.png b/AssumptionPlotter/vignettes/figures/r8.png deleted file mode 100644 index df4fcf6..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r8.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/r9.png b/AssumptionPlotter/vignettes/figures/r9.png deleted file mode 100644 index 42b1eaa..0000000 Binary files a/AssumptionPlotter/vignettes/figures/r9.png and /dev/null differ diff --git a/AssumptionPlotter/vignettes/figures/start_page.png b/AssumptionPlotter/vignettes/figures/start_page.png new file mode 100644 index 0000000..5f0f5ba Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/start_page.png differ diff --git a/AssumptionPlotter/vignettes/figures/summary_page.png b/AssumptionPlotter/vignettes/figures/summary_page.png new file mode 100644 index 0000000..548060a Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/summary_page.png differ diff --git a/AssumptionPlotter/vignettes/figures/zenodo_copy_link.png b/AssumptionPlotter/vignettes/figures/zenodo_copy_link.png new file mode 100644 index 0000000..176dadd Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/zenodo_copy_link.png differ diff --git a/AssumptionPlotter/vignettes/figures/zenodo_data.png b/AssumptionPlotter/vignettes/figures/zenodo_data.png new file mode 100644 index 0000000..25b1d99 Binary files /dev/null and b/AssumptionPlotter/vignettes/figures/zenodo_data.png differ diff --git a/AssumptionPlotter/vignettes/vignette-style.css b/AssumptionPlotter/vignettes/vignette-style.css new file mode 100644 index 0000000..212ac52 --- /dev/null +++ b/AssumptionPlotter/vignettes/vignette-style.css @@ -0,0 +1,18 @@ +/* Load Merriweather */ +@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap'); + +body { + font-family: "Merriweather", serif; +} + +/* Red headings */ +h1, h2, h3, h4, h5 { + color: #b22222; +} + +/* Red links */ +a { + color: #b22222; +} + +