insightsengineering / teal.goshawk

Modules that produce web interfaces through which longitudinal visualizations can be dynamically
https://insightsengineering.github.io/teal.goshawk/
Other
3 stars 2 forks source link

Line plot module: control symbol size #238

Open npaszty opened 1 year ago

npaszty commented 1 year ago

Feature description

stakeholders report that it is difficult to easily see the difference between the line plot lines when using a splitting variable. there are number of reasons for this. here are three ideas to help address. suggested order is hopefully simplest to more complicated. happy to hop on a call to discuss further and goshawk function would need updated but didn't open and issue in that package.

  1. Lines within a treatment arm are the same color and the same type. here's an example from the sample app filtering to only one treatment group and choosing SEX as the line splitting variable image

Feature: would it be straight forward to include a UI element like what is in boxplot to increase the size of the line plot symbols? image

  1. Another idea to help differentiate within the line splitting context was to be able to set different line color within a treatment group. currently the line color and type selector functions within a treatment group. image

Feature: I fear this would be quite complicated to add a nested line color and type selector within the treatment group value to control the split line aesthetics.

  1. Another idea was to have the line type set automatically when a splitting variable is chosen. currently the line type is set to solid but this can be changed manually. however we observed when setting different line type it doesn't look like that is reflected in the legend. maybe the legend symbol/line is not big enough to show the line type. image

Code of Conduct

Contribution Guidelines

Security Policy

npaszty commented 6 months ago

is there an update on this issue? thanks.

npaszty commented 2 weeks ago

@donyunardi

is there an update on this issue? thanks.

donyunardi commented 2 weeks ago

Hi @npaszty,

Thetm_g_gh_lineplot module has aesthetic setting, i.e. dot size, symbols, colors, position dodge, etc.

Here's what it looks like once I played around with this: image

Is the user aware of these settings? If not, could you review my output and let me know your thoughts? If this isn’t sufficient, we can discuss potential directions to explore to resolve this.

Steps to reproduce:

  1. Go to https://genentech.shinyapps.io/nest_longitudinal_stable/
  2. Filter by ARMCD, only select "C: Combination"
  3. Select "SEX" from "Select Line Splitting Variable" in the encoding area. image
