|
1 | | -#' Scatterplot and regression model |
| 1 | +#' `teal` module: Scatterplot and regression analysis |
2 | 2 | #' |
3 | 3 | #' Module for visualizing regression analysis, including scatterplots and |
4 | 4 | #' various regression diagnostics plots. |
|
14 | 14 | #' Regressor variables from an incoming dataset with filtering and selecting. |
15 | 15 | #' @param response (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
16 | 16 | #' Response variables from an incoming dataset with filtering and selecting. |
17 | | -#' @param alpha (`integer(1)` or `integer(3)`, optional) Specifies point opacity. |
18 | | -#' - When the length of `alpha` is one: the plot points will have a fixed opacity. |
19 | | -#' - When the length of `alpha` is three: the plot points opacity are dynamically adjusted based on |
20 | | -#' vector of `value`, `min`, and `max`. |
21 | | -#' @param size (`integer(1)` or `integer(3)`, optional) Specifies point size. |
22 | | -#' - When the length of `size` is one: the plot point sizes will have a fixed size. |
23 | | -#' - When the length of `size` is three: the plot points size are dynamically adjusted based on |
24 | | -#' vector of `value`, `min`, and `max`. |
25 | 17 | #' @param default_outlier_label (`character`, optional) The default column selected to label outliers. |
26 | 18 | #' @param default_plot_type (`numeric`, optional) Defaults to Response vs Regressor. |
27 | 19 | #' 1. Response vs Regressor |
|
31 | 23 | #' 5. Cook's distance |
32 | 24 | #' 6. Residuals vs Leverage |
33 | 25 | #' 7. Cook's dist vs Leverage |
| 26 | +#' @param label_segment_threshold (`numeric(1)` or `numeric(3)`) |
| 27 | +#' Minimum distance between label and point on the plot that triggers the creation of |
| 28 | +#' a line segment between the two. |
| 29 | +#' This may happen when the label cannot be placed next to the point as it overlaps another |
| 30 | +#' label or point. |
| 31 | +#' The value is used as the `min.segment.length` parameter to the [ggrepel::geom_text_repel()] function. |
| 32 | +#' |
| 33 | +#' It can take the following forms: |
| 34 | +#' - `numeric(1)`: Fixed value used for the minimum distance and the slider is not presented in the UI. |
| 35 | +#' - `numeric(3)`: A slider is presented in the UI (under "Plot settings") to adjust the minimum distance dynamically. |
| 36 | +#' |
| 37 | +#' It takes the form of `c(value, min, max)` and it is passed to the `value_min_max` |
| 38 | +#' argument in `teal.widgets::optionalSliderInputValMinMax`. |
34 | 39 | #' |
35 | 40 | #' @templateVar ggnames `r regression_names` |
36 | 41 | #' @template ggplot2_args_multi |
37 | 42 | #' |
| 43 | +#' @inherit shared_params return |
| 44 | +#' |
38 | 45 | #' @examples |
39 | 46 | #' # general data example |
40 | 47 | #' library(teal.widgets) |
@@ -141,26 +148,32 @@ tm_a_regression <- function(label = "Regression Analysis", |
141 | 148 | pre_output = NULL, |
142 | 149 | post_output = NULL, |
143 | 150 | default_plot_type = 1, |
144 | | - default_outlier_label = "USUBJID") { |
| 151 | + default_outlier_label = "USUBJID", |
| 152 | + label_segment_threshold = c(0.5, 0, 10)) { |
145 | 153 | logger::log_info("Initializing tm_a_regression") |
146 | 154 |
|
147 | | - # Normalizing data types |
| 155 | + # Normalize the parameters |
148 | 156 | if (inherits(regressor, "data_extract_spec")) regressor <- list(regressor) |
149 | 157 | if (inherits(response, "data_extract_spec")) response <- list(response) |
150 | 158 | if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) |
151 | 159 |
|
152 | 160 | # Start of assertions |
153 | 161 | checkmate::assert_string(label) |
154 | 162 | checkmate::assert_list(regressor, types = "data_extract_spec") |
| 163 | + |
155 | 164 | checkmate::assert_list(response, types = "data_extract_spec") |
156 | 165 | assert_single_selection(response) |
157 | 166 |
|
158 | 167 | checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
159 | 168 | checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 169 | + |
160 | 170 | checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
161 | 171 | checkmate::assert_numeric( |
162 | 172 | plot_width[1], |
163 | | - lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 173 | + lower = plot_width[2], |
| 174 | + upper = plot_width[3], |
| 175 | + null.ok = TRUE, |
| 176 | + .var.name = "plot_width" |
164 | 177 | ) |
165 | 178 |
|
166 | 179 | if (length(alpha) == 1) { |
@@ -188,8 +201,20 @@ tm_a_regression <- function(label = "Regression Analysis", |
188 | 201 |
|
189 | 202 | checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) |
190 | 203 | checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) |
191 | | - checkmate::assert_choice(default_plot_type, seq.int(1, length(plot_choices))) |
| 204 | + checkmate::assert_choice(default_plot_type, seq.int(1L, length(plot_choices))) |
192 | 205 | checkmate::assert_string(default_outlier_label) |
| 206 | + |
| 207 | + if (length(label_segment_threshold) == 1) { |
| 208 | + checkmate::assert_numeric(label_segment_threshold, any.missing = FALSE, finite = TRUE) |
| 209 | + } else { |
| 210 | + checkmate::assert_numeric(label_segment_threshold, len = 3, any.missing = FALSE, finite = TRUE) |
| 211 | + checkmate::assert_numeric( |
| 212 | + label_segment_threshold[1], |
| 213 | + lower = label_segment_threshold[2], |
| 214 | + upper = label_segment_threshold[3], |
| 215 | + .var.name = "label_segment_threshold" |
| 216 | + ) |
| 217 | + } |
193 | 218 | # End of assertions |
194 | 219 |
|
195 | 220 | # Make UI args |
@@ -287,6 +312,29 @@ ui_a_regression <- function(id, ...) { |
287 | 312 | title = "Plot settings", |
288 | 313 | teal.widgets::optionalSliderInputValMinMax(ns("alpha"), "Opacity:", args$alpha, ticks = FALSE), |
289 | 314 | teal.widgets::optionalSliderInputValMinMax(ns("size"), "Points size:", args$size, ticks = FALSE), |
| 315 | + teal.widgets::optionalSliderInputValMinMax( |
| 316 | + inputId = ns("label_min_segment"), |
| 317 | + label = div( |
| 318 | + class = "teal-tooltip", |
| 319 | + tagList( |
| 320 | + "Label min. segment:", |
| 321 | + icon("circle-info"), |
| 322 | + span( |
| 323 | + class = "tooltiptext", |
| 324 | + paste( |
| 325 | + "Use the slider to choose the cut-off value to define minimum distance between label and point", |
| 326 | + "that generates a line segment.", |
| 327 | + "It's only valid when 'Display outlier labels' is checked." |
| 328 | + ) |
| 329 | + ) |
| 330 | + ) |
| 331 | + ), |
| 332 | + value_min_max = args$label_segment_threshold, |
| 333 | + # Extra parameters to sliderInput |
| 334 | + ticks = FALSE, |
| 335 | + step = .1, |
| 336 | + round = FALSE |
| 337 | + ), |
290 | 338 | selectInput( |
291 | 339 | inputId = ns("ggtheme"), |
292 | 340 | label = "Theme (by ggplot):", |
@@ -467,10 +515,23 @@ srv_a_regression <- function(id, |
467 | 515 | ) |
468 | 516 | }) |
469 | 517 |
|
| 518 | + label_min_segment <- reactive({ |
| 519 | + input$label_min_segment |
| 520 | + }) |
| 521 | + |
470 | 522 | outlier_label <- reactive({ |
471 | 523 | substitute( |
472 | | - expr = geom_text(label = label_col, hjust = 0, vjust = 1, color = "red"), |
473 | | - env = list(label_col = label_col()) |
| 524 | + expr = ggrepel::geom_text_repel( |
| 525 | + label = label_col, |
| 526 | + color = "red", |
| 527 | + hjust = 0, |
| 528 | + vjust = 1, |
| 529 | + max.overlaps = Inf, |
| 530 | + min.segment.length = label_min_segment, |
| 531 | + segment.alpha = 0.5, |
| 532 | + seed = 123 |
| 533 | + ), |
| 534 | + env = list(label_col = label_col(), label_min_segment = label_min_segment()) |
474 | 535 | ) |
475 | 536 | }) |
476 | 537 |
|
@@ -638,16 +699,20 @@ srv_a_regression <- function(id, |
638 | 699 | plot <- substitute( |
639 | 700 | expr = plot + |
640 | 701 | stat_qq( |
641 | | - geom = "text", |
| 702 | + geom = ggrepel::GeomTextRepel, |
642 | 703 | label = label_col %>% |
643 | 704 | data.frame(label = .) %>% |
644 | 705 | dplyr::filter(label != "cooksd == NaN") %>% |
645 | 706 | unlist(), |
| 707 | + color = "red", |
646 | 708 | hjust = 0, |
647 | | - vjust = 1, |
648 | | - color = "red" |
| 709 | + vjust = 0, |
| 710 | + max.overlaps = Inf, |
| 711 | + min.segment.length = label_min_segment, |
| 712 | + segment.alpha = .5, |
| 713 | + seed = 123 |
649 | 714 | ), |
650 | | - env = list(plot = plot, label_col = label_col()) |
| 715 | + env = list(plot = plot, label_col = label_col(), label_min_segment = label_min_segment()) |
651 | 716 | ) |
652 | 717 | } |
653 | 718 |
|
|
0 commit comments