R Code to reproduce ```r # Add any code to install/load your NEST environment here library(shiny) library(dplyr) library(goshawk) library(teal.code) library(teal.data) library(teal.slice) library(teal) library(teal.transform) library(teal.goshawk) library(formatters) library(magrittr) library(rtables) library(tern) library(teal.modules.clinical) library(ggplot2) library(ggmosaic) library(teal.modules.general) library(DescTools) library(random.cdisc.data) library(stringr) library(sparkline) library(DescTools) library(magrittr) library(dplyr) library(random.cdisc.data) library(stringr) library(sparkline) ADSL <- radsl(seed = 1) ADLB <- radlb(ADSL, seed = 1) exclude_l2 <- c("") exclude_chg <- c("") arm_mapping <- list(`A: Drug X` = "Drug X 100mg", `C: Combination` = "Combination 100mg", `B: Placebo` = "Placebo") ADSL <- ADSL %>% filter(ITTFL == "Y") %>% mutate(TRTORD = case_when(TRT01P == "A: Drug X" ~ 1, TRT01P == "C: Combination" ~ 2, TRT01P == "B: Placebo" ~ 3, TRUE ~ as.numeric(NA)), TRTORD = TRTORD %make_label% "Treatment Order", TRT01P = as.character(arm_mapping[match(TRT01P, names(arm_mapping))]), TRT01P = factor(ARM) %>% reorder(TRTORD), TRT01P = TRT01P %make_label% "Planned Treatment for Period 01") char_vars_adsl <- names(Filter(isTRUE, sapply(ADSL, is.character))) ADSL <- ADSL %>% mutate_at(char_vars_adsl, factor) ADLB_SUBSET <- ADLB %>% filter(!is.na(AVAL)) %>% filter(ITTFL == "Y" & toupper(AVISIT) %like any% c("SCREEN%", "BASE%", "%WEEK%", "%FOLLOW%")) %>% select(c("STUDYID", "USUBJID", "ITTFL", "ARM", "ARMCD", "ACTARM", "ACTARMCD", "TRT01P", "TRT01A", "AVISIT", "AVISITN", "ADY", "PARAM", "PARAMCD", "AVAL", "AVALU", "BASE", "CHG", "PCHG", "ANRLO", "ANRHI", "LBSTRESC", "SEX", "RACE", "LOQFL")) %>% mutate(AVISITCD = case_when(toupper(AVISIT) == "SCREENING" ~ "SCR", toupper(AVISIT) == "BASELINE" ~ "BL", grepl("WEEK", toupper(AVISIT)) ~ paste("W", trimws(substr(AVISIT, start = 6, stop = stringr::str_locate(AVISIT, "DAY") - 1))), grepl("FOLLOW", toupper(AVISIT)) ~ "FU", TRUE ~ as.character(NA)), AVISITCDN = case_when(AVISITCD == "SCR" ~ -2, AVISITCD == "BL" ~ 0, grepl("W", AVISITCD) ~ as.numeric(gsub("[^0-9]+", "", AVISITCD)) * 7, AVISITCD == "FU" ~ 100, TRUE ~ as.numeric(NA)), TRTORD = case_when(TRT01P == "A: Drug X" ~ 1, TRT01P == "C: Combination" ~ 2, TRT01P == "B: Placebo" ~ 3, TRUE ~ as.numeric(NA)), LOQFL = if_else(as.character(LOQFL) == "Y", as.character(LOQFL), "N"), BASE2 = NA, CHG2 = NA, PCHG2 = NA) %>% rowwise() %>% group_by(PARAMCD) %>% mutate(LBSTRESC = ifelse(USUBJID %in% sample(USUBJID, 1, replace = TRUE), paste("<", round(runif(1, min = 25, max = 30))), LBSTRESC)) %>% mutate(LBSTRESC = ifelse(USUBJID %in% sample(USUBJID, 1, replace = TRUE), paste(">", round(runif(1, min = 70, max = 75))), LBSTRESC)) %>% ungroup() attr(ADLB_SUBSET[["LBSTRESC"]], "label") <- "Character Result/Finding in Std Format" attr(ADLB_SUBSET[["ANRLO"]], "label") <- "Analysis Normal Range Lower Limit" attr(ADLB_SUBSET[["ANRHI"]], "label") <- "Analysis Normal Range Upper Limit" PARAM_MINS <- ADLB_SUBSET %>% select(USUBJID, PARAMCD, AVAL) %>% group_by(PARAMCD) %>% summarise(AVAL_MIN = min(AVAL, na.rm = TRUE), .groups = "drop") %>% mutate(PARAMCD = PARAMCD %make_label% "Parameter Code") ADLB_SUPED1 <- ADLB_SUBSET %>% mutate(BASE2 = ifelse(toupper(AVISIT) == "SCREENING" & is.na(BASE2), AVAL, BASE2) %keep_label% BASE2) %>% mutate(CHG2 = ifelse(toupper(AVISIT) == "SCREENING" & is.na(CHG2), 0, CHG2) %keep_label% CHG2) %>% mutate(PCHG2 = ifelse(toupper(AVISIT) == "SCREENING" & is.na(PCHG2), 0, PCHG2) %keep_label% PCHG2) %>% mutate(BASE = ifelse(toupper(AVISIT) == "BASELINE" & is.na(BASE), AVAL, BASE) %keep_label% BASE) %>% mutate(CHG = ifelse(toupper(AVISIT) == "BASELINE" & is.na(CHG), 0, CHG) %keep_label% CHG) %>% mutate(PCHG = ifelse(toupper(AVISIT) == "BASELINE" & is.na(PCHG), 0, PCHG) %keep_label% PCHG) %>% mutate(TRTORD = TRTORD %make_label% "Treatment Order") ADLB_SUPED2 <- inner_join(PARAM_MINS, ADLB_SUPED1, by = "PARAMCD")[, union(names(ADLB_SUPED1), names(PARAM_MINS))] %>% mutate(AVALL2 = ifelse(PARAMCD %in% exclude_l2, AVAL, ifelse(PARAMCD %in% exclude_chg, NA, ifelse(AVAL == 0 & AVAL_MIN > 0, log2(AVAL_MIN/2), ifelse(AVAL == 0 & AVAL_MIN <= 0, NA, ifelse(AVAL > 0, log2(AVAL), NA))))) %make_label% "Log2 of AVAL") %>% mutate(BASEL2 = ifelse(PARAMCD %in% exclude_l2, BASE, ifelse(PARAMCD %in% exclude_chg, NA, ifelse(BASE == 0 & AVAL_MIN > 0, log2(AVAL_MIN/2), ifelse(BASE == 0 & AVAL_MIN <= 0, NA, ifelse(BASE > 0, log2(BASE), NA))))) %make_label% "Log2 of BASE") %>% mutate(BASE2L2 = ifelse(PARAMCD %in% exclude_l2, BASE2, ifelse(PARAMCD %in% exclude_chg, NA, ifelse(BASE2 == 0 & AVAL_MIN > 0, log2(AVAL_MIN/2), ifelse(BASE2 == 0 & AVAL_MIN <= 0, NA, ifelse(BASE2 > 0, log2(BASE2), NA))))) %make_label% "Log2 of BASE2") %>% mutate(AVAL_MIN = AVAL_MIN %make_label% "Minimum AVAL Within PARAMCD") ADLB <- ADLB_SUPED2 %>% mutate(TRT01P = as.character(arm_mapping[match(TRT01P, names(arm_mapping))]), TRT01P = factor(TRT01P) %>% reorder(TRTORD) %make_label% "Planned Treatment for Period 01", TRT01A = as.character(arm_mapping[match(TRT01A, names(arm_mapping))]), TRT01A = factor(TRT01A) %>% reorder(TRTORD) %make_label% "Actual Treatment for Period 01", LOQFL = LOQFL %make_label% "Limit of Quantification", AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN) %make_label% "Analysis Visit Window Code", AVISITCDN = AVISITCDN %make_label% "Analysis Visit Window Code (N)", BASE2 = BASE2 %make_label% "Screening Value", CHG2 = CHG2 %make_label% "Absolute Change from Screening", PCHG2 = PCHG2 %make_label% "Percent Change from Screening") ADLB_LOQS <- goshawk:::h_identify_loq_values(ADLB, flag_var = "LOQFL") ADLB <- left_join(ADLB, ADLB_LOQS, by = "PARAM") stopifnot(rlang::hash(ADSL) == "6a0d37a2dc4a81d7ff62dd65e4bc324a") stopifnot(rlang::hash(ADLB) == "3c8a641b2a2df269fbc095cacf0a4360") ADSL <- dplyr::filter(ADSL, ACTARM == "C: Combination") ADLB <- dplyr::inner_join(x = ADLB, y = ADSL[, c("STUDYID", "USUBJID"), drop = FALSE], by = c("STUDYID", "USUBJID")) ANL <- ADLB ANL <- ADLB %>% dplyr::filter(PARAMCD == "ALT") keep_index <- which(c(-2, 0, 7, 14, 21, 28, 35) %in% ANL[["AVISITCDN"]]) xtick <- (c(-2, 0, 7, 14, 21, 28, 35))[keep_index] xlabel <- (c("Screening", "Baseline", "Week 1", "Week 2", "Week 3", "Week 4", "Week 5"))[keep_index] p <- goshawk::g_lineplot(data = ANL[stats::complete.cases(ANL[, c("AVAL", "AVISITCDN")]), ], biomarker_var = "PARAMCD", biomarker_var_label = "PARAM", biomarker = "ALT", value_var = "AVAL", ylim = c(14.125, 24.575), trt_group = "TRT01A", trt_group_level = NULL, shape = "SEX", shape_type = c(F = "circle", M = "square"), time = "AVISITCDN", time_level = NULL, color_manual = c(`Drug X 100mg` = "#1e90ff", `Combination 100mg` = "#bb9990", Placebo = "#ffa07a"), line_type = c(`Drug X 100mg` = "solid", `Combination 100mg` = "solid", Placebo = "solid"), median = FALSE, hline_arb = numeric(0), hline_arb_label = character(0), hline_arb_color = character(0), xtick = xtick, xlabel = xlabel, rotate_xlab = TRUE, plot_height = 1000, plot_font_size = 12, dot_size = 2, dodge = 0.4, count_threshold = 0, table_font_size = 12, display_center_tbl = TRUE) print(p) ```