| 1 |
#' Variable Browser Teal Module |
|
| 2 |
#' |
|
| 3 |
#' The variable browser provides a table with variable names and labels and a |
|
| 4 |
#' plot that visualizes the content of a particular variable. |
|
| 5 |
#' |
|
| 6 |
#' @details Numeric columns with fewer than 30 distinct values can be treated as either factors |
|
| 7 |
#' or numbers with a checkbox allowing users to switch how they are treated (if < 6 unique values |
|
| 8 |
#' then the default is categorical, otherwise it is numeric). |
|
| 9 |
#' |
|
| 10 |
#' @inheritParams teal::module |
|
| 11 |
#' @inheritParams shared_params |
|
| 12 |
#' @param parent_dataname (`character(1)`) If this `dataname` exists in `datasets_selected` |
|
| 13 |
#' then an extra checkbox will be shown to allow users to not show variables in other datasets |
|
| 14 |
#' which exist in this `dataname`. |
|
| 15 |
#' This is typically used to remove `ADSL` columns in `CDISC` data. In non `CDISC` data this |
|
| 16 |
#' can be ignored. Defaults to `"ADSL"`. |
|
| 17 |
#' @param datasets_selected (`character`) A vector of datasets which should be |
|
| 18 |
#' shown and in what order. Names in the vector have to correspond with datasets names. |
|
| 19 |
#' If vector of length zero (default) then all datasets are shown. |
|
| 20 |
#' |
|
| 21 |
#' @aliases |
|
| 22 |
#' tm_variable_browser_ui, |
|
| 23 |
#' tm_variable_browser_srv, |
|
| 24 |
#' tm_variable_browser, |
|
| 25 |
#' variable_browser_ui, |
|
| 26 |
#' variable_browser_srv, |
|
| 27 |
#' variable_browser |
|
| 28 |
#' |
|
| 29 |
#' |
|
| 30 |
#' @export |
|
| 31 |
#' |
|
| 32 |
#' @examples |
|
| 33 |
#' |
|
| 34 |
#' ADSL <- teal.modules.general::rADSL |
|
| 35 |
#' ADTTE <- teal.modules.general::rADTTE |
|
| 36 |
#' |
|
| 37 |
#' app <- teal::init( |
|
| 38 |
#' data = teal.data::cdisc_data( |
|
| 39 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 40 |
#' teal.data::cdisc_dataset("ADTTE", ADTTE, code = "ADTTE <- teal.modules.general::rADTTE"),
|
|
| 41 |
#' check = TRUE |
|
| 42 |
#' ), |
|
| 43 |
#' modules( |
|
| 44 |
#' teal.modules.general::tm_variable_browser( |
|
| 45 |
#' label = "Variable browser", |
|
| 46 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 47 |
#' labs = list(subtitle = "Plot generated by Variable Browser Module") |
|
| 48 |
#' ), |
|
| 49 |
#' ) |
|
| 50 |
#' ) |
|
| 51 |
#' ) |
|
| 52 |
#' if (interactive()) {
|
|
| 53 |
#' shinyApp(app$ui, app$server) |
|
| 54 |
#' } |
|
| 55 |
tm_variable_browser <- function(label = "Variable Browser", |
|
| 56 |
datasets_selected = character(0), |
|
| 57 |
parent_dataname = "ADSL", |
|
| 58 |
pre_output = NULL, |
|
| 59 |
post_output = NULL, |
|
| 60 |
ggplot2_args = teal.widgets::ggplot2_args()) {
|
|
| 61 | ! |
logger::log_info("Initializing tm_variable_browser")
|
| 62 | ! |
if (!requireNamespace("sparkline", quietly = TRUE)) {
|
| 63 | ! |
stop("Cannot load sparkline - please install the package or restart your session.")
|
| 64 |
} |
|
| 65 | ! |
if (!requireNamespace("htmlwidgets", quietly = TRUE)) {
|
| 66 | ! |
stop("Cannot load htmlwidgets - please install the package or restart your session.")
|
| 67 |
} |
|
| 68 | ! |
if (!requireNamespace("jsonlite", quietly = TRUE)) {
|
| 69 | ! |
stop("Cannot load jsonlite - please install the package or restart your session.")
|
| 70 |
} |
|
| 71 | ! |
checkmate::assert_string(label) |
| 72 | ! |
checkmate::assert_character(datasets_selected) |
| 73 | ! |
checkmate::assert_character(parent_dataname, min.len = 0, max.len = 1) |
| 74 | ! |
checkmate::assert_class(ggplot2_args, "ggplot2_args") |
| 75 | ! |
datasets_selected <- unique(datasets_selected) |
| 76 | ||
| 77 | ! |
module( |
| 78 | ! |
label, |
| 79 | ! |
server = srv_variable_browser, |
| 80 | ! |
ui = ui_variable_browser, |
| 81 | ! |
datanames = "all", |
| 82 | ! |
server_args = list( |
| 83 | ! |
datasets_selected = datasets_selected, |
| 84 | ! |
parent_dataname = parent_dataname, |
| 85 | ! |
ggplot2_args = ggplot2_args |
| 86 |
), |
|
| 87 | ! |
ui_args = list( |
| 88 | ! |
datasets_selected = datasets_selected, |
| 89 | ! |
parent_dataname = parent_dataname, |
| 90 | ! |
pre_output = pre_output, |
| 91 | ! |
post_output = post_output |
| 92 |
) |
|
| 93 |
) |
|
| 94 |
} |
|
| 95 | ||
| 96 |
# ui function |
|
| 97 |
ui_variable_browser <- function(id, |
|
| 98 |
data, |
|
| 99 |
datasets_selected, |
|
| 100 |
parent_dataname, |
|
| 101 |
pre_output = NULL, |
|
| 102 |
post_output = NULL) {
|
|
| 103 | ! |
ns <- NS(id) |
| 104 | ||
| 105 | ! |
datanames <- names(data) |
| 106 | ||
| 107 | ! |
if (!identical(datasets_selected, character(0))) {
|
| 108 | ! |
stopifnot(all(datasets_selected %in% datanames)) |
| 109 | ! |
datanames <- datasets_selected |
| 110 |
} |
|
| 111 | ||
| 112 | ! |
shiny::tagList( |
| 113 | ! |
include_css_files("custom"),
|
| 114 | ! |
shinyjs::useShinyjs(), |
| 115 | ! |
teal.widgets::standard_layout( |
| 116 | ! |
output = fluidRow( |
| 117 | ! |
htmlwidgets::getDependency("sparkline"), # needed for sparklines to work
|
| 118 | ! |
column( |
| 119 | ! |
6, |
| 120 |
# variable browser |
|
| 121 | ! |
teal.widgets::white_small_well( |
| 122 | ! |
do.call( |
| 123 | ! |
tabsetPanel, |
| 124 | ! |
c( |
| 125 | ! |
id = ns("tabset_panel"),
|
| 126 | ! |
do.call( |
| 127 | ! |
tagList, |
| 128 | ! |
lapply(datanames, function(dataname) {
|
| 129 | ! |
tabPanel( |
| 130 | ! |
dataname, |
| 131 | ! |
div( |
| 132 | ! |
class = "mt-4", |
| 133 | ! |
textOutput(ns(paste0("dataset_summary_", dataname)))
|
| 134 |
), |
|
| 135 | ! |
div( |
| 136 | ! |
class = "mt-4", |
| 137 | ! |
teal.widgets::get_dt_rows( |
| 138 | ! |
ns(paste0( |
| 139 | ! |
"variable_browser_", dataname |
| 140 |
)), |
|
| 141 | ! |
ns( |
| 142 | ! |
paste0("variable_browser_", dataname, "_rows")
|
| 143 |
) |
|
| 144 |
), |
|
| 145 | ! |
DT::dataTableOutput(ns(paste0( |
| 146 | ! |
"variable_browser_", dataname |
| 147 | ! |
)), width = "100%") |
| 148 |
) |
|
| 149 |
) |
|
| 150 |
}) |
|
| 151 |
) |
|
| 152 |
) |
|
| 153 |
), |
|
| 154 | ! |
shinyjs::hidden({
|
| 155 | ! |
checkboxInput(ns("show_parent_vars"), "Show parent dataset variables", value = FALSE)
|
| 156 |
}) |
|
| 157 |
) |
|
| 158 |
), |
|
| 159 | ! |
column( |
| 160 | ! |
6, |
| 161 | ! |
teal.widgets::white_small_well( |
| 162 |
### Reporter |
|
| 163 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 164 |
### |
|
| 165 | ! |
div( |
| 166 | ! |
class = "block", |
| 167 | ! |
uiOutput(ns("ui_histogram_display"))
|
| 168 |
), |
|
| 169 | ! |
div( |
| 170 | ! |
class = "block", |
| 171 | ! |
uiOutput(ns("ui_numeric_display"))
|
| 172 |
), |
|
| 173 | ! |
teal.widgets::plot_with_settings_ui(ns("variable_plot")),
|
| 174 | ! |
br(), |
| 175 |
# input user-defined text size |
|
| 176 | ! |
teal.widgets::panel_item( |
| 177 | ! |
title = "Plot settings", |
| 178 | ! |
collapsed = TRUE, |
| 179 | ! |
selectInput( |
| 180 | ! |
inputId = ns("ggplot_theme"), label = "ggplot2 theme",
|
| 181 | ! |
choices = c( |
| 182 | ! |
"gray", "bw", "linedraw", "light", |
| 183 | ! |
"dark", "minimal", "classic", "void", "test" |
| 184 |
), |
|
| 185 | ! |
selected = "grey" |
| 186 |
), |
|
| 187 | ! |
fluidRow( |
| 188 | ! |
column(6, sliderInput( |
| 189 | ! |
inputId = ns("font_size"), label = "font size",
|
| 190 | ! |
min = 5L, max = 30L, value = 15L, step = 1L, ticks = FALSE |
| 191 |
)), |
|
| 192 | ! |
column(6, sliderInput( |
| 193 | ! |
inputId = ns("label_rotation"), label = "rotate x labels",
|
| 194 | ! |
min = 0L, max = 90L, value = 45L, step = 1, ticks = FALSE |
| 195 |
)) |
|
| 196 |
) |
|
| 197 |
), |
|
| 198 | ! |
br(), |
| 199 | ! |
teal.widgets::get_dt_rows(ns("variable_summary_table"), ns("variable_summary_table_rows")),
|
| 200 | ! |
DT::dataTableOutput(ns("variable_summary_table"))
|
| 201 |
) |
|
| 202 |
) |
|
| 203 |
), |
|
| 204 | ! |
pre_output = pre_output, |
| 205 | ! |
post_output = post_output |
| 206 |
) |
|
| 207 |
) |
|
| 208 |
} |
|
| 209 | ||
| 210 |
srv_variable_browser <- function(id, |
|
| 211 |
data, |
|
| 212 |
reporter, |
|
| 213 |
filter_panel_api, |
|
| 214 |
datasets_selected, parent_dataname, ggplot2_args) {
|
|
| 215 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 216 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 217 | ! |
checkmate::assert_class(data, "tdata") |
| 218 | ! |
moduleServer(id, function(input, output, session) {
|
| 219 |
# if there are < this number of unique records then a numeric |
|
| 220 |
# variable can be treated as a factor and all factors with < this groups |
|
| 221 |
# have their values plotted |
|
| 222 | ! |
.unique_records_for_factor <- 30 |
| 223 |
# if there are < this number of unique records then a numeric |
|
| 224 |
# variable is by default treated as a factor |
|
| 225 | ! |
.unique_records_default_as_factor <- 6 # nolint |
| 226 | ||
| 227 | ! |
datanames <- names(data) |
| 228 | ||
| 229 | ! |
checkmate::assert_character(datasets_selected) |
| 230 | ! |
checkmate::assert_subset(datasets_selected, datanames) |
| 231 | ! |
if (length(datasets_selected) != 0L) {
|
| 232 | ! |
datanames <- datasets_selected |
| 233 |
} |
|
| 234 | ||
| 235 |
# conditionally display checkbox |
|
| 236 | ! |
shinyjs::toggle( |
| 237 | ! |
id = "show_parent_vars", |
| 238 | ! |
condition = length(parent_dataname) > 0 && parent_dataname %in% datanames |
| 239 |
) |
|
| 240 | ||
| 241 | ! |
columns_names <- new.env() # nolint |
| 242 | ||
| 243 |
# plot_var$data holds the name of the currently selected dataset |
|
| 244 |
# plot_var$variable[[<dataset_name>]] holds the name of the currently selected |
|
| 245 |
# variable for dataset <dataset_name> |
|
| 246 | ! |
plot_var <- reactiveValues(data = NULL, variable = list()) |
| 247 | ||
| 248 | ! |
establish_updating_selection(datanames, input, plot_var, columns_names) |
| 249 | ||
| 250 |
# validations |
|
| 251 | ! |
validation_checks <- validate_input(input, plot_var, data) |
| 252 | ||
| 253 |
# data_for_analysis is a list with two elements: a column from a dataset and the column label |
|
| 254 | ! |
plotted_data <- reactive({
|
| 255 | ! |
validation_checks() |
| 256 | ||
| 257 | ! |
get_plotted_data(input, plot_var, data) |
| 258 |
}) |
|
| 259 | ||
| 260 | ! |
treat_numeric_as_factor <- reactive({
|
| 261 | ! |
if (is_num_var_short(.unique_records_for_factor, input, plotted_data)) {
|
| 262 | ! |
input$numeric_as_factor |
| 263 |
} else {
|
|
| 264 | ! |
FALSE |
| 265 |
} |
|
| 266 |
}) |
|
| 267 | ||
| 268 | ! |
render_tabset_panel_content( |
| 269 | ! |
input = input, |
| 270 | ! |
output = output, |
| 271 | ! |
data = data, |
| 272 | ! |
datanames = datanames, |
| 273 | ! |
parent_dataname = parent_dataname, |
| 274 | ! |
columns_names = columns_names, |
| 275 | ! |
plot_var = plot_var |
| 276 |
) |
|
| 277 |
# add used-defined text size to ggplot arguments passed from caller frame |
|
| 278 | ! |
all_ggplot2_args <- reactive({
|
| 279 | ! |
user_text <- teal.widgets::ggplot2_args( |
| 280 | ! |
theme = list( |
| 281 | ! |
"text" = ggplot2::element_text(size = input[["font_size"]]), |
| 282 | ! |
"axis.text.x" = ggplot2::element_text(angle = input[["label_rotation"]], hjust = 1) |
| 283 |
) |
|
| 284 |
) |
|
| 285 | ! |
user_theme <- utils::getFromNamespace(sprintf("theme_%s", input[["ggplot_theme"]]), ns = "ggplot2")
|
| 286 | ! |
user_theme <- user_theme() |
| 287 |
# temporary fix to circumvent assertion issue with resolve_ggplot2_args |
|
| 288 |
# drop problematic elements |
|
| 289 | ! |
user_theme <- user_theme[grep("strip.text.y.left", names(user_theme), fixed = TRUE, invert = TRUE)]
|
| 290 | ||
| 291 | ! |
teal.widgets::resolve_ggplot2_args( |
| 292 | ! |
user_plot = user_text, |
| 293 | ! |
user_default = teal.widgets::ggplot2_args(theme = user_theme), |
| 294 | ! |
module_plot = ggplot2_args |
| 295 |
) |
|
| 296 |
}) |
|
| 297 | ||
| 298 | ! |
output$ui_numeric_display <- renderUI({
|
| 299 | ! |
dataname <- input$tabset_panel |
| 300 | ! |
varname <- plot_var$variable[[input$tabset_panel]] |
| 301 | ! |
req(data, varname) |
| 302 | ||
| 303 | ! |
df <- data[[dataname]]() |
| 304 | ||
| 305 | ! |
numeric_ui <- tagList( |
| 306 | ! |
fluidRow( |
| 307 | ! |
div( |
| 308 | ! |
class = "col-md-4", |
| 309 | ! |
shinyWidgets::switchInput( |
| 310 | ! |
inputId = session$ns("display_density"),
|
| 311 | ! |
label = "Show density", |
| 312 | ! |
value = `if`(is.null(isolate(input$display_density)), TRUE, isolate(input$display_density)), |
| 313 | ! |
width = "50%", |
| 314 | ! |
labelWidth = "100px", |
| 315 | ! |
handleWidth = "50px" |
| 316 |
) |
|
| 317 |
), |
|
| 318 | ! |
div( |
| 319 | ! |
class = "col-md-4", |
| 320 | ! |
shinyWidgets::switchInput( |
| 321 | ! |
inputId = session$ns("remove_outliers"),
|
| 322 | ! |
label = "Remove outliers", |
| 323 | ! |
value = `if`(is.null(isolate(input$remove_outliers)), FALSE, isolate(input$remove_outliers)), |
| 324 | ! |
width = "50%", |
| 325 | ! |
labelWidth = "100px", |
| 326 | ! |
handleWidth = "50px" |
| 327 |
) |
|
| 328 |
), |
|
| 329 | ! |
div( |
| 330 | ! |
class = "col-md-4", |
| 331 | ! |
uiOutput(session$ns("outlier_definition_slider_ui"))
|
| 332 |
) |
|
| 333 |
), |
|
| 334 | ! |
div( |
| 335 | ! |
class = "ml-4", |
| 336 | ! |
uiOutput(session$ns("ui_density_help")),
|
| 337 | ! |
uiOutput(session$ns("ui_outlier_help"))
|
| 338 |
) |
|
| 339 |
) |
|
| 340 | ||
| 341 | ! |
if (is.numeric(df[[varname]])) {
|
| 342 | ! |
unique_entries <- length(unique(df[[varname]])) |
| 343 | ! |
if (unique_entries < .unique_records_for_factor && unique_entries > 0) {
|
| 344 | ! |
list( |
| 345 | ! |
checkboxInput( |
| 346 | ! |
session$ns("numeric_as_factor"),
|
| 347 | ! |
"Treat variable as factor", |
| 348 | ! |
value = `if`( |
| 349 | ! |
is.null(isolate(input$numeric_as_factor)), |
| 350 | ! |
unique_entries < .unique_records_default_as_factor, |
| 351 | ! |
isolate(input$numeric_as_factor) |
| 352 |
) |
|
| 353 |
), |
|
| 354 | ! |
conditionalPanel("!input.numeric_as_factor", ns = session$ns, numeric_ui)
|
| 355 |
) |
|
| 356 | ! |
} else if (unique_entries > 0) {
|
| 357 | ! |
numeric_ui |
| 358 |
} |
|
| 359 |
} else {
|
|
| 360 | ! |
NULL |
| 361 |
} |
|
| 362 |
}) |
|
| 363 | ||
| 364 | ! |
output$ui_histogram_display <- renderUI({
|
| 365 | ! |
dataname <- input$tabset_panel |
| 366 | ! |
varname <- plot_var$variable[[input$tabset_panel]] |
| 367 | ! |
req(data, varname) |
| 368 | ||
| 369 | ! |
df <- data[[dataname]]() |
| 370 | ||
| 371 | ! |
numeric_ui <- tagList(fluidRow( |
| 372 | ! |
div( |
| 373 | ! |
class = "col-md-4", |
| 374 | ! |
shinyWidgets::switchInput( |
| 375 | ! |
inputId = session$ns("remove_NA_hist"),
|
| 376 | ! |
label = "Remove NA values", |
| 377 | ! |
value = FALSE, |
| 378 | ! |
width = "50%", |
| 379 | ! |
labelWidth = "100px", |
| 380 | ! |
handleWidth = "50px" |
| 381 |
) |
|
| 382 |
) |
|
| 383 |
)) |
|
| 384 | ||
| 385 | ! |
var <- df[[varname]] |
| 386 | ! |
if (anyNA(var) && (is.factor(var) || is.character(var) || is.logical(var))) {
|
| 387 | ! |
groups <- unique(as.character(var)) |
| 388 | ! |
len_groups <- length(groups) |
| 389 | ! |
if (len_groups >= .unique_records_for_factor) {
|
| 390 | ! |
NULL |
| 391 |
} else {
|
|
| 392 | ! |
numeric_ui |
| 393 |
} |
|
| 394 |
} else {
|
|
| 395 | ! |
NULL |
| 396 |
} |
|
| 397 |
}) |
|
| 398 | ||
| 399 | ! |
output$outlier_definition_slider_ui <- renderUI({
|
| 400 | ! |
req(input$remove_outliers) |
| 401 | ! |
sliderInput( |
| 402 | ! |
inputId = session$ns("outlier_definition_slider"),
|
| 403 | ! |
div( |
| 404 | ! |
class = "teal-tooltip", |
| 405 | ! |
tagList( |
| 406 | ! |
"Outlier definition:", |
| 407 | ! |
icon("circle-info"),
|
| 408 | ! |
span( |
| 409 | ! |
class = "tooltiptext", |
| 410 | ! |
paste( |
| 411 | ! |
"Use the slider to choose the cut-off value to define outliers; the larger the value the", |
| 412 | ! |
"further below Q1/above Q3 points have to be in order to be classed as outliers" |
| 413 |
) |
|
| 414 |
) |
|
| 415 |
) |
|
| 416 |
), |
|
| 417 | ! |
min = 1, |
| 418 | ! |
max = 5, |
| 419 | ! |
value = 3, |
| 420 | ! |
step = 0.5 |
| 421 |
) |
|
| 422 |
}) |
|
| 423 | ||
| 424 | ! |
output$ui_density_help <- renderUI({
|
| 425 | ! |
req(is.logical(input$display_density)) |
| 426 | ! |
if (input$display_density) {
|
| 427 | ! |
tags$small(helpText(paste( |
| 428 | ! |
"Kernel density estimation with gaussian kernel", |
| 429 | ! |
"and bandwidth function bw.nrd0 (R default)" |
| 430 |
))) |
|
| 431 |
} else {
|
|
| 432 | ! |
NULL |
| 433 |
} |
|
| 434 |
}) |
|
| 435 | ||
| 436 | ! |
output$ui_outlier_help <- renderUI({
|
| 437 | ! |
req(is.logical(input$remove_outliers), input$outlier_definition_slider) |
| 438 | ! |
if (input$remove_outliers) {
|
| 439 | ! |
tags$small( |
| 440 | ! |
helpText( |
| 441 | ! |
withMathJax(paste0( |
| 442 | ! |
"Outlier data points (\\( X \\lt Q1 - ", input$outlier_definition_slider, "\\times IQR \\) or |
| 443 | ! |
\\(Q3 + ", input$outlier_definition_slider, "\\times IQR \\lt X\\)) |
| 444 | ! |
have not been displayed on the graph and will not be used for any kernel density estimations, ", |
| 445 | ! |
"although their values remain in the statisics table below." |
| 446 |
)) |
|
| 447 |
) |
|
| 448 |
) |
|
| 449 |
} else {
|
|
| 450 | ! |
NULL |
| 451 |
} |
|
| 452 |
}) |
|
| 453 | ||
| 454 | ||
| 455 | ! |
variable_plot_r <- reactive({
|
| 456 | ! |
display_density <- `if`(is.null(input$display_density), FALSE, input$display_density) |
| 457 | ! |
remove_outliers <- `if`(is.null(input$remove_outliers), FALSE, input$remove_outliers) |
| 458 | ||
| 459 | ! |
if (remove_outliers) {
|
| 460 | ! |
req(input$outlier_definition_slider) |
| 461 | ! |
outlier_definition <- as.numeric(input$outlier_definition_slider) |
| 462 |
} else {
|
|
| 463 | ! |
outlier_definition <- 0 |
| 464 |
} |
|
| 465 | ||
| 466 | ! |
plot_var_summary( |
| 467 | ! |
var = plotted_data()$data, |
| 468 | ! |
var_lab = plotted_data()$var_description, |
| 469 | ! |
numeric_as_factor = treat_numeric_as_factor(), |
| 470 | ! |
remove_NA_hist = input$remove_NA_hist, |
| 471 | ! |
display_density = display_density, |
| 472 | ! |
outlier_definition = outlier_definition, |
| 473 | ! |
records_for_factor = .unique_records_for_factor, |
| 474 | ! |
ggplot2_args = all_ggplot2_args() |
| 475 |
) |
|
| 476 |
}) |
|
| 477 | ||
| 478 | ! |
pws <- teal.widgets::plot_with_settings_srv( |
| 479 | ! |
id = "variable_plot", |
| 480 | ! |
plot_r = variable_plot_r, |
| 481 | ! |
height = c(500, 200, 2000) |
| 482 |
) |
|
| 483 | ||
| 484 | ! |
output$variable_summary_table <- DT::renderDataTable({
|
| 485 | ! |
var_summary_table( |
| 486 | ! |
plotted_data()$data, |
| 487 | ! |
treat_numeric_as_factor(), |
| 488 | ! |
input$variable_summary_table_rows, |
| 489 | ! |
if (!is.null(input$remove_outliers) && input$remove_outliers) {
|
| 490 | ! |
req(input$outlier_definition_slider) |
| 491 | ! |
as.numeric(input$outlier_definition_slider) |
| 492 |
} else {
|
|
| 493 | ! |
0 |
| 494 |
} |
|
| 495 |
) |
|
| 496 |
}) |
|
| 497 | ||
| 498 |
### REPORTER |
|
| 499 | ! |
if (with_reporter) {
|
| 500 | ! |
card_fun <- function(comment) {
|
| 501 | ! |
card <- teal::TealReportCard$new() |
| 502 | ! |
card$set_name("Variable Browser Plot")
|
| 503 | ! |
card$append_text("Variable Browser Plot", "header2")
|
| 504 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 505 | ! |
card$append_text("Plot", "header3")
|
| 506 | ! |
card$append_plot(variable_plot_r(), dim = pws$dim()) |
| 507 | ! |
if (!comment == "") {
|
| 508 | ! |
card$append_text("Comment", "header3")
|
| 509 | ! |
card$append_text(comment) |
| 510 |
} |
|
| 511 | ! |
card |
| 512 |
} |
|
| 513 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 514 |
} |
|
| 515 |
### |
|
| 516 |
}) |
|
| 517 |
} |
|
| 518 | ||
| 519 |
#' Summarizes missings occurrence |
|
| 520 |
#' |
|
| 521 |
#' Summarizes missings occurrence in vector |
|
| 522 |
#' @param x vector of any type and length |
|
| 523 |
#' @return text describing \code{NA} occurrence.
|
|
| 524 |
#' @keywords internal |
|
| 525 |
var_missings_info <- function(x) {
|
|
| 526 | ! |
return(sprintf("%s [%s%%]", sum(is.na(x)), round(mean(is.na(x) * 100), 2)))
|
| 527 |
} |
|
| 528 | ||
| 529 |
#' S3 generic for \code{sparkline} widget HTML
|
|
| 530 |
#' |
|
| 531 |
#' Generates the \code{sparkline} HTML code corresponding to the input array.
|
|
| 532 |
#' For numeric variables creates a box plot, for character and factors - bar plot. |
|
| 533 |
#' Produces an empty string for variables of other types. |
|
| 534 |
#' |
|
| 535 |
#' @param arr vector of any type and length |
|
| 536 |
#' @param width \code{numeric} the width of the \code{sparkline} widget (pixels)
|
|
| 537 |
#' @param ... \code{list} additional options passed to bar plots of \code{jquery.sparkline}; see
|
|
| 538 |
#' \href{https://omnipotent.net/jquery.sparkline/#common}{\code{jquery.sparkline docs}}
|
|
| 539 |
#' |
|
| 540 |
#' @return character variable containing the HTML code of the \code{sparkline} HTML widget
|
|
| 541 |
#' @keywords internal |
|
| 542 |
#' |
|
| 543 |
create_sparklines <- function(arr, width = 150, ...) {
|
|
| 544 | ! |
if (all(is.null(arr))) {
|
| 545 | ! |
return("")
|
| 546 |
} |
|
| 547 | ! |
UseMethod("create_sparklines")
|
| 548 |
} |
|
| 549 | ||
| 550 |
#' Default method for \code{\link{create_sparklines}}
|
|
| 551 |
#' |
|
| 552 |
#' |
|
| 553 |
#' @export |
|
| 554 |
#' @keywords internal |
|
| 555 |
#' @rdname create_sparklines |
|
| 556 |
create_sparklines.default <- function(arr, width = 150, ...) { # nolint
|
|
| 557 | ! |
return(as.character(tags$code("unsupported variable type", class = "text-blue")))
|
| 558 |
} |
|
| 559 | ||
| 560 |
#' Generates the HTML code for the \code{sparkline} widget
|
|
| 561 |
#' |
|
| 562 |
#' @param bar_spacing \code{numeric} the spacing between the bars (in pixels)
|
|
| 563 |
#' @param bar_width \code{numeric} the width of the bars (in pixels)
|
|
| 564 |
#' |
|
| 565 |
#' @return \code{character} with HTML code for the \code{sparkline} widget
|
|
| 566 |
#' |
|
| 567 |
#' @export |
|
| 568 |
#' @keywords internal |
|
| 569 |
#' @rdname create_sparklines |
|
| 570 |
create_sparklines.Date <- function(arr, width = 150, bar_spacing = 5, bar_width = 20, ...) { # nolint
|
|
| 571 | ! |
arr_num <- as.numeric(arr) |
| 572 | ! |
arr_num <- sort(arr_num, decreasing = FALSE, method = "radix") |
| 573 | ! |
binwidth <- get_bin_width(arr_num, 1) |
| 574 | ! |
bins <- floor(diff(range(arr_num)) / binwidth) + 1 |
| 575 | ! |
if (all(is.na(bins))) {
|
| 576 | ! |
return(as.character(tags$code("only NA", class = "text-blue")))
|
| 577 | ! |
} else if (bins == 1) {
|
| 578 | ! |
return(as.character(tags$code("one date", class = "text-blue")))
|
| 579 |
} |
|
| 580 | ! |
counts <- as.vector(unname(base::table(cut(arr_num, breaks = bins)))) |
| 581 | ! |
max_value <- max(counts) |
| 582 | ||
| 583 | ! |
start_bins <- as.integer(seq(1, length(arr_num), length.out = bins)) |
| 584 | ! |
labels_start <- as.character(as.Date(arr_num[start_bins], origin = as.Date("1970-01-01")))
|
| 585 | ! |
labels <- paste("Start:", labels_start)
|
| 586 | ||
| 587 | ! |
sparkline::spk_chr( |
| 588 | ! |
unname(counts), |
| 589 | ! |
type = "bar", |
| 590 | ! |
chartRangeMin = 0, |
| 591 | ! |
chartRangeMax = max_value, |
| 592 | ! |
width = width, |
| 593 | ! |
barWidth = bar_width, |
| 594 | ! |
barSpacing = bar_spacing, |
| 595 | ! |
tooltipFormatter = custom_sparkline_formatter(labels, counts) |
| 596 |
) |
|
| 597 |
} |
|
| 598 | ||
| 599 |
#' Generates the HTML code for the \code{sparkline} widget
|
|
| 600 |
#' |
|
| 601 |
#' |
|
| 602 |
#' @param bar_spacing \code{numeric} the spacing between the bars (in pixels)
|
|
| 603 |
#' @param bar_width \code{numeric} the width of the bars (in pixels)
|
|
| 604 |
#' |
|
| 605 |
#' @return \code{character} with HTML code for the \code{sparkline} widget
|
|
| 606 |
#' |
|
| 607 |
#' @export |
|
| 608 |
#' @keywords internal |
|
| 609 |
#' @rdname create_sparklines |
|
| 610 |
create_sparklines.POSIXct <- function(arr, width = 150, bar_spacing = 5, bar_width = 20, ...) { # nolint
|
|
| 611 | ! |
arr_num <- as.numeric(arr) |
| 612 | ! |
arr_num <- sort(arr_num, decreasing = FALSE, method = "radix") |
| 613 | ! |
binwidth <- get_bin_width(arr_num, 1) |
| 614 | ! |
bins <- floor(diff(range(arr_num)) / binwidth) + 1 |
| 615 | ! |
if (all(is.na(bins))) {
|
| 616 | ! |
return(as.character(tags$code("only NA", class = "text-blue")))
|
| 617 | ! |
} else if (bins == 1) {
|
| 618 | ! |
return(as.character(tags$code("one date-time", class = "text-blue")))
|
| 619 |
} |
|
| 620 | ! |
counts <- as.vector(unname(base::table(cut(arr_num, breaks = bins)))) |
| 621 | ! |
max_value <- max(counts) |
| 622 | ||
| 623 | ! |
start_bins <- as.integer(seq(1, length(arr_num), length.out = bins)) |
| 624 | ! |
labels_start <- as.character(format(as.POSIXct(arr_num[start_bins], origin = as.Date("1970-01-01")), "%Y-%m-%d"))
|
| 625 | ! |
labels <- paste("Start:", labels_start)
|
| 626 | ||
| 627 | ! |
sparkline::spk_chr( |
| 628 | ! |
unname(counts), |
| 629 | ! |
type = "bar", |
| 630 | ! |
chartRangeMin = 0, |
| 631 | ! |
chartRangeMax = max_value, |
| 632 | ! |
width = width, |
| 633 | ! |
barWidth = bar_width, |
| 634 | ! |
barSpacing = bar_spacing, |
| 635 | ! |
tooltipFormatter = custom_sparkline_formatter(labels, counts) |
| 636 |
) |
|
| 637 |
} |
|
| 638 | ||
| 639 |
#' Generates the HTML code for the \code{sparkline} widget
|
|
| 640 |
#' |
|
| 641 |
#' |
|
| 642 |
#' @param bar_spacing \code{numeric} the spacing between the bars (in pixels)
|
|
| 643 |
#' @param bar_width \code{numeric} the width of the bars (in pixels)
|
|
| 644 |
#' |
|
| 645 |
#' @return \code{character} with HTML code for the \code{sparkline} widget
|
|
| 646 |
#' |
|
| 647 |
#' @export |
|
| 648 |
#' @keywords internal |
|
| 649 |
#' @rdname create_sparklines |
|
| 650 |
create_sparklines.POSIXlt <- function(arr, width = 150, bar_spacing = 5, bar_width = 20, ...) { # nolint
|
|
| 651 | ! |
arr_num <- as.numeric(arr) |
| 652 | ! |
arr_num <- sort(arr_num, decreasing = FALSE, method = "radix") |
| 653 | ! |
binwidth <- get_bin_width(arr_num, 1) |
| 654 | ! |
bins <- floor(diff(range(arr_num)) / binwidth) + 1 |
| 655 | ! |
if (all(is.na(bins))) {
|
| 656 | ! |
return(as.character(tags$code("only NA", class = "text-blue")))
|
| 657 | ! |
} else if (bins == 1) {
|
| 658 | ! |
return(as.character(tags$code("one date-time", class = "text-blue")))
|
| 659 |
} |
|
| 660 | ! |
counts <- as.vector(unname(base::table(cut(arr_num, breaks = bins)))) |
| 661 | ! |
max_value <- max(counts) |
| 662 | ||
| 663 | ! |
start_bins <- as.integer(seq(1, length(arr_num), length.out = bins)) |
| 664 | ! |
labels_start <- as.character(format(as.POSIXct(arr_num[start_bins], origin = as.Date("1970-01-01")), "%Y-%m-%d"))
|
| 665 | ! |
labels <- paste("Start:", labels_start)
|
| 666 | ||
| 667 | ! |
sparkline::spk_chr( |
| 668 | ! |
unname(counts), |
| 669 | ! |
type = "bar", |
| 670 | ! |
chartRangeMin = 0, |
| 671 | ! |
chartRangeMax = max_value, |
| 672 | ! |
width = width, |
| 673 | ! |
barWidth = bar_width, |
| 674 | ! |
barSpacing = bar_spacing, |
| 675 | ! |
tooltipFormatter = custom_sparkline_formatter(labels, counts) |
| 676 |
) |
|
| 677 |
} |
|
| 678 | ||
| 679 | ||
| 680 |
#' Generates the HTML code for the \code{sparkline} widget
|
|
| 681 |
#' |
|
| 682 |
#' Coerces a character vector to factor and delegates to the \code{create_sparklines.factor}
|
|
| 683 |
#' |
|
| 684 |
#' |
|
| 685 |
#' @return \code{character} with HTML code for the \code{sparkline} widget
|
|
| 686 |
#' |
|
| 687 |
#' @export |
|
| 688 |
#' @keywords internal |
|
| 689 |
#' @rdname create_sparklines |
|
| 690 |
create_sparklines.character <- function(arr, ...) { # nolint
|
|
| 691 | ! |
return(create_sparklines(as.factor(arr))) |
| 692 |
} |
|
| 693 | ||
| 694 | ||
| 695 |
#' Generates the HTML code for the \code{sparkline} widget
|
|
| 696 |
#' |
|
| 697 |
#' Coerces logical vector to factor and delegates to the \code{create_sparklines.factor}
|
|
| 698 |
#' |
|
| 699 |
#' |
|
| 700 |
#' @return \code{character} with HTML code for the \code{sparkline} widget
|
|
| 701 |
#' |
|
| 702 |
#' @export |
|
| 703 |
#' @keywords internal |
|
| 704 |
#' @rdname create_sparklines |
|
| 705 |
create_sparklines.logical <- function(arr, ...) { # nolint
|
|
| 706 | ! |
return(create_sparklines(as.factor(arr))) |
| 707 |
} |
|
| 708 | ||
| 709 | ||
| 710 |
#' Generates the \code{sparkline} HTML code
|
|
| 711 |
#' |
|
| 712 |
#' @param bar_spacing \code{numeric} spacing between the bars (in pixels)
|
|
| 713 |
#' @param bar_width \code{numeric} width of the bars (in pixels)
|
|
| 714 |
#' |
|
| 715 |
#' @return \code{character} with HTML code for the \code{sparkline} widget
|
|
| 716 |
#' |
|
| 717 |
#' @export |
|
| 718 |
#' @keywords internal |
|
| 719 |
#' @rdname create_sparklines |
|
| 720 |
create_sparklines.factor <- function(arr, width = 150, bar_spacing = 5, bar_width = 20, ...) { # nolint
|
|
| 721 | ! |
decreasing_order <- TRUE |
| 722 | ||
| 723 | ! |
counts <- table(arr) |
| 724 | ! |
if (length(counts) >= 100) {
|
| 725 | ! |
return(as.character(tags$code("> 99 levels", class = "text-blue")))
|
| 726 | ! |
} else if (length(counts) == 0) {
|
| 727 | ! |
return(as.character(tags$code("no levels", class = "text-blue")))
|
| 728 | ! |
} else if (length(counts) == 1) {
|
| 729 | ! |
return(as.character(tags$code("one level", class = "text-blue")))
|
| 730 |
} |
|
| 731 | ||
| 732 |
# Summarize the occurences of different levels |
|
| 733 |
# and get the maximum and minimum number of occurences |
|
| 734 |
# This is needed for the sparkline to correctly display the bar plots |
|
| 735 |
# Otherwise they are cropped |
|
| 736 | ! |
counts <- sort(counts, decreasing = decreasing_order, method = "radix") |
| 737 | ! |
max_value <- if (decreasing_order) counts[1] else counts[length[counts]] |
| 738 | ! |
max_value <- unname(max_value) |
| 739 | ||
| 740 | ! |
sparkline::spk_chr( |
| 741 | ! |
unname(counts), |
| 742 | ! |
type = "bar", |
| 743 | ! |
chartRangeMin = 0, |
| 744 | ! |
chartRangeMax = max_value, |
| 745 | ! |
width = width, |
| 746 | ! |
barWidth = bar_width, |
| 747 | ! |
barSpacing = bar_spacing, |
| 748 | ! |
tooltipFormatter = custom_sparkline_formatter(names(counts), as.vector(counts)) |
| 749 |
) |
|
| 750 |
} |
|
| 751 | ||
| 752 |
#' Generates the \code{sparkline} HTML code
|
|
| 753 |
#' |
|
| 754 |
#' |
|
| 755 |
#' @return \code{character} with HTML code for the \code{sparkline} widget
|
|
| 756 |
#' |
|
| 757 |
#' @export |
|
| 758 |
#' @keywords internal |
|
| 759 |
#' @rdname create_sparklines |
|
| 760 |
create_sparklines.numeric <- function(arr, width = 150, ...) { # nolint
|
|
| 761 | ! |
if (any(is.infinite(arr))) {
|
| 762 | ! |
return(as.character(tags$code("infinite values", class = "text-blue")))
|
| 763 |
} |
|
| 764 | ! |
if (length(arr) > 100000) {
|
| 765 | ! |
return(as.character(tags$code("Too many rows (>100000)", class = "text-blue")))
|
| 766 |
} |
|
| 767 | ||
| 768 | ! |
arr <- arr[!is.na(arr)] |
| 769 | ! |
res <- sparkline::spk_chr(unname(arr), type = "box", width = width, ...) |
| 770 | ! |
return(res) |
| 771 |
} |
|
| 772 | ||
| 773 |
#' Summarizes variable |
|
| 774 |
#' |
|
| 775 |
#' Creates html summary with statistics relevant to data type. For numeric values it returns central |
|
| 776 |
#' tendency measures, for factor returns level counts, for Date date range, for other just |
|
| 777 |
#' number of levels. |
|
| 778 |
#' @param x vector of any type |
|
| 779 |
#' @param numeric_as_factor \code{logical} should the numeric variable be treated as a factor
|
|
| 780 |
#' @param dt_rows \code{numeric} current/latest `DT` page length
|
|
| 781 |
#' @param outlier_definition If 0 no outliers are removed, otherwise |
|
| 782 |
#' outliers (those more than outlier_definition*IQR below/above Q1/Q3 be removed) |
|
| 783 |
#' @return text with simple statistics. |
|
| 784 |
#' @keywords internal |
|
| 785 |
var_summary_table <- function(x, numeric_as_factor, dt_rows, outlier_definition) {
|
|
| 786 | ! |
if (is.null(dt_rows)) {
|
| 787 | ! |
dt_rows <- 10 |
| 788 |
} |
|
| 789 | ! |
if (is.numeric(x) && !numeric_as_factor) {
|
| 790 | ! |
req(!any(is.infinite(x))) |
| 791 | ||
| 792 | ! |
x <- remove_outliers_from(x, outlier_definition) |
| 793 | ||
| 794 | ! |
qvals <- round(stats::quantile(x, na.rm = TRUE, probs = c(0.25, 0.5, 0.75), type = 2), 2) |
| 795 |
# classical central tendency measures |
|
| 796 | ||
| 797 | ! |
summary <- |
| 798 | ! |
data.frame( |
| 799 | ! |
Statistic = c("min", "Q1", "median", "mean", "Q3", "max", "sd", "n"),
|
| 800 | ! |
Value = c( |
| 801 | ! |
round(min(x, na.rm = TRUE), 2), |
| 802 | ! |
qvals[1], |
| 803 | ! |
qvals[2], |
| 804 | ! |
round(mean(x, na.rm = TRUE), 2), |
| 805 | ! |
qvals[3], |
| 806 | ! |
round(max(x, na.rm = TRUE), 2), |
| 807 | ! |
round(stats::sd(x, na.rm = TRUE), 2), |
| 808 | ! |
length(x[!is.na(x)]) |
| 809 |
) |
|
| 810 |
) |
|
| 811 | ||
| 812 | ! |
DT::datatable(summary, rownames = FALSE, options = list(dom = "<t>", pageLength = dt_rows)) |
| 813 | ! |
} else if (is.factor(x) || is.character(x) || (is.numeric(x) && numeric_as_factor) || is.logical(x)) {
|
| 814 |
# make sure factor is ordered numeric |
|
| 815 | ! |
if (is.numeric(x)) {
|
| 816 | ! |
x <- factor(x, levels = sort(unique(x))) |
| 817 |
} |
|
| 818 | ||
| 819 | ! |
level_counts <- table(x) |
| 820 | ! |
max_levels_signif <- nchar(level_counts) |
| 821 | ||
| 822 | ! |
if (!all(is.na(x))) {
|
| 823 | ! |
levels <- names(level_counts) |
| 824 | ! |
counts <- sprintf( |
| 825 | ! |
"%s [%.2f%%]", |
| 826 | ! |
format(level_counts, width = max_levels_signif), prop.table(level_counts) * 100 |
| 827 |
) |
|
| 828 |
} else {
|
|
| 829 | ! |
levels <- character(0) |
| 830 | ! |
counts <- numeric(0) |
| 831 |
} |
|
| 832 | ||
| 833 | ! |
summary <- data.frame( |
| 834 | ! |
Level = levels, |
| 835 | ! |
Count = counts, |
| 836 | ! |
stringsAsFactors = FALSE |
| 837 |
) |
|
| 838 | ||
| 839 |
# sort the dataset in decreasing order of counts (needed as character variables default to alphabetical) |
|
| 840 | ! |
summary <- summary[order(summary$Count, decreasing = TRUE), ] |
| 841 | ||
| 842 | ! |
dom_opts <- if (nrow(summary) <= 10) {
|
| 843 | ! |
"<t>" |
| 844 |
} else {
|
|
| 845 | ! |
"<lf<t>ip>" |
| 846 |
} |
|
| 847 | ! |
DT::datatable(summary, rownames = FALSE, options = list(dom = dom_opts, pageLength = dt_rows)) |
| 848 | ! |
} else if (inherits(x, "Date") || inherits(x, "POSIXct") || inherits(x, "POSIXlt")) {
|
| 849 | ! |
summary <- |
| 850 | ! |
data.frame( |
| 851 | ! |
Statistic = c("min", "median", "max"),
|
| 852 | ! |
Value = c( |
| 853 | ! |
min(x, na.rm = TRUE), |
| 854 | ! |
stats::median(x, na.rm = TRUE), |
| 855 | ! |
max(x, na.rm = TRUE) |
| 856 |
) |
|
| 857 |
) |
|
| 858 | ! |
DT::datatable(summary, rownames = FALSE, options = list(dom = "<t>", pageLength = dt_rows)) |
| 859 |
} else {
|
|
| 860 | ! |
NULL |
| 861 |
} |
|
| 862 |
} |
|
| 863 | ||
| 864 | ||
| 865 |
#' Plot variable |
|
| 866 |
#' |
|
| 867 |
#' Creates summary plot with statistics relevant to data type. |
|
| 868 |
#' @inheritParams shared_params |
|
| 869 |
#' @param var vector of any type to be plotted. For numeric variables it produces histogram with |
|
| 870 |
#' density line, for factors it creates frequency plot |
|
| 871 |
#' @param var_lab text describing selected variable to be displayed on the plot |
|
| 872 |
#' @param numeric_as_factor \code{logical} should the numeric variable be treated as a factor
|
|
| 873 |
#' @param display_density \code{logical} Should density estimation be displayed for numeric values?
|
|
| 874 |
#' @param remove_NA_hist \code{logical} Should \code{NA} values be removed for histogram of factor like variables.
|
|
| 875 |
#' @param outlier_definition If 0 no outliers are removed, otherwise |
|
| 876 |
#' outliers (those more than outlier_definition*IQR below/above Q1/Q3 be removed) |
|
| 877 |
#' @param records_for_factor \code{numeric} if the number of factor levels is >= than this value then
|
|
| 878 |
#' a graph of the factors isn't shown, only a list of values. |
|
| 879 |
#' @return plot |
|
| 880 |
#' @keywords internal |
|
| 881 |
plot_var_summary <- function(var, |
|
| 882 |
var_lab, |
|
| 883 |
numeric_as_factor, |
|
| 884 |
display_density = is.numeric(var), |
|
| 885 |
remove_NA_hist = FALSE, # nolint |
|
| 886 |
outlier_definition, |
|
| 887 |
records_for_factor, |
|
| 888 |
ggplot2_args) {
|
|
| 889 | ! |
checkmate::assert_flag(display_density) |
| 890 | ! |
checkmate::assert_class(ggplot2_args, "ggplot2_args") |
| 891 | ||
| 892 | ! |
grid::grid.newpage() |
| 893 | ||
| 894 | ! |
plot_main <- if (is.factor(var) || is.character(var) || is.logical(var)) {
|
| 895 | ! |
groups <- unique(as.character(var)) |
| 896 | ! |
len_groups <- length(groups) |
| 897 | ! |
if (len_groups >= records_for_factor) {
|
| 898 | ! |
grid::textGrob( |
| 899 | ! |
sprintf( |
| 900 | ! |
"%s unique values\n%s:\n %s\n ...\n %s", |
| 901 | ! |
len_groups, |
| 902 | ! |
var_lab, |
| 903 | ! |
paste(utils::head(groups), collapse = ",\n "), |
| 904 | ! |
paste(utils::tail(groups), collapse = ",\n ") |
| 905 |
), |
|
| 906 | ! |
x = grid::unit(1, "line"), |
| 907 | ! |
y = grid::unit(1, "npc") - grid::unit(1, "line"), |
| 908 | ! |
just = c("left", "top")
|
| 909 |
) |
|
| 910 |
} else {
|
|
| 911 | ! |
var <- if (isTRUE(remove_NA_hist)) as.vector(stats::na.omit(var)) else var |
| 912 | ! |
ggplot(data.frame(var), aes(x = forcats::fct_infreq(as.factor(var)))) + |
| 913 | ! |
geom_bar(stat = "count", aes(fill = ifelse(is.na(var), "withcolor", "")), show.legend = FALSE) + |
| 914 | ! |
scale_fill_manual(values = c("gray50", "tan"))
|
| 915 |
} |
|
| 916 | ! |
} else if (is.numeric(var)) {
|
| 917 | ! |
validate(need(any(!is.na(var)), "No data left to visualize.")) |
| 918 | ||
| 919 |
# Filter out NA |
|
| 920 | ! |
var <- var[which(!is.na(var))] |
| 921 | ||
| 922 | ! |
validate(need(!any(is.infinite(var)), "Cannot display graph when data includes infinite values")) |
| 923 | ||
| 924 | ! |
if (numeric_as_factor) {
|
| 925 | ! |
var <- factor(var, levels = sort(unique(var))) |
| 926 | ! |
p <- qplot(var) |
| 927 |
} else {
|
|
| 928 |
# remove outliers |
|
| 929 | ! |
if (outlier_definition != 0) {
|
| 930 | ! |
number_records <- length(var) |
| 931 | ! |
var <- remove_outliers_from(var, outlier_definition) |
| 932 | ! |
number_outliers <- number_records - length(var) |
| 933 | ! |
outlier_text <- paste0( |
| 934 | ! |
number_outliers, " outliers (",
|
| 935 | ! |
round(number_outliers / number_records * 100, 2), |
| 936 | ! |
"% of non-missing records) not shown" |
| 937 |
) |
|
| 938 | ! |
validate(need( |
| 939 | ! |
length(var) > 1, |
| 940 | ! |
"At least two data points must remain after removing outliers for this graph to be displayed" |
| 941 |
)) |
|
| 942 |
} |
|
| 943 |
## histogram |
|
| 944 | ! |
binwidth <- get_bin_width(var) |
| 945 | ! |
p <- ggplot(data = data.frame(var = var), aes(x = var, y = after_stat(count))) + |
| 946 | ! |
geom_histogram(binwidth = binwidth) + |
| 947 | ! |
scale_y_continuous( |
| 948 | ! |
sec.axis = sec_axis( |
| 949 | ! |
trans = ~ . / nrow(data.frame(var = var)), |
| 950 | ! |
labels = scales::percent, |
| 951 | ! |
name = "proportion (in %)" |
| 952 |
) |
|
| 953 |
) |
|
| 954 | ||
| 955 | ! |
if (display_density) {
|
| 956 | ! |
p <- p + geom_density(aes(y = after_stat(count * binwidth))) |
| 957 |
} |
|
| 958 | ||
| 959 | ! |
if (outlier_definition != 0) {
|
| 960 | ! |
p <- p + annotate( |
| 961 | ! |
geom = "text", |
| 962 | ! |
label = outlier_text, |
| 963 | ! |
x = Inf, y = Inf, |
| 964 | ! |
hjust = 1.02, vjust = 1.2, |
| 965 | ! |
color = "black", |
| 966 |
# explicitly modify geom text size according |
|
| 967 | ! |
size = ggplot2_args[["theme"]][["text"]][["size"]] / 3.5 |
| 968 |
) |
|
| 969 |
} |
|
| 970 | ! |
p |
| 971 |
} |
|
| 972 | ! |
} else if (inherits(var, "Date") || inherits(var, "POSIXct") || inherits(var, "POSIXlt")) {
|
| 973 | ! |
var_num <- as.numeric(var) |
| 974 | ! |
binwidth <- get_bin_width(var_num, 1) |
| 975 | ! |
p <- ggplot(data = data.frame(var = var), aes(x = var, y = after_stat(count))) + |
| 976 | ! |
geom_histogram(binwidth = binwidth) |
| 977 |
} else {
|
|
| 978 | ! |
grid::textGrob( |
| 979 | ! |
paste(strwrap( |
| 980 | ! |
utils::capture.output(utils::str(var)), |
| 981 | ! |
width = .9 * grid::convertWidth(grid::unit(1, "npc"), "char", TRUE) |
| 982 | ! |
), collapse = "\n"), |
| 983 | ! |
x = grid::unit(1, "line"), y = grid::unit(1, "npc") - grid::unit(1, "line"), just = c("left", "top")
|
| 984 |
) |
|
| 985 |
} |
|
| 986 | ||
| 987 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 988 | ! |
labs = list(x = var_lab) |
| 989 |
) |
|
| 990 |
### |
|
| 991 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 992 | ! |
ggplot2_args, |
| 993 | ! |
module_plot = dev_ggplot2_args |
| 994 |
) |
|
| 995 | ||
| 996 | ! |
if (is.ggplot(plot_main)) {
|
| 997 | ! |
if (is.numeric(var) && !numeric_as_factor) {
|
| 998 |
# numeric not as factor |
|
| 999 | ! |
plot_main <- plot_main + |
| 1000 | ! |
theme_light() + |
| 1001 | ! |
list( |
| 1002 | ! |
labs = do.call("labs", all_ggplot2_args$labs),
|
| 1003 | ! |
theme = do.call("theme", all_ggplot2_args$theme)
|
| 1004 |
) |
|
| 1005 |
} else {
|
|
| 1006 |
# factor low number of levels OR numeric as factor OR Date |
|
| 1007 | ! |
plot_main <- plot_main + |
| 1008 | ! |
theme_light() + |
| 1009 | ! |
list( |
| 1010 | ! |
labs = do.call("labs", all_ggplot2_args$labs),
|
| 1011 | ! |
theme = do.call("theme", all_ggplot2_args$theme)
|
| 1012 |
) |
|
| 1013 |
} |
|
| 1014 | ! |
plot_main <- ggplotGrob(plot_main) |
| 1015 |
} |
|
| 1016 | ||
| 1017 | ! |
grid::grid.draw(plot_main) |
| 1018 | ! |
plot_main |
| 1019 |
} |
|
| 1020 | ||
| 1021 |
#' Returns a short variable description. |
|
| 1022 |
#' |
|
| 1023 |
#' @description |
|
| 1024 |
#' The format of the variable description is: |
|
| 1025 |
#' `"<Long variable label> [<dataset name>.<variable name>]"` |
|
| 1026 |
#' |
|
| 1027 |
#' Example: `"Study Identifier [ADSL.STUDYID]"` |
|
| 1028 |
#' |
|
| 1029 |
#' @param data (`tdata`) the object containing the dataset |
|
| 1030 |
#' @param dataset_name (`character`) the name of the dataset containing the variable |
|
| 1031 |
#' @param var_name (`character`) the name of the variable |
|
| 1032 |
#' @keywords internal |
|
| 1033 |
get_var_description <- function(data, dataset_name, var_name) {
|
|
| 1034 | ! |
varlabel <- var_labels(data[[dataset_name]]())[[var_name]] |
| 1035 | ! |
sprintf( |
| 1036 | ! |
"%s [%s.%s]", |
| 1037 | ! |
if (is.na(varlabel)) var_name else varlabel, |
| 1038 | ! |
dataset_name, |
| 1039 | ! |
var_name |
| 1040 |
) |
|
| 1041 |
} |
|
| 1042 | ||
| 1043 |
is_num_var_short <- function(.unique_records_for_factor, input, data_for_analysis) {
|
|
| 1044 | ! |
length(unique(data_for_analysis()$data)) < .unique_records_for_factor && !is.null(input$numeric_as_factor) |
| 1045 |
} |
|
| 1046 | ||
| 1047 |
#' Validates the variable browser inputs |
|
| 1048 |
#' |
|
| 1049 |
#' @param input (`session$input`) the shiny session input |
|
| 1050 |
#' @param plot_var (`list`) list of a data frame and an array of variable names |
|
| 1051 |
#' @param data (`tdata`) the datasets passed to the module |
|
| 1052 |
#' |
|
| 1053 |
#' @returns `logical` TRUE if validations pass; a Shiny validation error otherwise |
|
| 1054 |
#' @keywords internal |
|
| 1055 |
validate_input <- function(input, plot_var, data) {
|
|
| 1056 | ! |
reactive({
|
| 1057 | ! |
dataset_name <- input$tabset_panel |
| 1058 | ! |
varname <- plot_var$variable[[input$tabset_panel]] |
| 1059 | ||
| 1060 | ! |
validate(need(dataset_name, "No data selected")) |
| 1061 | ! |
validate(need(varname, "No variable selected")) |
| 1062 | ||
| 1063 | ! |
df <- data[[dataset_name]]() |
| 1064 | ! |
teal::validate_has_data(df, 1) |
| 1065 | ! |
teal::validate_has_variable(varname = varname, data = df, "Variable not available") |
| 1066 | ||
| 1067 | ! |
TRUE |
| 1068 |
}) |
|
| 1069 |
} |
|
| 1070 | ||
| 1071 |
get_plotted_data <- function(input, plot_var, data) {
|
|
| 1072 | ! |
dataset_name <- input$tabset_panel |
| 1073 | ! |
varname <- plot_var$variable[[input$tabset_panel]] |
| 1074 | ! |
df <- data[[dataset_name]]() |
| 1075 | ||
| 1076 | ! |
var_description <- var_labels(df)[[varname]] |
| 1077 | ! |
list(data = df[[varname]], var_description = var_description) |
| 1078 |
} |
|
| 1079 | ||
| 1080 |
#' Renders the left-hand side `tabset` panel of the module |
|
| 1081 |
#' |
|
| 1082 |
#' @param datanames (`character`) the name of the dataset |
|
| 1083 |
#' @param parent_dataname (`character`) the name of a parent `dataname` to filter out variables from |
|
| 1084 |
#' @param data (`tdata`) the object containing all datasets |
|
| 1085 |
#' @param input (`session$input`) the shiny session input |
|
| 1086 |
#' @param output (`session$output`) the shiny session output |
|
| 1087 |
#' @param columns_names (`environment`) the environment containing bindings for each dataset |
|
| 1088 |
#' @param plot_var (`list`) the list containing the currently selected dataset (tab) and its column names |
|
| 1089 |
#' @keywords internal |
|
| 1090 |
render_tabset_panel_content <- function(datanames, parent_dataname, output, data, input, columns_names, plot_var) {
|
|
| 1091 | ! |
lapply(datanames, render_single_tab, |
| 1092 | ! |
input = input, |
| 1093 | ! |
output = output, |
| 1094 | ! |
data = data, |
| 1095 | ! |
parent_dataname = parent_dataname, |
| 1096 | ! |
columns_names = columns_names, |
| 1097 | ! |
plot_var = plot_var |
| 1098 |
) |
|
| 1099 |
} |
|
| 1100 | ||
| 1101 |
#' Renders a single tab in the left-hand side tabset panel |
|
| 1102 |
#' |
|
| 1103 |
#' @description |
|
| 1104 |
#' Renders a single tab in the left-hand side tabset panel. The rendered tab contains |
|
| 1105 |
#' information about one dataset out of many presented in the module. |
|
| 1106 |
#' |
|
| 1107 |
#' @param dataset_name (`character`) the name of the dataset contained in the rendered tab |
|
| 1108 |
#' @param parent_dataname (`character`) the name of a parent `dataname` to filter out variables from |
|
| 1109 |
#' @inheritParams render_tabset_panel_content |
|
| 1110 |
#' @keywords internal |
|
| 1111 |
render_single_tab <- function(dataset_name, parent_dataname, output, data, input, columns_names, plot_var) {
|
|
| 1112 | ! |
render_tab_header(dataset_name, output, data) |
| 1113 | ||
| 1114 | ! |
render_tab_table( |
| 1115 | ! |
dataset_name = dataset_name, |
| 1116 | ! |
parent_dataname = parent_dataname, |
| 1117 | ! |
output = output, |
| 1118 | ! |
data = data, |
| 1119 | ! |
input = input, |
| 1120 | ! |
columns_names = columns_names, |
| 1121 | ! |
plot_var = plot_var |
| 1122 |
) |
|
| 1123 |
} |
|
| 1124 | ||
| 1125 |
#' Renders the text headlining a single tab in the left-hand side tabset panel |
|
| 1126 |
#' |
|
| 1127 |
#' @param dataset_name (`character`) the name of the dataset of the tab |
|
| 1128 |
#' @inheritParams render_tabset_panel_content |
|
| 1129 |
#' @keywords internal |
|
| 1130 |
render_tab_header <- function(dataset_name, output, data) {
|
|
| 1131 | ! |
dataset_ui_id <- paste0("dataset_summary_", dataset_name)
|
| 1132 | ! |
output[[dataset_ui_id]] <- renderText({
|
| 1133 | ! |
df <- data[[dataset_name]]() |
| 1134 | ! |
join_keys <- get_join_keys(data) |
| 1135 | ! |
if (!is.null(join_keys)) {
|
| 1136 | ! |
key <- get_join_keys(data)$get(dataset_name)[[dataset_name]] |
| 1137 |
} else {
|
|
| 1138 | ! |
key <- NULL |
| 1139 |
} |
|
| 1140 | ! |
sprintf( |
| 1141 | ! |
"Dataset with %s unique key rows and %s variables", |
| 1142 | ! |
nrow(unique(`if`(length(key) > 0, df[, key, drop = FALSE], df))), |
| 1143 | ! |
ncol(df) |
| 1144 |
) |
|
| 1145 |
}) |
|
| 1146 |
} |
|
| 1147 | ||
| 1148 |
#' Renders the table for a single dataset in the left-hand side tabset panel |
|
| 1149 |
#' |
|
| 1150 |
#' @description |
|
| 1151 |
#' The table contains column names, column labels, |
|
| 1152 |
#' small summary about NA values and `sparkline` (if appropriate). |
|
| 1153 |
#' |
|
| 1154 |
#' @param dataset_name (`character`) the name of the dataset |
|
| 1155 |
#' @param parent_dataname (`character`) the name of a parent `dataname` to filter out variables from |
|
| 1156 |
#' @inheritParams render_tabset_panel_content |
|
| 1157 |
#' @keywords internal |
|
| 1158 |
render_tab_table <- function(dataset_name, parent_dataname, output, data, input, columns_names, plot_var) {
|
|
| 1159 | ! |
table_ui_id <- paste0("variable_browser_", dataset_name)
|
| 1160 | ||
| 1161 | ! |
output[[table_ui_id]] <- DT::renderDataTable({
|
| 1162 | ! |
df <- data[[dataset_name]]() |
| 1163 | ||
| 1164 | ! |
get_vars_df <- function(input, dataset_name, parent_name, data) {
|
| 1165 | ! |
data_cols <- colnames(data[[dataset_name]]()) |
| 1166 | ! |
if (isTRUE(input$show_parent_vars)) {
|
| 1167 | ! |
data_cols |
| 1168 | ! |
} else if (dataset_name != parent_name && parent_name %in% names(data)) {
|
| 1169 | ! |
setdiff(data_cols, colnames(data[[parent_name]]())) |
| 1170 |
} else {
|
|
| 1171 | ! |
data_cols |
| 1172 |
} |
|
| 1173 |
} |
|
| 1174 | ||
| 1175 | ! |
if (length(parent_dataname) > 0) {
|
| 1176 | ! |
df_vars <- get_vars_df(input, dataset_name, parent_dataname, data) |
| 1177 | ! |
df <- df[df_vars] |
| 1178 |
} |
|
| 1179 | ||
| 1180 | ! |
if (is.null(df) || ncol(df) == 0) {
|
| 1181 | ! |
columns_names[[dataset_name]] <- character(0) |
| 1182 | ! |
df_output <- data.frame( |
| 1183 | ! |
Type = character(0), |
| 1184 | ! |
Variable = character(0), |
| 1185 | ! |
Label = character(0), |
| 1186 | ! |
Missings = character(0), |
| 1187 | ! |
Sparklines = character(0), |
| 1188 | ! |
stringsAsFactors = FALSE |
| 1189 |
) |
|
| 1190 |
} else {
|
|
| 1191 |
# extract data variable labels |
|
| 1192 | ! |
labels <- teal.data::col_labels(df) |
| 1193 | ||
| 1194 | ! |
columns_names[[dataset_name]] <- names(labels) |
| 1195 | ||
| 1196 |
# calculate number of missing values |
|
| 1197 | ! |
missings <- vapply( |
| 1198 | ! |
df, |
| 1199 | ! |
var_missings_info, |
| 1200 | ! |
FUN.VALUE = character(1), |
| 1201 | ! |
USE.NAMES = FALSE |
| 1202 |
) |
|
| 1203 | ||
| 1204 |
# get icons proper for the data types |
|
| 1205 | ! |
icons <- stats::setNames(teal.slice:::variable_types(df), colnames(df)) |
| 1206 | ||
| 1207 | ! |
join_keys <- get_join_keys(data) |
| 1208 | ! |
if (!is.null(join_keys)) {
|
| 1209 | ! |
icons[intersect(join_keys$get(dataset_name)[[dataset_name]], colnames(df))] <- "primary_key" |
| 1210 |
} |
|
| 1211 | ! |
icons <- variable_type_icons(icons) |
| 1212 | ||
| 1213 |
# generate sparklines |
|
| 1214 | ! |
sparklines_html <- vapply( |
| 1215 | ! |
df, |
| 1216 | ! |
create_sparklines, |
| 1217 | ! |
FUN.VALUE = character(1), |
| 1218 | ! |
USE.NAMES = FALSE |
| 1219 |
) |
|
| 1220 | ||
| 1221 | ! |
df_output <- data.frame( |
| 1222 | ! |
Type = icons, |
| 1223 | ! |
Variable = names(labels), |
| 1224 | ! |
Label = labels, |
| 1225 | ! |
Missings = missings, |
| 1226 | ! |
Sparklines = sparklines_html, |
| 1227 | ! |
stringsAsFactors = FALSE |
| 1228 |
) |
|
| 1229 |
} |
|
| 1230 | ||
| 1231 |
# Select row 1 as default / fallback |
|
| 1232 | ! |
selected_ix <- 1 |
| 1233 |
# Define starting page index (base-0 index of the first item on page |
|
| 1234 |
# note: in many cases it's not the item itself |
|
| 1235 | ! |
selected_page_ix <- 0 |
| 1236 | ||
| 1237 |
# Retrieve current selected variable if any |
|
| 1238 | ! |
isolated_variable <- shiny::isolate(plot_var$variable[[dataset_name]]) |
| 1239 | ||
| 1240 | ! |
if (!is.null(isolated_variable)) {
|
| 1241 | ! |
index <- which(columns_names[[dataset_name]] == isolated_variable)[1] |
| 1242 | ! |
if (!is.null(index) && !is.na(index) && length(index) > 0) selected_ix <- index |
| 1243 |
} |
|
| 1244 | ||
| 1245 |
# Retrieve the index of the first item of the current page |
|
| 1246 |
# it works with varying number of entries on the page (10, 25, ...) |
|
| 1247 | ! |
table_id_sel <- paste0("variable_browser_", dataset_name, "_state")
|
| 1248 | ! |
dt_state <- shiny::isolate(input[[table_id_sel]]) |
| 1249 | ! |
if (selected_ix != 1 && !is.null(dt_state)) {
|
| 1250 | ! |
selected_page_ix <- floor(selected_ix / dt_state$length) * dt_state$length |
| 1251 |
} |
|
| 1252 | ||
| 1253 | ! |
DT::datatable( |
| 1254 | ! |
df_output, |
| 1255 | ! |
escape = FALSE, |
| 1256 | ! |
rownames = FALSE, |
| 1257 | ! |
selection = list(mode = "single", target = "row", selected = selected_ix), |
| 1258 | ! |
options = list( |
| 1259 | ! |
fnDrawCallback = htmlwidgets::JS("function() { HTMLWidgets.staticRender(); }"),
|
| 1260 | ! |
pageLength = input[[paste0(table_ui_id, "_rows")]], |
| 1261 | ! |
displayStart = selected_page_ix |
| 1262 |
) |
|
| 1263 |
) |
|
| 1264 |
}) |
|
| 1265 |
} |
|
| 1266 | ||
| 1267 |
#' Creates observers updating the currently selected column |
|
| 1268 |
#' |
|
| 1269 |
#' @description |
|
| 1270 |
#' The created observers update the column currently selected in the left-hand side |
|
| 1271 |
#' tabset panel. |
|
| 1272 |
#' |
|
| 1273 |
#' @note |
|
| 1274 |
#' Creates an observer for each dataset (each tab in the tabset panel). |
|
| 1275 |
#' |
|
| 1276 |
#' @inheritParams render_tabset_panel_content |
|
| 1277 |
#' @keywords internal |
|
| 1278 |
establish_updating_selection <- function(datanames, input, plot_var, columns_names) {
|
|
| 1279 | ! |
lapply(datanames, function(dataset_name) {
|
| 1280 | ! |
table_ui_id <- paste0("variable_browser_", dataset_name)
|
| 1281 | ! |
table_id_sel <- paste0(table_ui_id, "_rows_selected") |
| 1282 | ! |
observeEvent(input[[table_id_sel]], {
|
| 1283 | ! |
plot_var$data <- dataset_name |
| 1284 | ! |
plot_var$variable[[dataset_name]] <- columns_names[[dataset_name]][input[[table_id_sel]]] |
| 1285 |
}) |
|
| 1286 |
}) |
|
| 1287 |
} |
|
| 1288 | ||
| 1289 |
get_bin_width <- function(x_vec, scaling_factor = 2) {
|
|
| 1290 | ! |
x_vec <- x_vec[!is.na(x_vec)] |
| 1291 | ! |
qntls <- stats::quantile(x_vec, probs = c(0.1, 0.25, 0.75, 0.9), type = 2) |
| 1292 | ! |
iqr <- qntls[3] - qntls[2] |
| 1293 | ! |
binwidth <- max(scaling_factor * iqr / length(x_vec) ^ (1 / 3), sqrt(qntls[4] - qntls[1])) # styler: off |
| 1294 | ! |
binwidth <- ifelse(binwidth == 0, 1, binwidth) |
| 1295 |
# to ensure at least two bins when variable span is very small |
|
| 1296 | ! |
x_span <- diff(range(x_vec)) |
| 1297 | ! |
if (isTRUE(x_span / binwidth >= 2)) binwidth else x_span / 2 |
| 1298 |
} |
|
| 1299 | ||
| 1300 |
custom_sparkline_formatter <- function(labels, counts) {
|
|
| 1301 | ! |
htmlwidgets::JS( |
| 1302 | ! |
sprintf( |
| 1303 | ! |
"function(sparkline, options, field) {
|
| 1304 | ! |
return 'ID: ' + %s[field[0].offset] + '<br>' + 'Count: ' + %s[field[0].offset]; |
| 1305 |
}", |
|
| 1306 | ! |
jsonlite::toJSON(labels), |
| 1307 | ! |
jsonlite::toJSON(counts) |
| 1308 |
) |
|
| 1309 |
) |
|
| 1310 |
} |
|
| 1311 | ||
| 1312 |
#' Removes the outlier observation from an array |
|
| 1313 |
#' |
|
| 1314 |
#' @param var (`numeric`) a numeric vector |
|
| 1315 |
#' @param outlier_definition (`numeric`) if `0` then no outliers are removed, otherwise |
|
| 1316 |
#' outliers (those more than `outlier_definition*IQR below/above Q1/Q3`) are removed |
|
| 1317 |
#' @returns (`numeric`) vector without the outlier values |
|
| 1318 |
#' @keywords internal |
|
| 1319 |
remove_outliers_from <- function(var, outlier_definition) {
|
|
| 1320 | 3x |
if (outlier_definition == 0) {
|
| 1321 | 1x |
return(var) |
| 1322 |
} |
|
| 1323 | 2x |
q1_q3 <- stats::quantile(var, probs = c(0.25, 0.75), type = 2, na.rm = TRUE) |
| 1324 | 2x |
iqr <- q1_q3[2] - q1_q3[1] |
| 1325 | 2x |
var[var >= q1_q3[1] - outlier_definition * iqr & var <= q1_q3[2] + outlier_definition * iqr] |
| 1326 |
} |
| 1 |
#' Outliers Module |
|
| 2 |
#' |
|
| 3 |
#' Module to analyze and identify outliers using different methods |
|
| 4 |
#' |
|
| 5 |
#' @inheritParams teal::module |
|
| 6 |
#' @inheritParams shared_params |
|
| 7 |
#' |
|
| 8 |
#' @param outlier_var (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 9 |
#' variable to consider for the outliers analysis. |
|
| 10 |
#' @param categorical_var (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 11 |
#' categorical factor to split the selected outlier variables on. |
|
| 12 |
#' |
|
| 13 |
#' @templateVar ggnames "Boxplot","Density Plot","Cumulative Distribution Plot" |
|
| 14 |
#' @template ggplot2_args_multi |
|
| 15 |
#' |
|
| 16 |
#' @export |
|
| 17 |
#' |
|
| 18 |
#' @examples |
|
| 19 |
#' |
|
| 20 |
#' ADSL <- teal.modules.general::rADSL |
|
| 21 |
#' |
|
| 22 |
#' fact_vars_adsl <- names(Filter(isTRUE, sapply(ADSL, is.factor))) |
|
| 23 |
#' vars <- choices_selected(variable_choices(ADSL, fact_vars_adsl)) |
|
| 24 |
#' |
|
| 25 |
#' app <- teal::init( |
|
| 26 |
#' data = teal.data::cdisc_data( |
|
| 27 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 28 |
#' check = TRUE |
|
| 29 |
#' ), |
|
| 30 |
#' modules = teal::modules( |
|
| 31 |
#' teal.modules.general::tm_outliers( |
|
| 32 |
#' outlier_var = list( |
|
| 33 |
#' teal.transform::data_extract_spec( |
|
| 34 |
#' dataname = "ADSL", |
|
| 35 |
#' select = select_spec( |
|
| 36 |
#' label = "Select variable:", |
|
| 37 |
#' choices = variable_choices(ADSL, c("AGE", "BMRKR1")),
|
|
| 38 |
#' selected = "AGE", |
|
| 39 |
#' multiple = FALSE, |
|
| 40 |
#' fixed = FALSE |
|
| 41 |
#' ) |
|
| 42 |
#' ) |
|
| 43 |
#' ), |
|
| 44 |
#' categorical_var = list( |
|
| 45 |
#' teal.transform::data_extract_spec( |
|
| 46 |
#' dataname = "ADSL", |
|
| 47 |
#' filter = teal.transform::filter_spec( |
|
| 48 |
#' vars = vars, |
|
| 49 |
#' choices = value_choices(ADSL, vars$selected), |
|
| 50 |
#' selected = value_choices(ADSL, vars$selected), |
|
| 51 |
#' multiple = TRUE |
|
| 52 |
#' ) |
|
| 53 |
#' ) |
|
| 54 |
#' ), |
|
| 55 |
#' ggplot2_args = list( |
|
| 56 |
#' teal.widgets::ggplot2_args( |
|
| 57 |
#' labs = list(subtitle = "Plot generated by Outliers Module") |
|
| 58 |
#' ) |
|
| 59 |
#' ) |
|
| 60 |
#' ) |
|
| 61 |
#' ) |
|
| 62 |
#' ) |
|
| 63 |
#' if (interactive()) {
|
|
| 64 |
#' shinyApp(app$ui, app$server) |
|
| 65 |
#' } |
|
| 66 |
#' |
|
| 67 |
tm_outliers <- function(label = "Outliers Module", |
|
| 68 |
outlier_var, |
|
| 69 |
categorical_var = NULL, |
|
| 70 |
ggtheme = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
|
| 71 |
ggplot2_args = teal.widgets::ggplot2_args(), |
|
| 72 |
plot_height = c(600, 200, 2000), |
|
| 73 |
plot_width = NULL, |
|
| 74 |
pre_output = NULL, |
|
| 75 |
post_output = NULL) {
|
|
| 76 | ! |
logger::log_info("Initializing tm_outliers")
|
| 77 | ! |
if (inherits(outlier_var, "data_extract_spec")) outlier_var <- list(outlier_var) |
| 78 | ! |
if (inherits(categorical_var, "data_extract_spec")) categorical_var <- list(categorical_var) |
| 79 | ! |
if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) |
| 80 | ||
| 81 | ! |
ggtheme <- match.arg(ggtheme) |
| 82 | ! |
checkmate::assert_string(label) |
| 83 | ! |
checkmate::assert_list(outlier_var, types = "data_extract_spec") |
| 84 | ! |
checkmate::assert_list(categorical_var, types = "data_extract_spec", null.ok = TRUE) |
| 85 | ! |
if (is.list(categorical_var)) {
|
| 86 | ! |
lapply(categorical_var, function(x) {
|
| 87 | ! |
if (length(x$filter) > 1L) {
|
| 88 | ! |
stop("tm_outliers: categorical_var data_extract_specs may only specify one filter_spec", call. = FALSE)
|
| 89 |
} |
|
| 90 |
}) |
|
| 91 |
} |
|
| 92 | ! |
plot_choices <- c("Boxplot", "Density Plot", "Cumulative Distribution Plot")
|
| 93 | ! |
checkmate::assert_list(ggplot2_args, types = "ggplot2_args") |
| 94 | ! |
checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices))
|
| 95 | ||
| 96 | ! |
args <- as.list(environment()) |
| 97 | ||
| 98 | ! |
data_extract_list <- list( |
| 99 | ! |
outlier_var = outlier_var, |
| 100 | ! |
categorical_var = categorical_var |
| 101 |
) |
|
| 102 | ||
| 103 | ! |
module( |
| 104 | ! |
label = label, |
| 105 | ! |
server = srv_outliers, |
| 106 | ! |
server_args = c( |
| 107 | ! |
data_extract_list, |
| 108 | ! |
list(plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args) |
| 109 |
), |
|
| 110 | ! |
ui = ui_outliers, |
| 111 | ! |
ui_args = args, |
| 112 | ! |
datanames = teal.transform::get_extract_datanames(data_extract_list) |
| 113 |
) |
|
| 114 |
} |
|
| 115 | ||
| 116 |
ui_outliers <- function(id, ...) {
|
|
| 117 | ! |
args <- list(...) |
| 118 | ! |
ns <- NS(id) |
| 119 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset(args$outlier_var, args$categorical_var) |
| 120 | ||
| 121 | ! |
teal.widgets::standard_layout( |
| 122 | ! |
output = teal.widgets::white_small_well( |
| 123 | ! |
uiOutput(ns("total_outliers")),
|
| 124 | ! |
DT::dataTableOutput(ns("summary_table")),
|
| 125 | ! |
uiOutput(ns("total_missing")),
|
| 126 | ! |
br(), hr(), |
| 127 | ! |
tabsetPanel( |
| 128 | ! |
id = ns("tabs"),
|
| 129 | ! |
tabPanel( |
| 130 | ! |
"Boxplot", |
| 131 | ! |
teal.widgets::plot_with_settings_ui(id = ns("box_plot"))
|
| 132 |
), |
|
| 133 | ! |
tabPanel( |
| 134 | ! |
"Density Plot", |
| 135 | ! |
teal.widgets::plot_with_settings_ui(id = ns("density_plot"))
|
| 136 |
), |
|
| 137 | ! |
tabPanel( |
| 138 | ! |
"Cumulative Distribution Plot", |
| 139 | ! |
teal.widgets::plot_with_settings_ui(id = ns("cum_density_plot"))
|
| 140 |
) |
|
| 141 |
), |
|
| 142 | ! |
br(), hr(), |
| 143 | ! |
uiOutput(ns("table_ui_wrap"))
|
| 144 |
), |
|
| 145 | ! |
encoding = div( |
| 146 |
### Reporter |
|
| 147 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 148 |
### |
|
| 149 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 150 | ! |
teal.transform::datanames_input(args[c("outlier_var", "categorical_var")]),
|
| 151 | ! |
teal.transform::data_extract_ui( |
| 152 | ! |
id = ns("outlier_var"),
|
| 153 | ! |
label = "Variable", |
| 154 | ! |
data_extract_spec = args$outlier_var, |
| 155 | ! |
is_single_dataset = is_single_dataset_value |
| 156 |
), |
|
| 157 | ! |
if (!is.null(args$categorical_var)) {
|
| 158 | ! |
teal.transform::data_extract_ui( |
| 159 | ! |
id = ns("categorical_var"),
|
| 160 | ! |
label = "Categorical factor", |
| 161 | ! |
data_extract_spec = args$categorical_var, |
| 162 | ! |
is_single_dataset = is_single_dataset_value |
| 163 |
) |
|
| 164 |
}, |
|
| 165 | ! |
conditionalPanel( |
| 166 | ! |
condition = paste0("input['", ns("tabs"), "'] == 'Boxplot'"),
|
| 167 | ! |
teal.widgets::optionalSelectInput( |
| 168 | ! |
inputId = ns("boxplot_alts"),
|
| 169 | ! |
label = "Plot type", |
| 170 | ! |
choices = c("Box plot", "Violin plot"),
|
| 171 | ! |
selected = "Box plot", |
| 172 | ! |
multiple = FALSE |
| 173 |
) |
|
| 174 |
), |
|
| 175 | ! |
shinyjs::hidden(checkboxInput(ns("split_outliers"), "Define outliers based on group splitting", value = FALSE)),
|
| 176 | ! |
shinyjs::hidden(checkboxInput(ns("order_by_outlier"), "Re-order categories by outliers [by %]", value = FALSE)),
|
| 177 | ! |
teal.widgets::panel_group( |
| 178 | ! |
teal.widgets::panel_item( |
| 179 | ! |
title = "Method parameters", |
| 180 | ! |
collapsed = FALSE, |
| 181 | ! |
teal.widgets::optionalSelectInput( |
| 182 | ! |
inputId = ns("method"),
|
| 183 | ! |
label = "Method", |
| 184 | ! |
choices = c("IQR", "Z-score", "Percentile"),
|
| 185 | ! |
selected = "IQR", |
| 186 | ! |
multiple = FALSE |
| 187 |
), |
|
| 188 | ! |
conditionalPanel( |
| 189 | ! |
condition = |
| 190 | ! |
paste0("input['", ns("method"), "'] == 'IQR'"),
|
| 191 | ! |
sliderInput( |
| 192 | ! |
ns("iqr_slider"),
|
| 193 | ! |
"Outlier range:", |
| 194 | ! |
min = 1, |
| 195 | ! |
max = 5, |
| 196 | ! |
value = 3, |
| 197 | ! |
step = 0.5 |
| 198 |
) |
|
| 199 |
), |
|
| 200 | ! |
conditionalPanel( |
| 201 | ! |
condition = |
| 202 | ! |
paste0("input['", ns("method"), "'] == 'Z-score'"),
|
| 203 | ! |
sliderInput( |
| 204 | ! |
ns("zscore_slider"),
|
| 205 | ! |
"Outlier range:", |
| 206 | ! |
min = 1, |
| 207 | ! |
max = 5, |
| 208 | ! |
value = 3, |
| 209 | ! |
step = 0.5 |
| 210 |
) |
|
| 211 |
), |
|
| 212 | ! |
conditionalPanel( |
| 213 | ! |
condition = |
| 214 | ! |
paste0("input['", ns("method"), "'] == 'Percentile'"),
|
| 215 | ! |
sliderInput( |
| 216 | ! |
ns("percentile_slider"),
|
| 217 | ! |
"Outlier range:", |
| 218 | ! |
min = 0.001, |
| 219 | ! |
max = 0.5, |
| 220 | ! |
value = 0.01, |
| 221 | ! |
step = 0.001 |
| 222 |
) |
|
| 223 |
), |
|
| 224 | ! |
uiOutput(ns("ui_outlier_help"))
|
| 225 |
) |
|
| 226 |
), |
|
| 227 | ! |
teal.widgets::panel_item( |
| 228 | ! |
title = "Plot settings", |
| 229 | ! |
selectInput( |
| 230 | ! |
inputId = ns("ggtheme"),
|
| 231 | ! |
label = "Theme (by ggplot):", |
| 232 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 233 | ! |
selected = args$ggtheme, |
| 234 | ! |
multiple = FALSE |
| 235 |
) |
|
| 236 |
) |
|
| 237 |
), |
|
| 238 | ! |
forms = tagList( |
| 239 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"),
|
| 240 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 241 |
), |
|
| 242 | ! |
pre_output = args$pre_output, |
| 243 | ! |
post_output = args$post_output |
| 244 |
) |
|
| 245 |
} |
|
| 246 | ||
| 247 |
srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var, |
|
| 248 |
categorical_var, plot_height, plot_width, ggplot2_args) {
|
|
| 249 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 250 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 251 | ! |
checkmate::assert_class(data, "tdata") |
| 252 | ! |
moduleServer(id, function(input, output, session) {
|
| 253 | ! |
vars <- list(outlier_var = outlier_var, categorical_var = categorical_var) |
| 254 | ||
| 255 | ! |
rule_diff <- function(other) {
|
| 256 | ! |
function(value) {
|
| 257 | ! |
othervalue <- tryCatch(selector_list()[[other]]()[["select"]], error = function(e) NULL) |
| 258 | ! |
if (!is.null(othervalue) && identical(othervalue, value)) {
|
| 259 | ! |
"`Variable` and `Categorical factor` cannot be the same" |
| 260 |
} |
|
| 261 |
} |
|
| 262 |
} |
|
| 263 | ||
| 264 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 265 | ! |
data_extract = vars, |
| 266 | ! |
datasets = data, |
| 267 | ! |
select_validation_rule = list( |
| 268 | ! |
outlier_var = shinyvalidate::compose_rules( |
| 269 | ! |
shinyvalidate::sv_required("Please select a variable"),
|
| 270 | ! |
rule_diff("categorical_var")
|
| 271 |
), |
|
| 272 | ! |
categorical_var = rule_diff("outlier_var")
|
| 273 |
) |
|
| 274 |
) |
|
| 275 | ||
| 276 | ! |
iv_r <- reactive({
|
| 277 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 278 | ! |
iv$add_rule("method", shinyvalidate::sv_required("Please select a method"))
|
| 279 | ! |
iv$add_rule("boxplot_alts", shinyvalidate::sv_required("Please select Plot Type"))
|
| 280 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list) |
| 281 |
}) |
|
| 282 | ||
| 283 | ! |
reactive_select_input <- reactive({
|
| 284 | ! |
if (is.null(selector_list()$categorical_var) || length(selector_list()$categorical_var()$select) == 0) {
|
| 285 | ! |
selector_list()[names(selector_list()) != "categorical_var"] |
| 286 |
} else {
|
|
| 287 | ! |
selector_list() |
| 288 |
} |
|
| 289 |
}) |
|
| 290 | ||
| 291 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 292 | ! |
selector_list = reactive_select_input, |
| 293 | ! |
datasets = data, |
| 294 | ! |
join_keys = get_join_keys(data), |
| 295 | ! |
merge_function = "dplyr::inner_join" |
| 296 |
) |
|
| 297 | ||
| 298 | ! |
anl_merged_q <- reactive({
|
| 299 | ! |
req(anl_merged_input()) |
| 300 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 301 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 302 |
}) |
|
| 303 | ||
| 304 | ! |
merged <- list( |
| 305 | ! |
anl_input_r = anl_merged_input, |
| 306 | ! |
anl_q_r = anl_merged_q |
| 307 |
) |
|
| 308 | ||
| 309 | ! |
n_outlier_missing <- reactive({
|
| 310 | ! |
shiny::req(iv_r()$is_valid()) |
| 311 | ! |
outlier_var <- as.vector(merged$anl_input_r()$columns_source$outlier_var) |
| 312 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 313 | ! |
sum(is.na(ANL[[outlier_var]])) |
| 314 |
}) |
|
| 315 | ||
| 316 | ! |
common_code_q <- reactive({
|
| 317 | ! |
shiny::req(iv_r()$is_valid()) |
| 318 | ||
| 319 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 320 | ! |
qenv <- merged$anl_q_r() |
| 321 | ||
| 322 | ! |
outlier_var <- as.vector(merged$anl_input_r()$columns_source$outlier_var) |
| 323 | ! |
categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) |
| 324 | ! |
order_by_outlier <- input$order_by_outlier # nolint |
| 325 | ! |
method <- input$method |
| 326 | ! |
split_outliers <- input$split_outliers |
| 327 | ! |
teal::validate_has_data( |
| 328 |
# missing values in the categorical variable may be used to form a category of its own |
|
| 329 | ! |
`if`( |
| 330 | ! |
length(categorical_var) == 0, |
| 331 | ! |
ANL, |
| 332 | ! |
ANL[, names(ANL) != categorical_var] |
| 333 |
), |
|
| 334 | ! |
min_nrow = 10, |
| 335 | ! |
complete = TRUE, |
| 336 | ! |
allow_inf = FALSE |
| 337 |
) |
|
| 338 | ! |
validate(need(is.numeric(ANL[[outlier_var]]), "`Variable` is not numeric")) |
| 339 | ! |
validate(need(length(unique(ANL[[outlier_var]])) > 1, "Variable has no variation, i.e. only one unique value")) |
| 340 | ||
| 341 |
# show/hide split_outliers |
|
| 342 | ! |
if (length(categorical_var) == 0) {
|
| 343 | ! |
shinyjs::hide("split_outliers")
|
| 344 | ! |
if (n_outlier_missing() > 0) {
|
| 345 | ! |
qenv <- teal.code::eval_code( |
| 346 | ! |
qenv, |
| 347 | ! |
substitute( |
| 348 | ! |
expr = ANL <- ANL %>% dplyr::filter(!is.na(outlier_var_name)), # nolint |
| 349 | ! |
env = list(outlier_var_name = as.name(outlier_var)) |
| 350 |
) |
|
| 351 |
) |
|
| 352 |
} |
|
| 353 |
} else {
|
|
| 354 | ! |
validate(need( |
| 355 | ! |
is.factor(ANL[[categorical_var]]) || |
| 356 | ! |
is.character(ANL[[categorical_var]]) || |
| 357 | ! |
is.integer(ANL[[categorical_var]]), |
| 358 | ! |
"`Categorical factor` must be `factor`, `character`, or `integer`" |
| 359 |
)) |
|
| 360 | ||
| 361 | ! |
if (n_outlier_missing() > 0) {
|
| 362 | ! |
qenv <- teal.code::eval_code( |
| 363 | ! |
qenv, |
| 364 | ! |
substitute( |
| 365 | ! |
expr = ANL <- ANL %>% dplyr::filter(!is.na(outlier_var_name)), # nolint |
| 366 | ! |
env = list(outlier_var_name = as.name(outlier_var)) |
| 367 |
) |
|
| 368 |
) |
|
| 369 |
} |
|
| 370 | ! |
shinyjs::show("split_outliers")
|
| 371 |
} |
|
| 372 | ||
| 373 |
# slider |
|
| 374 | ! |
outlier_definition_param <- if (method == "IQR") { # nolint
|
| 375 | ! |
input$iqr_slider |
| 376 | ! |
} else if (method == "Z-score") {
|
| 377 | ! |
input$zscore_slider |
| 378 | ! |
} else if (method == "Percentile") {
|
| 379 | ! |
input$percentile_slider |
| 380 |
} |
|
| 381 | ||
| 382 |
# this is utils function that converts a %>% NULL %>% b into a %>% b |
|
| 383 | ! |
remove_pipe_null <- function(x) {
|
| 384 | ! |
if (length(x) == 1) {
|
| 385 | ! |
return(x) |
| 386 |
} |
|
| 387 | ! |
if (identical(x[[1]], as.name("%>%")) && is.null(x[[3]])) {
|
| 388 | ! |
return(remove_pipe_null(x[[2]])) |
| 389 |
} |
|
| 390 | ! |
return(as.call(c(x[[1]], lapply(x[-1], remove_pipe_null)))) |
| 391 |
} |
|
| 392 | ||
| 393 | ! |
qenv <- teal.code::eval_code( |
| 394 | ! |
qenv, |
| 395 | ! |
substitute( |
| 396 | ! |
expr = {
|
| 397 | ! |
ANL_OUTLIER <- ANL %>% # nolint |
| 398 | ! |
group_expr %>% # styler: off |
| 399 | ! |
dplyr::mutate(is_outlier = {
|
| 400 | ! |
q1_q3 <- stats::quantile(outlier_var_name, probs = c(0.25, 0.75)) |
| 401 | ! |
iqr <- q1_q3[2] - q1_q3[1] |
| 402 | ! |
!(outlier_var_name >= q1_q3[1] - 1.5 * iqr & outlier_var_name <= q1_q3[2] + 1.5 * iqr) |
| 403 |
}) %>% |
|
| 404 | ! |
calculate_outliers %>% # styler: off |
| 405 | ! |
ungroup_expr %>% # styler: off |
| 406 | ! |
dplyr::filter(is_outlier | is_outlier_selected) %>% |
| 407 | ! |
dplyr::select(-is_outlier) |
| 408 | ! |
ANL_OUTLIER # used to display table when running show-r-code code |
| 409 |
}, |
|
| 410 | ! |
env = list( |
| 411 | ! |
calculate_outliers = if (method == "IQR") {
|
| 412 | ! |
substitute( |
| 413 | ! |
expr = dplyr::mutate(is_outlier_selected = {
|
| 414 | ! |
q1_q3 <- stats::quantile(outlier_var_name, probs = c(0.25, 0.75)) |
| 415 | ! |
iqr <- q1_q3[2] - q1_q3[1] |
| 416 | ! |
!(outlier_var_name >= q1_q3[1] - outlier_definition_param * iqr & |
| 417 | ! |
outlier_var_name <= q1_q3[2] + outlier_definition_param * iqr) |
| 418 |
}), |
|
| 419 | ! |
env = list( |
| 420 | ! |
outlier_var_name = as.name(outlier_var), |
| 421 | ! |
outlier_definition_param = outlier_definition_param |
| 422 |
) |
|
| 423 |
) |
|
| 424 | ! |
} else if (method == "Z-score") {
|
| 425 | ! |
substitute( |
| 426 | ! |
expr = dplyr::mutate( |
| 427 | ! |
is_outlier_selected = abs(outlier_var_name - mean(outlier_var_name)) / |
| 428 | ! |
stats::sd(outlier_var_name) > outlier_definition_param |
| 429 |
), |
|
| 430 | ! |
env = list( |
| 431 | ! |
outlier_var_name = as.name(outlier_var), |
| 432 | ! |
outlier_definition_param = outlier_definition_param |
| 433 |
) |
|
| 434 |
) |
|
| 435 | ! |
} else if (method == "Percentile") {
|
| 436 | ! |
substitute( |
| 437 | ! |
expr = dplyr::mutate( |
| 438 | ! |
is_outlier_selected = outlier_var_name < stats::quantile(outlier_var_name, outlier_definition_param) | |
| 439 | ! |
outlier_var_name > stats::quantile(outlier_var_name, 1 - outlier_definition_param) |
| 440 |
), |
|
| 441 | ! |
env = list( |
| 442 | ! |
outlier_var_name = as.name(outlier_var), |
| 443 | ! |
outlier_definition_param = outlier_definition_param |
| 444 |
) |
|
| 445 |
) |
|
| 446 |
}, |
|
| 447 | ! |
outlier_var_name = as.name(outlier_var), |
| 448 | ! |
group_expr = if (isTRUE(split_outliers) && length(categorical_var) != 0) {
|
| 449 | ! |
substitute(dplyr::group_by(x), list(x = as.name(categorical_var))) |
| 450 |
}, |
|
| 451 | ! |
ungroup_expr = if (isTRUE(split_outliers) && length(categorical_var) != 0) {
|
| 452 | ! |
substitute(dplyr::ungroup()) |
| 453 |
} |
|
| 454 |
) |
|
| 455 |
) %>% |
|
| 456 | ! |
remove_pipe_null() |
| 457 |
) |
|
| 458 | ||
| 459 | ! |
if (length(categorical_var) > 0) {
|
| 460 | ! |
qenv <- teal.code::eval_code( |
| 461 | ! |
qenv, |
| 462 | ! |
substitute( |
| 463 | ! |
expr = summary_table_pre <- ANL_OUTLIER %>% |
| 464 | ! |
dplyr::filter(is_outlier_selected) %>% |
| 465 | ! |
dplyr::select(outlier_var_name, categorical_var_name) %>% |
| 466 | ! |
dplyr::group_by(categorical_var_name) %>% |
| 467 | ! |
dplyr::summarise(n_outliers = dplyr::n()) %>% |
| 468 | ! |
dplyr::right_join( |
| 469 | ! |
ANL %>% |
| 470 | ! |
dplyr::select(outlier_var_name, categorical_var_name) %>% |
| 471 | ! |
dplyr::group_by(categorical_var_name) %>% |
| 472 | ! |
dplyr::summarise( |
| 473 | ! |
total_in_cat = dplyr::n(), |
| 474 | ! |
n_na = sum(is.na(outlier_var_name) | is.na(categorical_var_name)) |
| 475 |
), |
|
| 476 | ! |
by = categorical_var |
| 477 |
) %>% |
|
| 478 |
# This is important as there may be categorical variables with natural orderings, e.g. AGE. |
|
| 479 |
# The plots should be displayed by default in increasing order in these situations. |
|
| 480 |
# dplyr::arrange will sort integer, factor, and character data types in the expected way. |
|
| 481 | ! |
dplyr::arrange(categorical_var_name) %>% |
| 482 | ! |
dplyr::mutate( |
| 483 | ! |
n_outliers = dplyr::if_else(is.na(n_outliers), 0, as.numeric(n_outliers)), |
| 484 | ! |
display_str = dplyr::if_else( |
| 485 | ! |
n_outliers > 0, |
| 486 | ! |
sprintf("%d [%.02f%%]", n_outliers, 100 * n_outliers / total_in_cat),
|
| 487 | ! |
"0" |
| 488 |
), |
|
| 489 | ! |
display_str_na = dplyr::if_else( |
| 490 | ! |
n_na > 0, |
| 491 | ! |
sprintf("%d [%.02f%%]", n_na, 100 * n_na / total_in_cat),
|
| 492 | ! |
"0" |
| 493 |
), |
|
| 494 | ! |
order = seq_along(n_outliers) |
| 495 |
), |
|
| 496 | ! |
env = list( |
| 497 | ! |
categorical_var = categorical_var, |
| 498 | ! |
categorical_var_name = as.name(categorical_var), |
| 499 | ! |
outlier_var_name = as.name(outlier_var) |
| 500 |
) |
|
| 501 |
) |
|
| 502 |
) |
|
| 503 |
# now to handle when user chooses to order based on amount of outliers |
|
| 504 | ! |
if (order_by_outlier) {
|
| 505 | ! |
qenv <- teal.code::eval_code( |
| 506 | ! |
qenv, |
| 507 | ! |
quote( |
| 508 | ! |
summary_table_pre <- summary_table_pre %>% |
| 509 | ! |
dplyr::arrange(desc(n_outliers / total_in_cat)) %>% |
| 510 | ! |
dplyr::mutate(order = seq_len(nrow(summary_table_pre))) |
| 511 |
) |
|
| 512 |
) |
|
| 513 |
} |
|
| 514 | ||
| 515 | ! |
qenv <- teal.code::eval_code( |
| 516 | ! |
qenv, |
| 517 | ! |
substitute( |
| 518 | ! |
expr = {
|
| 519 |
# In order for geom_rug to work properly when reordering takes place inside facet_grid, |
|
| 520 |
# all tables must have the column used for reording. |
|
| 521 |
# In this case, the column used for reordering is `order`. |
|
| 522 | ! |
ANL_OUTLIER <- dplyr::left_join( # nolint |
| 523 | ! |
ANL_OUTLIER, |
| 524 | ! |
summary_table_pre[, c("order", categorical_var)],
|
| 525 | ! |
by = categorical_var |
| 526 |
) |
|
| 527 |
# so that x axis of plot aligns with columns of summary table, from most outliers to least by percentage |
|
| 528 | ! |
ANL <- ANL %>% # nolint |
| 529 | ! |
dplyr::left_join( |
| 530 | ! |
dplyr::select(summary_table_pre, categorical_var_name, order), |
| 531 | ! |
by = categorical_var |
| 532 |
) %>% |
|
| 533 | ! |
dplyr::arrange(order) |
| 534 | ! |
summary_table <- summary_table_pre %>% |
| 535 | ! |
dplyr::select( |
| 536 | ! |
categorical_var_name, |
| 537 | ! |
Outliers = display_str, Missings = display_str_na, Total = total_in_cat |
| 538 |
) %>% |
|
| 539 | ! |
dplyr::mutate_all(as.character) %>% |
| 540 | ! |
tidyr::pivot_longer(-categorical_var_name) %>% |
| 541 | ! |
tidyr::pivot_wider(names_from = categorical_var, values_from = value) %>% |
| 542 | ! |
tibble::column_to_rownames("name")
|
| 543 | ! |
summary_table |
| 544 |
}, |
|
| 545 | ! |
env = list( |
| 546 | ! |
categorical_var = categorical_var, |
| 547 | ! |
categorical_var_name = as.name(categorical_var) |
| 548 |
) |
|
| 549 |
) |
|
| 550 |
) |
|
| 551 |
} |
|
| 552 | ||
| 553 | ! |
if (length(categorical_var) > 0 && nrow(qenv[["ANL_OUTLIER"]]) > 0) {
|
| 554 | ! |
shinyjs::show("order_by_outlier")
|
| 555 |
} else {
|
|
| 556 | ! |
shinyjs::hide("order_by_outlier")
|
| 557 |
} |
|
| 558 | ||
| 559 | ! |
qenv |
| 560 |
}) |
|
| 561 | ||
| 562 | ! |
output$summary_table <- DT::renderDataTable( |
| 563 | ! |
expr = {
|
| 564 | ! |
if (iv_r()$is_valid()) {
|
| 565 | ! |
categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) |
| 566 | ! |
if (!is.null(categorical_var)) {
|
| 567 | ! |
DT::datatable( |
| 568 | ! |
common_code_q()[["summary_table"]], |
| 569 | ! |
options = list( |
| 570 | ! |
dom = "t", |
| 571 | ! |
autoWidth = TRUE, |
| 572 | ! |
columnDefs = list(list(width = "200px", targets = "_all")) |
| 573 |
) |
|
| 574 |
) |
|
| 575 |
} |
|
| 576 |
} |
|
| 577 |
} |
|
| 578 |
) |
|
| 579 | ||
| 580 |
# boxplot/violinplot #nolint |
|
| 581 | ! |
boxplot_q <- reactive({
|
| 582 | ! |
req(common_code_q()) |
| 583 | ! |
ANL <- common_code_q()[["ANL"]] # nolint |
| 584 | ! |
ANL_OUTLIER <- common_code_q()[["ANL_OUTLIER"]] # nolint |
| 585 | ||
| 586 | ! |
outlier_var <- as.vector(merged$anl_input_r()$columns_source$outlier_var) |
| 587 | ! |
categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) |
| 588 | ||
| 589 |
# validation |
|
| 590 | ! |
teal::validate_has_data(ANL, 1) |
| 591 | ||
| 592 |
# boxplot |
|
| 593 | ! |
plot_call <- quote(ANL %>% ggplot()) # nolint |
| 594 | ||
| 595 | ! |
plot_call <- if (input$boxplot_alts == "Box plot") {
|
| 596 | ! |
substitute(expr = plot_call + geom_boxplot(outlier.shape = NA), env = list(plot_call = plot_call)) |
| 597 | ! |
} else if (input$boxplot_alts == "Violin plot") {
|
| 598 | ! |
substitute(expr = plot_call + geom_violin(), env = list(plot_call = plot_call)) |
| 599 |
} else {
|
|
| 600 | ! |
NULL |
| 601 |
} |
|
| 602 | ||
| 603 | ! |
plot_call <- if (identical(categorical_var, character(0)) || is.null(categorical_var)) {
|
| 604 | ! |
inner_call <- substitute( |
| 605 | ! |
expr = plot_call + |
| 606 | ! |
aes(x = "Entire dataset", y = outlier_var_name) + |
| 607 | ! |
scale_x_discrete(), |
| 608 | ! |
env = list(plot_call = plot_call, outlier_var_name = as.name(outlier_var)) |
| 609 |
) |
|
| 610 | ! |
if (nrow(ANL_OUTLIER) > 0) {
|
| 611 | ! |
substitute( |
| 612 | ! |
expr = inner_call + geom_point( |
| 613 | ! |
data = ANL_OUTLIER, |
| 614 | ! |
aes(x = "Entire dataset", y = outlier_var_name, color = is_outlier_selected) |
| 615 |
), |
|
| 616 | ! |
env = list(inner_call = inner_call, outlier_var_name = as.name(outlier_var)) |
| 617 |
) |
|
| 618 |
} else {
|
|
| 619 | ! |
inner_call |
| 620 |
} |
|
| 621 |
} else {
|
|
| 622 | ! |
substitute( |
| 623 | ! |
expr = plot_call + |
| 624 | ! |
aes(y = outlier_var_name, x = reorder(categorical_var_name, order)) + |
| 625 | ! |
xlab(categorical_var) + |
| 626 | ! |
scale_x_discrete() + |
| 627 | ! |
geom_point( |
| 628 | ! |
data = ANL_OUTLIER, |
| 629 | ! |
aes(x = as.factor(categorical_var_name), y = outlier_var_name, color = is_outlier_selected) |
| 630 |
), |
|
| 631 | ! |
env = list( |
| 632 | ! |
plot_call = plot_call, |
| 633 | ! |
outlier_var_name = as.name(outlier_var), |
| 634 | ! |
categorical_var_name = as.name(categorical_var), |
| 635 | ! |
categorical_var = categorical_var |
| 636 |
) |
|
| 637 |
) |
|
| 638 |
} |
|
| 639 | ||
| 640 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 641 | ! |
labs = list(color = "Is outlier?"), |
| 642 | ! |
theme = list(legend.position = "top") |
| 643 |
) |
|
| 644 | ||
| 645 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 646 | ! |
user_plot = ggplot2_args[["Boxplot"]], |
| 647 | ! |
user_default = ggplot2_args$default, |
| 648 | ! |
module_plot = dev_ggplot2_args |
| 649 |
) |
|
| 650 | ||
| 651 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 652 | ! |
all_ggplot2_args, |
| 653 | ! |
ggtheme = input$ggtheme |
| 654 |
) |
|
| 655 | ||
| 656 | ! |
teal.code::eval_code( |
| 657 | ! |
common_code_q(), |
| 658 | ! |
substitute( |
| 659 | ! |
expr = g <- plot_call + |
| 660 | ! |
scale_color_manual(values = c("TRUE" = "red", "FALSE" = "black")) +
|
| 661 | ! |
labs + ggthemes + themes, |
| 662 | ! |
env = list( |
| 663 | ! |
plot_call = plot_call, |
| 664 | ! |
labs = parsed_ggplot2_args$labs, |
| 665 | ! |
ggthemes = parsed_ggplot2_args$ggtheme, |
| 666 | ! |
themes = parsed_ggplot2_args$theme |
| 667 |
) |
|
| 668 |
) |
|
| 669 |
) %>% |
|
| 670 | ! |
teal.code::eval_code(quote(print(g))) |
| 671 |
}) |
|
| 672 | ||
| 673 |
# density plot |
|
| 674 | ! |
density_plot_q <- reactive({
|
| 675 | ! |
ANL <- common_code_q()[["ANL"]] # nolint |
| 676 | ! |
ANL_OUTLIER <- common_code_q()[["ANL_OUTLIER"]] # nolint |
| 677 | ||
| 678 | ! |
outlier_var <- as.vector(merged$anl_input_r()$columns_source$outlier_var) |
| 679 | ! |
categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) |
| 680 | ||
| 681 |
# validation |
|
| 682 | ! |
teal::validate_has_data(ANL, 1) |
| 683 |
# plot |
|
| 684 | ! |
plot_call <- substitute( |
| 685 | ! |
expr = ANL %>% |
| 686 | ! |
ggplot(aes(x = outlier_var_name)) + |
| 687 | ! |
geom_density() + |
| 688 | ! |
geom_rug(data = ANL_OUTLIER, aes(x = outlier_var_name, color = is_outlier_selected)) + |
| 689 | ! |
scale_color_manual(values = c("TRUE" = "red", "FALSE" = "black")),
|
| 690 | ! |
env = list(outlier_var_name = as.name(outlier_var)) |
| 691 |
) |
|
| 692 | ||
| 693 | ! |
plot_call <- if (identical(categorical_var, character(0)) || is.null(categorical_var)) {
|
| 694 | ! |
substitute(expr = plot_call, env = list(plot_call = plot_call)) |
| 695 |
} else {
|
|
| 696 | ! |
substitute( |
| 697 | ! |
expr = plot_call + facet_grid(~ reorder(categorical_var_name, order)), |
| 698 | ! |
env = list(plot_call = plot_call, categorical_var_name = as.name(categorical_var)) |
| 699 |
) |
|
| 700 |
} |
|
| 701 | ||
| 702 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 703 | ! |
labs = list(color = "Is outlier?"), |
| 704 | ! |
theme = list(legend.position = "top") |
| 705 |
) |
|
| 706 | ||
| 707 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 708 | ! |
user_plot = ggplot2_args[["Density Plot"]], |
| 709 | ! |
user_default = ggplot2_args$default, |
| 710 | ! |
module_plot = dev_ggplot2_args |
| 711 |
) |
|
| 712 | ||
| 713 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 714 | ! |
all_ggplot2_args, |
| 715 | ! |
ggtheme = input$ggtheme |
| 716 |
) |
|
| 717 | ||
| 718 | ! |
teal.code::eval_code( |
| 719 | ! |
common_code_q(), |
| 720 | ! |
substitute( |
| 721 | ! |
expr = g <- plot_call + labs + ggthemes + themes, |
| 722 | ! |
env = list( |
| 723 | ! |
plot_call = plot_call, |
| 724 | ! |
labs = parsed_ggplot2_args$labs, |
| 725 | ! |
themes = parsed_ggplot2_args$theme, |
| 726 | ! |
ggthemes = parsed_ggplot2_args$ggtheme |
| 727 |
) |
|
| 728 |
) |
|
| 729 |
) %>% |
|
| 730 | ! |
teal.code::eval_code(quote(print(g))) |
| 731 |
}) |
|
| 732 | ||
| 733 |
# Cumulative distribution plot |
|
| 734 | ! |
cumulative_plot_q <- reactive({
|
| 735 | ! |
ANL <- common_code_q()[["ANL"]] # nolint |
| 736 | ! |
ANL_OUTLIER <- common_code_q()[["ANL_OUTLIER"]] # nolint |
| 737 | ||
| 738 | ! |
qenv <- common_code_q() |
| 739 | ||
| 740 | ! |
outlier_var <- as.vector(merged$anl_input_r()$columns_source$outlier_var) |
| 741 | ! |
categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) |
| 742 | ||
| 743 |
# validation |
|
| 744 | ! |
teal::validate_has_data(ANL, 1) |
| 745 | ||
| 746 |
# plot |
|
| 747 | ! |
plot_call <- substitute( |
| 748 | ! |
expr = ANL %>% ggplot(aes(x = outlier_var_name)) + |
| 749 | ! |
stat_ecdf(), |
| 750 | ! |
env = list(outlier_var_name = as.name(outlier_var)) |
| 751 |
) |
|
| 752 | ! |
if (length(categorical_var) == 0) {
|
| 753 | ! |
qenv <- teal.code::eval_code( |
| 754 | ! |
qenv, |
| 755 | ! |
substitute( |
| 756 | ! |
expr = {
|
| 757 | ! |
ecdf_df <- ANL %>% |
| 758 | ! |
dplyr::mutate( |
| 759 | ! |
y = stats::ecdf(ANL[[outlier_var]])(ANL[[outlier_var]]) |
| 760 |
) |
|
| 761 | ||
| 762 | ! |
outlier_points <- dplyr::left_join( |
| 763 | ! |
ecdf_df, |
| 764 | ! |
ANL_OUTLIER, |
| 765 | ! |
by = dplyr::setdiff(names(ecdf_df), "y") |
| 766 |
) %>% |
|
| 767 | ! |
dplyr::filter(!is.na(is_outlier_selected)) |
| 768 |
}, |
|
| 769 | ! |
env = list(outlier_var = outlier_var) |
| 770 |
) |
|
| 771 |
) |
|
| 772 |
} else {
|
|
| 773 | ! |
qenv <- teal.code::eval_code( |
| 774 | ! |
qenv, |
| 775 | ! |
substitute( |
| 776 | ! |
expr = {
|
| 777 | ! |
all_categories <- lapply( |
| 778 | ! |
unique(ANL[[categorical_var]]), |
| 779 | ! |
function(x) {
|
| 780 | ! |
ANL <- ANL %>% dplyr::filter(get(categorical_var) == x) # nolint |
| 781 | ! |
anl_outlier2 <- ANL_OUTLIER %>% dplyr::filter(get(categorical_var) == x) |
| 782 | ! |
ecdf_df <- ANL %>% |
| 783 | ! |
dplyr::mutate(y = stats::ecdf(ANL[[outlier_var]])(ANL[[outlier_var]])) |
| 784 | ||
| 785 | ! |
dplyr::left_join( |
| 786 | ! |
ecdf_df, |
| 787 | ! |
anl_outlier2, |
| 788 | ! |
by = dplyr::setdiff(names(ecdf_df), "y") |
| 789 |
) %>% |
|
| 790 | ! |
dplyr::filter(!is.na(is_outlier_selected)) |
| 791 |
} |
|
| 792 |
) |
|
| 793 | ! |
outlier_points <- do.call(rbind, all_categories) |
| 794 |
}, |
|
| 795 | ! |
env = list(categorical_var = categorical_var, outlier_var = outlier_var) |
| 796 |
) |
|
| 797 |
) |
|
| 798 | ! |
plot_call <- substitute( |
| 799 | ! |
expr = plot_call + facet_grid(~ reorder(categorical_var_name, order)), |
| 800 | ! |
env = list(plot_call = plot_call, categorical_var_name = as.name(categorical_var)) |
| 801 |
) |
|
| 802 |
} |
|
| 803 | ||
| 804 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 805 | ! |
labs = list(color = "Is outlier?"), |
| 806 | ! |
theme = list(legend.position = "top") |
| 807 |
) |
|
| 808 | ||
| 809 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 810 | ! |
user_plot = ggplot2_args[["Cumulative Distribution Plot"]], |
| 811 | ! |
user_default = ggplot2_args$default, |
| 812 | ! |
module_plot = dev_ggplot2_args |
| 813 |
) |
|
| 814 | ||
| 815 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 816 | ! |
all_ggplot2_args, |
| 817 | ! |
ggtheme = input$ggtheme |
| 818 |
) |
|
| 819 | ||
| 820 | ! |
teal.code::eval_code( |
| 821 | ! |
qenv, |
| 822 | ! |
substitute( |
| 823 | ! |
expr = g <- plot_call + |
| 824 | ! |
geom_point(data = outlier_points, aes(x = outlier_var_name, y = y, color = is_outlier_selected)) + |
| 825 | ! |
scale_color_manual(values = c("TRUE" = "red", "FALSE" = "black")) +
|
| 826 | ! |
labs + ggthemes + themes, |
| 827 | ! |
env = list( |
| 828 | ! |
plot_call = plot_call, |
| 829 | ! |
outlier_var_name = as.name(outlier_var), |
| 830 | ! |
labs = parsed_ggplot2_args$labs, |
| 831 | ! |
themes = parsed_ggplot2_args$theme, |
| 832 | ! |
ggthemes = parsed_ggplot2_args$ggtheme |
| 833 |
) |
|
| 834 |
) |
|
| 835 |
) %>% |
|
| 836 | ! |
teal.code::eval_code(quote(print(g))) |
| 837 |
}) |
|
| 838 | ||
| 839 | ! |
final_q <- reactive({
|
| 840 | ! |
req(input$tabs) |
| 841 | ! |
tab_type <- input$tabs |
| 842 | ! |
if (tab_type == "Boxplot") {
|
| 843 | ! |
boxplot_q() |
| 844 | ! |
} else if (tab_type == "Density Plot") {
|
| 845 | ! |
density_plot_q() |
| 846 | ! |
} else if (tab_type == "Cumulative Distribution Plot") {
|
| 847 | ! |
cumulative_plot_q() |
| 848 |
} |
|
| 849 |
}) |
|
| 850 | ||
| 851 |
# slider text |
|
| 852 | ! |
output$ui_outlier_help <- renderUI({
|
| 853 | ! |
req(input$method) |
| 854 | ! |
if (input$method == "IQR") {
|
| 855 | ! |
req(input$iqr_slider) |
| 856 | ! |
tags$small( |
| 857 | ! |
withMathJax( |
| 858 | ! |
helpText( |
| 859 | ! |
"Outlier data points (\\(x \\lt Q1 - ", input$iqr_slider, "\\times IQR\\) or \\( |
| 860 | ! |
Q3 + ", input$iqr_slider, "\\times IQR \\lt x\\)) |
| 861 | ! |
are displayed in red on the plot and can be visualized in the table below." |
| 862 |
), |
|
| 863 | ! |
if (input$split_outliers) {
|
| 864 | ! |
withMathJax(helpText("Note: Quantiles are calculated per group."))
|
| 865 |
} |
|
| 866 |
) |
|
| 867 |
) |
|
| 868 | ! |
} else if (input$method == "Z-score") {
|
| 869 | ! |
req(input$zscore_slider) |
| 870 | ! |
tags$small( |
| 871 | ! |
withMathJax( |
| 872 | ! |
helpText( |
| 873 | ! |
"Outlier data points (\\(Zscore(x) < -", input$zscore_slider, |
| 874 | ! |
"\\) or \\(", input$zscore_slider, "< Zscore(x) \\))
|
| 875 | ! |
are displayed in red on the plot and can be visualized in the table below." |
| 876 |
), |
|
| 877 | ! |
if (input$split_outliers) {
|
| 878 | ! |
withMathJax(helpText(" Note: Z-scores are calculated per group."))
|
| 879 |
} |
|
| 880 |
) |
|
| 881 |
) |
|
| 882 | ! |
} else if (input$method == "Percentile") {
|
| 883 | ! |
req(input$percentile_slider) |
| 884 | ! |
tags$small( |
| 885 | ! |
withMathJax( |
| 886 | ! |
helpText( |
| 887 | ! |
"Outlier/extreme data points (\\( Percentile(x) <", input$percentile_slider, |
| 888 | ! |
"\\) or \\(", 1 - input$percentile_slider, " < Percentile(x) \\))
|
| 889 | ! |
are displayed in red on the plot and can be visualized in the table below." |
| 890 |
), |
|
| 891 | ! |
if (input$split_outliers) {
|
| 892 | ! |
withMathJax(helpText("Note: Percentiles are calculated per group."))
|
| 893 |
} |
|
| 894 |
) |
|
| 895 |
) |
|
| 896 |
} |
|
| 897 |
}) |
|
| 898 | ||
| 899 | ! |
boxplot_r <- reactive({
|
| 900 | ! |
teal::validate_inputs(iv_r()) |
| 901 | ! |
boxplot_q()[["g"]] |
| 902 |
}) |
|
| 903 | ! |
density_plot_r <- reactive({
|
| 904 | ! |
teal::validate_inputs(iv_r()) |
| 905 | ! |
density_plot_q()[["g"]] |
| 906 |
}) |
|
| 907 | ! |
cumulative_plot_r <- reactive({
|
| 908 | ! |
teal::validate_inputs(iv_r()) |
| 909 | ! |
cumulative_plot_q()[["g"]] |
| 910 |
}) |
|
| 911 | ||
| 912 | ! |
box_pws <- teal.widgets::plot_with_settings_srv( |
| 913 | ! |
id = "box_plot", |
| 914 | ! |
plot_r = boxplot_r, |
| 915 | ! |
height = plot_height, |
| 916 | ! |
width = plot_width, |
| 917 | ! |
brushing = TRUE |
| 918 |
) |
|
| 919 | ||
| 920 | ! |
density_pws <- teal.widgets::plot_with_settings_srv( |
| 921 | ! |
id = "density_plot", |
| 922 | ! |
plot_r = density_plot_r, |
| 923 | ! |
height = plot_height, |
| 924 | ! |
width = plot_width, |
| 925 | ! |
brushing = TRUE |
| 926 |
) |
|
| 927 | ||
| 928 | ! |
cum_density_pws <- teal.widgets::plot_with_settings_srv( |
| 929 | ! |
id = "cum_density_plot", |
| 930 | ! |
plot_r = cumulative_plot_r, |
| 931 | ! |
height = plot_height, |
| 932 | ! |
width = plot_width, |
| 933 | ! |
brushing = TRUE |
| 934 |
) |
|
| 935 | ||
| 936 | ! |
dataname <- names(data)[[1]] |
| 937 | ||
| 938 | ! |
choices <- teal.transform::variable_choices(data[[dataname]]()) |
| 939 | ||
| 940 | ! |
observeEvent(common_code_q(), {
|
| 941 | ! |
ANL_OUTLIER <- common_code_q()[["ANL_OUTLIER"]] # nolint |
| 942 | ! |
teal.widgets::updateOptionalSelectInput( |
| 943 | ! |
session, |
| 944 | ! |
inputId = "table_ui_columns", |
| 945 | ! |
choices = dplyr::setdiff(choices, names(ANL_OUTLIER)), |
| 946 | ! |
selected = isolate(input$table_ui_columns) |
| 947 |
) |
|
| 948 |
}) |
|
| 949 | ||
| 950 | ! |
output$table_ui <- DT::renderDataTable( |
| 951 | ! |
expr = {
|
| 952 | ! |
tab <- input$tabs |
| 953 | ! |
req(tab) # tab is NULL upon app launch, hence will crash without this statement |
| 954 | ! |
outlier_var <- as.vector(merged$anl_input_r()$columns_source$outlier_var) |
| 955 | ! |
categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) |
| 956 | ||
| 957 | ! |
ANL_OUTLIER <- common_code_q()[["ANL_OUTLIER"]] # nolint |
| 958 | ! |
ANL <- common_code_q()[["ANL"]] # nolint |
| 959 | ! |
plot_brush <- if (tab == "Boxplot") {
|
| 960 | ! |
boxplot_r() |
| 961 | ! |
box_pws$brush() |
| 962 | ! |
} else if (tab == "Density Plot") {
|
| 963 | ! |
density_plot_r() |
| 964 | ! |
density_pws$brush() |
| 965 | ! |
} else if (tab == "Cumulative Distribution Plot") {
|
| 966 | ! |
cumulative_plot_r() |
| 967 | ! |
cum_density_pws$brush() |
| 968 |
} |
|
| 969 | ||
| 970 |
# removing unused column ASAP |
|
| 971 | ! |
ANL_OUTLIER$order <- ANL$order <- NULL # nolint |
| 972 | ||
| 973 | ! |
display_table <- if (!is.null(plot_brush)) {
|
| 974 | ! |
if (length(categorical_var) > 0) {
|
| 975 |
# due to reordering, the x-axis label may be changed to something like "reorder(categorical_var, order)" |
|
| 976 | ! |
if (tab == "Boxplot") {
|
| 977 | ! |
plot_brush$mapping$x <- categorical_var |
| 978 |
} else {
|
|
| 979 |
# the other plots use facetting |
|
| 980 |
# so it is panelvar1 that gets relabelled to "reorder(categorical_var, order)" |
|
| 981 | ! |
plot_brush$mapping$panelvar1 <- categorical_var |
| 982 |
} |
|
| 983 |
} else {
|
|
| 984 | ! |
if (tab == "Boxplot") {
|
| 985 |
# in boxplot with no categorical variable, there is no column in ANL that would correspond to x-axis |
|
| 986 |
# so a column needs to be inserted with the value "Entire dataset" because that's the label used in plot |
|
| 987 | ! |
ANL[[plot_brush$mapping$x]] <- "Entire dataset" # nolint |
| 988 |
} |
|
| 989 |
} |
|
| 990 | ||
| 991 |
# in density and cumulative plots, ANL does not have a column corresponding to y-axis. |
|
| 992 |
# so they need to be computed and attached to ANL |
|
| 993 | ! |
if (tab == "Density Plot") {
|
| 994 | ! |
plot_brush$mapping$y <- "density" |
| 995 | ! |
ANL$density <- plot_brush$ymin # nolint #either ymin or ymax will work |
| 996 | ! |
} else if (tab == "Cumulative Distribution Plot") {
|
| 997 | ! |
plot_brush$mapping$y <- "cdf" |
| 998 | ! |
if (length(categorical_var) > 0) {
|
| 999 | ! |
ANL <- ANL %>% # nolint |
| 1000 | ! |
dplyr::group_by(!!as.name(plot_brush$mapping$panelvar1)) %>% |
| 1001 | ! |
dplyr::mutate(cdf = stats::ecdf(!!as.name(outlier_var))(!!as.name(outlier_var))) |
| 1002 |
} else {
|
|
| 1003 | ! |
ANL$cdf <- stats::ecdf(ANL[[outlier_var]])(ANL[[outlier_var]]) # nolint |
| 1004 |
} |
|
| 1005 |
} |
|
| 1006 | ||
| 1007 | ! |
brushed_rows <- brushedPoints(ANL, plot_brush) |
| 1008 | ! |
if (nrow(brushed_rows) > 0) {
|
| 1009 |
# now we need to remove extra column from ANL so that it will have the same columns as ANL_OUTLIER |
|
| 1010 |
# so that dplyr::intersect will work |
|
| 1011 | ! |
if (tab == "Density Plot") {
|
| 1012 | ! |
brushed_rows$density <- NULL |
| 1013 | ! |
} else if (tab == "Cumulative Distribution Plot") {
|
| 1014 | ! |
brushed_rows$cdf <- NULL |
| 1015 | ! |
} else if (tab == "Boxplot" && length(categorical_var) == 0) {
|
| 1016 | ! |
brushed_rows[[plot_brush$mapping$x]] <- NULL |
| 1017 |
} |
|
| 1018 |
# is_outlier_selected is part of ANL_OUTLIER so needed here |
|
| 1019 | ! |
brushed_rows$is_outlier_selected <- TRUE |
| 1020 | ! |
dplyr::intersect(ANL_OUTLIER, brushed_rows) |
| 1021 |
} else {
|
|
| 1022 | ! |
ANL_OUTLIER[0, ] |
| 1023 |
} |
|
| 1024 |
} else {
|
|
| 1025 | ! |
ANL_OUTLIER[ANL_OUTLIER$is_outlier_selected, ] |
| 1026 |
} |
|
| 1027 | ||
| 1028 | ! |
display_table$is_outlier_selected <- NULL |
| 1029 | ! |
keys <- get_join_keys(data)$get(dataname)[[dataname]] |
| 1030 | ! |
datas <- data[[dataname]]() |
| 1031 | ! |
dplyr::left_join( |
| 1032 | ! |
display_table, |
| 1033 | ! |
dplyr::select(datas, dplyr::setdiff(names(datas), dplyr::setdiff(names(display_table), keys))), |
| 1034 | ! |
by = keys |
| 1035 |
) %>% |
|
| 1036 | ! |
dplyr::select(union(names(display_table), input$table_ui_columns)) |
| 1037 |
}, |
|
| 1038 | ! |
options = list( |
| 1039 | ! |
searching = FALSE, language = list( |
| 1040 | ! |
zeroRecords = "The brushed area does not contain outlier observations for the currently defined threshold" |
| 1041 |
), |
|
| 1042 | ! |
pageLength = input$table_ui_rows |
| 1043 |
) |
|
| 1044 |
) |
|
| 1045 | ||
| 1046 | ! |
output$total_outliers <- renderUI({
|
| 1047 | ! |
shiny::req(iv_r()$is_valid()) |
| 1048 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 1049 | ! |
ANL_OUTLIER <- common_code_q()[["ANL_OUTLIER"]] # nolint |
| 1050 | ! |
teal::validate_has_data(ANL, 1) |
| 1051 | ! |
ANL_OUTLIER_SELECTED <- ANL_OUTLIER[ANL_OUTLIER$is_outlier_selected, ] # nolint |
| 1052 | ! |
h5( |
| 1053 | ! |
sprintf( |
| 1054 | ! |
"%s %d / %d [%.02f%%]", |
| 1055 | ! |
"Total number of outlier(s):", |
| 1056 | ! |
nrow(ANL_OUTLIER_SELECTED), |
| 1057 | ! |
nrow(ANL), |
| 1058 | ! |
100 * nrow(ANL_OUTLIER_SELECTED) / nrow(ANL) |
| 1059 |
) |
|
| 1060 |
) |
|
| 1061 |
}) |
|
| 1062 | ||
| 1063 | ! |
output$total_missing <- renderUI({
|
| 1064 | ! |
if (n_outlier_missing() > 0) {
|
| 1065 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 1066 | ! |
helpText( |
| 1067 | ! |
sprintf( |
| 1068 | ! |
"%s %d / %d [%.02f%%]", |
| 1069 | ! |
"Total number of row(s) with missing values:", |
| 1070 | ! |
n_outlier_missing(), |
| 1071 | ! |
nrow(ANL), |
| 1072 | ! |
100 * (n_outlier_missing()) / nrow(ANL) |
| 1073 |
) |
|
| 1074 |
) |
|
| 1075 |
} |
|
| 1076 |
}) |
|
| 1077 | ||
| 1078 | ! |
output$table_ui_wrap <- renderUI({
|
| 1079 | ! |
shiny::req(iv_r()$is_valid()) |
| 1080 | ! |
tagList( |
| 1081 | ! |
teal.widgets::optionalSelectInput( |
| 1082 | ! |
inputId = session$ns("table_ui_columns"),
|
| 1083 | ! |
label = "Choose additional columns", |
| 1084 | ! |
choices = NULL, |
| 1085 | ! |
selected = NULL, |
| 1086 | ! |
multiple = TRUE |
| 1087 |
), |
|
| 1088 | ! |
h4("Outlier Table"),
|
| 1089 | ! |
teal.widgets::get_dt_rows(session$ns("table_ui"), session$ns("table_ui_rows")),
|
| 1090 | ! |
DT::dataTableOutput(session$ns("table_ui"))
|
| 1091 |
) |
|
| 1092 |
}) |
|
| 1093 | ||
| 1094 | ! |
teal.widgets::verbatim_popup_srv( |
| 1095 | ! |
id = "warning", |
| 1096 | ! |
verbatim_content = reactive(teal.code::get_warnings(final_q())), |
| 1097 | ! |
title = "Warning", |
| 1098 | ! |
disabled = reactive(is.null(teal.code::get_warnings(final_q()))) |
| 1099 |
) |
|
| 1100 | ||
| 1101 | ! |
teal.widgets::verbatim_popup_srv( |
| 1102 | ! |
id = "rcode", |
| 1103 | ! |
verbatim_content = reactive(teal.code::get_code(final_q())), |
| 1104 | ! |
title = "Show R Code for Outlier" |
| 1105 |
) |
|
| 1106 | ||
| 1107 |
### REPORTER |
|
| 1108 | ! |
if (with_reporter) {
|
| 1109 | ! |
card_fun <- function(comment) {
|
| 1110 | ! |
card <- teal::TealReportCard$new() |
| 1111 | ! |
tab_type <- input$tabs |
| 1112 | ! |
card$set_name(paste0("Outliers - ", tab_type))
|
| 1113 | ! |
card$append_text(tab_type, "header2") |
| 1114 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 1115 | ||
| 1116 | ! |
categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) |
| 1117 | ! |
if (length(categorical_var) > 0) {
|
| 1118 | ! |
summary_table <- common_code_q()[["summary_table"]] |
| 1119 | ! |
card$append_text("Summary Table", "header3")
|
| 1120 | ! |
card$append_table(summary_table) |
| 1121 |
} |
|
| 1122 | ! |
card$append_text("Plot", "header3")
|
| 1123 | ! |
if (tab_type == "Boxplot") {
|
| 1124 | ! |
card$append_plot(boxplot_r(), dim = box_pws$dim()) |
| 1125 | ! |
} else if (tab_type == "Density Plot") {
|
| 1126 | ! |
card$append_plot(density_plot_r(), dim = density_pws$dim()) |
| 1127 | ! |
} else if (tab_type == "Cumulative Distribution Plot") {
|
| 1128 | ! |
card$append_plot(cumulative_plot_r(), dim = cum_density_pws$dim()) |
| 1129 |
} |
|
| 1130 | ! |
if (!comment == "") {
|
| 1131 | ! |
card$append_text("Comment", "header3")
|
| 1132 | ! |
card$append_text(comment) |
| 1133 |
} |
|
| 1134 | ! |
card$append_src(paste(teal.code::get_code(final_q()), collapse = "\n")) |
| 1135 | ! |
card |
| 1136 |
} |
|
| 1137 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 1138 |
} |
|
| 1139 |
### |
|
| 1140 |
}) |
|
| 1141 |
} |
| 1 |
#' Univariate and bivariate visualizations |
|
| 2 |
#' @md |
|
| 3 |
#' |
|
| 4 |
#' @inheritParams teal::module |
|
| 5 |
#' @inheritParams shared_params |
|
| 6 |
#' @param x (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 7 |
#' Variable names selected to plot along the x-axis by default. Variable can be numeric, factor or character. |
|
| 8 |
#' No empty selections are allowed! |
|
| 9 |
#' @param y (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 10 |
#' Variable names selected to plot along the y-axis by default. Variable can be numeric, factor or character. |
|
| 11 |
#' @param use_density optional, (`logical`) value for whether density (`TRUE`) is plotted or |
|
| 12 |
#' frequency (`FALSE`). Defaults to frequency (`FALSE`). |
|
| 13 |
#' @param row_facet optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 14 |
#' Variables for row facetting. |
|
| 15 |
#' @param col_facet optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 16 |
#' Variables for col facetting. |
|
| 17 |
#' @param facet optional, (`logical`) to specify whether the facet encodings `ui` elements are toggled |
|
| 18 |
#' on and shown to the user by default. Defaults to `TRUE` if either `row_facet` or `column_facet` |
|
| 19 |
#' are supplied. |
|
| 20 |
#' @param color_settings (`logical`) Whether coloring, filling and size should be applied |
|
| 21 |
#' and `UI` tool offered to the user. |
|
| 22 |
#' @param color optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 23 |
#' Variables selected for the outline color inside the coloring settings. |
|
| 24 |
#' It will be applied when `color_settings` is set to `TRUE`. |
|
| 25 |
#' @param fill optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 26 |
#' Variables selected for the fill color inside the coloring settings. |
|
| 27 |
#' It will be applied when `color_settings` is set to `TRUE`. |
|
| 28 |
#' @param size optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 29 |
#' Variables selected for the size of `geom_point` plots inside the coloring settings. |
|
| 30 |
#' It will be applied when `color_settings` is set to `TRUE`. |
|
| 31 |
#' @param free_x_scales optional, (`logical`) Whether X scaling shall be changeable. |
|
| 32 |
#' Does not allow scaling to be changed by default (`FALSE`). |
|
| 33 |
#' @param free_y_scales optional, (`logical`) Whether Y scaling shall be changeable. |
|
| 34 |
#' Does not allow scaling to be changed by default (`FALSE`). |
|
| 35 |
#' @param swap_axes optional, (`logical`) Whether to swap X and Y axes. Defaults to `FALSE`. |
|
| 36 |
#' |
|
| 37 |
#' @details |
|
| 38 |
#' This is a general module to visualize 1 & 2 dimensional data. |
|
| 39 |
#' |
|
| 40 |
#' @note |
|
| 41 |
#' For more examples, please see the vignette "Using bivariate plot" via |
|
| 42 |
#' `vignette("using-bivariate-plot", package = "teal.modules.general")`.
|
|
| 43 |
#' |
|
| 44 |
#' @export |
|
| 45 |
#' |
|
| 46 |
#' @examples |
|
| 47 |
#' # Bivariate plot of selected variable (AGE) against selected (SEX) |
|
| 48 |
#' ADSL <- teal.modules.general::rADSL |
|
| 49 |
#' |
|
| 50 |
#' app <- teal::init( |
|
| 51 |
#' data = teal.data::cdisc_data( |
|
| 52 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 53 |
#' check = TRUE |
|
| 54 |
#' ), |
|
| 55 |
#' modules = teal::modules( |
|
| 56 |
#' teal.modules.general::tm_g_bivariate( |
|
| 57 |
#' x = teal.transform::data_extract_spec( |
|
| 58 |
#' dataname = "ADSL", |
|
| 59 |
#' select = teal.transform::select_spec( |
|
| 60 |
#' label = "Select variable:", |
|
| 61 |
#' choices = teal.transform::variable_choices(ADSL), |
|
| 62 |
#' selected = "AGE", |
|
| 63 |
#' fixed = FALSE |
|
| 64 |
#' ) |
|
| 65 |
#' ), |
|
| 66 |
#' y = teal.transform::data_extract_spec( |
|
| 67 |
#' dataname = "ADSL", |
|
| 68 |
#' select = teal.transform::select_spec( |
|
| 69 |
#' label = "Select variable:", |
|
| 70 |
#' choices = teal.transform::variable_choices(ADSL), |
|
| 71 |
#' selected = "SEX", |
|
| 72 |
#' multiple = FALSE, |
|
| 73 |
#' fixed = FALSE |
|
| 74 |
#' ) |
|
| 75 |
#' ), |
|
| 76 |
#' row_facet = teal.transform::data_extract_spec( |
|
| 77 |
#' dataname = "ADSL", |
|
| 78 |
#' select = teal.transform::select_spec( |
|
| 79 |
#' label = "Select variable:", |
|
| 80 |
#' choices = teal.transform::variable_choices(ADSL), |
|
| 81 |
#' selected = "ARM", |
|
| 82 |
#' fixed = FALSE |
|
| 83 |
#' ) |
|
| 84 |
#' ), |
|
| 85 |
#' col_facet = teal.transform::data_extract_spec( |
|
| 86 |
#' dataname = "ADSL", |
|
| 87 |
#' select = teal.transform::select_spec( |
|
| 88 |
#' label = "Select variable:", |
|
| 89 |
#' choices = teal.transform::variable_choices(ADSL), |
|
| 90 |
#' selected = "COUNTRY", |
|
| 91 |
#' fixed = FALSE |
|
| 92 |
#' ) |
|
| 93 |
#' ), |
|
| 94 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 95 |
#' labs = list(subtitle = "Plot generated by Bivariate Module") |
|
| 96 |
#' ) |
|
| 97 |
#' ) |
|
| 98 |
#' ) |
|
| 99 |
#' ) |
|
| 100 |
#' if (interactive()) {
|
|
| 101 |
#' shinyApp(app$ui, app$server) |
|
| 102 |
#' } |
|
| 103 |
tm_g_bivariate <- function(label = "Bivariate Plots", |
|
| 104 |
x, |
|
| 105 |
y, |
|
| 106 |
row_facet = NULL, |
|
| 107 |
col_facet = NULL, |
|
| 108 |
facet = !is.null(row_facet) || !is.null(col_facet), |
|
| 109 |
color = NULL, |
|
| 110 |
fill = NULL, |
|
| 111 |
size = NULL, |
|
| 112 |
use_density = FALSE, |
|
| 113 |
color_settings = FALSE, |
|
| 114 |
free_x_scales = FALSE, |
|
| 115 |
free_y_scales = FALSE, |
|
| 116 |
plot_height = c(600, 200, 2000), |
|
| 117 |
plot_width = NULL, |
|
| 118 |
rotate_xaxis_labels = FALSE, |
|
| 119 |
swap_axes = FALSE, |
|
| 120 |
ggtheme = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
|
| 121 |
ggplot2_args = teal.widgets::ggplot2_args(), |
|
| 122 |
pre_output = NULL, |
|
| 123 |
post_output = NULL) {
|
|
| 124 | ! |
logger::log_info("Initializing tm_g_bivariate")
|
| 125 | ! |
if (inherits(x, "data_extract_spec")) x <- list(x) |
| 126 | ! |
if (inherits(y, "data_extract_spec")) y <- list(y) |
| 127 | ! |
if (inherits(row_facet, "data_extract_spec")) row_facet <- list(row_facet) |
| 128 | ! |
if (inherits(col_facet, "data_extract_spec")) col_facet <- list(col_facet) |
| 129 | ! |
if (inherits(color, "data_extract_spec")) color <- list(color) |
| 130 | ! |
if (inherits(fill, "data_extract_spec")) fill <- list(fill) |
| 131 | ! |
if (inherits(size, "data_extract_spec")) size <- list(size) |
| 132 | ||
| 133 | ! |
checkmate::assert_list(x, types = "data_extract_spec") |
| 134 | ! |
if (!all(vapply(x, function(x) !x$select$multiple, logical(1)))) {
|
| 135 | ! |
stop("'x' should not allow multiple selection")
|
| 136 |
} |
|
| 137 | ! |
checkmate::assert_list(y, types = "data_extract_spec") |
| 138 | ! |
if (!all(vapply(y, function(x) !x$select$multiple, logical(1)))) {
|
| 139 | ! |
stop("'y' should not allow multiple selection")
|
| 140 |
} |
|
| 141 | ! |
checkmate::assert_list(row_facet, types = "data_extract_spec", null.ok = TRUE) |
| 142 | ! |
if (!all(vapply(row_facet, function(x) !x$select$multiple, logical(1)))) {
|
| 143 | ! |
stop("'row_facet' should not allow multiple selection")
|
| 144 |
} |
|
| 145 | ! |
checkmate::assert_list(col_facet, types = "data_extract_spec", null.ok = TRUE) |
| 146 | ! |
if (!all(vapply(col_facet, function(x) !x$select$multiple, logical(1)))) {
|
| 147 | ! |
stop("'col_facet' should not allow multiple selection")
|
| 148 |
} |
|
| 149 | ! |
checkmate::assert_list(color, types = "data_extract_spec", null.ok = TRUE) |
| 150 | ! |
if (!all(vapply(color, function(x) !x$select$multiple, logical(1)))) {
|
| 151 | ! |
stop("'color' should not allow multiple selection")
|
| 152 |
} |
|
| 153 | ! |
checkmate::assert_list(fill, types = "data_extract_spec", null.ok = TRUE) |
| 154 | ! |
if (!all(vapply(fill, function(x) !x$select$multiple, logical(1)))) {
|
| 155 | ! |
stop("'fill' should not allow multiple selection")
|
| 156 |
} |
|
| 157 | ! |
checkmate::assert_list(size, types = "data_extract_spec", null.ok = TRUE) |
| 158 | ! |
if (!all(vapply(size, function(x) !x$select$multiple, logical(1)))) {
|
| 159 | ! |
stop("'size' should not allow multiple selection")
|
| 160 |
} |
|
| 161 | ||
| 162 | ! |
ggtheme <- match.arg(ggtheme) |
| 163 | ! |
checkmate::assert_string(label) |
| 164 | ! |
checkmate::assert_flag(use_density) |
| 165 | ! |
checkmate::assert_flag(color_settings) |
| 166 | ! |
checkmate::assert_flag(free_x_scales) |
| 167 | ! |
checkmate::assert_flag(free_y_scales) |
| 168 | ! |
checkmate::assert_flag(rotate_xaxis_labels) |
| 169 | ! |
checkmate::assert_flag(swap_axes) |
| 170 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
| 171 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 172 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
| 173 | ! |
checkmate::assert_numeric( |
| 174 | ! |
plot_width[1], |
| 175 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 176 |
) |
|
| 177 | ! |
checkmate::assert_class(ggplot2_args, "ggplot2_args") |
| 178 | ||
| 179 | ! |
if (color_settings) {
|
| 180 | ! |
if (is.null(color)) {
|
| 181 | ! |
color <- x |
| 182 | ! |
color[[1]]$select <- teal.transform::select_spec(choices = color[[1]]$select$choices, selected = NULL) |
| 183 |
} |
|
| 184 | ! |
if (is.null(fill)) {
|
| 185 | ! |
fill <- x |
| 186 | ! |
fill[[1]]$select <- teal.transform::select_spec(choices = fill[[1]]$select$choices, selected = NULL) |
| 187 |
} |
|
| 188 | ! |
if (is.null(size)) {
|
| 189 | ! |
size <- x |
| 190 | ! |
size[[1]]$select <- teal.transform::select_spec(choices = size[[1]]$select$choices, selected = NULL) |
| 191 |
} |
|
| 192 |
} else {
|
|
| 193 | ! |
if (!is.null(c(color, fill, size))) {
|
| 194 | ! |
stop("'color_settings' argument needs to be set to TRUE if 'color', 'fill', and/or 'size' is/are supplied.")
|
| 195 |
} |
|
| 196 |
} |
|
| 197 | ||
| 198 | ! |
args <- as.list(environment()) |
| 199 | ||
| 200 | ! |
data_extract_list <- list( |
| 201 | ! |
x = x, |
| 202 | ! |
y = y, |
| 203 | ! |
row_facet = row_facet, |
| 204 | ! |
col_facet = col_facet, |
| 205 | ! |
color_settings = color_settings, |
| 206 | ! |
color = color, |
| 207 | ! |
fill = fill, |
| 208 | ! |
size = size |
| 209 |
) |
|
| 210 | ||
| 211 | ! |
module( |
| 212 | ! |
label = label, |
| 213 | ! |
server = srv_g_bivariate, |
| 214 | ! |
ui = ui_g_bivariate, |
| 215 | ! |
ui_args = args, |
| 216 | ! |
server_args = c( |
| 217 | ! |
data_extract_list, |
| 218 | ! |
list(plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args) |
| 219 |
), |
|
| 220 | ! |
datanames = teal.transform::get_extract_datanames(data_extract_list) |
| 221 |
) |
|
| 222 |
} |
|
| 223 | ||
| 224 |
ui_g_bivariate <- function(id, ...) {
|
|
| 225 | ! |
args <- list(...) |
| 226 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset( |
| 227 | ! |
args$x, args$y, args$row_facet, args$col_facet, args$color, args$fill, args$size |
| 228 |
) |
|
| 229 | ||
| 230 | ! |
ns <- NS(id) |
| 231 | ! |
teal.widgets::standard_layout( |
| 232 | ! |
output = teal.widgets::white_small_well( |
| 233 | ! |
tags$div(teal.widgets::plot_with_settings_ui(id = ns("myplot")))
|
| 234 |
), |
|
| 235 | ! |
encoding = div( |
| 236 |
### Reporter |
|
| 237 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 238 |
### |
|
| 239 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 240 | ! |
teal.transform::datanames_input(args[c("x", "y", "row_facet", "col_facet", "color", "fill", "size")]),
|
| 241 | ! |
teal.transform::data_extract_ui( |
| 242 | ! |
id = ns("x"),
|
| 243 | ! |
label = "X variable", |
| 244 | ! |
data_extract_spec = args$x, |
| 245 | ! |
is_single_dataset = is_single_dataset_value |
| 246 |
), |
|
| 247 | ! |
teal.transform::data_extract_ui( |
| 248 | ! |
id = ns("y"),
|
| 249 | ! |
label = "Y variable", |
| 250 | ! |
data_extract_spec = args$y, |
| 251 | ! |
is_single_dataset = is_single_dataset_value |
| 252 |
), |
|
| 253 | ! |
conditionalPanel( |
| 254 | ! |
condition = |
| 255 | ! |
"$(\"button[data-id*='-x-dataset'][data-id$='-select']\").text() == '- Nothing selected - ' || |
| 256 | ! |
$(\"button[data-id*='-y-dataset'][data-id$='-select']\").text() == '- Nothing selected - ' ", |
| 257 | ! |
shinyWidgets::radioGroupButtons( |
| 258 | ! |
inputId = ns("use_density"),
|
| 259 | ! |
label = NULL, |
| 260 | ! |
choices = c("frequency", "density"),
|
| 261 | ! |
selected = ifelse(args$use_density, "density", "frequency"), |
| 262 | ! |
justified = TRUE |
| 263 |
) |
|
| 264 |
), |
|
| 265 | ! |
if (!is.null(args$row_facet) || !is.null(args$col_facet)) {
|
| 266 | ! |
div( |
| 267 | ! |
class = "data-extract-box", |
| 268 | ! |
tags$label("Facetting"),
|
| 269 | ! |
shinyWidgets::switchInput(inputId = ns("facetting"), value = args$facet, size = "mini"),
|
| 270 | ! |
conditionalPanel( |
| 271 | ! |
condition = paste0("input['", ns("facetting"), "']"),
|
| 272 | ! |
div( |
| 273 | ! |
if (!is.null(args$row_facet)) {
|
| 274 | ! |
teal.transform::data_extract_ui( |
| 275 | ! |
id = ns("row_facet"),
|
| 276 | ! |
label = "Row facetting variable", |
| 277 | ! |
data_extract_spec = args$row_facet, |
| 278 | ! |
is_single_dataset = is_single_dataset_value |
| 279 |
) |
|
| 280 |
}, |
|
| 281 | ! |
if (!is.null(args$col_facet)) {
|
| 282 | ! |
teal.transform::data_extract_ui( |
| 283 | ! |
id = ns("col_facet"),
|
| 284 | ! |
label = "Column facetting variable", |
| 285 | ! |
data_extract_spec = args$col_facet, |
| 286 | ! |
is_single_dataset = is_single_dataset_value |
| 287 |
) |
|
| 288 |
}, |
|
| 289 | ! |
checkboxInput(ns("free_x_scales"), "free x scales", value = args$free_x_scales),
|
| 290 | ! |
checkboxInput(ns("free_y_scales"), "free y scales", value = args$free_y_scales)
|
| 291 |
) |
|
| 292 |
) |
|
| 293 |
) |
|
| 294 |
}, |
|
| 295 | ! |
if (args$color_settings) {
|
| 296 |
# Put a grey border around the coloring settings |
|
| 297 | ! |
div( |
| 298 | ! |
class = "data-extract-box", |
| 299 | ! |
tags$label("Color settings"),
|
| 300 | ! |
shinyWidgets::switchInput(inputId = ns("coloring"), value = TRUE, size = "mini"),
|
| 301 | ! |
conditionalPanel( |
| 302 | ! |
condition = paste0("input['", ns("coloring"), "']"),
|
| 303 | ! |
div( |
| 304 | ! |
teal.transform::data_extract_ui( |
| 305 | ! |
id = ns("color"),
|
| 306 | ! |
label = "Outline color by variable", |
| 307 | ! |
data_extract_spec = args$color, |
| 308 | ! |
is_single_dataset = is_single_dataset_value |
| 309 |
), |
|
| 310 | ! |
teal.transform::data_extract_ui( |
| 311 | ! |
id = ns("fill"),
|
| 312 | ! |
label = "Fill color by variable", |
| 313 | ! |
data_extract_spec = args$fill, |
| 314 | ! |
is_single_dataset = is_single_dataset_value |
| 315 |
), |
|
| 316 | ! |
div( |
| 317 | ! |
id = ns("size_settings"),
|
| 318 | ! |
teal.transform::data_extract_ui( |
| 319 | ! |
id = ns("size"),
|
| 320 | ! |
label = "Size of points by variable (only if x and y are numeric)", |
| 321 | ! |
data_extract_spec = args$size, |
| 322 | ! |
is_single_dataset = is_single_dataset_value |
| 323 |
) |
|
| 324 |
) |
|
| 325 |
) |
|
| 326 |
) |
|
| 327 |
) |
|
| 328 |
}, |
|
| 329 | ! |
teal.widgets::panel_group( |
| 330 | ! |
teal.widgets::panel_item( |
| 331 | ! |
title = "Plot settings", |
| 332 | ! |
checkboxInput(ns("rotate_xaxis_labels"), "Rotate X axis labels", value = args$rotate_xaxis_labels),
|
| 333 | ! |
checkboxInput(ns("swap_axes"), "Swap axes", value = args$swap_axes),
|
| 334 | ! |
selectInput( |
| 335 | ! |
inputId = ns("ggtheme"),
|
| 336 | ! |
label = "Theme (by ggplot):", |
| 337 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 338 | ! |
selected = args$ggtheme, |
| 339 | ! |
multiple = FALSE |
| 340 |
), |
|
| 341 | ! |
sliderInput( |
| 342 | ! |
ns("alpha"), "Opacity Scatterplot:",
|
| 343 | ! |
min = 0, max = 1, |
| 344 | ! |
step = .05, value = .5, ticks = FALSE |
| 345 |
), |
|
| 346 | ! |
sliderInput( |
| 347 | ! |
ns("fixed_size"), "Scatterplot point size:",
|
| 348 | ! |
min = 1, max = 8, |
| 349 | ! |
step = 1, value = 2, ticks = FALSE |
| 350 |
), |
|
| 351 | ! |
checkboxInput(ns("add_lines"), "Add lines"),
|
| 352 |
) |
|
| 353 |
) |
|
| 354 |
), |
|
| 355 | ! |
forms = tagList( |
| 356 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), button_label = "Show Warnings"),
|
| 357 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 358 |
), |
|
| 359 | ! |
pre_output = args$pre_output, |
| 360 | ! |
post_output = args$post_output |
| 361 |
) |
|
| 362 |
} |
|
| 363 | ||
| 364 | ||
| 365 |
srv_g_bivariate <- function(id, |
|
| 366 |
data, |
|
| 367 |
reporter, |
|
| 368 |
filter_panel_api, |
|
| 369 |
x, |
|
| 370 |
y, |
|
| 371 |
row_facet, |
|
| 372 |
col_facet, |
|
| 373 |
color_settings = FALSE, |
|
| 374 |
color, |
|
| 375 |
fill, |
|
| 376 |
size, |
|
| 377 |
plot_height, |
|
| 378 |
plot_width, |
|
| 379 |
ggplot2_args) {
|
|
| 380 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 381 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 382 | ! |
checkmate::assert_class(data, "tdata") |
| 383 | ! |
moduleServer(id, function(input, output, session) {
|
| 384 | ! |
data_extract <- list( |
| 385 | ! |
x = x, y = y, row_facet = row_facet, col_facet = col_facet, |
| 386 | ! |
color = color, fill = fill, size = size |
| 387 |
) |
|
| 388 | ||
| 389 | ! |
rule_var <- function(other) {
|
| 390 | ! |
function(value) {
|
| 391 | ! |
othervalue <- selector_list()[[other]]()$select |
| 392 | ! |
if (length(value) == 0L && length(othervalue) == 0L) {
|
| 393 | ! |
"Please select at least one of x-variable or y-variable" |
| 394 |
} |
|
| 395 |
} |
|
| 396 |
} |
|
| 397 | ! |
rule_diff <- function(other) {
|
| 398 | ! |
function(value) {
|
| 399 | ! |
othervalue <- selector_list()[[other]]()[["select"]] |
| 400 | ! |
if (!is.null(othervalue)) {
|
| 401 | ! |
if (identical(value, othervalue)) {
|
| 402 | ! |
"Row and column facetting variables must be different." |
| 403 |
} |
|
| 404 |
} |
|
| 405 |
} |
|
| 406 |
} |
|
| 407 | ||
| 408 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 409 | ! |
data_extract = data_extract, |
| 410 | ! |
datasets = data, |
| 411 | ! |
select_validation_rule = list( |
| 412 | ! |
x = rule_var("y"),
|
| 413 | ! |
y = rule_var("x"),
|
| 414 | ! |
row_facet = shinyvalidate::compose_rules( |
| 415 | ! |
shinyvalidate::sv_optional(), |
| 416 | ! |
rule_diff("col_facet")
|
| 417 |
), |
|
| 418 | ! |
col_facet = shinyvalidate::compose_rules( |
| 419 | ! |
shinyvalidate::sv_optional(), |
| 420 | ! |
rule_diff("row_facet")
|
| 421 |
) |
|
| 422 |
) |
|
| 423 |
) |
|
| 424 | ||
| 425 | ! |
iv_r <- reactive({
|
| 426 | ! |
iv_facet <- shinyvalidate::InputValidator$new() |
| 427 | ! |
iv_child <- teal.transform::compose_and_enable_validators(iv_facet, selector_list, |
| 428 | ! |
validator_names = c("row_facet", "col_facet")
|
| 429 |
) |
|
| 430 | ! |
iv_child$condition(~ isTRUE(input$facetting)) |
| 431 | ||
| 432 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 433 | ! |
iv$add_validator(iv_child) |
| 434 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list, validator_names = c("x", "y"))
|
| 435 |
}) |
|
| 436 | ||
| 437 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 438 | ! |
selector_list = selector_list, |
| 439 | ! |
datasets = data, |
| 440 | ! |
join_keys = get_join_keys(data) |
| 441 |
) |
|
| 442 | ||
| 443 | ! |
anl_merged_q <- reactive({
|
| 444 | ! |
req(anl_merged_input()) |
| 445 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 446 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 447 |
}) |
|
| 448 | ||
| 449 | ! |
merged <- list( |
| 450 | ! |
anl_input_r = anl_merged_input, |
| 451 | ! |
anl_q_r = anl_merged_q |
| 452 |
) |
|
| 453 | ||
| 454 | ! |
output_q <- reactive({
|
| 455 | ! |
teal::validate_inputs(iv_r()) |
| 456 | ||
| 457 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 458 | ! |
teal::validate_has_data(ANL, 3) |
| 459 | ||
| 460 | ! |
x_col_vec <- as.vector(merged$anl_input_r()$columns_source$x) |
| 461 | ! |
x_name <- `if`(is.null(x_col_vec), character(0), x_col_vec) |
| 462 | ! |
y_col_vec <- as.vector(merged$anl_input_r()$columns_source$y) |
| 463 | ! |
y_name <- `if`(is.null(y_col_vec), character(0), y_col_vec) |
| 464 | ||
| 465 | ! |
row_facet_name <- as.vector(merged$anl_input_r()$columns_source$row_facet) |
| 466 | ! |
col_facet_name <- as.vector(merged$anl_input_r()$columns_source$col_facet) |
| 467 | ! |
color_name <- if ("color" %in% names(merged$anl_input_r()$columns_source)) {
|
| 468 | ! |
as.vector(merged$anl_input_r()$columns_source$color) |
| 469 |
} else {
|
|
| 470 | ! |
character(0) |
| 471 |
} |
|
| 472 | ! |
fill_name <- if ("fill" %in% names(merged$anl_input_r()$columns_source)) {
|
| 473 | ! |
as.vector(merged$anl_input_r()$columns_source$fill) |
| 474 |
} else {
|
|
| 475 | ! |
character(0) |
| 476 |
} |
|
| 477 | ! |
size_name <- if ("size" %in% names(merged$anl_input_r()$columns_source)) {
|
| 478 | ! |
as.vector(merged$anl_input_r()$columns_source$size) |
| 479 |
} else {
|
|
| 480 | ! |
character(0) |
| 481 |
} |
|
| 482 | ||
| 483 | ! |
use_density <- input$use_density == "density" |
| 484 | ! |
free_x_scales <- input$free_x_scales |
| 485 | ! |
free_y_scales <- input$free_y_scales |
| 486 | ! |
ggtheme <- input$ggtheme |
| 487 | ! |
rotate_xaxis_labels <- input$rotate_xaxis_labels |
| 488 | ! |
swap_axes <- input$swap_axes |
| 489 | ||
| 490 | ! |
is_scatterplot <- all(vapply(ANL[c(x_name, y_name)], is.numeric, logical(1))) && |
| 491 | ! |
length(x_name) > 0 && length(y_name) > 0 |
| 492 | ||
| 493 | ! |
if (is_scatterplot) {
|
| 494 | ! |
shinyjs::show("alpha")
|
| 495 | ! |
alpha <- input$alpha # nolint |
| 496 | ! |
shinyjs::show("add_lines")
|
| 497 | ||
| 498 | ! |
if (color_settings && input$coloring) {
|
| 499 | ! |
shinyjs::hide("fixed_size")
|
| 500 | ! |
shinyjs::show("size_settings")
|
| 501 | ! |
size <- NULL |
| 502 |
} else {
|
|
| 503 | ! |
shinyjs::show("fixed_size")
|
| 504 | ! |
size <- input$fixed_size |
| 505 |
} |
|
| 506 |
} else {
|
|
| 507 | ! |
shinyjs::hide("add_lines")
|
| 508 | ! |
updateCheckboxInput(session, "add_lines", value = FALSE) |
| 509 | ! |
shinyjs::hide("alpha")
|
| 510 | ! |
shinyjs::hide("fixed_size")
|
| 511 | ! |
shinyjs::hide("size_settings")
|
| 512 | ! |
alpha <- 1 |
| 513 | ! |
size <- NULL |
| 514 |
} |
|
| 515 | ||
| 516 | ||
| 517 | ! |
teal::validate_has_data(ANL[, c(x_name, y_name)], 3, complete = TRUE, allow_inf = FALSE) |
| 518 | ||
| 519 | ! |
cl <- bivariate_plot_call( |
| 520 | ! |
data_name = "ANL", |
| 521 | ! |
x = x_name, |
| 522 | ! |
y = y_name, |
| 523 | ! |
x_class = ifelse(!identical(x_name, character(0)), class(ANL[[x_name]]), "NULL"), |
| 524 | ! |
y_class = ifelse(!identical(y_name, character(0)), class(ANL[[y_name]]), "NULL"), |
| 525 | ! |
x_label = varname_w_label(x_name, ANL), |
| 526 | ! |
y_label = varname_w_label(y_name, ANL), |
| 527 | ! |
freq = !use_density, |
| 528 | ! |
theme = ggtheme, |
| 529 | ! |
rotate_xaxis_labels = rotate_xaxis_labels, |
| 530 | ! |
swap_axes = swap_axes, |
| 531 | ! |
alpha = alpha, |
| 532 | ! |
size = size, |
| 533 | ! |
ggplot2_args = ggplot2_args |
| 534 |
) |
|
| 535 | ||
| 536 | ! |
facetting <- (isTRUE(input$facetting) && (!is.null(row_facet_name) || !is.null(col_facet_name))) |
| 537 | ||
| 538 | ! |
if (facetting) {
|
| 539 | ! |
facet_cl <- facet_ggplot_call(row_facet_name, col_facet_name, free_x_scales, free_y_scales) |
| 540 | ||
| 541 | ! |
if (!is.null(facet_cl)) {
|
| 542 | ! |
cl <- call("+", cl, facet_cl)
|
| 543 |
} |
|
| 544 |
} |
|
| 545 | ||
| 546 | ! |
if (input$add_lines) {
|
| 547 | ! |
cl <- call("+", cl, quote(geom_line(size = 1)))
|
| 548 |
} |
|
| 549 | ||
| 550 | ! |
coloring_cl <- NULL |
| 551 | ! |
if (color_settings) {
|
| 552 | ! |
if (input$coloring) {
|
| 553 | ! |
coloring_cl <- coloring_ggplot_call( |
| 554 | ! |
colour = color_name, |
| 555 | ! |
fill = fill_name, |
| 556 | ! |
size = size_name, |
| 557 | ! |
is_point = any(grepl("geom_point", cl %>% deparse()))
|
| 558 |
) |
|
| 559 | ! |
legend_lbls <- substitute( |
| 560 | ! |
expr = labs(color = color_name, fill = fill_name, size = size_name), |
| 561 | ! |
env = list( |
| 562 | ! |
color_name = varname_w_label(color_name, ANL), |
| 563 | ! |
fill_name = varname_w_label(fill_name, ANL), |
| 564 | ! |
size_name = varname_w_label(size_name, ANL) |
| 565 |
) |
|
| 566 |
) |
|
| 567 |
} |
|
| 568 | ! |
if (!is.null(coloring_cl)) {
|
| 569 | ! |
cl <- call("+", call("+", cl, coloring_cl), legend_lbls)
|
| 570 |
} |
|
| 571 |
} |
|
| 572 | ||
| 573 |
# Add labels to facets |
|
| 574 | ! |
nulled_row_facet_name <- varname_w_label(row_facet_name, ANL) |
| 575 | ! |
nulled_col_facet_name <- varname_w_label(col_facet_name, ANL) |
| 576 | ! |
without_facet <- (is.null(nulled_row_facet_name) && is.null(nulled_col_facet_name)) || !facetting |
| 577 | ||
| 578 | ! |
print_call <- if (without_facet) {
|
| 579 | ! |
quote(print(p)) |
| 580 |
} else {
|
|
| 581 | ! |
substitute( |
| 582 | ! |
expr = {
|
| 583 |
# Add facetting labels |
|
| 584 |
# optional: grid.newpage() #nolintr |
|
| 585 | ! |
p <- add_facet_labels(p, xfacet_label = nulled_col_facet_name, yfacet_label = nulled_row_facet_name) |
| 586 | ! |
grid::grid.newpage() |
| 587 | ! |
grid::grid.draw(p) |
| 588 |
}, |
|
| 589 | ! |
env = list(nulled_col_facet_name = nulled_col_facet_name, nulled_row_facet_name = nulled_row_facet_name) |
| 590 |
) |
|
| 591 |
} |
|
| 592 | ||
| 593 | ! |
teal.code::eval_code(merged$anl_q_r(), substitute(expr = p <- cl, env = list(cl = cl))) %>% |
| 594 | ! |
teal.code::eval_code(print_call) |
| 595 |
}) |
|
| 596 | ||
| 597 | ! |
plot_r <- shiny::reactive({
|
| 598 | ! |
output_q()[["p"]] |
| 599 |
}) |
|
| 600 | ||
| 601 | ! |
pws <- teal.widgets::plot_with_settings_srv( |
| 602 | ! |
id = "myplot", |
| 603 | ! |
plot_r = plot_r, |
| 604 | ! |
height = plot_height, |
| 605 | ! |
width = plot_width |
| 606 |
) |
|
| 607 | ||
| 608 | ! |
teal.widgets::verbatim_popup_srv( |
| 609 | ! |
id = "warning", |
| 610 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 611 | ! |
title = "Warning", |
| 612 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 613 |
) |
|
| 614 | ||
| 615 | ! |
teal.widgets::verbatim_popup_srv( |
| 616 | ! |
id = "rcode", |
| 617 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 618 | ! |
title = "Bivariate Plot" |
| 619 |
) |
|
| 620 | ||
| 621 |
### REPORTER |
|
| 622 | ! |
if (with_reporter) {
|
| 623 | ! |
card_fun <- function(comment) {
|
| 624 | ! |
card <- teal::TealReportCard$new() |
| 625 | ! |
card$set_name("Bivariate Plot")
|
| 626 | ! |
card$append_text("Bivariate Plot", "header2")
|
| 627 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 628 | ! |
card$append_text("Plot", "header3")
|
| 629 | ! |
card$append_plot(plot_r(), dim = pws$dim()) |
| 630 | ! |
if (!comment == "") {
|
| 631 | ! |
card$append_text("Comment", "header3")
|
| 632 | ! |
card$append_text(comment) |
| 633 |
} |
|
| 634 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 635 | ! |
card |
| 636 |
} |
|
| 637 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 638 |
} |
|
| 639 |
### |
|
| 640 |
}) |
|
| 641 |
} |
|
| 642 | ||
| 643 | ||
| 644 |
#' Get Substituted ggplot call |
|
| 645 |
#' |
|
| 646 |
#' @noRd |
|
| 647 |
#' |
|
| 648 |
#' @examples |
|
| 649 |
#' |
|
| 650 |
#' bivariate_plot_call("ANL", "BAGE", "RACE", "numeric", "factor")
|
|
| 651 |
#' bivariate_plot_call("ANL", "BAGE", character(0), "numeric", "NULL")
|
|
| 652 |
bivariate_plot_call <- function(data_name, |
|
| 653 |
x = character(0), |
|
| 654 |
y = character(0), |
|
| 655 |
x_class = "NULL", |
|
| 656 |
y_class = "NULL", |
|
| 657 |
x_label = NULL, |
|
| 658 |
y_label = NULL, |
|
| 659 |
freq = TRUE, |
|
| 660 |
theme = "gray", |
|
| 661 |
rotate_xaxis_labels = FALSE, |
|
| 662 |
swap_axes = FALSE, |
|
| 663 |
alpha = double(0), |
|
| 664 |
size = 2, |
|
| 665 |
ggplot2_args = teal.widgets::ggplot2_args()) {
|
|
| 666 | ! |
supported_types <- c("NULL", "numeric", "integer", "factor", "character", "logical")
|
| 667 | ! |
validate(need(x_class %in% supported_types, paste0("Data type '", x_class, "' is not supported.")))
|
| 668 | ! |
validate(need(y_class %in% supported_types, paste0("Data type '", y_class, "' is not supported.")))
|
| 669 | ||
| 670 | ||
| 671 | ! |
if (identical(x, character(0))) {
|
| 672 | ! |
x <- x_label <- "-" |
| 673 |
} else {
|
|
| 674 | ! |
x <- if (is.call(x)) x else as.name(x) |
| 675 |
} |
|
| 676 | ! |
if (identical(y, character(0))) {
|
| 677 | ! |
y <- y_label <- "-" |
| 678 |
} else {
|
|
| 679 | ! |
y <- if (is.call(y)) y else as.name(y) |
| 680 |
} |
|
| 681 | ||
| 682 | ! |
cl <- bivariate_ggplot_call( |
| 683 | ! |
x_class = x_class, |
| 684 | ! |
y_class = y_class, |
| 685 | ! |
freq = freq, |
| 686 | ! |
theme = theme, |
| 687 | ! |
rotate_xaxis_labels = rotate_xaxis_labels, |
| 688 | ! |
swap_axes = swap_axes, |
| 689 | ! |
alpha = alpha, |
| 690 | ! |
size = size, |
| 691 | ! |
ggplot2_args = ggplot2_args, |
| 692 | ! |
x = x, |
| 693 | ! |
y = y, |
| 694 | ! |
xlab = x_label, |
| 695 | ! |
ylab = y_label, |
| 696 | ! |
data_name = data_name |
| 697 |
) |
|
| 698 |
} |
|
| 699 | ||
| 700 |
substitute_q <- function(x, env) {
|
|
| 701 | ! |
stopifnot(is.language(x)) |
| 702 | ! |
call <- substitute(substitute(x, env), list(x = x)) |
| 703 | ! |
eval(call) |
| 704 |
} |
|
| 705 | ||
| 706 | ||
| 707 |
#' Create ggplot part of plot call |
|
| 708 |
#' |
|
| 709 |
#' Due to the type of the x and y variable the plot type is chosen |
|
| 710 |
#' |
|
| 711 |
#' @noRd |
|
| 712 |
#' |
|
| 713 |
#' @examples |
|
| 714 |
#' bivariate_ggplot_call("numeric", "NULL")
|
|
| 715 |
#' bivariate_ggplot_call("numeric", "NULL", freq = FALSE)
|
|
| 716 |
#' |
|
| 717 |
#' bivariate_ggplot_call("NULL", "numeric")
|
|
| 718 |
#' bivariate_ggplot_call("NULL", "numeric", freq = FALSE)
|
|
| 719 |
#' |
|
| 720 |
#' bivariate_ggplot_call("NULL", "factor")
|
|
| 721 |
#' bivariate_ggplot_call("NULL", "factor", freq = FALSE)
|
|
| 722 |
#' |
|
| 723 |
#' bivariate_ggplot_call("factor", "NULL")
|
|
| 724 |
#' bivariate_ggplot_call("factor", "NULL", freq = FALSE)
|
|
| 725 |
#' |
|
| 726 |
#' bivariate_ggplot_call("numeric", "numeric")
|
|
| 727 |
#' bivariate_ggplot_call("numeric", "factor")
|
|
| 728 |
#' bivariate_ggplot_call("factor", "numeric")
|
|
| 729 |
#' bivariate_ggplot_call("factor", "factor")
|
|
| 730 |
bivariate_ggplot_call <- function(x_class = c("NULL", "numeric", "integer", "factor", "character", "logical"),
|
|
| 731 |
y_class = c("NULL", "numeric", "integer", "factor", "character", "logical"),
|
|
| 732 |
freq = TRUE, |
|
| 733 |
theme = "gray", |
|
| 734 |
rotate_xaxis_labels = FALSE, |
|
| 735 |
swap_axes = FALSE, |
|
| 736 |
size = double(0), |
|
| 737 |
alpha = double(0), |
|
| 738 |
x = NULL, |
|
| 739 |
y = NULL, |
|
| 740 |
xlab = "-", |
|
| 741 |
ylab = "-", |
|
| 742 |
data_name = "ANL", |
|
| 743 |
ggplot2_args = teal.widgets::ggplot2_args()) {
|
|
| 744 | 42x |
x_class <- match.arg(x_class) |
| 745 | 42x |
y_class <- match.arg(y_class) |
| 746 | ||
| 747 | 42x |
if (x_class %in% c("character", "logical")) {
|
| 748 | 12x |
x_class <- "factor" |
| 749 |
} |
|
| 750 | 42x |
if (x_class %in% c("integer")) {
|
| 751 | ! |
x_class <- "numeric" |
| 752 |
} |
|
| 753 | 42x |
if (y_class %in% c("character", "logical")) {
|
| 754 | 8x |
y_class <- "factor" |
| 755 |
} |
|
| 756 | 42x |
if (y_class %in% c("integer")) {
|
| 757 | ! |
y_class <- "numeric" |
| 758 |
} |
|
| 759 | ||
| 760 | 42x |
if (all(c(x_class, y_class) == "NULL")) {
|
| 761 | ! |
stop("either x or y is required")
|
| 762 |
} |
|
| 763 | ||
| 764 | 42x |
reduce_plot_call <- function(...) {
|
| 765 | 104x |
args <- Filter(Negate(is.null), list(...)) |
| 766 | 104x |
Reduce(function(x, y) call("+", x, y), args)
|
| 767 |
} |
|
| 768 | ||
| 769 | 42x |
plot_call <- substitute(ggplot(data_name), env = list(data_name = as.name(data_name))) |
| 770 | ||
| 771 |
# Single data plots |
|
| 772 | 42x |
if (x_class == "numeric" && y_class == "NULL") {
|
| 773 | 6x |
plot_call <- reduce_plot_call(plot_call, substitute(aes(x = xval), env = list(xval = x))) |
| 774 | ||
| 775 | 6x |
if (freq) {
|
| 776 | 4x |
plot_call <- reduce_plot_call( |
| 777 | 4x |
plot_call, |
| 778 | 4x |
quote(geom_histogram(bins = 30)), |
| 779 | 4x |
quote(ylab("Frequency"))
|
| 780 |
) |
|
| 781 |
} else {
|
|
| 782 | 2x |
plot_call <- reduce_plot_call( |
| 783 | 2x |
plot_call, |
| 784 | 2x |
quote(geom_histogram(bins = 30, aes(y = after_stat(density)))), |
| 785 | 2x |
quote(geom_density(aes(y = after_stat(density)))), |
| 786 | 2x |
quote(ylab("Density"))
|
| 787 |
) |
|
| 788 |
} |
|
| 789 | 36x |
} else if (x_class == "NULL" && y_class == "numeric") {
|
| 790 | 6x |
plot_call <- reduce_plot_call(plot_call, substitute(aes(x = yval), env = list(yval = y))) |
| 791 | ||
| 792 | 6x |
if (freq) {
|
| 793 | 4x |
plot_call <- reduce_plot_call( |
| 794 | 4x |
plot_call, |
| 795 | 4x |
quote(geom_histogram(bins = 30)), |
| 796 | 4x |
quote(ylab("Frequency"))
|
| 797 |
) |
|
| 798 |
} else {
|
|
| 799 | 2x |
plot_call <- reduce_plot_call( |
| 800 | 2x |
plot_call, |
| 801 | 2x |
quote(geom_histogram(bins = 30, aes(y = after_stat(density)))), |
| 802 | 2x |
quote(geom_density(aes(y = after_stat(density)))), |
| 803 | 2x |
quote(ylab("Density"))
|
| 804 |
) |
|
| 805 |
} |
|
| 806 | 30x |
} else if (x_class == "factor" && y_class == "NULL") {
|
| 807 | 4x |
plot_call <- reduce_plot_call(plot_call, substitute(aes(x = xval), env = list(xval = x))) |
| 808 | ||
| 809 | 4x |
if (freq) {
|
| 810 | 2x |
plot_call <- reduce_plot_call( |
| 811 | 2x |
plot_call, |
| 812 | 2x |
quote(geom_bar()), |
| 813 | 2x |
quote(ylab("Frequency"))
|
| 814 |
) |
|
| 815 |
} else {
|
|
| 816 | 2x |
plot_call <- reduce_plot_call( |
| 817 | 2x |
plot_call, |
| 818 | 2x |
quote(geom_bar(aes(y = after_stat(prop), group = 1))), |
| 819 | 2x |
quote(ylab("Fraction"))
|
| 820 |
) |
|
| 821 |
} |
|
| 822 | 26x |
} else if (x_class == "NULL" && y_class == "factor") {
|
| 823 | 4x |
plot_call <- reduce_plot_call(plot_call, substitute(aes(x = yval), env = list(yval = y))) |
| 824 | ||
| 825 | 4x |
if (freq) {
|
| 826 | 2x |
plot_call <- reduce_plot_call( |
| 827 | 2x |
plot_call, |
| 828 | 2x |
quote(geom_bar()), |
| 829 | 2x |
quote(ylab("Frequency"))
|
| 830 |
) |
|
| 831 |
} else {
|
|
| 832 | 2x |
plot_call <- reduce_plot_call( |
| 833 | 2x |
plot_call, |
| 834 | 2x |
quote(geom_bar(aes(y = after_stat(prop), group = 1))), |
| 835 | 2x |
quote(ylab("Fraction"))
|
| 836 |
) |
|
| 837 |
} |
|
| 838 |
# Numeric Plots |
|
| 839 | 22x |
} else if (x_class == "numeric" && y_class == "numeric") {
|
| 840 | 2x |
plot_call <- reduce_plot_call( |
| 841 | 2x |
plot_call, |
| 842 | 2x |
substitute(aes(x = xval, y = yval), env = list(xval = x, yval = y)), |
| 843 |
# pch = 21 for consistent coloring behaviour b/w all geoms (outline and fill properties) |
|
| 844 | 2x |
`if`( |
| 845 | 2x |
!is.null(size), |
| 846 | 2x |
substitute( |
| 847 | 2x |
geom_point(alpha = alphaval, size = sizeval, pch = 21), |
| 848 | 2x |
env = list(alphaval = alpha, sizeval = size) |
| 849 |
), |
|
| 850 | 2x |
substitute( |
| 851 | 2x |
geom_point(alpha = alphaval, pch = 21), |
| 852 | 2x |
env = list(alphaval = alpha) |
| 853 |
) |
|
| 854 |
) |
|
| 855 |
) |
|
| 856 | 20x |
} else if ((x_class == "numeric" && y_class == "factor") || (x_class == "factor" && y_class == "numeric")) {
|
| 857 | 6x |
plot_call <- reduce_plot_call( |
| 858 | 6x |
plot_call, |
| 859 | 6x |
substitute(aes(x = xval, y = yval), env = list(xval = x, yval = y)), |
| 860 | 6x |
quote(geom_boxplot()) |
| 861 |
) |
|
| 862 |
# Factor and character plots |
|
| 863 | 14x |
} else if (x_class == "factor" && y_class == "factor") {
|
| 864 | 14x |
plot_call <- reduce_plot_call( |
| 865 | 14x |
plot_call, |
| 866 | 14x |
substitute( |
| 867 | 14x |
ggmosaic::geom_mosaic(aes(x = ggmosaic::product(xval), fill = yval), na.rm = TRUE), |
| 868 | 14x |
env = list(xval = x, yval = y) |
| 869 |
) |
|
| 870 |
) |
|
| 871 |
} else {
|
|
| 872 | ! |
stop("x y type combination not allowed")
|
| 873 |
} |
|
| 874 | ||
| 875 | 42x |
labs_base <- if (x_class == "NULL") {
|
| 876 | 10x |
list(x = substitute(ylab, list(ylab = ylab))) |
| 877 | 42x |
} else if (y_class == "NULL") {
|
| 878 | 10x |
list(x = substitute(xlab, list(xlab = xlab))) |
| 879 |
} else {
|
|
| 880 | 22x |
list( |
| 881 | 22x |
x = substitute(xlab, list(xlab = xlab)), |
| 882 | 22x |
y = substitute(ylab, list(ylab = ylab)) |
| 883 |
) |
|
| 884 |
} |
|
| 885 | ||
| 886 | 42x |
dev_ggplot2_args <- teal.widgets::ggplot2_args(labs = labs_base) |
| 887 | ||
| 888 | 42x |
if (rotate_xaxis_labels) {
|
| 889 | ! |
dev_ggplot2_args$theme <- list(axis.text.x = quote(element_text(angle = 45, hjust = 1))) |
| 890 |
} |
|
| 891 | ||
| 892 | 42x |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 893 | 42x |
user_plot = ggplot2_args, |
| 894 | 42x |
module_plot = dev_ggplot2_args |
| 895 |
) |
|
| 896 | ||
| 897 | 42x |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args(all_ggplot2_args, ggtheme = theme) |
| 898 | ||
| 899 | 42x |
plot_call <- reduce_plot_call( |
| 900 | 42x |
plot_call, |
| 901 | 42x |
parsed_ggplot2_args$labs, |
| 902 | 42x |
parsed_ggplot2_args$ggtheme, |
| 903 | 42x |
parsed_ggplot2_args$theme |
| 904 |
) |
|
| 905 | ||
| 906 | 42x |
if (swap_axes) {
|
| 907 | ! |
plot_call <- reduce_plot_call(plot_call, quote(coord_flip())) |
| 908 |
} |
|
| 909 | ||
| 910 | 42x |
return(plot_call) |
| 911 |
} |
|
| 912 | ||
| 913 | ||
| 914 |
#' Create facet call |
|
| 915 |
#' |
|
| 916 |
#' @noRd |
|
| 917 |
#' |
|
| 918 |
#' @examples |
|
| 919 |
#' |
|
| 920 |
#' facet_ggplot_call(LETTERS[1:3]) |
|
| 921 |
#' facet_ggplot_call(NULL, LETTERS[23:26]) |
|
| 922 |
#' facet_ggplot_call(LETTERS[1:3], LETTERS[23:26]) |
|
| 923 |
facet_ggplot_call <- function(row_facet = character(0), |
|
| 924 |
col_facet = character(0), |
|
| 925 |
free_x_scales = FALSE, |
|
| 926 |
free_y_scales = FALSE) {
|
|
| 927 | ! |
scales <- if (free_x_scales && free_y_scales) {
|
| 928 | ! |
"free" |
| 929 | ! |
} else if (free_x_scales) {
|
| 930 | ! |
"free_x" |
| 931 | ! |
} else if (free_y_scales) {
|
| 932 | ! |
"free_y" |
| 933 |
} else {
|
|
| 934 | ! |
"fixed" |
| 935 |
} |
|
| 936 | ||
| 937 | ! |
if (identical(row_facet, character(0)) && identical(col_facet, character(0))) {
|
| 938 | ! |
NULL |
| 939 | ! |
} else if (!identical(row_facet, character(0)) && !identical(col_facet, character(0))) {
|
| 940 | ! |
call( |
| 941 | ! |
"facet_grid", |
| 942 | ! |
rows = call_fun_dots("vars", row_facet),
|
| 943 | ! |
cols = call_fun_dots("vars", col_facet),
|
| 944 | ! |
scales = scales |
| 945 |
) |
|
| 946 | ! |
} else if (identical(row_facet, character(0)) && !identical(col_facet, character(0))) {
|
| 947 | ! |
call("facet_grid", cols = call_fun_dots("vars", col_facet), scales = scales)
|
| 948 | ! |
} else if (!identical(row_facet, character(0)) && identical(col_facet, character(0))) {
|
| 949 | ! |
call("facet_grid", rows = call_fun_dots("vars", row_facet), scales = scales)
|
| 950 |
} |
|
| 951 |
} |
|
| 952 | ||
| 953 |
coloring_ggplot_call <- function(colour, |
|
| 954 |
fill, |
|
| 955 |
size, |
|
| 956 |
is_point = FALSE) {
|
|
| 957 | 15x |
if (!identical(colour, character(0)) && !identical(fill, character(0)) && |
| 958 | 15x |
is_point && !identical(size, character(0))) {
|
| 959 | 1x |
substitute( |
| 960 | 1x |
expr = aes(colour = colour_name, fill = fill_name, size = size_name), |
| 961 | 1x |
env = list(colour_name = as.name(colour), fill_name = as.name(fill), size_name = as.name(size)) |
| 962 |
) |
|
| 963 | 14x |
} else if (identical(colour, character(0)) && !identical(fill, character(0)) && |
| 964 | 14x |
is_point && identical(size, character(0))) {
|
| 965 | 1x |
substitute(expr = aes(fill = fill_name), env = list(fill_name = as.name(fill))) |
| 966 | 13x |
} else if (!identical(colour, character(0)) && !identical(fill, character(0)) && |
| 967 | 13x |
(!is_point || identical(size, character(0)))) {
|
| 968 | 3x |
substitute( |
| 969 | 3x |
expr = aes(colour = colour_name, fill = fill_name), |
| 970 | 3x |
env = list(colour_name = as.name(colour), fill_name = as.name(fill)) |
| 971 |
) |
|
| 972 | 10x |
} else if (!identical(colour, character(0)) && identical(fill, character(0)) && |
| 973 | 10x |
(!is_point || identical(size, character(0)))) {
|
| 974 | 1x |
substitute(expr = aes(colour = colour_name), env = list(colour_name = as.name(colour))) |
| 975 | 9x |
} else if (identical(colour, character(0)) && !identical(fill, character(0)) && |
| 976 | 9x |
(!is_point || identical(size, character(0)))) {
|
| 977 | 2x |
substitute(expr = aes(fill = fill_name), env = list(fill_name = as.name(fill))) |
| 978 | 7x |
} else if (identical(colour, character(0)) && identical(fill, character(0)) && |
| 979 | 7x |
is_point && !identical(size, character(0))) {
|
| 980 | 1x |
substitute(expr = aes(size = size_name), env = list(size_name = as.name(size))) |
| 981 | 6x |
} else if (!identical(colour, character(0)) && identical(fill, character(0)) && |
| 982 | 6x |
is_point && !identical(size, character(0))) {
|
| 983 | 1x |
substitute( |
| 984 | 1x |
expr = aes(colour = colour_name, size = size_name), |
| 985 | 1x |
env = list(colour_name = as.name(colour), size_name = as.name(size)) |
| 986 |
) |
|
| 987 | 5x |
} else if (identical(colour, character(0)) && !identical(fill, character(0)) && |
| 988 | 5x |
is_point && !identical(size, character(0))) {
|
| 989 | 1x |
substitute( |
| 990 | 1x |
expr = aes(colour = colour_name, fill = fill_name, size = size_name), |
| 991 | 1x |
env = list(colour_name = as.name(fill), fill_name = as.name(fill), size_name = as.name(size)) |
| 992 |
) |
|
| 993 |
} else {
|
|
| 994 | 4x |
NULL |
| 995 |
} |
|
| 996 |
} |
| 1 |
#' Scatterplot and Regression Model |
|
| 2 |
#' @md |
|
| 3 |
#' |
|
| 4 |
#' @inheritParams teal::module |
|
| 5 |
#' @inheritParams shared_params |
|
| 6 |
#' @param regressor (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 7 |
#' Regressor variables from an incoming dataset with filtering and selecting. |
|
| 8 |
#' @param response (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 9 |
#' Response variables from an incoming dataset with filtering and selecting. |
|
| 10 |
#' @param alpha optional, (`numeric`) If scalar then the plot points will have a fixed opacity. If a |
|
| 11 |
#' slider should be presented to adjust the plot point opacity dynamically then it can be a vector of |
|
| 12 |
#' length three with `c(value, min, max)`. |
|
| 13 |
#' @param size optional, (`numeric`) If scalar then the plot point sizes will have a fixed size |
|
| 14 |
#' If a slider should be presented to adjust the plot point sizes dynamically then it can be a |
|
| 15 |
#' vector of length three with `c(value, min, max)`. |
|
| 16 |
#' @param default_outlier_label optional, (`character`) The default column selected to label outliers. |
|
| 17 |
#' @param default_plot_type optional, (`numeric`) Defaults to Response vs Regressor. |
|
| 18 |
#' 1. Response vs Regressor |
|
| 19 |
#' 2. Residuals vs Fitted |
|
| 20 |
#' 3. Normal Q-Q |
|
| 21 |
#' 4. Scale-Location |
|
| 22 |
#' 5. Cook's distance |
|
| 23 |
#' 6. Residuals vs Leverage |
|
| 24 |
#' 7. Cook's dist vs Leverage |
|
| 25 |
#' |
|
| 26 |
#' @templateVar ggnames `r regression_names` |
|
| 27 |
#' @template ggplot2_args_multi |
|
| 28 |
#' |
|
| 29 |
#' @note For more examples, please see the vignette "Using regression plots" via |
|
| 30 |
#' `vignette("using-regression-plots", package = "teal.modules.general")`.
|
|
| 31 |
#' @export |
|
| 32 |
#' |
|
| 33 |
#' @examples |
|
| 34 |
#' # Regression graphs from selected response variable (BMRKR1) and |
|
| 35 |
#' # selected regressors (AGE) |
|
| 36 |
#' |
|
| 37 |
#' ADSL <- teal.modules.general::rADSL |
|
| 38 |
#' |
|
| 39 |
#' app <- teal::init( |
|
| 40 |
#' data = teal.data::cdisc_data( |
|
| 41 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 42 |
#' check = TRUE |
|
| 43 |
#' ), |
|
| 44 |
#' modules = teal::modules( |
|
| 45 |
#' teal.modules.general::tm_a_regression( |
|
| 46 |
#' label = "Regression", |
|
| 47 |
#' response = teal.transform::data_extract_spec( |
|
| 48 |
#' dataname = "ADSL", |
|
| 49 |
#' select = teal.transform::select_spec( |
|
| 50 |
#' label = "Select variable:", |
|
| 51 |
#' choices = "BMRKR1", |
|
| 52 |
#' selected = "BMRKR1", |
|
| 53 |
#' multiple = FALSE, |
|
| 54 |
#' fixed = TRUE |
|
| 55 |
#' ) |
|
| 56 |
#' ), |
|
| 57 |
#' regressor = teal.transform::data_extract_spec( |
|
| 58 |
#' dataname = "ADSL", |
|
| 59 |
#' select = teal.transform::select_spec( |
|
| 60 |
#' label = "Select variables:", |
|
| 61 |
#' choices = teal.transform::variable_choices(ADSL, c("AGE", "SEX", "RACE")),
|
|
| 62 |
#' selected = "AGE", |
|
| 63 |
#' multiple = TRUE, |
|
| 64 |
#' fixed = FALSE |
|
| 65 |
#' ) |
|
| 66 |
#' ), |
|
| 67 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 68 |
#' labs = list(subtitle = "Plot generated by Regression Module") |
|
| 69 |
#' ) |
|
| 70 |
#' ) |
|
| 71 |
#' ) |
|
| 72 |
#' ) |
|
| 73 |
#' if (interactive()) {
|
|
| 74 |
#' shinyApp(app$ui, app$server) |
|
| 75 |
#' } |
|
| 76 |
tm_a_regression <- function(label = "Regression Analysis", |
|
| 77 |
regressor, |
|
| 78 |
response, |
|
| 79 |
plot_height = c(600, 200, 2000), |
|
| 80 |
plot_width = NULL, |
|
| 81 |
alpha = c(1, 0, 1), |
|
| 82 |
size = c(2, 1, 8), |
|
| 83 |
ggtheme = c( |
|
| 84 |
"gray", "bw", "linedraw", "light", "dark", |
|
| 85 |
"minimal", "classic", "void", "test" |
|
| 86 |
), |
|
| 87 |
ggplot2_args = teal.widgets::ggplot2_args(), |
|
| 88 |
pre_output = NULL, |
|
| 89 |
post_output = NULL, |
|
| 90 |
default_plot_type = 1, |
|
| 91 |
default_outlier_label = "USUBJID") {
|
|
| 92 | ! |
logger::log_info("Initializing tm_a_regression")
|
| 93 | ! |
if (inherits(regressor, "data_extract_spec")) regressor <- list(regressor) |
| 94 | ! |
if (inherits(response, "data_extract_spec")) response <- list(response) |
| 95 | ! |
if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) |
| 96 | ||
| 97 | ! |
checkmate::assert_string(label) |
| 98 | ! |
checkmate::assert_list(response, types = "data_extract_spec") |
| 99 | ! |
if (!all(vapply(response, function(x) !(x$select$multiple), logical(1)))) {
|
| 100 | ! |
stop("'response' should not allow multiple selection")
|
| 101 |
} |
|
| 102 | ! |
checkmate::assert_list(regressor, types = "data_extract_spec") |
| 103 | ! |
ggtheme <- match.arg(ggtheme) |
| 104 | ! |
checkmate::assert_string(default_outlier_label) |
| 105 | ! |
plot_choices <- c( |
| 106 | ! |
"Response vs Regressor", "Residuals vs Fitted", "Normal Q-Q", "Scale-Location", |
| 107 | ! |
"Cook's distance", "Residuals vs Leverage", "Cook's dist vs Leverage" |
| 108 |
) |
|
| 109 | ! |
checkmate::assert_list(ggplot2_args, types = "ggplot2_args") |
| 110 | ! |
checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices))
|
| 111 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
| 112 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 113 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
| 114 | ! |
checkmate::assert_numeric( |
| 115 | ! |
plot_width[1], |
| 116 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 117 |
) |
|
| 118 | ||
| 119 |
# Send ui args |
|
| 120 | ! |
args <- as.list(environment()) |
| 121 | ! |
args[["plot_choices"]] <- plot_choices |
| 122 | ! |
data_extract_list <- list( |
| 123 | ! |
regressor = regressor, |
| 124 | ! |
response = response |
| 125 |
) |
|
| 126 | ||
| 127 | ! |
module( |
| 128 | ! |
label = label, |
| 129 | ! |
server = srv_a_regression, |
| 130 | ! |
ui = ui_a_regression, |
| 131 | ! |
ui_args = args, |
| 132 | ! |
server_args = c( |
| 133 | ! |
data_extract_list, |
| 134 | ! |
list( |
| 135 | ! |
plot_height = plot_height, |
| 136 | ! |
plot_width = plot_width, |
| 137 | ! |
default_outlier_label = default_outlier_label, |
| 138 | ! |
ggplot2_args = ggplot2_args |
| 139 |
) |
|
| 140 |
), |
|
| 141 | ! |
datanames = teal.transform::get_extract_datanames(data_extract_list) |
| 142 |
) |
|
| 143 |
} |
|
| 144 | ||
| 145 |
ui_a_regression <- function(id, ...) {
|
|
| 146 | ! |
ns <- NS(id) |
| 147 | ! |
args <- list(...) |
| 148 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset(args$regressor, args$response) |
| 149 | ||
| 150 | ! |
teal.widgets::standard_layout( |
| 151 | ! |
output = teal.widgets::white_small_well(tags$div( |
| 152 | ! |
teal.widgets::plot_with_settings_ui(id = ns("myplot")),
|
| 153 | ! |
tags$div(verbatimTextOutput(ns("text")))
|
| 154 |
)), |
|
| 155 | ! |
encoding = div( |
| 156 |
### Reporter |
|
| 157 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 158 |
### |
|
| 159 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 160 | ! |
teal.transform::datanames_input(args[c("response", "regressor")]),
|
| 161 | ! |
teal.transform::data_extract_ui( |
| 162 | ! |
id = ns("response"),
|
| 163 | ! |
label = "Response variable", |
| 164 | ! |
data_extract_spec = args$response, |
| 165 | ! |
is_single_dataset = is_single_dataset_value |
| 166 |
), |
|
| 167 | ! |
teal.transform::data_extract_ui( |
| 168 | ! |
id = ns("regressor"),
|
| 169 | ! |
label = "Regressor variables", |
| 170 | ! |
data_extract_spec = args$regressor, |
| 171 | ! |
is_single_dataset = is_single_dataset_value |
| 172 |
), |
|
| 173 | ! |
radioButtons( |
| 174 | ! |
ns("plot_type"),
|
| 175 | ! |
label = "Plot type:", |
| 176 | ! |
choices = args$plot_choices, |
| 177 | ! |
selected = args$plot_choices[args$default_plot_type] |
| 178 |
), |
|
| 179 | ! |
checkboxInput(ns("show_outlier"), label = "Display outlier labels", value = TRUE),
|
| 180 | ! |
conditionalPanel( |
| 181 | ! |
condition = "input['show_outlier']", |
| 182 | ! |
ns = ns, |
| 183 | ! |
teal.widgets::optionalSliderInput( |
| 184 | ! |
ns("outlier"),
|
| 185 | ! |
div( |
| 186 | ! |
class = "teal-tooltip", |
| 187 | ! |
tagList( |
| 188 | ! |
"Outlier definition:", |
| 189 | ! |
icon("circle-info"),
|
| 190 | ! |
span( |
| 191 | ! |
class = "tooltiptext", |
| 192 | ! |
paste( |
| 193 | ! |
"Use the slider to choose the cut-off value to define outliers.", |
| 194 | ! |
"Points with a Cook's distance greater than", |
| 195 | ! |
"the value on the slider times the mean of the Cook's distance of the dataset will have labels." |
| 196 |
) |
|
| 197 |
) |
|
| 198 |
) |
|
| 199 |
), |
|
| 200 | ! |
min = 1, max = 10, value = 9, ticks = FALSE, step = .1 |
| 201 |
), |
|
| 202 | ! |
teal.widgets::optionalSelectInput( |
| 203 | ! |
ns("label_var"),
|
| 204 | ! |
multiple = FALSE, |
| 205 | ! |
label = "Outlier label" |
| 206 |
) |
|
| 207 |
), |
|
| 208 | ! |
teal.widgets::panel_group( |
| 209 | ! |
teal.widgets::panel_item( |
| 210 | ! |
title = "Plot settings", |
| 211 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("alpha"), "Opacity:", args$alpha, ticks = FALSE),
|
| 212 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("size"), "Points size:", args$size, ticks = FALSE),
|
| 213 | ! |
selectInput( |
| 214 | ! |
inputId = ns("ggtheme"),
|
| 215 | ! |
label = "Theme (by ggplot):", |
| 216 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 217 | ! |
selected = args$ggtheme, |
| 218 | ! |
multiple = FALSE |
| 219 |
) |
|
| 220 |
) |
|
| 221 |
) |
|
| 222 |
), |
|
| 223 | ! |
forms = tagList( |
| 224 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"),
|
| 225 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 226 |
), |
|
| 227 | ! |
pre_output = args$pre_output, |
| 228 | ! |
post_output = args$post_output |
| 229 |
) |
|
| 230 |
} |
|
| 231 | ||
| 232 | ||
| 233 |
srv_a_regression <- function(id, |
|
| 234 |
data, |
|
| 235 |
reporter, |
|
| 236 |
filter_panel_api, |
|
| 237 |
response, |
|
| 238 |
regressor, |
|
| 239 |
plot_height, |
|
| 240 |
plot_width, |
|
| 241 |
ggplot2_args, |
|
| 242 |
default_outlier_label) {
|
|
| 243 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 244 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 245 | ! |
checkmate::assert_class(data, "tdata") |
| 246 | ! |
moduleServer(id, function(input, output, session) {
|
| 247 | ! |
rule_rvr1 <- function(value) {
|
| 248 | ! |
if (isTRUE(input$plot_type == "Response vs Regressor")) {
|
| 249 | ! |
if (length(value) > 1L) {
|
| 250 | ! |
"This plot can only have one regressor." |
| 251 |
} |
|
| 252 |
} |
|
| 253 |
} |
|
| 254 | ! |
rule_rvr2 <- function(other) {
|
| 255 | ! |
function(value) {
|
| 256 | ! |
if (isTRUE(input$plot_type == "Response vs Regressor")) {
|
| 257 | ! |
otherval <- selector_list()[[other]]()$select |
| 258 | ! |
if (isTRUE(value == otherval)) {
|
| 259 | ! |
"Response and Regressor must be different." |
| 260 |
} |
|
| 261 |
} |
|
| 262 |
} |
|
| 263 |
} |
|
| 264 | ||
| 265 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 266 | ! |
data_extract = list(response = response, regressor = regressor), |
| 267 | ! |
datasets = data, |
| 268 | ! |
select_validation_rule = list( |
| 269 | ! |
regressor = shinyvalidate::compose_rules( |
| 270 | ! |
shinyvalidate::sv_required("At least one regressor should be selected."),
|
| 271 | ! |
rule_rvr1, |
| 272 | ! |
rule_rvr2("response")
|
| 273 |
), |
|
| 274 | ! |
response = shinyvalidate::compose_rules( |
| 275 | ! |
shinyvalidate::sv_required("At least one response should be selected."),
|
| 276 | ! |
rule_rvr2("regressor")
|
| 277 |
) |
|
| 278 |
) |
|
| 279 |
) |
|
| 280 | ||
| 281 | ! |
iv_r <- reactive({
|
| 282 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 283 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list) |
| 284 |
}) |
|
| 285 | ||
| 286 | ! |
iv_out <- shinyvalidate::InputValidator$new() |
| 287 | ! |
iv_out$condition(~ isTRUE(input$show_outlier)) |
| 288 | ! |
iv_out$add_rule("label_var", shinyvalidate::sv_required("Please provide an `Outlier label` variable"))
|
| 289 | ! |
iv_out$enable() |
| 290 | ||
| 291 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 292 | ! |
selector_list = selector_list, |
| 293 | ! |
datasets = data, |
| 294 | ! |
join_keys = get_join_keys(data) |
| 295 |
) |
|
| 296 | ||
| 297 | ! |
regression_var <- reactive({
|
| 298 | ! |
teal::validate_inputs(iv_r()) |
| 299 | ||
| 300 | ! |
list( |
| 301 | ! |
response = as.vector(anl_merged_input()$columns_source$response), |
| 302 | ! |
regressor = as.vector(anl_merged_input()$columns_source$regressor) |
| 303 |
) |
|
| 304 |
}) |
|
| 305 | ||
| 306 | ! |
anl_merged_q <- reactive({
|
| 307 | ! |
req(anl_merged_input()) |
| 308 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 309 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 310 |
}) |
|
| 311 | ||
| 312 |
# sets qenv object and populates it with data merge call and fit expression |
|
| 313 | ! |
fit_r <- reactive({
|
| 314 | ! |
ANL <- anl_merged_q()[["ANL"]] # nolint |
| 315 | ! |
teal::validate_has_data(ANL, 10) |
| 316 | ||
| 317 | ! |
validate(need(is.numeric(ANL[regression_var()$response][[1]]), "Response variable should be numeric.")) |
| 318 | ||
| 319 | ! |
teal::validate_has_data( |
| 320 | ! |
ANL[, c(regression_var()$response, regression_var()$regressor)], 10, |
| 321 | ! |
complete = TRUE, allow_inf = FALSE |
| 322 |
) |
|
| 323 | ||
| 324 | ! |
form <- stats::as.formula( |
| 325 | ! |
paste( |
| 326 | ! |
regression_var()$response, |
| 327 | ! |
paste( |
| 328 | ! |
regression_var()$regressor, |
| 329 | ! |
collapse = " + " |
| 330 |
), |
|
| 331 | ! |
sep = " ~ " |
| 332 |
) |
|
| 333 |
) |
|
| 334 | ||
| 335 | ! |
if (input$show_outlier) {
|
| 336 | ! |
opts <- teal.transform::variable_choices(ANL) |
| 337 | ! |
selected <- if (!is.null(isolate(input$label_var)) && isolate(input$label_var) %in% as.character(opts)) {
|
| 338 | ! |
isolate(input$label_var) |
| 339 |
} else {
|
|
| 340 | ! |
if (length(opts[as.character(opts) == default_outlier_label]) == 0) {
|
| 341 | ! |
opts[[1]] |
| 342 |
} else {
|
|
| 343 | ! |
opts[as.character(opts) == default_outlier_label] |
| 344 |
} |
|
| 345 |
} |
|
| 346 | ! |
teal.widgets::updateOptionalSelectInput( |
| 347 | ! |
session = session, |
| 348 | ! |
inputId = "label_var", |
| 349 | ! |
choices = opts, |
| 350 | ! |
selected = selected |
| 351 |
) |
|
| 352 | ||
| 353 | ! |
data <- fortify(stats::lm(form, data = ANL)) |
| 354 | ! |
cooksd <- data$.cooksd[!is.nan(data$.cooksd)] |
| 355 | ! |
max_outlier <- max(ceiling(max(cooksd) / mean(cooksd)), 2) |
| 356 | ! |
cur_outlier <- isolate(input$outlier) |
| 357 | ! |
updateSliderInput( |
| 358 | ! |
session = session, |
| 359 | ! |
inputId = "outlier", |
| 360 | ! |
min = 1, |
| 361 | ! |
max = max_outlier, |
| 362 | ! |
value = if (cur_outlier < max_outlier) cur_outlier else max_outlier * .9 |
| 363 |
) |
|
| 364 |
} |
|
| 365 | ||
| 366 | ! |
anl_merged_q() %>% |
| 367 | ! |
teal.code::eval_code(substitute(fit <- stats::lm(form, data = ANL), env = list(form = form))) %>% |
| 368 | ! |
teal.code::eval_code(quote({
|
| 369 | ! |
for (regressor in names(fit$contrasts)) {
|
| 370 | ! |
alts <- paste0(levels(ANL[[regressor]]), collapse = "|") |
| 371 | ! |
names(fit$coefficients) <- gsub( |
| 372 | ! |
paste0("^(", regressor, ")(", alts, ")$"), paste0("\\1", ": ", "\\2"), names(fit$coefficients)
|
| 373 |
) |
|
| 374 |
} |
|
| 375 |
})) %>% |
|
| 376 | ! |
teal.code::eval_code(quote(summary(fit))) |
| 377 |
}) |
|
| 378 | ||
| 379 | ! |
label_col <- reactive({
|
| 380 | ! |
teal::validate_inputs(iv_out) |
| 381 | ||
| 382 | ! |
substitute( |
| 383 | ! |
expr = dplyr::if_else( |
| 384 | ! |
data$.cooksd > outliers * mean(data$.cooksd, na.rm = TRUE), |
| 385 | ! |
as.character(stats::na.omit(ANL)[[label_var]]), |
| 386 |
"" |
|
| 387 |
) %>% |
|
| 388 | ! |
dplyr::if_else(is.na(.), "cooksd == NaN", .), |
| 389 | ! |
env = list(outliers = input$outlier, label_var = input$label_var) |
| 390 |
) |
|
| 391 |
}) |
|
| 392 | ||
| 393 | ! |
outlier_label <- reactive({
|
| 394 | ! |
substitute( |
| 395 | ! |
expr = geom_text(label = label_col, hjust = 0, vjust = 1, color = "red"), |
| 396 | ! |
env = list(label_col = label_col()) |
| 397 |
) |
|
| 398 |
}) |
|
| 399 | ||
| 400 | ! |
output_q <- reactive({
|
| 401 | ! |
alpha <- input$alpha # nolint |
| 402 | ! |
size <- input$size # nolint |
| 403 | ! |
ggtheme <- input$ggtheme # nolint |
| 404 | ! |
input_type <- input$plot_type |
| 405 | ! |
show_outlier <- input$show_outlier |
| 406 | ||
| 407 | ! |
teal::validate_inputs(iv_r()) |
| 408 | ||
| 409 | ! |
plot_type_0 <- function() {
|
| 410 | ! |
fit <- fit_r()[["fit"]] |
| 411 | ! |
ANL <- anl_merged_q()[["ANL"]] # nolint |
| 412 | ||
| 413 | ! |
stopifnot(ncol(fit$model) == 2) |
| 414 | ||
| 415 | ! |
if (!is.factor(ANL[[regression_var()$regressor]])) {
|
| 416 | ! |
shinyjs::show("size")
|
| 417 | ! |
shinyjs::show("alpha")
|
| 418 | ! |
plot <- substitute( |
| 419 | ! |
env = list( |
| 420 | ! |
regressor = regression_var()$regressor, |
| 421 | ! |
response = regression_var()$response, |
| 422 | ! |
size = size, |
| 423 | ! |
alpha = alpha |
| 424 |
), |
|
| 425 | ! |
expr = ggplot( |
| 426 | ! |
fit$model[, 2:1], |
| 427 | ! |
aes_string(regressor, response) |
| 428 |
) + |
|
| 429 | ! |
geom_point(size = size, alpha = alpha) + |
| 430 | ! |
stat_smooth( |
| 431 | ! |
method = "lm", |
| 432 | ! |
formula = y ~ x, |
| 433 | ! |
se = FALSE |
| 434 |
) |
|
| 435 |
) |
|
| 436 | ! |
if (show_outlier) {
|
| 437 | ! |
plot <- substitute( |
| 438 | ! |
expr = plot + outlier_label, |
| 439 | ! |
env = list(plot = plot, outlier_label = outlier_label()) |
| 440 |
) |
|
| 441 |
} |
|
| 442 |
} else {
|
|
| 443 | ! |
shinyjs::hide("size")
|
| 444 | ! |
shinyjs::hide("alpha")
|
| 445 | ! |
plot <- substitute( |
| 446 | ! |
expr = ggplot(fit$model[, 2:1], aes_string(regressor, response)) + |
| 447 | ! |
geom_boxplot(), |
| 448 | ! |
env = list(regressor = regression_var()$regressor, response = regression_var()$response) |
| 449 |
) |
|
| 450 | ! |
if (show_outlier) {
|
| 451 | ! |
plot <- substitute(expr = plot + outlier_label, env = list(plot = plot, outlier_label = outlier_label())) |
| 452 |
} |
|
| 453 |
} |
|
| 454 | ||
| 455 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 456 | ! |
teal.widgets::resolve_ggplot2_args( |
| 457 | ! |
user_plot = ggplot2_args[["Response vs Regressor"]], |
| 458 | ! |
user_default = ggplot2_args$default, |
| 459 | ! |
module_plot = teal.widgets::ggplot2_args( |
| 460 | ! |
labs = list( |
| 461 | ! |
title = "Response vs Regressor", |
| 462 | ! |
x = varname_w_label(regression_var()$regressor, ANL), |
| 463 | ! |
y = varname_w_label(regression_var()$response, ANL) |
| 464 |
), |
|
| 465 | ! |
theme = list() |
| 466 |
) |
|
| 467 |
), |
|
| 468 | ! |
ggtheme = ggtheme |
| 469 |
) |
|
| 470 | ||
| 471 | ! |
teal.code::eval_code( |
| 472 | ! |
fit_r(), |
| 473 | ! |
substitute( |
| 474 | ! |
expr = {
|
| 475 | ! |
class(fit$residuals) <- NULL |
| 476 | ! |
data <- fortify(fit) |
| 477 | ! |
g <- plot |
| 478 | ! |
print(g) |
| 479 |
}, |
|
| 480 | ! |
env = list( |
| 481 | ! |
plot = Reduce(function(x, y) call("+", x, y), c(plot, parsed_ggplot2_args))
|
| 482 |
) |
|
| 483 |
) |
|
| 484 |
) |
|
| 485 |
} |
|
| 486 | ||
| 487 | ! |
plot_base <- function() {
|
| 488 | ! |
base_fit <- fit_r() |
| 489 | ! |
teal.code::eval_code( |
| 490 | ! |
base_fit, |
| 491 | ! |
quote({
|
| 492 | ! |
class(fit$residuals) <- NULL |
| 493 | ||
| 494 | ! |
data <- ggplot2::fortify(fit) |
| 495 | ||
| 496 | ! |
smooth <- function(x, y) {
|
| 497 | ! |
as.data.frame(stats::lowess(x, y, f = 2 / 3, iter = 3)) |
| 498 |
} |
|
| 499 | ||
| 500 | ! |
smoothy_aes <- ggplot2::aes_string(x = "x", y = "y") |
| 501 | ||
| 502 | ! |
reg_form <- deparse(fit$call[[2]]) |
| 503 |
}) |
|
| 504 |
) |
|
| 505 |
} |
|
| 506 | ||
| 507 | ! |
plot_type_1 <- function(plot_base) {
|
| 508 | ! |
shinyjs::show("size")
|
| 509 | ! |
shinyjs::show("alpha")
|
| 510 | ! |
plot <- substitute( |
| 511 | ! |
expr = ggplot(data = data, aes(.fitted, .resid)) + |
| 512 | ! |
geom_point(size = size, alpha = alpha) + |
| 513 | ! |
geom_hline(yintercept = 0, linetype = "dashed", size = 1) + |
| 514 | ! |
geom_line(data = smoothy, mapping = smoothy_aes), |
| 515 | ! |
env = list(size = size, alpha = alpha) |
| 516 |
) |
|
| 517 | ! |
if (show_outlier) {
|
| 518 | ! |
plot <- substitute(expr = plot + outlier_label, env = list(plot = plot, outlier_label = outlier_label())) |
| 519 |
} |
|
| 520 | ||
| 521 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 522 | ! |
teal.widgets::resolve_ggplot2_args( |
| 523 | ! |
user_plot = ggplot2_args[["Residuals vs Fitted"]], |
| 524 | ! |
user_default = ggplot2_args$default, |
| 525 | ! |
module_plot = teal.widgets::ggplot2_args( |
| 526 | ! |
labs = list( |
| 527 | ! |
x = quote(paste0("Fitted values\nlm(", reg_form, ")")),
|
| 528 | ! |
y = "Residuals", |
| 529 | ! |
title = "Residuals vs Fitted" |
| 530 |
) |
|
| 531 |
) |
|
| 532 |
), |
|
| 533 | ! |
ggtheme = ggtheme |
| 534 |
) |
|
| 535 | ||
| 536 | ! |
teal.code::eval_code( |
| 537 | ! |
plot_base, |
| 538 | ! |
substitute( |
| 539 | ! |
expr = {
|
| 540 | ! |
smoothy <- smooth(data$.fitted, data$.resid) |
| 541 | ! |
g <- plot |
| 542 | ! |
print(g) |
| 543 |
}, |
|
| 544 | ! |
env = list( |
| 545 | ! |
plot = Reduce(function(x, y) call("+", x, y), c(plot, parsed_ggplot2_args))
|
| 546 |
) |
|
| 547 |
) |
|
| 548 |
) |
|
| 549 |
} |
|
| 550 | ||
| 551 | ! |
plot_type_2 <- function(plot_base) {
|
| 552 | ! |
shinyjs::show("size")
|
| 553 | ! |
shinyjs::show("alpha")
|
| 554 | ! |
plot <- substitute( |
| 555 | ! |
expr = ggplot(data = data, aes(sample = .stdresid)) + |
| 556 | ! |
stat_qq(size = size, alpha = alpha) + |
| 557 | ! |
geom_abline(linetype = "dashed"), |
| 558 | ! |
env = list(size = size, alpha = alpha) |
| 559 |
) |
|
| 560 | ! |
if (show_outlier) {
|
| 561 | ! |
plot <- substitute( |
| 562 | ! |
expr = plot + |
| 563 | ! |
stat_qq( |
| 564 | ! |
geom = "text", |
| 565 | ! |
label = label_col %>% |
| 566 | ! |
data.frame(label = .) %>% |
| 567 | ! |
dplyr::filter(label != "cooksd == NaN") %>% |
| 568 | ! |
unlist(), |
| 569 | ! |
hjust = 0, |
| 570 | ! |
vjust = 1, |
| 571 | ! |
color = "red" |
| 572 |
), |
|
| 573 | ! |
env = list(plot = plot, label_col = label_col()) |
| 574 |
) |
|
| 575 |
} |
|
| 576 | ||
| 577 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 578 | ! |
teal.widgets::resolve_ggplot2_args( |
| 579 | ! |
user_plot = ggplot2_args[["Normal Q-Q"]], |
| 580 | ! |
user_default = ggplot2_args$default, |
| 581 | ! |
module_plot = teal.widgets::ggplot2_args( |
| 582 | ! |
labs = list( |
| 583 | ! |
x = quote(paste0("Theoretical Quantiles\nlm(", reg_form, ")")),
|
| 584 | ! |
y = "Standardized residuals", |
| 585 | ! |
title = "Normal Q-Q" |
| 586 |
) |
|
| 587 |
) |
|
| 588 |
), |
|
| 589 | ! |
ggtheme = ggtheme |
| 590 |
) |
|
| 591 | ||
| 592 | ! |
teal.code::eval_code( |
| 593 | ! |
plot_base, |
| 594 | ! |
substitute( |
| 595 | ! |
expr = {
|
| 596 | ! |
g <- plot |
| 597 | ! |
print(g) |
| 598 |
}, |
|
| 599 | ! |
env = list( |
| 600 | ! |
plot = Reduce(function(x, y) call("+", x, y), c(plot, parsed_ggplot2_args))
|
| 601 |
) |
|
| 602 |
) |
|
| 603 |
) |
|
| 604 |
} |
|
| 605 | ||
| 606 | ! |
plot_type_3 <- function(plot_base) {
|
| 607 | ! |
shinyjs::show("size")
|
| 608 | ! |
shinyjs::show("alpha")
|
| 609 | ! |
plot <- substitute( |
| 610 | ! |
expr = ggplot(data = data, aes(.fitted, sqrt(abs(.stdresid)))) + |
| 611 | ! |
geom_point(size = size, alpha = alpha) + |
| 612 | ! |
geom_line(data = smoothy, mapping = smoothy_aes), |
| 613 | ! |
env = list(size = size, alpha = alpha) |
| 614 |
) |
|
| 615 | ! |
if (show_outlier) {
|
| 616 | ! |
plot <- substitute(expr = plot + outlier_label, env = list(plot = plot, outlier_label = outlier_label())) |
| 617 |
} |
|
| 618 | ||
| 619 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 620 | ! |
teal.widgets::resolve_ggplot2_args( |
| 621 | ! |
user_plot = ggplot2_args[["Scale-Location"]], |
| 622 | ! |
user_default = ggplot2_args$default, |
| 623 | ! |
module_plot = teal.widgets::ggplot2_args( |
| 624 | ! |
labs = list( |
| 625 | ! |
x = quote(paste0("Fitted values\nlm(", reg_form, ")")),
|
| 626 | ! |
y = quote(expression(sqrt(abs(`Standardized residuals`)))), |
| 627 | ! |
title = "Scale-Location" |
| 628 |
) |
|
| 629 |
) |
|
| 630 |
), |
|
| 631 | ! |
ggtheme = ggtheme |
| 632 |
) |
|
| 633 | ||
| 634 | ! |
teal.code::eval_code( |
| 635 | ! |
plot_base, |
| 636 | ! |
substitute( |
| 637 | ! |
expr = {
|
| 638 | ! |
smoothy <- smooth(data$.fitted, sqrt(abs(data$.stdresid))) |
| 639 | ! |
g <- plot |
| 640 | ! |
print(g) |
| 641 |
}, |
|
| 642 | ! |
env = list( |
| 643 | ! |
plot = Reduce(function(x, y) call("+", x, y), c(plot, parsed_ggplot2_args))
|
| 644 |
) |
|
| 645 |
) |
|
| 646 |
) |
|
| 647 |
} |
|
| 648 | ||
| 649 | ! |
plot_type_4 <- function(plot_base) {
|
| 650 | ! |
shinyjs::hide("size")
|
| 651 | ! |
shinyjs::show("alpha")
|
| 652 | ! |
plot <- substitute( |
| 653 | ! |
expr = ggplot(data = data, aes(seq_along(.cooksd), .cooksd)) + |
| 654 | ! |
geom_col(alpha = alpha), |
| 655 | ! |
env = list(alpha = alpha) |
| 656 |
) |
|
| 657 | ! |
if (show_outlier) {
|
| 658 | ! |
plot <- substitute( |
| 659 | ! |
expr = plot + |
| 660 | ! |
geom_hline( |
| 661 | ! |
yintercept = c( |
| 662 | ! |
outlier * mean(data$.cooksd, na.rm = TRUE), |
| 663 | ! |
mean(data$.cooksd, na.rm = TRUE) |
| 664 |
), |
|
| 665 | ! |
color = "red", |
| 666 | ! |
linetype = "dashed" |
| 667 |
) + |
|
| 668 | ! |
geom_text( |
| 669 | ! |
aes( |
| 670 | ! |
x = 0, |
| 671 | ! |
y = mean(data$.cooksd, na.rm = TRUE), |
| 672 | ! |
label = paste("mu", "=", round(mean(data$.cooksd, na.rm = TRUE), 4)),
|
| 673 | ! |
vjust = -1, |
| 674 | ! |
hjust = 0, |
| 675 | ! |
color = "red", |
| 676 | ! |
angle = 90 |
| 677 |
), |
|
| 678 | ! |
parse = TRUE, |
| 679 | ! |
show.legend = FALSE |
| 680 |
) + |
|
| 681 | ! |
outlier_label, |
| 682 | ! |
env = list(plot = plot, outlier = input$outlier, outlier_label = outlier_label()) |
| 683 |
) |
|
| 684 |
} |
|
| 685 | ||
| 686 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 687 | ! |
teal.widgets::resolve_ggplot2_args( |
| 688 | ! |
user_plot = ggplot2_args[["Cook's distance"]], |
| 689 | ! |
user_default = ggplot2_args$default, |
| 690 | ! |
module_plot = teal.widgets::ggplot2_args( |
| 691 | ! |
labs = list( |
| 692 | ! |
x = quote(paste0("Obs. number\nlm(", reg_form, ")")),
|
| 693 | ! |
y = "Cook's distance", |
| 694 | ! |
title = "Cook's distance" |
| 695 |
) |
|
| 696 |
) |
|
| 697 |
), |
|
| 698 | ! |
ggtheme = ggtheme |
| 699 |
) |
|
| 700 | ||
| 701 | ! |
teal.code::eval_code( |
| 702 | ! |
plot_base, |
| 703 | ! |
substitute( |
| 704 | ! |
expr = {
|
| 705 | ! |
g <- plot |
| 706 | ! |
print(g) |
| 707 |
}, |
|
| 708 | ! |
env = list( |
| 709 | ! |
plot = Reduce(function(x, y) call("+", x, y), c(plot, parsed_ggplot2_args))
|
| 710 |
) |
|
| 711 |
) |
|
| 712 |
) |
|
| 713 |
} |
|
| 714 | ||
| 715 | ||
| 716 | ! |
plot_type_5 <- function(plot_base) {
|
| 717 | ! |
shinyjs::show("size")
|
| 718 | ! |
shinyjs::show("alpha")
|
| 719 | ! |
plot <- substitute( |
| 720 | ! |
expr = ggplot(data = data, aes(.hat, .stdresid)) + |
| 721 | ! |
geom_vline( |
| 722 | ! |
size = 1, |
| 723 | ! |
colour = "black", |
| 724 | ! |
linetype = "dashed", |
| 725 | ! |
xintercept = 0 |
| 726 |
) + |
|
| 727 | ! |
geom_hline( |
| 728 | ! |
size = 1, |
| 729 | ! |
colour = "black", |
| 730 | ! |
linetype = "dashed", |
| 731 | ! |
yintercept = 0 |
| 732 |
) + |
|
| 733 | ! |
geom_point(size = size, alpha = alpha) + |
| 734 | ! |
geom_line(data = smoothy, mapping = smoothy_aes), |
| 735 | ! |
env = list(size = size, alpha = alpha) |
| 736 |
) |
|
| 737 | ! |
if (show_outlier) {
|
| 738 | ! |
plot <- substitute(expr = plot + outlier_label, env = list(plot = plot, outlier_label = outlier_label())) |
| 739 |
} |
|
| 740 | ||
| 741 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 742 | ! |
teal.widgets::resolve_ggplot2_args( |
| 743 | ! |
user_plot = ggplot2_args[["Residuals vs Leverage"]], |
| 744 | ! |
user_default = ggplot2_args$default, |
| 745 | ! |
module_plot = teal.widgets::ggplot2_args( |
| 746 | ! |
labs = list( |
| 747 | ! |
x = quote(paste0("Standardized residuals\nlm(", reg_form, ")")),
|
| 748 | ! |
y = "Leverage", |
| 749 | ! |
title = "Residuals vs Leverage" |
| 750 |
) |
|
| 751 |
) |
|
| 752 |
), |
|
| 753 | ! |
ggtheme = ggtheme |
| 754 |
) |
|
| 755 | ||
| 756 | ! |
teal.code::eval_code( |
| 757 | ! |
plot_base, |
| 758 | ! |
substitute( |
| 759 | ! |
expr = {
|
| 760 | ! |
smoothy <- smooth(data$.hat, data$.stdresid) |
| 761 | ! |
g <- plot |
| 762 | ! |
print(g) |
| 763 |
}, |
|
| 764 | ! |
env = list( |
| 765 | ! |
plot = Reduce(function(x, y) call("+", x, y), c(plot, parsed_ggplot2_args))
|
| 766 |
) |
|
| 767 |
) |
|
| 768 |
) |
|
| 769 |
} |
|
| 770 | ||
| 771 | ! |
plot_type_6 <- function(plot_base) {
|
| 772 | ! |
shinyjs::show("size")
|
| 773 | ! |
shinyjs::show("alpha")
|
| 774 | ! |
plot <- substitute( |
| 775 | ! |
expr = ggplot(data = data, aes(.hat, .cooksd)) + |
| 776 | ! |
geom_vline(xintercept = 0, colour = NA) + |
| 777 | ! |
geom_abline( |
| 778 | ! |
slope = seq(0, 3, by = 0.5), |
| 779 | ! |
colour = "black", |
| 780 | ! |
linetype = "dashed", |
| 781 | ! |
size = 1 |
| 782 |
) + |
|
| 783 | ! |
geom_line(data = smoothy, mapping = smoothy_aes) + |
| 784 | ! |
geom_point(size = size, alpha = alpha), |
| 785 | ! |
env = list(size = size, alpha = alpha) |
| 786 |
) |
|
| 787 | ! |
if (show_outlier) {
|
| 788 | ! |
plot <- substitute(expr = plot + outlier_label, env = list(plot = plot, outlier_label = outlier_label())) |
| 789 |
} |
|
| 790 | ||
| 791 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 792 | ! |
teal.widgets::resolve_ggplot2_args( |
| 793 | ! |
user_plot = ggplot2_args[["Cook's dist vs Leverage"]], |
| 794 | ! |
user_default = ggplot2_args$default, |
| 795 | ! |
module_plot = teal.widgets::ggplot2_args( |
| 796 | ! |
labs = list( |
| 797 | ! |
x = quote(paste0("Leverage\nlm(", reg_form, ")")),
|
| 798 | ! |
y = "Cooks's distance", |
| 799 | ! |
title = "Cook's dist vs Leverage" |
| 800 |
) |
|
| 801 |
) |
|
| 802 |
), |
|
| 803 | ! |
ggtheme = ggtheme |
| 804 |
) |
|
| 805 | ||
| 806 | ! |
teal.code::eval_code( |
| 807 | ! |
plot_base, |
| 808 | ! |
substitute( |
| 809 | ! |
expr = {
|
| 810 | ! |
smoothy <- smooth(data$.hat, data$.cooksd) |
| 811 | ! |
g <- plot |
| 812 | ! |
print(g) |
| 813 |
}, |
|
| 814 | ! |
env = list( |
| 815 | ! |
plot = Reduce(function(x, y) call("+", x, y), c(plot, parsed_ggplot2_args))
|
| 816 |
) |
|
| 817 |
) |
|
| 818 |
) |
|
| 819 |
} |
|
| 820 | ||
| 821 | ! |
qenv <- if (input_type == "Response vs Regressor") {
|
| 822 | ! |
plot_type_0() |
| 823 |
} else {
|
|
| 824 | ! |
plot_base_q <- plot_base() |
| 825 | ! |
switch(input_type, |
| 826 | ! |
"Residuals vs Fitted" = plot_base_q %>% plot_type_1(), |
| 827 | ! |
"Normal Q-Q" = plot_base_q %>% plot_type_2(), |
| 828 | ! |
"Scale-Location" = plot_base_q %>% plot_type_3(), |
| 829 | ! |
"Cook's distance" = plot_base_q %>% plot_type_4(), |
| 830 | ! |
"Residuals vs Leverage" = plot_base_q %>% plot_type_5(), |
| 831 | ! |
"Cook's dist vs Leverage" = plot_base_q %>% plot_type_6() |
| 832 |
) |
|
| 833 |
} |
|
| 834 | ! |
qenv |
| 835 |
}) |
|
| 836 | ||
| 837 | ||
| 838 | ! |
fitted <- reactive(output_q()[["fit"]]) |
| 839 | ! |
plot_r <- reactive(output_q()[["g"]]) |
| 840 | ||
| 841 |
# Insert the plot into a plot_with_settings module from teal.widgets |
|
| 842 | ! |
pws <- teal.widgets::plot_with_settings_srv( |
| 843 | ! |
id = "myplot", |
| 844 | ! |
plot_r = plot_r, |
| 845 | ! |
height = plot_height, |
| 846 | ! |
width = plot_width |
| 847 |
) |
|
| 848 | ||
| 849 | ! |
output$text <- renderText({
|
| 850 | ! |
req(iv_r()$is_valid()) |
| 851 | ! |
req(iv_out$is_valid()) |
| 852 | ! |
paste(utils::capture.output(summary(teal.code::dev_suppress(fitted())))[-1], |
| 853 | ! |
collapse = "\n" |
| 854 |
) |
|
| 855 |
}) |
|
| 856 | ||
| 857 | ! |
teal.widgets::verbatim_popup_srv( |
| 858 | ! |
id = "warning", |
| 859 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 860 | ! |
title = "Warning", |
| 861 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 862 |
) |
|
| 863 | ||
| 864 | ! |
teal.widgets::verbatim_popup_srv( |
| 865 | ! |
id = "rcode", |
| 866 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 867 | ! |
title = "R code for the regression plot", |
| 868 |
) |
|
| 869 | ||
| 870 |
### REPORTER |
|
| 871 | ! |
if (with_reporter) {
|
| 872 | ! |
card_fun <- function(comment) {
|
| 873 | ! |
card <- teal::TealReportCard$new() |
| 874 | ! |
card$set_name("Linear Regression Plot")
|
| 875 | ! |
card$append_text("Linear Regression Plot", "header2")
|
| 876 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 877 | ! |
card$append_text("Plot", "header3")
|
| 878 | ! |
card$append_plot(plot_r(), dim = pws$dim()) |
| 879 | ! |
if (!comment == "") {
|
| 880 | ! |
card$append_text("Comment", "header3")
|
| 881 | ! |
card$append_text(comment) |
| 882 |
} |
|
| 883 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 884 | ! |
card |
| 885 |
} |
|
| 886 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 887 |
} |
|
| 888 |
### |
|
| 889 |
}) |
|
| 890 |
} |
|
| 891 | ||
| 892 |
regression_names <- paste0( |
|
| 893 |
'"Response vs Regressor", "Residuals vs Fitted", ', |
|
| 894 |
'"Scale-Location", "Cook\'s distance", "Residuals vs Leverage"", "Cook\'s dist vs Leverage"' |
|
| 895 |
) |
| 1 |
#' Missing data module |
|
| 2 |
#' |
|
| 3 |
#' Present analysis of missing observations and patients. |
|
| 4 |
#' |
|
| 5 |
#' @inheritParams teal::module |
|
| 6 |
#' @inheritParams shared_params |
|
| 7 |
#' @param parent_dataname (`character(1)`) If this `dataname` exists in then "the by subject"graph is displayed. |
|
| 8 |
#' For `CDISC` data. In non `CDISC` data this can be ignored. Defaults to `"ADSL"`. |
|
| 9 |
#' @param ggtheme optional, (`character`) `ggplot2` theme to be used by default. |
|
| 10 |
#' One of `c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test")`.
|
|
| 11 |
#' Each theme can be chosen by the user during the session. Defaults to `"classic"`. |
|
| 12 |
#' |
|
| 13 |
#' @templateVar ggnames "Summary Obs", "Summary Patients", "Combinations Main", "Combinations Hist", "By Subject" |
|
| 14 |
#' @template ggplot2_args_multi |
|
| 15 |
#' |
|
| 16 |
#' @export |
|
| 17 |
#' |
|
| 18 |
#' @examples |
|
| 19 |
#' library(nestcolor) |
|
| 20 |
#' |
|
| 21 |
#' ADSL <- teal.modules.general::rADSL |
|
| 22 |
#' ADRS <- teal.modules.general::rADRS |
|
| 23 |
#' |
|
| 24 |
#' app <- teal::init( |
|
| 25 |
#' data = teal.data::cdisc_data( |
|
| 26 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 27 |
#' teal.data::cdisc_dataset("ADRS", ADRS, code = "ADRS <- teal.modules.general::rADRS"),
|
|
| 28 |
#' check = TRUE |
|
| 29 |
#' ), |
|
| 30 |
#' modules = teal::modules( |
|
| 31 |
#' teal.modules.general::tm_missing_data( |
|
| 32 |
#' ggplot2_args = list( |
|
| 33 |
#' "Combinations Hist" = teal.widgets::ggplot2_args( |
|
| 34 |
#' labs = list(subtitle = "Plot produced by Missing Data Module", caption = NULL) |
|
| 35 |
#' ), |
|
| 36 |
#' "Combinations Main" = teal.widgets::ggplot2_args(labs = list(title = NULL)) |
|
| 37 |
#' ) |
|
| 38 |
#' ) |
|
| 39 |
#' ) |
|
| 40 |
#' ) |
|
| 41 |
#' if (interactive()) {
|
|
| 42 |
#' shinyApp(app$ui, app$server) |
|
| 43 |
#' } |
|
| 44 |
tm_missing_data <- function(label = "Missing data", |
|
| 45 |
plot_height = c(600, 400, 5000), |
|
| 46 |
plot_width = NULL, |
|
| 47 |
parent_dataname = "ADSL", |
|
| 48 |
ggtheme = c( |
|
| 49 |
"classic", "gray", "bw", "linedraw", |
|
| 50 |
"light", "dark", "minimal", "void", "test" |
|
| 51 |
), |
|
| 52 |
ggplot2_args = list( |
|
| 53 |
"Combinations Hist" = teal.widgets::ggplot2_args(labs = list(caption = NULL)), |
|
| 54 |
"Combinations Main" = teal.widgets::ggplot2_args(labs = list(title = NULL)) |
|
| 55 |
), |
|
| 56 |
pre_output = NULL, |
|
| 57 |
post_output = NULL) {
|
|
| 58 | ! |
if (!requireNamespace("gridExtra", quietly = TRUE)) {
|
| 59 | ! |
stop("Cannot load gridExtra - please install the package or restart your session.")
|
| 60 |
} |
|
| 61 | ! |
if (!requireNamespace("rlang", quietly = TRUE)) {
|
| 62 | ! |
stop("Cannot load rlang - please install the package or restart your session.")
|
| 63 |
} |
|
| 64 | ! |
logger::log_info("Initializing tm_missing_data")
|
| 65 | ! |
if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) |
| 66 | ||
| 67 | ! |
checkmate::assert_string(label) |
| 68 | ! |
checkmate::assert_character(parent_dataname, min.len = 0, max.len = 1) |
| 69 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
| 70 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 71 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
| 72 | ! |
checkmate::assert_numeric( |
| 73 | ! |
plot_width[1], |
| 74 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 75 |
) |
|
| 76 | ! |
ggtheme <- match.arg(ggtheme) |
| 77 | ! |
plot_choices <- c("Summary Obs", "Summary Patients", "Combinations Main", "Combinations Hist", "By Subject")
|
| 78 | ! |
checkmate::assert_list(ggplot2_args, types = "ggplot2_args") |
| 79 | ! |
checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices))
|
| 80 | ||
| 81 | ! |
module( |
| 82 | ! |
label, |
| 83 | ! |
server = srv_page_missing_data, |
| 84 | ! |
server_args = list( |
| 85 | ! |
parent_dataname = parent_dataname, plot_height = plot_height, |
| 86 | ! |
plot_width = plot_width, ggplot2_args = ggplot2_args |
| 87 |
), |
|
| 88 | ! |
ui = ui_page_missing_data, |
| 89 | ! |
datanames = "all", |
| 90 | ! |
ui_args = list( |
| 91 | ! |
parent_dataname = parent_dataname, pre_output = pre_output, |
| 92 | ! |
post_output = post_output, ggtheme = ggtheme |
| 93 |
) |
|
| 94 |
) |
|
| 95 |
} |
|
| 96 | ||
| 97 |
ui_page_missing_data <- function(id, data, parent_dataname, pre_output = NULL, post_output = NULL, ggtheme) {
|
|
| 98 | ! |
ns <- NS(id) |
| 99 | ! |
datanames <- names(data) |
| 100 | ||
| 101 | ! |
if_subject_plot <- length(parent_dataname) > 0 && parent_dataname %in% datanames |
| 102 | ||
| 103 | ! |
shiny::tagList( |
| 104 | ! |
include_css_files("custom"),
|
| 105 | ! |
teal.widgets::standard_layout( |
| 106 | ! |
output = teal.widgets::white_small_well( |
| 107 | ! |
div( |
| 108 | ! |
class = "flex", |
| 109 | ! |
column( |
| 110 | ! |
width = 12, |
| 111 | ! |
do.call( |
| 112 | ! |
tabsetPanel, |
| 113 | ! |
c( |
| 114 | ! |
id = ns("dataname_tab"),
|
| 115 | ! |
lapply( |
| 116 | ! |
datanames, |
| 117 | ! |
function(x) {
|
| 118 | ! |
tabPanel( |
| 119 | ! |
title = x, |
| 120 | ! |
column( |
| 121 | ! |
width = 12, |
| 122 | ! |
div( |
| 123 | ! |
class = "mt-4", |
| 124 | ! |
ui_missing_data(id = ns(x), by_subject_plot = if_subject_plot) |
| 125 |
) |
|
| 126 |
) |
|
| 127 |
) |
|
| 128 |
} |
|
| 129 |
) |
|
| 130 |
) |
|
| 131 |
) |
|
| 132 |
) |
|
| 133 |
) |
|
| 134 |
), |
|
| 135 | ! |
encoding = div( |
| 136 | ! |
tagList( |
| 137 | ! |
lapply( |
| 138 | ! |
datanames, |
| 139 | ! |
function(x) {
|
| 140 | ! |
conditionalPanel( |
| 141 | ! |
is_tab_active_js(ns("dataname_tab"), x),
|
| 142 | ! |
encoding_missing_data( |
| 143 | ! |
id = ns(x), |
| 144 | ! |
summary_per_patient = if_subject_plot, |
| 145 | ! |
ggtheme = ggtheme, |
| 146 | ! |
datanames = datanames |
| 147 |
) |
|
| 148 |
) |
|
| 149 |
} |
|
| 150 |
) |
|
| 151 |
) |
|
| 152 |
), |
|
| 153 | ! |
pre_output = pre_output, |
| 154 | ! |
post_output = post_output |
| 155 |
) |
|
| 156 |
) |
|
| 157 |
} |
|
| 158 | ||
| 159 |
srv_page_missing_data <- function(id, data, reporter, filter_panel_api, parent_dataname, |
|
| 160 |
plot_height, plot_width, ggplot2_args) {
|
|
| 161 | ! |
moduleServer(id, function(input, output, session) {
|
| 162 | ! |
lapply( |
| 163 | ! |
names(data), |
| 164 | ! |
function(x) {
|
| 165 | ! |
srv_missing_data( |
| 166 | ! |
id = x, |
| 167 | ! |
data = data, |
| 168 | ! |
reporter = reporter, |
| 169 | ! |
filter_panel_api = filter_panel_api, |
| 170 | ! |
dataname = x, |
| 171 | ! |
parent_dataname = parent_dataname, |
| 172 | ! |
plot_height = plot_height, |
| 173 | ! |
plot_width = plot_width, |
| 174 | ! |
ggplot2_args = ggplot2_args |
| 175 |
) |
|
| 176 |
} |
|
| 177 |
) |
|
| 178 |
}) |
|
| 179 |
} |
|
| 180 | ||
| 181 |
ui_missing_data <- function(id, by_subject_plot = FALSE) {
|
|
| 182 | ! |
ns <- NS(id) |
| 183 | ||
| 184 | ! |
tab_list <- list( |
| 185 | ! |
tabPanel( |
| 186 | ! |
"Summary", |
| 187 | ! |
teal.widgets::plot_with_settings_ui(id = ns("summary_plot")),
|
| 188 | ! |
helpText( |
| 189 | ! |
p(paste( |
| 190 | ! |
'The "Summary" graph shows the number of missing values per variable (both absolute and percentage),', |
| 191 | ! |
"sorted by magnitude." |
| 192 |
)), |
|
| 193 | ! |
p( |
| 194 | ! |
'The "summary per patients" graph is showing how many subjects have at least one missing observation', |
| 195 | ! |
"for each variable. It will be most useful for panel datasets." |
| 196 |
) |
|
| 197 |
) |
|
| 198 |
), |
|
| 199 | ! |
tabPanel( |
| 200 | ! |
"Combinations", |
| 201 | ! |
teal.widgets::plot_with_settings_ui(id = ns("combination_plot")),
|
| 202 | ! |
helpText( |
| 203 | ! |
p(paste( |
| 204 | ! |
'The "Combinations" graph is used to explore the relationship between the missing data within', |
| 205 | ! |
"different columns of the dataset.", |
| 206 | ! |
"It shows the different patterns of missingness in the rows of the data.", |
| 207 | ! |
'For example, suppose that 70 rows of the data have exactly columns "A" and "B" missing.', |
| 208 | ! |
"In this case there would be a bar of height 70 in the top graph and", |
| 209 | ! |
'the column below this in the second graph would have rows "A" and "B" cells shaded red.' |
| 210 |
)), |
|
| 211 | ! |
p(paste( |
| 212 | ! |
"Due to the large number of missing data patterns possible, only those with a large set of observations", |
| 213 | ! |
'are shown in the graph and the "Combination cut-off" slider can be used to adjust the number shown.' |
| 214 |
)) |
|
| 215 |
) |
|
| 216 |
), |
|
| 217 | ! |
tabPanel( |
| 218 | ! |
"By Variable Levels", |
| 219 | ! |
teal.widgets::get_dt_rows(ns("levels_table"), ns("levels_table_rows")),
|
| 220 | ! |
DT::dataTableOutput(ns("levels_table"))
|
| 221 |
) |
|
| 222 |
) |
|
| 223 | ! |
if (isTRUE(by_subject_plot)) {
|
| 224 | ! |
tab_list <- append( |
| 225 | ! |
tab_list, |
| 226 | ! |
list(tabPanel( |
| 227 | ! |
"Grouped by Subject", |
| 228 | ! |
teal.widgets::plot_with_settings_ui(id = ns("by_subject_plot")),
|
| 229 | ! |
helpText( |
| 230 | ! |
p(paste( |
| 231 | ! |
"This graph shows the missingness with respect to subjects rather than individual rows of the", |
| 232 | ! |
"dataset. Each row represents one dataset variable and each column a single subject. Only subjects", |
| 233 | ! |
"with at least one record in this dataset are shown. For a given subject, if they have any missing", |
| 234 | ! |
"values of a specific variable then the appropriate cell in the graph is marked as missing." |
| 235 |
)) |
|
| 236 |
) |
|
| 237 |
)) |
|
| 238 |
) |
|
| 239 |
} |
|
| 240 | ||
| 241 | ! |
do.call( |
| 242 | ! |
tabsetPanel, |
| 243 | ! |
c( |
| 244 | ! |
id = ns("summary_type"),
|
| 245 | ! |
tab_list |
| 246 |
) |
|
| 247 |
) |
|
| 248 |
} |
|
| 249 | ||
| 250 |
encoding_missing_data <- function(id, summary_per_patient = FALSE, ggtheme, datanames) {
|
|
| 251 | ! |
ns <- NS(id) |
| 252 | ||
| 253 | ! |
tagList( |
| 254 |
### Reporter |
|
| 255 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 256 |
### |
|
| 257 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 258 | ! |
helpText( |
| 259 | ! |
paste0("Dataset", `if`(length(datanames) > 1, "s", ""), ":"),
|
| 260 | ! |
tags$code(paste(datanames, collapse = ", ")) |
| 261 |
), |
|
| 262 | ! |
uiOutput(ns("variables")),
|
| 263 | ! |
actionButton( |
| 264 | ! |
ns("filter_na"),
|
| 265 | ! |
span("Select only vars with missings", class = "whitespace-normal"),
|
| 266 | ! |
width = "100%", |
| 267 | ! |
class = "mb-4" |
| 268 |
), |
|
| 269 | ! |
conditionalPanel( |
| 270 | ! |
is_tab_active_js(ns("summary_type"), "Summary"),
|
| 271 | ! |
checkboxInput( |
| 272 | ! |
ns("any_na"),
|
| 273 | ! |
div( |
| 274 | ! |
class = "teal-tooltip", |
| 275 | ! |
tagList( |
| 276 | ! |
"Add **anyna** variable", |
| 277 | ! |
icon("circle-info"),
|
| 278 | ! |
span( |
| 279 | ! |
class = "tooltiptext", |
| 280 | ! |
"Describes the number of observations with at least one missing value in any variable." |
| 281 |
) |
|
| 282 |
) |
|
| 283 |
), |
|
| 284 | ! |
value = FALSE |
| 285 |
), |
|
| 286 | ! |
if (summary_per_patient) {
|
| 287 | ! |
checkboxInput( |
| 288 | ! |
ns("if_patients_plot"),
|
| 289 | ! |
div( |
| 290 | ! |
class = "teal-tooltip", |
| 291 | ! |
tagList( |
| 292 | ! |
"Add summary per patients", |
| 293 | ! |
icon("circle-info"),
|
| 294 | ! |
span( |
| 295 | ! |
class = "tooltiptext", |
| 296 | ! |
paste( |
| 297 | ! |
"Displays the number of missing values per observation,", |
| 298 | ! |
"where the x-axis is sorted by observation appearance in the table." |
| 299 |
) |
|
| 300 |
) |
|
| 301 |
) |
|
| 302 |
), |
|
| 303 | ! |
value = FALSE |
| 304 |
) |
|
| 305 |
} |
|
| 306 |
), |
|
| 307 | ! |
conditionalPanel( |
| 308 | ! |
is_tab_active_js(ns("summary_type"), "Combinations"),
|
| 309 | ! |
uiOutput(ns("cutoff"))
|
| 310 |
), |
|
| 311 | ! |
conditionalPanel( |
| 312 | ! |
is_tab_active_js(ns("summary_type"), "By Variable Levels"),
|
| 313 | ! |
tagList( |
| 314 | ! |
uiOutput(ns("group_by_var_ui")),
|
| 315 | ! |
uiOutput(ns("group_by_vals_ui")),
|
| 316 | ! |
radioButtons( |
| 317 | ! |
ns("count_type"),
|
| 318 | ! |
label = "Display missing as", |
| 319 | ! |
choices = c("counts", "proportions"),
|
| 320 | ! |
selected = "counts", |
| 321 | ! |
inline = TRUE |
| 322 |
) |
|
| 323 |
) |
|
| 324 |
), |
|
| 325 | ! |
teal.widgets::panel_item( |
| 326 | ! |
title = "Plot settings", |
| 327 | ! |
selectInput( |
| 328 | ! |
inputId = ns("ggtheme"),
|
| 329 | ! |
label = "Theme (by ggplot):", |
| 330 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 331 | ! |
selected = ggtheme, |
| 332 | ! |
multiple = FALSE |
| 333 |
) |
|
| 334 |
), |
|
| 335 | ! |
hr(), |
| 336 | ! |
forms = tagList( |
| 337 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"),
|
| 338 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 339 |
), |
|
| 340 |
) |
|
| 341 |
} |
|
| 342 | ||
| 343 |
srv_missing_data <- function(id, data, reporter, filter_panel_api, dataname, parent_dataname, |
|
| 344 |
plot_height, plot_width, ggplot2_args) {
|
|
| 345 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 346 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 347 | ! |
checkmate::assert_class(data, "tdata") |
| 348 | ! |
moduleServer(id, function(input, output, session) {
|
| 349 | ! |
prev_group_by_var <- reactiveVal("")
|
| 350 | ! |
data_r <- data[[dataname]] |
| 351 | ! |
data_keys <- reactive(get_join_keys(data)$get(dataname)[[dataname]]) |
| 352 | ||
| 353 | ! |
iv_r <- reactive({
|
| 354 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 355 | ! |
iv$add_rule( |
| 356 | ! |
"variables_select", |
| 357 | ! |
shinyvalidate::sv_required("At least one reference variable needs to be selected.")
|
| 358 |
) |
|
| 359 | ! |
iv$add_rule( |
| 360 | ! |
"variables_select", |
| 361 | ! |
~ if (length(setdiff((.), data_keys())) < 1) "Please also select non-key columns." |
| 362 |
) |
|
| 363 | ! |
iv_summary_table <- shinyvalidate::InputValidator$new() |
| 364 | ! |
iv_summary_table$condition(~ isTRUE(input$summary_type == "By Variable Levels")) |
| 365 | ! |
iv_summary_table$add_rule("count_type", shinyvalidate::sv_required("Please select type of counts"))
|
| 366 | ! |
iv_summary_table$add_rule( |
| 367 | ! |
"group_by_vals", |
| 368 | ! |
shinyvalidate::sv_required("Please select both group-by variable and values")
|
| 369 |
) |
|
| 370 | ! |
iv_summary_table$add_rule( |
| 371 | ! |
"group_by_var", |
| 372 | ! |
~ if (length(.) > 0 && length(input$variables_select) == 1 && (.) == input$variables_select) {
|
| 373 | ! |
"If only one reference variable is selected it must not be the grouping variable." |
| 374 |
} |
|
| 375 |
) |
|
| 376 | ! |
iv_summary_table$add_rule( |
| 377 | ! |
"variables_select", |
| 378 | ! |
~ if (length(input$group_by_var) > 0 && length(.) == 1 && (.) == input$group_by_var) {
|
| 379 | ! |
"If only one reference variable is selected it must not be the grouping variable." |
| 380 |
} |
|
| 381 |
) |
|
| 382 | ! |
iv$add_validator(iv_summary_table) |
| 383 | ! |
iv$enable() |
| 384 | ! |
iv |
| 385 |
}) |
|
| 386 | ||
| 387 | ||
| 388 | ! |
data_parent_keys <- reactive({
|
| 389 | ! |
if (length(parent_dataname) > 0 && parent_dataname %in% names(data)) {
|
| 390 | ! |
keys <- get_join_keys(data)$get(dataname) |
| 391 | ! |
if (parent_dataname %in% names(keys)) {
|
| 392 | ! |
keys[[parent_dataname]] |
| 393 |
} else {
|
|
| 394 | ! |
keys[[dataname]] |
| 395 |
} |
|
| 396 |
} else {
|
|
| 397 | ! |
NULL |
| 398 |
} |
|
| 399 |
}) |
|
| 400 | ||
| 401 | ! |
common_code_q <- reactive({
|
| 402 | ! |
teal::validate_inputs(iv_r()) |
| 403 | ||
| 404 | ! |
group_var <- input$group_by_var |
| 405 | ! |
anl <- data_r() |
| 406 | ! |
qenv <- teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) |
| 407 | ||
| 408 | ! |
qenv <- if (!is.null(selected_vars()) && length(selected_vars()) != ncol(anl)) {
|
| 409 | ! |
teal.code::eval_code( |
| 410 | ! |
qenv, |
| 411 | ! |
substitute( |
| 412 | ! |
expr = ANL <- anl_name[, selected_vars, drop = FALSE], # nolint |
| 413 | ! |
env = list(anl_name = as.name(dataname), selected_vars = selected_vars()) |
| 414 |
) |
|
| 415 |
) |
|
| 416 |
} else {
|
|
| 417 | ! |
teal.code::eval_code( |
| 418 | ! |
qenv, |
| 419 | ! |
substitute(expr = ANL <- anl_name, env = list(anl_name = as.name(dataname))) # nolint |
| 420 |
) |
|
| 421 |
} |
|
| 422 | ||
| 423 | ! |
if (input$summary_type == "By Variable Levels" && !is.null(group_var) && !(group_var %in% selected_vars())) {
|
| 424 | ! |
qenv <- teal.code::eval_code( |
| 425 | ! |
qenv, |
| 426 | ! |
substitute( |
| 427 | ! |
expr = ANL[[group_var]] <- anl_name[[group_var]], # nolint |
| 428 | ! |
env = list(group_var = group_var, anl_name = as.name(dataname)) |
| 429 |
) |
|
| 430 |
) |
|
| 431 |
} |
|
| 432 | ||
| 433 | ! |
new_col_name <- "**anyna**" # nolint variable assigned and used |
| 434 | ||
| 435 | ! |
qenv <- teal.code::eval_code( |
| 436 | ! |
qenv, |
| 437 | ! |
substitute( |
| 438 | ! |
expr = |
| 439 | ! |
create_cols_labels <- function(cols, just_label = FALSE) {
|
| 440 | ! |
column_labels <- column_labels_value |
| 441 | ! |
column_labels[is.na(column_labels) | length(column_labels) == 0] <- "" |
| 442 | ! |
if (just_label) {
|
| 443 | ! |
labels <- column_labels[cols] |
| 444 |
} else {
|
|
| 445 | ! |
labels <- ifelse(cols == new_col_name | cols == "", cols, paste0(column_labels[cols], " [", cols, "]")) |
| 446 |
} |
|
| 447 | ! |
return(labels) |
| 448 |
}, |
|
| 449 | ! |
env = list( |
| 450 | ! |
new_col_name = new_col_name, |
| 451 | ! |
column_labels_value = c(var_labels(data_r())[selected_vars()], |
| 452 | ! |
new_col_name = new_col_name |
| 453 |
) |
|
| 454 |
) |
|
| 455 |
) |
|
| 456 |
) |
|
| 457 | ! |
qenv |
| 458 |
}) |
|
| 459 | ||
| 460 | ! |
selected_vars <- reactive({
|
| 461 | ! |
req(input$variables_select) |
| 462 | ! |
keys <- data_keys() |
| 463 | ! |
vars <- unique(c(keys, input$variables_select)) |
| 464 | ! |
vars |
| 465 |
}) |
|
| 466 | ||
| 467 | ! |
vars_summary <- reactive({
|
| 468 | ! |
na_count <- data_r() %>% |
| 469 | ! |
sapply(function(x) mean(is.na(x)), USE.NAMES = TRUE) %>% |
| 470 | ! |
sort(decreasing = TRUE) |
| 471 | ||
| 472 | ! |
tibble::tibble( |
| 473 | ! |
key = names(na_count), |
| 474 | ! |
value = unname(na_count), |
| 475 | ! |
label = cut(na_count, breaks = seq(from = 0, to = 1, by = 0.1), include.lowest = TRUE) |
| 476 |
) |
|
| 477 |
}) |
|
| 478 | ||
| 479 | ! |
output$variables <- renderUI({
|
| 480 | ! |
choices <- split(x = vars_summary()$key, f = vars_summary()$label, drop = TRUE) %>% rev() |
| 481 | ! |
selected <- choices <- unname(unlist(choices)) |
| 482 | ||
| 483 | ! |
teal.widgets::optionalSelectInput( |
| 484 | ! |
session$ns("variables_select"),
|
| 485 | ! |
label = "Select variables", |
| 486 | ! |
label_help = HTML(paste0("Dataset: ", tags$code(dataname))),
|
| 487 | ! |
choices = teal.transform::variable_choices(data_r(), choices), |
| 488 | ! |
selected = selected, |
| 489 | ! |
multiple = TRUE |
| 490 |
) |
|
| 491 |
}) |
|
| 492 | ||
| 493 | ! |
observeEvent(input$filter_na, {
|
| 494 | ! |
choices <- vars_summary() %>% |
| 495 | ! |
dplyr::select(!!as.name("key")) %>%
|
| 496 | ! |
getElement(name = 1) |
| 497 | ||
| 498 | ! |
selected <- vars_summary() %>% |
| 499 | ! |
dplyr::filter(!!as.name("value") > 0) %>%
|
| 500 | ! |
dplyr::select(!!as.name("key")) %>%
|
| 501 | ! |
getElement(name = 1) |
| 502 | ||
| 503 | ! |
teal.widgets::updateOptionalSelectInput( |
| 504 | ! |
session = session, |
| 505 | ! |
inputId = "variables_select", |
| 506 | ! |
choices = teal.transform::variable_choices(data_r()), |
| 507 | ! |
selected = selected |
| 508 |
) |
|
| 509 |
}) |
|
| 510 | ||
| 511 | ! |
output$group_by_var_ui <- renderUI({
|
| 512 | ! |
all_choices <- teal.transform::variable_choices(data_r()) |
| 513 | ! |
cat_choices <- all_choices[!sapply(data_r(), function(x) is.numeric(x) || inherits(x, "POSIXct"))] |
| 514 | ! |
validate( |
| 515 | ! |
need(cat_choices, "Dataset does not have any non-numeric or non-datetime variables to use to group data with") |
| 516 |
) |
|
| 517 | ! |
teal.widgets::optionalSelectInput( |
| 518 | ! |
session$ns("group_by_var"),
|
| 519 | ! |
label = "Group by variable", |
| 520 | ! |
choices = cat_choices, |
| 521 | ! |
selected = `if`( |
| 522 | ! |
is.null(isolate(input$group_by_var)), |
| 523 | ! |
cat_choices[1], |
| 524 | ! |
isolate(input$group_by_var) |
| 525 |
), |
|
| 526 | ! |
multiple = FALSE, |
| 527 | ! |
label_help = paste0("Dataset: ", dataname)
|
| 528 |
) |
|
| 529 |
}) |
|
| 530 | ||
| 531 | ! |
output$group_by_vals_ui <- renderUI({
|
| 532 | ! |
req(input$group_by_var) |
| 533 | ||
| 534 | ! |
choices <- teal.transform::value_choices(data_r(), input$group_by_var, input$group_by_var) |
| 535 | ! |
prev_choices <- isolate(input$group_by_vals) |
| 536 | ||
| 537 |
# determine selected value based on filtered data |
|
| 538 |
# display those previously selected values that are still available |
|
| 539 | ! |
selected <- if (!is.null(prev_choices) && any(prev_choices %in% choices)) {
|
| 540 | ! |
prev_choices[match(choices[choices %in% prev_choices], prev_choices)] |
| 541 | ! |
} else if (!is.null(prev_choices) && |
| 542 | ! |
!any(prev_choices %in% choices) && |
| 543 | ! |
isolate(prev_group_by_var()) == input$group_by_var) {
|
| 544 |
# if not any previously selected value is available and the grouping variable is the same, |
|
| 545 |
# then display NULL |
|
| 546 | ! |
NULL |
| 547 |
} else {
|
|
| 548 |
# if new grouping variable (i.e. not any previously selected value is available), |
|
| 549 |
# then display all choices |
|
| 550 | ! |
choices |
| 551 |
} |
|
| 552 | ||
| 553 | ! |
prev_group_by_var(input$group_by_var) # set current group_by_var |
| 554 | ! |
validate(need(length(choices) < 100, "Please select group-by variable with fewer than 100 unique values")) |
| 555 | ||
| 556 | ! |
teal.widgets::optionalSelectInput( |
| 557 | ! |
session$ns("group_by_vals"),
|
| 558 | ! |
label = "Filter levels", |
| 559 | ! |
choices = choices, |
| 560 | ! |
selected = selected, |
| 561 | ! |
multiple = TRUE, |
| 562 | ! |
label_help = paste0("Dataset: ", dataname)
|
| 563 |
) |
|
| 564 |
}) |
|
| 565 | ||
| 566 | ! |
summary_plot_q <- reactive({
|
| 567 | ! |
req(input$summary_type == "Summary") # needed to trigger show r code update on tab change |
| 568 | ! |
teal::validate_has_data(data_r(), 1) |
| 569 | ||
| 570 | ! |
qenv <- common_code_q() |
| 571 | ||
| 572 | ! |
if (input$any_na) {
|
| 573 | ! |
new_col_name <- "**anyna**" # nolint (local variable is assigned and used) |
| 574 | ! |
qenv <- teal.code::eval_code( |
| 575 | ! |
qenv, |
| 576 | ! |
substitute( |
| 577 | ! |
expr = ANL[[new_col_name]] <- ifelse(rowSums(is.na(ANL)) > 0, NA, FALSE), # nolint |
| 578 | ! |
env = list(new_col_name = new_col_name) |
| 579 |
) |
|
| 580 |
) |
|
| 581 |
} |
|
| 582 | ||
| 583 | ! |
qenv <- teal.code::eval_code( |
| 584 | ! |
qenv, |
| 585 | ! |
substitute( |
| 586 | ! |
expr = analysis_vars <- setdiff(colnames(ANL), data_keys), |
| 587 | ! |
env = list(data_keys = data_keys()) |
| 588 |
) |
|
| 589 |
) %>% |
|
| 590 | ! |
teal.code::eval_code( |
| 591 | ! |
substitute( |
| 592 | ! |
expr = summary_plot_obs <- data_frame_call[, analysis_vars] %>% |
| 593 | ! |
dplyr::summarise_all(list(function(x) sum(is.na(x)))) %>% |
| 594 | ! |
tidyr::pivot_longer(tidyselect::everything(), names_to = "col", values_to = "n_na") %>% |
| 595 | ! |
dplyr::mutate(n_not_na = nrow(ANL) - n_na) %>% |
| 596 | ! |
tidyr::pivot_longer(-col, names_to = "isna", values_to = "n") %>% |
| 597 | ! |
dplyr::mutate(isna = isna == "n_na", n_pct = n / nrow(ANL) * 100), |
| 598 | ! |
env = list(data_frame_call = if (!inherits(data_r(), "tbl_df")) {
|
| 599 | ! |
quote(tibble::as_tibble(ANL)) |
| 600 |
} else {
|
|
| 601 | ! |
quote(ANL) |
| 602 |
}) |
|
| 603 |
) |
|
| 604 |
) %>% |
|
| 605 |
# x axis ordering according to number of missing values and alphabet |
|
| 606 | ! |
teal.code::eval_code( |
| 607 | ! |
quote( |
| 608 | ! |
expr = x_levels <- dplyr::filter(summary_plot_obs, isna) %>% |
| 609 | ! |
dplyr::arrange(n_pct, dplyr::desc(col)) %>% |
| 610 | ! |
dplyr::pull(col) %>% |
| 611 | ! |
create_cols_labels() |
| 612 |
) |
|
| 613 |
) |
|
| 614 | ||
| 615 |
# always set "**anyna**" level as the last one |
|
| 616 | ! |
if (isolate(input$any_na)) {
|
| 617 | ! |
qenv <- teal.code::eval_code( |
| 618 | ! |
qenv, |
| 619 | ! |
quote(x_levels <- c(setdiff(x_levels, "**anyna**"), "**anyna**")) |
| 620 |
) |
|
| 621 |
} |
|
| 622 | ||
| 623 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 624 | ! |
labs = list(x = "Variable", y = "Missing observations"), |
| 625 | ! |
theme = list(legend.position = "bottom", axis.text.x = quote(element_text(angle = 45, hjust = 1))) |
| 626 |
) |
|
| 627 | ||
| 628 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 629 | ! |
user_plot = ggplot2_args[["Summary Obs"]], |
| 630 | ! |
user_default = ggplot2_args$default, |
| 631 | ! |
module_plot = dev_ggplot2_args |
| 632 |
) |
|
| 633 | ||
| 634 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 635 | ! |
all_ggplot2_args, |
| 636 | ! |
ggtheme = input$ggtheme |
| 637 |
) |
|
| 638 | ||
| 639 | ! |
qenv <- teal.code::eval_code( |
| 640 | ! |
qenv, |
| 641 | ! |
substitute( |
| 642 | ! |
p1 <- summary_plot_obs %>% |
| 643 | ! |
ggplot() + |
| 644 | ! |
aes( |
| 645 | ! |
x = factor(create_cols_labels(col), levels = x_levels), |
| 646 | ! |
y = n_pct, |
| 647 | ! |
fill = isna |
| 648 |
) + |
|
| 649 | ! |
geom_bar(position = "fill", stat = "identity") + |
| 650 | ! |
scale_fill_manual( |
| 651 | ! |
name = "", |
| 652 | ! |
values = c("grey90", c(getOption("ggplot2.discrete.colour")[2], "#ff2951ff")[1]),
|
| 653 | ! |
labels = c("Present", "Missing")
|
| 654 |
) + |
|
| 655 | ! |
scale_y_continuous(labels = scales::percent_format(), breaks = seq(0, 1, by = 0.1), expand = c(0, 0)) + |
| 656 | ! |
geom_text( |
| 657 | ! |
aes(label = ifelse(isna == TRUE, sprintf("%d [%.02f%%]", n, n_pct), ""), y = 1),
|
| 658 | ! |
hjust = 1, |
| 659 | ! |
color = "black" |
| 660 |
) + |
|
| 661 | ! |
labs + |
| 662 | ! |
ggthemes + |
| 663 | ! |
themes + |
| 664 | ! |
coord_flip(), |
| 665 | ! |
env = list( |
| 666 | ! |
labs = parsed_ggplot2_args$labs, |
| 667 | ! |
themes = parsed_ggplot2_args$theme, |
| 668 | ! |
ggthemes = parsed_ggplot2_args$ggtheme |
| 669 |
) |
|
| 670 |
) |
|
| 671 |
) |
|
| 672 | ||
| 673 | ! |
if (isTRUE(input$if_patients_plot)) {
|
| 674 | ! |
qenv <- teal.code::eval_code( |
| 675 | ! |
qenv, |
| 676 | ! |
substitute( |
| 677 | ! |
expr = parent_keys <- keys, |
| 678 | ! |
env = list(keys = data_parent_keys()) |
| 679 |
) |
|
| 680 |
) %>% |
|
| 681 | ! |
teal.code::eval_code(quote(ndistinct_subjects <- dplyr::n_distinct(ANL[, parent_keys]))) %>% |
| 682 | ! |
teal.code::eval_code( |
| 683 | ! |
quote( |
| 684 | ! |
summary_plot_patients <- ANL[, c(parent_keys, analysis_vars)] %>% |
| 685 | ! |
dplyr::group_by_at(parent_keys) %>% |
| 686 | ! |
dplyr::summarise_all(anyNA) %>% |
| 687 | ! |
tidyr::pivot_longer(cols = !tidyselect::all_of(parent_keys), names_to = "col", values_to = "anyna") %>% |
| 688 | ! |
dplyr::group_by_at(c("col")) %>%
|
| 689 | ! |
dplyr::summarise(count_na = sum(anyna)) %>% |
| 690 | ! |
dplyr::mutate(count_not_na = ndistinct_subjects - count_na) %>% |
| 691 | ! |
tidyr::pivot_longer(-c(col), names_to = "isna", values_to = "n") %>% |
| 692 | ! |
dplyr::mutate(isna = isna == "count_na", n_pct = n / ndistinct_subjects * 100) %>% |
| 693 | ! |
dplyr::arrange_at(c("isna", "n"), .funs = dplyr::desc)
|
| 694 |
) |
|
| 695 |
) |
|
| 696 | ||
| 697 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 698 | ! |
labs = list(x = "", y = "Missing patients"), |
| 699 | ! |
theme = list( |
| 700 | ! |
legend.position = "bottom", |
| 701 | ! |
axis.text.x = quote(element_text(angle = 45, hjust = 1)), |
| 702 | ! |
axis.text.y = quote(element_blank()) |
| 703 |
) |
|
| 704 |
) |
|
| 705 | ||
| 706 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 707 | ! |
user_plot = ggplot2_args[["Summary Patients"]], |
| 708 | ! |
user_default = ggplot2_args$default, |
| 709 | ! |
module_plot = dev_ggplot2_args |
| 710 |
) |
|
| 711 | ||
| 712 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 713 | ! |
all_ggplot2_args, |
| 714 | ! |
ggtheme = input$ggtheme |
| 715 |
) |
|
| 716 | ||
| 717 | ! |
qenv <- teal.code::eval_code( |
| 718 | ! |
qenv, |
| 719 | ! |
substitute( |
| 720 | ! |
p2 <- summary_plot_patients %>% |
| 721 | ! |
ggplot() + |
| 722 | ! |
aes_( |
| 723 | ! |
x = ~ factor(create_cols_labels(col), levels = x_levels), |
| 724 | ! |
y = ~n_pct, |
| 725 | ! |
fill = ~isna |
| 726 |
) + |
|
| 727 | ! |
geom_bar(alpha = 1, stat = "identity", position = "fill") + |
| 728 | ! |
scale_y_continuous(labels = scales::percent_format(), breaks = seq(0, 1, by = 0.1), expand = c(0, 0)) + |
| 729 | ! |
scale_fill_manual( |
| 730 | ! |
name = "", |
| 731 | ! |
values = c("grey90", c(getOption("ggplot2.discrete.colour")[2], "#ff2951ff")[1]),
|
| 732 | ! |
labels = c("Present", "Missing")
|
| 733 |
) + |
|
| 734 | ! |
geom_text( |
| 735 | ! |
aes(label = ifelse(isna == TRUE, sprintf("%d [%.02f%%]", n, n_pct), ""), y = 1),
|
| 736 | ! |
hjust = 1, |
| 737 | ! |
color = "black" |
| 738 |
) + |
|
| 739 | ! |
labs + |
| 740 | ! |
ggthemes + |
| 741 | ! |
themes + |
| 742 | ! |
coord_flip(), |
| 743 | ! |
env = list( |
| 744 | ! |
labs = parsed_ggplot2_args$labs, |
| 745 | ! |
themes = parsed_ggplot2_args$theme, |
| 746 | ! |
ggthemes = parsed_ggplot2_args$ggtheme |
| 747 |
) |
|
| 748 |
) |
|
| 749 |
) %>% |
|
| 750 | ! |
teal.code::eval_code( |
| 751 | ! |
quote({
|
| 752 | ! |
g1 <- ggplotGrob(p1) |
| 753 | ! |
g2 <- ggplotGrob(p2) |
| 754 | ! |
g <- gridExtra::gtable_cbind(g1, g2, size = "first") |
| 755 | ! |
g$heights <- grid::unit.pmax(g1$heights, g2$heights) |
| 756 | ! |
grid::grid.newpage() |
| 757 |
}) |
|
| 758 |
) |
|
| 759 |
} else {
|
|
| 760 | ! |
qenv <- teal.code::eval_code( |
| 761 | ! |
qenv, |
| 762 | ! |
quote({
|
| 763 | ! |
g <- ggplotGrob(p1) |
| 764 | ! |
grid::grid.newpage() |
| 765 |
}) |
|
| 766 |
) |
|
| 767 |
} |
|
| 768 | ||
| 769 | ! |
teal.code::eval_code( |
| 770 | ! |
qenv, |
| 771 | ! |
quote(grid::grid.draw(g)) |
| 772 |
) |
|
| 773 |
}) |
|
| 774 | ||
| 775 | ! |
summary_plot_r <- reactive(summary_plot_q()[["g"]]) |
| 776 | ||
| 777 | ! |
combination_cutoff_q <- reactive({
|
| 778 | ! |
req(common_code_q()) |
| 779 | ! |
teal.code::eval_code( |
| 780 | ! |
common_code_q(), |
| 781 | ! |
quote( |
| 782 | ! |
combination_cutoff <- ANL %>% |
| 783 | ! |
dplyr::mutate_all(is.na) %>% |
| 784 | ! |
dplyr::group_by_all() %>% |
| 785 | ! |
dplyr::tally() %>% |
| 786 | ! |
dplyr::ungroup() |
| 787 |
) |
|
| 788 |
) |
|
| 789 |
}) |
|
| 790 | ||
| 791 | ! |
output$cutoff <- renderUI({
|
| 792 | ! |
x <- combination_cutoff_q()[["combination_cutoff"]]$n |
| 793 | ||
| 794 |
# select 10-th from the top |
|
| 795 | ! |
n <- length(x) |
| 796 | ! |
idx <- max(1, n - 10) |
| 797 | ! |
prev_value <- isolate(input$combination_cutoff) |
| 798 | ! |
value <- `if`( |
| 799 | ! |
is.null(prev_value) || prev_value > max(x) || prev_value < min(x), |
| 800 | ! |
sort(x, partial = idx)[idx], prev_value |
| 801 |
) |
|
| 802 | ||
| 803 | ! |
teal.widgets::optionalSliderInputValMinMax( |
| 804 | ! |
session$ns("combination_cutoff"),
|
| 805 | ! |
"Combination cut-off", |
| 806 | ! |
c(value, range(x)) |
| 807 |
) |
|
| 808 |
}) |
|
| 809 | ||
| 810 | ! |
combination_plot_q <- reactive({
|
| 811 | ! |
req(input$summary_type == "Combinations", input$combination_cutoff, combination_cutoff_q()) |
| 812 | ! |
teal::validate_has_data(data_r(), 1) |
| 813 | ||
| 814 | ! |
qenv <- teal.code::eval_code( |
| 815 | ! |
combination_cutoff_q(), |
| 816 | ! |
substitute( |
| 817 | ! |
expr = data_combination_plot_cutoff <- combination_cutoff %>% |
| 818 | ! |
dplyr::filter(n >= combination_cutoff_value) %>% |
| 819 | ! |
dplyr::mutate(id = rank(-n, ties.method = "first")) %>% |
| 820 | ! |
tidyr::pivot_longer(-c(n, id), names_to = "key", values_to = "value") %>% |
| 821 | ! |
dplyr::arrange(n), |
| 822 | ! |
env = list(combination_cutoff_value = input$combination_cutoff) |
| 823 |
) |
|
| 824 |
) |
|
| 825 | ||
| 826 |
# find keys in dataset not selected in the UI and remove them from dataset |
|
| 827 | ! |
keys_not_selected <- setdiff(data_keys(), input$variables_select) |
| 828 | ! |
if (length(keys_not_selected) > 0) {
|
| 829 | ! |
qenv <- teal.code::eval_code( |
| 830 | ! |
qenv, |
| 831 | ! |
substitute( |
| 832 | ! |
expr = data_combination_plot_cutoff <- data_combination_plot_cutoff %>% |
| 833 | ! |
dplyr::filter(!key %in% keys_not_selected), |
| 834 | ! |
env = list(keys_not_selected = keys_not_selected) |
| 835 |
) |
|
| 836 |
) |
|
| 837 |
} |
|
| 838 | ||
| 839 | ! |
qenv <- teal.code::eval_code( |
| 840 | ! |
qenv, |
| 841 | ! |
quote( |
| 842 | ! |
labels <- data_combination_plot_cutoff %>% |
| 843 | ! |
dplyr::filter(key == key[[1]]) %>% |
| 844 | ! |
getElement(name = 1) |
| 845 |
) |
|
| 846 |
) |
|
| 847 | ||
| 848 | ! |
dev_ggplot2_args1 <- teal.widgets::ggplot2_args( |
| 849 | ! |
labs = list(x = "", y = ""), |
| 850 | ! |
theme = list( |
| 851 | ! |
legend.position = "bottom", |
| 852 | ! |
axis.text.x = quote(element_blank()) |
| 853 |
) |
|
| 854 |
) |
|
| 855 | ||
| 856 | ! |
all_ggplot2_args1 <- teal.widgets::resolve_ggplot2_args( |
| 857 | ! |
user_plot = ggplot2_args[["Combinations Hist"]], |
| 858 | ! |
user_default = ggplot2_args$default, |
| 859 | ! |
module_plot = dev_ggplot2_args1 |
| 860 |
) |
|
| 861 | ||
| 862 | ! |
parsed_ggplot2_args1 <- teal.widgets::parse_ggplot2_args( |
| 863 | ! |
all_ggplot2_args1, |
| 864 | ! |
ggtheme = "void" |
| 865 |
) |
|
| 866 | ||
| 867 | ! |
dev_ggplot2_args2 <- teal.widgets::ggplot2_args( |
| 868 | ! |
labs = list(x = "", y = ""), |
| 869 | ! |
theme = list( |
| 870 | ! |
legend.position = "bottom", |
| 871 | ! |
axis.text.x = quote(element_blank()), |
| 872 | ! |
axis.ticks = quote(element_blank()), |
| 873 | ! |
panel.grid.major = quote(element_blank()) |
| 874 |
) |
|
| 875 |
) |
|
| 876 | ||
| 877 | ! |
all_ggplot2_args2 <- teal.widgets::resolve_ggplot2_args( |
| 878 | ! |
user_plot = ggplot2_args[["Combinations Main"]], |
| 879 | ! |
user_default = ggplot2_args$default, |
| 880 | ! |
module_plot = dev_ggplot2_args2 |
| 881 |
) |
|
| 882 | ||
| 883 | ! |
parsed_ggplot2_args2 <- teal.widgets::parse_ggplot2_args( |
| 884 | ! |
all_ggplot2_args2, |
| 885 | ! |
ggtheme = input$ggtheme |
| 886 |
) |
|
| 887 | ||
| 888 | ! |
teal.code::eval_code( |
| 889 | ! |
qenv, |
| 890 | ! |
substitute( |
| 891 | ! |
expr = {
|
| 892 | ! |
p1 <- data_combination_plot_cutoff %>% |
| 893 | ! |
dplyr::select(id, n) %>% |
| 894 | ! |
dplyr::distinct() %>% |
| 895 | ! |
ggplot(aes(x = id, y = n)) + |
| 896 | ! |
geom_bar(stat = "identity", fill = c(getOption("ggplot2.discrete.colour")[2], "#ff2951ff")[1]) +
|
| 897 | ! |
geom_text(aes(label = n), position = position_dodge(width = 0.9), vjust = -0.25) + |
| 898 | ! |
ylim(c(0, max(data_combination_plot_cutoff$n) * 1.5)) + |
| 899 | ! |
labs1 + |
| 900 | ! |
ggthemes1 + |
| 901 | ! |
themes1 |
| 902 | ||
| 903 | ! |
graph_number_rows <- length(unique(data_combination_plot_cutoff$id)) |
| 904 | ! |
graph_number_cols <- nrow(data_combination_plot_cutoff) / graph_number_rows |
| 905 | ||
| 906 | ! |
p2 <- data_combination_plot_cutoff %>% ggplot() + |
| 907 | ! |
aes(x = create_cols_labels(key), y = id - 0.5, fill = value) + |
| 908 | ! |
geom_tile(alpha = 0.85, height = 0.95) + |
| 909 | ! |
scale_fill_manual( |
| 910 | ! |
name = "", |
| 911 | ! |
values = c("grey90", c(getOption("ggplot2.discrete.colour")[2], "#ff2951ff")[1]),
|
| 912 | ! |
labels = c("Present", "Missing")
|
| 913 |
) + |
|
| 914 | ! |
geom_hline(yintercept = seq_len(1 + graph_number_rows) - 1) + |
| 915 | ! |
geom_vline(xintercept = seq_len(1 + graph_number_cols) - 0.5, linetype = "dotted") + |
| 916 | ! |
coord_flip() + |
| 917 | ! |
labs2 + |
| 918 | ! |
ggthemes2 + |
| 919 | ! |
themes2 |
| 920 | ||
| 921 | ! |
g1 <- ggplotGrob(p1) |
| 922 | ! |
g2 <- ggplotGrob(p2) |
| 923 | ||
| 924 | ! |
g <- gridExtra::gtable_rbind(g1, g2, size = "last") |
| 925 | ! |
g$heights[7] <- grid::unit(0.2, "null") # rescale to get the bar chart smaller |
| 926 | ! |
grid::grid.newpage() |
| 927 | ! |
grid::grid.draw(g) |
| 928 |
}, |
|
| 929 | ! |
env = list( |
| 930 | ! |
labs1 = parsed_ggplot2_args1$labs, |
| 931 | ! |
themes1 = parsed_ggplot2_args1$theme, |
| 932 | ! |
ggthemes1 = parsed_ggplot2_args1$ggtheme, |
| 933 | ! |
labs2 = parsed_ggplot2_args2$labs, |
| 934 | ! |
themes2 = parsed_ggplot2_args2$theme, |
| 935 | ! |
ggthemes2 = parsed_ggplot2_args2$ggtheme |
| 936 |
) |
|
| 937 |
) |
|
| 938 |
) |
|
| 939 |
}) |
|
| 940 | ||
| 941 | ! |
combination_plot_r <- reactive(combination_plot_q()[["g"]]) |
| 942 | ||
| 943 | ! |
summary_table_q <- reactive({
|
| 944 | ! |
req( |
| 945 | ! |
input$summary_type == "By Variable Levels", # needed to trigger show r code update on tab change |
| 946 | ! |
common_code_q() |
| 947 |
) |
|
| 948 | ! |
teal::validate_has_data(data_r(), 1) |
| 949 | ||
| 950 |
# extract the ANL dataset for use in further validation |
|
| 951 | ! |
anl <- common_code_q()[["ANL"]] |
| 952 | ||
| 953 | ! |
group_var <- input$group_by_var |
| 954 | ! |
validate( |
| 955 | ! |
need( |
| 956 | ! |
is.null(group_var) || |
| 957 | ! |
length(unique(anl[[group_var]])) < 100, |
| 958 | ! |
"Please select group-by variable with fewer than 100 unique values" |
| 959 |
) |
|
| 960 |
) |
|
| 961 | ||
| 962 | ! |
group_vals <- input$group_by_vals # nolint (local variable is assigned and used) |
| 963 | ! |
variables_select <- input$variables_select |
| 964 | ! |
vars <- unique(variables_select, group_var) |
| 965 | ! |
count_type <- input$count_type # nolint (local variable is assigned and used) |
| 966 | ||
| 967 | ! |
if (!is.null(selected_vars()) && length(selected_vars()) != ncol(anl)) {
|
| 968 | ! |
variables <- selected_vars() # nolint (local variable is assigned and used) |
| 969 |
} else {
|
|
| 970 | ! |
variables <- colnames(anl) |
| 971 |
} |
|
| 972 | ||
| 973 | ! |
summ_fn <- if (input$count_type == "counts") {
|
| 974 | ! |
function(x) sum(is.na(x)) |
| 975 |
} else {
|
|
| 976 | ! |
function(x) round(sum(is.na(x)) / length(x), 4) |
| 977 |
} |
|
| 978 | ||
| 979 | ! |
qenv <- common_code_q() |
| 980 | ||
| 981 | ! |
if (!is.null(group_var)) {
|
| 982 | ! |
qenv <- teal.code::eval_code( |
| 983 | ! |
qenv, |
| 984 | ! |
substitute( |
| 985 | ! |
expr = {
|
| 986 | ! |
summary_data <- ANL %>% |
| 987 | ! |
dplyr::mutate(group_var_name := forcats::fct_na_value_to_level(as.factor(group_var_name), "NA")) %>% |
| 988 | ! |
dplyr::group_by_at(group_var) %>% |
| 989 | ! |
dplyr::filter(group_var_name %in% group_vals) |
| 990 | ||
| 991 | ! |
count_data <- dplyr::summarise(summary_data, n = dplyr::n()) |
| 992 | ||
| 993 | ! |
summary_data <- dplyr::summarise_all(summary_data, summ_fn) %>% |
| 994 | ! |
dplyr::mutate(group_var_name := paste0(group_var, ":", group_var_name, "(N=", count_data$n, ")")) %>% |
| 995 | ! |
tidyr::pivot_longer(!tidyselect::all_of(group_var), names_to = "Variable", values_to = "out") %>% |
| 996 | ! |
tidyr::pivot_wider(names_from = group_var, values_from = "out") %>% |
| 997 | ! |
dplyr::mutate(`Variable label` = create_cols_labels(Variable, just_label = TRUE), .after = Variable) |
| 998 |
}, |
|
| 999 | ! |
env = list( |
| 1000 | ! |
group_var = group_var, group_var_name = as.name(group_var), group_vals = group_vals, summ_fn = summ_fn |
| 1001 |
) |
|
| 1002 |
) |
|
| 1003 |
) |
|
| 1004 |
} else {
|
|
| 1005 | ! |
qenv <- teal.code::eval_code( |
| 1006 | ! |
qenv, |
| 1007 | ! |
substitute( |
| 1008 | ! |
expr = summary_data <- ANL %>% |
| 1009 | ! |
dplyr::summarise_all(summ_fn) %>% |
| 1010 | ! |
tidyr::pivot_longer(tidyselect::everything(), |
| 1011 | ! |
names_to = "Variable", |
| 1012 | ! |
values_to = paste0("Missing (N=", nrow(ANL), ")")
|
| 1013 |
) %>% |
|
| 1014 | ! |
dplyr::mutate(`Variable label` = create_cols_labels(Variable), .after = Variable), |
| 1015 | ! |
env = list(summ_fn = summ_fn) |
| 1016 |
) |
|
| 1017 |
) |
|
| 1018 |
} |
|
| 1019 | ||
| 1020 | ! |
teal.code::eval_code(qenv, quote(summary_data)) |
| 1021 |
}) |
|
| 1022 | ||
| 1023 | ! |
summary_table_r <- reactive(summary_table_q()[["summary_data"]]) |
| 1024 | ||
| 1025 | ! |
by_subject_plot_q <- reactive({
|
| 1026 |
# needed to trigger show r code update on tab change |
|
| 1027 | ! |
req(input$summary_type == "Grouped by Subject", common_code_q()) |
| 1028 | ||
| 1029 | ! |
teal::validate_has_data(data_r(), 1) |
| 1030 | ||
| 1031 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 1032 | ! |
labs = list(x = "", y = ""), |
| 1033 | ! |
theme = list(legend.position = "bottom", axis.text.x = quote(element_blank())) |
| 1034 |
) |
|
| 1035 | ||
| 1036 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 1037 | ! |
user_plot = ggplot2_args[["By Subject"]], |
| 1038 | ! |
user_default = ggplot2_args$default, |
| 1039 | ! |
module_plot = dev_ggplot2_args |
| 1040 |
) |
|
| 1041 | ||
| 1042 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 1043 | ! |
all_ggplot2_args, |
| 1044 | ! |
ggtheme = input$ggtheme |
| 1045 |
) |
|
| 1046 | ||
| 1047 | ! |
teal.code::eval_code( |
| 1048 | ! |
common_code_q(), |
| 1049 | ! |
substitute( |
| 1050 | ! |
expr = parent_keys <- keys, |
| 1051 | ! |
env = list(keys = data_parent_keys()) |
| 1052 |
) |
|
| 1053 |
) %>% |
|
| 1054 | ! |
teal.code::eval_code( |
| 1055 | ! |
substitute( |
| 1056 | ! |
expr = analysis_vars <- setdiff(colnames(ANL), data_keys), |
| 1057 | ! |
env = list(data_keys = data_keys()) |
| 1058 |
) |
|
| 1059 |
) %>% |
|
| 1060 | ! |
teal.code::eval_code( |
| 1061 | ! |
quote({
|
| 1062 | ! |
summary_plot_patients <- ANL[, c(parent_keys, analysis_vars)] %>% |
| 1063 | ! |
dplyr::group_by_at(parent_keys) %>% |
| 1064 | ! |
dplyr::mutate(id = dplyr::cur_group_id()) %>% |
| 1065 | ! |
dplyr::ungroup() %>% |
| 1066 | ! |
dplyr::group_by_at(c(parent_keys, "id")) %>% |
| 1067 | ! |
dplyr::summarise_all(anyNA) %>% |
| 1068 | ! |
dplyr::ungroup() |
| 1069 | ||
| 1070 |
# order subjects by decreasing number of missing and then by |
|
| 1071 |
# missingness pattern (defined using sha1) |
|
| 1072 | ! |
order_subjects <- summary_plot_patients %>% |
| 1073 | ! |
dplyr::select(-"id", -tidyselect::all_of(parent_keys)) %>% |
| 1074 | ! |
dplyr::transmute( |
| 1075 | ! |
id = dplyr::row_number(), |
| 1076 | ! |
number_NA = apply(., 1, sum), |
| 1077 | ! |
sha = apply(., 1, rlang::hash) |
| 1078 |
) %>% |
|
| 1079 | ! |
dplyr::arrange(dplyr::desc(number_NA), sha) %>% |
| 1080 | ! |
getElement(name = "id") |
| 1081 | ||
| 1082 |
# order columns by decreasing percent of missing values |
|
| 1083 | ! |
ordered_columns <- summary_plot_patients %>% |
| 1084 | ! |
dplyr::select(-"id", -tidyselect::all_of(parent_keys)) %>% |
| 1085 | ! |
dplyr::summarise( |
| 1086 | ! |
column = create_cols_labels(colnames(.)), |
| 1087 | ! |
na_count = apply(., MARGIN = 2, FUN = sum), |
| 1088 | ! |
na_percent = na_count / nrow(.) * 100 |
| 1089 |
) %>% |
|
| 1090 | ! |
dplyr::arrange(na_percent, dplyr::desc(column)) |
| 1091 | ||
| 1092 | ! |
summary_plot_patients <- summary_plot_patients %>% |
| 1093 | ! |
tidyr::gather("col", "isna", -"id", -tidyselect::all_of(parent_keys)) %>%
|
| 1094 | ! |
dplyr::mutate(col = create_cols_labels(col)) |
| 1095 |
}) |
|
| 1096 |
) %>% |
|
| 1097 | ! |
teal.code::eval_code( |
| 1098 | ! |
substitute( |
| 1099 | ! |
expr = {
|
| 1100 | ! |
g <- ggplot(summary_plot_patients, aes( |
| 1101 | ! |
x = factor(id, levels = order_subjects), |
| 1102 | ! |
y = factor(col, levels = ordered_columns[["column"]]), |
| 1103 | ! |
fill = isna |
| 1104 |
)) + |
|
| 1105 | ! |
geom_raster() + |
| 1106 | ! |
annotate( |
| 1107 | ! |
"text", |
| 1108 | ! |
x = length(order_subjects), |
| 1109 | ! |
y = seq_len(nrow(ordered_columns)), |
| 1110 | ! |
hjust = 1, |
| 1111 | ! |
label = sprintf("%d [%.02f%%]", ordered_columns[["na_count"]], ordered_columns[["na_percent"]])
|
| 1112 |
) + |
|
| 1113 | ! |
scale_fill_manual( |
| 1114 | ! |
name = "", |
| 1115 | ! |
values = c("grey90", c(getOption("ggplot2.discrete.colour")[2], "#ff2951ff")[1]),
|
| 1116 | ! |
labels = c("Present", "Missing (at least one)")
|
| 1117 |
) + |
|
| 1118 | ! |
labs + |
| 1119 | ! |
ggthemes + |
| 1120 | ! |
themes |
| 1121 | ! |
print(g) |
| 1122 |
}, |
|
| 1123 | ! |
env = list( |
| 1124 | ! |
labs = parsed_ggplot2_args$labs, |
| 1125 | ! |
themes = parsed_ggplot2_args$theme, |
| 1126 | ! |
ggthemes = parsed_ggplot2_args$ggtheme |
| 1127 |
) |
|
| 1128 |
) |
|
| 1129 |
) |
|
| 1130 |
}) |
|
| 1131 | ||
| 1132 | ! |
by_subject_plot_r <- reactive(by_subject_plot_q()[["g"]]) |
| 1133 | ||
| 1134 | ! |
output$levels_table <- DT::renderDataTable( |
| 1135 | ! |
expr = {
|
| 1136 | ! |
if (length(input$variables_select) == 0) {
|
| 1137 |
# so that zeroRecords message gets printed |
|
| 1138 |
# using tibble as it supports weird column names, such as " " |
|
| 1139 | ! |
tibble::tibble(` ` = logical(0)) |
| 1140 |
} else {
|
|
| 1141 | ! |
summary_table_r() |
| 1142 |
} |
|
| 1143 |
}, |
|
| 1144 | ! |
options = list(language = list(zeroRecords = "No variable selected"), pageLength = input$levels_table_rows) |
| 1145 |
) |
|
| 1146 | ||
| 1147 | ! |
pws1 <- teal.widgets::plot_with_settings_srv( |
| 1148 | ! |
id = "summary_plot", |
| 1149 | ! |
plot_r = summary_plot_r, |
| 1150 | ! |
height = plot_height, |
| 1151 | ! |
width = plot_width |
| 1152 |
) |
|
| 1153 | ||
| 1154 | ! |
pws2 <- teal.widgets::plot_with_settings_srv( |
| 1155 | ! |
id = "combination_plot", |
| 1156 | ! |
plot_r = combination_plot_r, |
| 1157 | ! |
height = plot_height, |
| 1158 | ! |
width = plot_width |
| 1159 |
) |
|
| 1160 | ||
| 1161 | ! |
pws3 <- teal.widgets::plot_with_settings_srv( |
| 1162 | ! |
id = "by_subject_plot", |
| 1163 | ! |
plot_r = by_subject_plot_r, |
| 1164 | ! |
height = plot_height, |
| 1165 | ! |
width = plot_width |
| 1166 |
) |
|
| 1167 | ||
| 1168 | ! |
final_q <- reactive({
|
| 1169 | ! |
req(input$summary_type) |
| 1170 | ! |
sum_type <- input$summary_type |
| 1171 | ! |
if (sum_type == "Summary") {
|
| 1172 | ! |
summary_plot_q() |
| 1173 | ! |
} else if (sum_type == "Combinations") {
|
| 1174 | ! |
combination_plot_q() |
| 1175 | ! |
} else if (sum_type == "By Variable Levels") {
|
| 1176 | ! |
summary_table_q() |
| 1177 | ! |
} else if (sum_type == "Grouped by Subject") {
|
| 1178 | ! |
by_subject_plot_q() |
| 1179 |
} |
|
| 1180 |
}) |
|
| 1181 | ||
| 1182 | ! |
teal.widgets::verbatim_popup_srv( |
| 1183 | ! |
id = "warning", |
| 1184 | ! |
verbatim_content = reactive(teal.code::get_warnings(final_q())), |
| 1185 | ! |
title = "Warning", |
| 1186 | ! |
disabled = reactive(is.null(teal.code::get_warnings(final_q()))) |
| 1187 |
) |
|
| 1188 | ||
| 1189 | ! |
teal.widgets::verbatim_popup_srv( |
| 1190 | ! |
id = "rcode", |
| 1191 | ! |
verbatim_content = reactive(teal.code::get_code(final_q())), |
| 1192 | ! |
title = "Show R Code for Missing Data" |
| 1193 |
) |
|
| 1194 | ||
| 1195 |
### REPORTER |
|
| 1196 | ! |
if (with_reporter) {
|
| 1197 | ! |
card_fun <- function(comment) {
|
| 1198 | ! |
card <- teal::TealReportCard$new() |
| 1199 | ! |
sum_type <- input$summary_type |
| 1200 | ! |
title <- if (sum_type == "By Variable Levels") paste0(sum_type, " Table") else paste0(sum_type, " Plot") |
| 1201 | ! |
title_dataname <- paste(title, dataname, sep = " - ") |
| 1202 | ! |
card$set_name(paste("Missing Data", sum_type, dataname, sep = " - "))
|
| 1203 | ! |
card$append_text(title_dataname, "header2") |
| 1204 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 1205 | ! |
if (sum_type == "Summary") {
|
| 1206 | ! |
card$append_text("Plot", "header3")
|
| 1207 | ! |
card$append_plot(summary_plot_r(), dim = pws1$dim()) |
| 1208 | ! |
} else if (sum_type == "Combinations") {
|
| 1209 | ! |
card$append_text("Plot", "header3")
|
| 1210 | ! |
card$append_plot(combination_plot_r(), dim = pws2$dim()) |
| 1211 | ! |
} else if (sum_type == "By Variable Levels") {
|
| 1212 | ! |
card$append_text("Table", "header3")
|
| 1213 | ! |
card$append_table(summary_table_r[["summary_data"]]) |
| 1214 | ! |
} else if (sum_type == "Grouped by Subject") {
|
| 1215 | ! |
card$append_text("Plot", "header3")
|
| 1216 | ! |
card$append_plot(by_subject_plot_r(), dim = pws3$dim()) |
| 1217 |
} |
|
| 1218 | ! |
if (!comment == "") {
|
| 1219 | ! |
card$append_text("Comment", "header3")
|
| 1220 | ! |
card$append_text(comment) |
| 1221 |
} |
|
| 1222 | ! |
card$append_src(paste(teal.code::get_code(final_q()), collapse = "\n")) |
| 1223 | ! |
card |
| 1224 |
} |
|
| 1225 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 1226 |
} |
|
| 1227 |
### |
|
| 1228 |
}) |
|
| 1229 |
} |
| 1 |
#' Create a simple cross-table |
|
| 2 |
#' @md |
|
| 3 |
#' |
|
| 4 |
#' @inheritParams teal::module |
|
| 5 |
#' @inheritParams shared_params |
|
| 6 |
#' @param x (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 7 |
#' Object with all available choices with pre-selected option for variable X - row values. In case |
|
| 8 |
#' of `data_extract_spec` use `select_spec(..., ordered = TRUE)` if table elements should be |
|
| 9 |
#' rendered according to selection order. |
|
| 10 |
#' @param y (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 11 |
#' Object with all available choices with pre-selected option for variable Y - column values |
|
| 12 |
#' \code{data_extract_spec} must not allow multiple selection in this case.
|
|
| 13 |
#' |
|
| 14 |
#' @param show_percentage optional, (`logical`) Whether to show percentages |
|
| 15 |
#' (relevant only when `x` is a `factor`). Defaults to `TRUE`. |
|
| 16 |
#' @param show_total optional, (`logical`) Whether to show total column. Defaults to `TRUE`. |
|
| 17 |
#' |
|
| 18 |
#' @note For more examples, please see the vignette "Using cross table" via |
|
| 19 |
#' `vignette("using-cross-table", package = "teal.modules.general")`.
|
|
| 20 |
#' |
|
| 21 |
#' @export |
|
| 22 |
#' |
|
| 23 |
#' @examples |
|
| 24 |
#' # Percentage cross table of variables from ADSL dataset |
|
| 25 |
#' |
|
| 26 |
#' ADSL <- teal.modules.general::rADSL |
|
| 27 |
#' |
|
| 28 |
#' app <- teal::init( |
|
| 29 |
#' data = teal.data::cdisc_data( |
|
| 30 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 31 |
#' check = TRUE |
|
| 32 |
#' ), |
|
| 33 |
#' modules = teal::modules( |
|
| 34 |
#' teal.modules.general::tm_t_crosstable( |
|
| 35 |
#' label = "Cross Table", |
|
| 36 |
#' x = teal.transform::data_extract_spec( |
|
| 37 |
#' dataname = "ADSL", |
|
| 38 |
#' select = teal.transform::select_spec( |
|
| 39 |
#' label = "Select variable:", |
|
| 40 |
#' choices = variable_choices(ADSL, subset = function(data) {
|
|
| 41 |
#' idx <- !vapply(data, inherits, logical(1), c("Date", "POSIXct", "POSIXlt"))
|
|
| 42 |
#' return(names(data)[idx]) |
|
| 43 |
#' }), |
|
| 44 |
#' selected = "COUNTRY", |
|
| 45 |
#' multiple = TRUE, |
|
| 46 |
#' ordered = TRUE, |
|
| 47 |
#' fixed = FALSE |
|
| 48 |
#' ) |
|
| 49 |
#' ), |
|
| 50 |
#' y = teal.transform::data_extract_spec( |
|
| 51 |
#' dataname = "ADSL", |
|
| 52 |
#' select = teal.transform::select_spec( |
|
| 53 |
#' label = "Select variable:", |
|
| 54 |
#' choices = variable_choices(ADSL, subset = function(data) {
|
|
| 55 |
#' idx <- vapply(data, is.factor, logical(1)) |
|
| 56 |
#' return(names(data)[idx]) |
|
| 57 |
#' }), |
|
| 58 |
#' selected = "SEX", |
|
| 59 |
#' multiple = FALSE, |
|
| 60 |
#' fixed = FALSE |
|
| 61 |
#' ) |
|
| 62 |
#' ), |
|
| 63 |
#' basic_table_args = teal.widgets::basic_table_args( |
|
| 64 |
#' subtitles = "Table generated by Crosstable Module" |
|
| 65 |
#' ) |
|
| 66 |
#' ) |
|
| 67 |
#' ) |
|
| 68 |
#' ) |
|
| 69 |
#' if (interactive()) {
|
|
| 70 |
#' shinyApp(app$ui, app$server) |
|
| 71 |
#' } |
|
| 72 |
#' |
|
| 73 |
tm_t_crosstable <- function(label = "Cross Table", |
|
| 74 |
x, |
|
| 75 |
y, |
|
| 76 |
show_percentage = TRUE, |
|
| 77 |
show_total = TRUE, |
|
| 78 |
pre_output = NULL, |
|
| 79 |
post_output = NULL, |
|
| 80 |
basic_table_args = teal.widgets::basic_table_args()) {
|
|
| 81 | ! |
logger::log_info("Initializing tm_t_crosstable")
|
| 82 | ! |
if (!requireNamespace("rtables", quietly = TRUE)) {
|
| 83 | ! |
stop("Cannot load rtables - please install the package or restart your session.")
|
| 84 |
} |
|
| 85 | ! |
if (inherits(x, "data_extract_spec")) x <- list(x) |
| 86 | ! |
if (inherits(y, "data_extract_spec")) y <- list(y) |
| 87 | ||
| 88 | ! |
checkmate::assert_string(label) |
| 89 | ! |
checkmate::assert_list(x, types = "data_extract_spec") |
| 90 | ! |
checkmate::assert_list(y, types = "data_extract_spec") |
| 91 | ! |
if (any(vapply(y, function(x) x$select$multiple, logical(1)))) {
|
| 92 | ! |
stop("'y' should not allow multiple selection")
|
| 93 |
} |
|
| 94 | ! |
checkmate::assert_flag(show_percentage) |
| 95 | ! |
checkmate::assert_flag(show_total) |
| 96 | ! |
checkmate::assert_class(basic_table_args, classes = "basic_table_args") |
| 97 | ||
| 98 | ! |
ui_args <- as.list(environment()) |
| 99 | ||
| 100 | ! |
server_args <- list( |
| 101 | ! |
label = label, |
| 102 | ! |
x = x, |
| 103 | ! |
y = y, |
| 104 | ! |
basic_table_args = basic_table_args |
| 105 |
) |
|
| 106 | ||
| 107 | ! |
module( |
| 108 | ! |
label = label, |
| 109 | ! |
server = srv_t_crosstable, |
| 110 | ! |
ui = ui_t_crosstable, |
| 111 | ! |
ui_args = ui_args, |
| 112 | ! |
server_args = server_args, |
| 113 | ! |
datanames = teal.transform::get_extract_datanames(list(x = x, y = y)) |
| 114 |
) |
|
| 115 |
} |
|
| 116 | ||
| 117 |
ui_t_crosstable <- function(id, x, y, show_percentage, show_total, pre_output, post_output, ...) {
|
|
| 118 | ! |
ns <- NS(id) |
| 119 | ! |
is_single_dataset <- teal.transform::is_single_dataset(x, y) |
| 120 | ||
| 121 | ! |
join_default_options <- c( |
| 122 | ! |
"Full Join" = "dplyr::full_join", |
| 123 | ! |
"Inner Join" = "dplyr::inner_join", |
| 124 | ! |
"Left Join" = "dplyr::left_join", |
| 125 | ! |
"Right Join" = "dplyr::right_join" |
| 126 |
) |
|
| 127 | ||
| 128 | ! |
teal.widgets::standard_layout( |
| 129 | ! |
output = teal.widgets::white_small_well( |
| 130 | ! |
textOutput(ns("title")),
|
| 131 | ! |
teal.widgets::table_with_settings_ui(ns("table"))
|
| 132 |
), |
|
| 133 | ! |
encoding = div( |
| 134 |
### Reporter |
|
| 135 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 136 |
### |
|
| 137 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 138 | ! |
teal.transform::datanames_input(list(x, y)), |
| 139 | ! |
teal.transform::data_extract_ui(ns("x"), label = "Row values", x, is_single_dataset = is_single_dataset),
|
| 140 | ! |
teal.transform::data_extract_ui(ns("y"), label = "Column values", y, is_single_dataset = is_single_dataset),
|
| 141 | ! |
teal.widgets::optionalSelectInput( |
| 142 | ! |
ns("join_fun"),
|
| 143 | ! |
label = "Row to Column type of join", |
| 144 | ! |
choices = join_default_options, |
| 145 | ! |
selected = join_default_options[1], |
| 146 | ! |
multiple = FALSE |
| 147 |
), |
|
| 148 | ! |
tags$hr(), |
| 149 | ! |
teal.widgets::panel_group( |
| 150 | ! |
teal.widgets::panel_item( |
| 151 | ! |
title = "Table settings", |
| 152 | ! |
checkboxInput(ns("show_percentage"), "Show column percentage", value = show_percentage),
|
| 153 | ! |
checkboxInput(ns("show_total"), "Show total column", value = show_total)
|
| 154 |
) |
|
| 155 |
) |
|
| 156 |
), |
|
| 157 | ! |
forms = tagList( |
| 158 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"),
|
| 159 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 160 |
), |
|
| 161 | ! |
pre_output = pre_output, |
| 162 | ! |
post_output = post_output |
| 163 |
) |
|
| 164 |
} |
|
| 165 | ||
| 166 |
srv_t_crosstable <- function(id, data, reporter, filter_panel_api, label, x, y, basic_table_args) {
|
|
| 167 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 168 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 169 | ! |
checkmate::assert_class(data, "tdata") |
| 170 | ! |
moduleServer(id, function(input, output, session) {
|
| 171 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 172 | ! |
data_extract = list(x = x, y = y), |
| 173 | ! |
datasets = data, |
| 174 | ! |
select_validation_rule = list( |
| 175 | ! |
x = shinyvalidate::sv_required("Please define column for row variable."),
|
| 176 | ! |
y = shinyvalidate::sv_required("Please define column for column variable.")
|
| 177 |
) |
|
| 178 |
) |
|
| 179 | ||
| 180 | ! |
iv_r <- reactive({
|
| 181 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 182 | ! |
iv$add_rule("join_fun", function(value) {
|
| 183 | ! |
if (!identical(selector_list()$x()$dataname, selector_list()$y()$dataname)) {
|
| 184 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 185 | ! |
"Please select a joining function." |
| 186 |
} |
|
| 187 |
} |
|
| 188 |
}) |
|
| 189 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list) |
| 190 |
}) |
|
| 191 | ||
| 192 | ! |
observeEvent( |
| 193 | ! |
eventExpr = {
|
| 194 | ! |
req(!is.null(selector_list()$x()) && !is.null(selector_list()$y())) |
| 195 | ! |
list(selector_list()$x(), selector_list()$y()) |
| 196 |
}, |
|
| 197 | ! |
handlerExpr = {
|
| 198 | ! |
if (identical(selector_list()$x()$dataname, selector_list()$y()$dataname)) {
|
| 199 | ! |
shinyjs::hide("join_fun")
|
| 200 |
} else {
|
|
| 201 | ! |
shinyjs::show("join_fun")
|
| 202 |
} |
|
| 203 |
} |
|
| 204 |
) |
|
| 205 | ||
| 206 | ! |
merge_function <- reactive({
|
| 207 | ! |
if (is.null(input$join_fun)) {
|
| 208 | ! |
"dplyr::full_join" |
| 209 |
} else {
|
|
| 210 | ! |
input$join_fun |
| 211 |
} |
|
| 212 |
}) |
|
| 213 | ||
| 214 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 215 | ! |
datasets = data, |
| 216 | ! |
join_keys = get_join_keys(data), |
| 217 | ! |
selector_list = selector_list, |
| 218 | ! |
merge_function = merge_function |
| 219 |
) |
|
| 220 | ||
| 221 | ! |
anl_merged_q <- reactive({
|
| 222 | ! |
req(anl_merged_input()) |
| 223 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 224 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 225 |
}) |
|
| 226 | ||
| 227 | ! |
merged <- list( |
| 228 | ! |
anl_input_r = anl_merged_input, |
| 229 | ! |
anl_q_r = anl_merged_q |
| 230 |
) |
|
| 231 | ||
| 232 | ! |
output_q <- reactive({
|
| 233 | ! |
teal::validate_inputs(iv_r()) |
| 234 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 235 | ||
| 236 |
# As this is a summary |
|
| 237 | ! |
x_name <- as.vector(merged$anl_input_r()$columns_source$x) |
| 238 | ! |
y_name <- as.vector(merged$anl_input_r()$columns_source$y) |
| 239 | ||
| 240 | ! |
teal::validate_has_data(ANL, 3) |
| 241 | ! |
teal::validate_has_data(ANL[, c(x_name, y_name)], 3, complete = TRUE, allow_inf = FALSE) |
| 242 | ||
| 243 | ! |
is_allowed_class <- function(x) is.numeric(x) || is.factor(x) || is.character(x) || is.logical(x) |
| 244 | ! |
validate(need( |
| 245 | ! |
all(vapply(ANL[x_name], is_allowed_class, logical(1))), |
| 246 | ! |
"Selected row variable has an unsupported data type." |
| 247 |
)) |
|
| 248 | ! |
validate(need( |
| 249 | ! |
is_allowed_class(ANL[[y_name]]), |
| 250 | ! |
"Selected column variable has an unsupported data type." |
| 251 |
)) |
|
| 252 | ||
| 253 | ! |
show_percentage <- input$show_percentage # nolint |
| 254 | ! |
show_total <- input$show_total # nolint |
| 255 | ||
| 256 | ! |
plot_title <- paste( |
| 257 | ! |
"Cross-Table of", |
| 258 | ! |
paste0(varname_w_label(x_name, ANL), collapse = ", "), |
| 259 | ! |
"(rows)", "vs.", |
| 260 | ! |
varname_w_label(y_name, ANL), |
| 261 | ! |
"(columns)" |
| 262 |
) |
|
| 263 | ||
| 264 | ! |
labels_vec <- vapply( |
| 265 | ! |
x_name, |
| 266 | ! |
varname_w_label, |
| 267 | ! |
character(1), |
| 268 | ! |
ANL |
| 269 |
) |
|
| 270 | ||
| 271 | ! |
teal.code::eval_code( |
| 272 | ! |
merged$anl_q_r(), |
| 273 | ! |
substitute( |
| 274 | ! |
expr = {
|
| 275 | ! |
title <- plot_title |
| 276 |
}, |
|
| 277 | ! |
env = list(plot_title = plot_title) |
| 278 |
) |
|
| 279 |
) %>% |
|
| 280 | ! |
teal.code::eval_code( |
| 281 | ! |
substitute( |
| 282 | ! |
expr = {
|
| 283 | ! |
lyt <- basic_tables %>% |
| 284 | ! |
split_call %>% # styler: off |
| 285 | ! |
rtables::add_colcounts() %>% |
| 286 | ! |
tern::summarize_vars( |
| 287 | ! |
vars = x_name, |
| 288 | ! |
var_labels = labels_vec, |
| 289 | ! |
na.rm = FALSE, |
| 290 | ! |
denom = "N_col", |
| 291 | ! |
.stats = c("mean_sd", "median", "range", count_value)
|
| 292 |
) |
|
| 293 |
}, |
|
| 294 | ! |
env = list( |
| 295 | ! |
basic_tables = teal.widgets::parse_basic_table_args( |
| 296 | ! |
basic_table_args = teal.widgets::resolve_basic_table_args(basic_table_args) |
| 297 |
), |
|
| 298 | ! |
split_call = if (show_total) {
|
| 299 | ! |
substitute( |
| 300 | ! |
expr = rtables::split_cols_by( |
| 301 | ! |
y_name, |
| 302 | ! |
split_fun = rtables::add_overall_level(label = "Total", first = FALSE) |
| 303 |
), |
|
| 304 | ! |
env = list(y_name = y_name) |
| 305 |
) |
|
| 306 |
} else {
|
|
| 307 | ! |
substitute(rtables::split_cols_by(y_name), env = list(y_name = y_name)) |
| 308 |
}, |
|
| 309 | ! |
x_name = x_name, |
| 310 | ! |
labels_vec = labels_vec, |
| 311 | ! |
count_value = ifelse(show_percentage, "count_fraction", "count") |
| 312 |
) |
|
| 313 |
) |
|
| 314 |
) %>% |
|
| 315 | ! |
teal.code::eval_code( |
| 316 | ! |
substitute( |
| 317 | ! |
expr = {
|
| 318 | ! |
ANL <- tern::df_explicit_na(ANL) # nolint |
| 319 | ! |
tbl <- rtables::build_table(lyt = lyt, df = ANL[order(ANL[[y_name]]), ]) |
| 320 | ! |
tbl |
| 321 |
}, |
|
| 322 | ! |
env = list(y_name = y_name) |
| 323 |
) |
|
| 324 |
) |
|
| 325 |
}) |
|
| 326 | ||
| 327 | ! |
output$title <- renderText(output_q()[["title"]]) |
| 328 | ||
| 329 | ! |
table_r <- reactive({
|
| 330 | ! |
shiny::req(iv_r()$is_valid()) |
| 331 | ! |
output_q()[["tbl"]] |
| 332 |
}) |
|
| 333 | ||
| 334 | ! |
teal.widgets::table_with_settings_srv( |
| 335 | ! |
id = "table", |
| 336 | ! |
table_r = table_r |
| 337 |
) |
|
| 338 | ||
| 339 | ! |
teal.widgets::verbatim_popup_srv( |
| 340 | ! |
id = "warning", |
| 341 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 342 | ! |
title = "Warning", |
| 343 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 344 |
) |
|
| 345 | ||
| 346 | ! |
teal.widgets::verbatim_popup_srv( |
| 347 | ! |
id = "rcode", |
| 348 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 349 | ! |
title = "Show R Code for Cross-Table" |
| 350 |
) |
|
| 351 | ||
| 352 |
### REPORTER |
|
| 353 | ! |
if (with_reporter) {
|
| 354 | ! |
card_fun <- function(comment) {
|
| 355 | ! |
card <- teal::TealReportCard$new() |
| 356 | ! |
card$set_name("Cross Table")
|
| 357 | ! |
card$append_text("Cross Table", "header2")
|
| 358 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 359 | ! |
card$append_text("Table", "header3")
|
| 360 | ! |
card$append_table(table_r()) |
| 361 | ! |
if (!comment == "") {
|
| 362 | ! |
card$append_text("Comment", "header3")
|
| 363 | ! |
card$append_text(comment) |
| 364 |
} |
|
| 365 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 366 | ! |
card |
| 367 |
} |
|
| 368 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 369 |
} |
|
| 370 |
### |
|
| 371 |
}) |
|
| 372 |
} |
| 1 |
#' Response Plots |
|
| 2 |
#' @md |
|
| 3 |
#' |
|
| 4 |
#' @inheritParams teal::module |
|
| 5 |
#' @inheritParams shared_params |
|
| 6 |
#' @param response (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 7 |
#' Which variable to use as the response. You can define one fixed column by using the |
|
| 8 |
#' setting `fixed = TRUE` inside the `select_spec`. |
|
| 9 |
#' `data_extract_spec` must not allow multiple selection in this case. |
|
| 10 |
#' @param x (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 11 |
#' Which variable to use on the X-axis of the response plot. Allow the user to select multiple |
|
| 12 |
#' columns from the `data` allowed in teal. |
|
| 13 |
#' `data_extract_spec` must not allow multiple selection in this case. |
|
| 14 |
#' @param row_facet optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 15 |
#' Which data columns to use for faceting rows. |
|
| 16 |
#' @param col_facet optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 17 |
#' Which data to use for faceting columns. |
|
| 18 |
#' @param coord_flip optional, (`logical`) Whether to flip coordinates between `x` and `response`. |
|
| 19 |
#' @param count_labels optional, (`logical`) Whether to show count labels. |
|
| 20 |
#' Defaults to `TRUE`. |
|
| 21 |
#' @param freq optional, (`logical`) Whether to display frequency (`TRUE`) or density (`FALSE`). |
|
| 22 |
#' Defaults to density (`FALSE`). |
|
| 23 |
#' |
|
| 24 |
#' @note For more examples, please see the vignette "Using response plot" via |
|
| 25 |
#' \code{vignette("using-response-plot", package = "teal.modules.general")}.
|
|
| 26 |
#' @export |
|
| 27 |
#' @examples |
|
| 28 |
#' # Response plot with selected response (BMRKR1) and selected x variable (RACE) |
|
| 29 |
#' library(nestcolor) |
|
| 30 |
#' |
|
| 31 |
#' ADSL <- teal.modules.general::rADSL |
|
| 32 |
#' |
|
| 33 |
#' app <- teal::init( |
|
| 34 |
#' data = teal.data::cdisc_data( |
|
| 35 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 36 |
#' check = TRUE |
|
| 37 |
#' ), |
|
| 38 |
#' modules = teal::modules( |
|
| 39 |
#' teal.modules.general::tm_g_response( |
|
| 40 |
#' label = "Response Plots", |
|
| 41 |
#' response = teal.transform::data_extract_spec( |
|
| 42 |
#' dataname = "ADSL", |
|
| 43 |
#' select = teal.transform::select_spec( |
|
| 44 |
#' label = "Select variable:", |
|
| 45 |
#' choices = teal.transform::variable_choices(ADSL, c("BMRKR2", "COUNTRY")),
|
|
| 46 |
#' selected = "BMRKR2", |
|
| 47 |
#' multiple = FALSE, |
|
| 48 |
#' fixed = FALSE |
|
| 49 |
#' ) |
|
| 50 |
#' ), |
|
| 51 |
#' x = teal.transform::data_extract_spec( |
|
| 52 |
#' dataname = "ADSL", |
|
| 53 |
#' select = teal.transform::select_spec( |
|
| 54 |
#' label = "Select variable:", |
|
| 55 |
#' choices = teal.transform::variable_choices(ADSL, c("SEX", "RACE")),
|
|
| 56 |
#' selected = "RACE", |
|
| 57 |
#' multiple = FALSE, |
|
| 58 |
#' fixed = FALSE |
|
| 59 |
#' ) |
|
| 60 |
#' ), |
|
| 61 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 62 |
#' labs = list(subtitle = "Plot generated by Response Module") |
|
| 63 |
#' ) |
|
| 64 |
#' ) |
|
| 65 |
#' ) |
|
| 66 |
#' ) |
|
| 67 |
#' if (interactive()) {
|
|
| 68 |
#' shinyApp(app$ui, app$server) |
|
| 69 |
#' } |
|
| 70 |
tm_g_response <- function(label = "Response Plot", |
|
| 71 |
response, |
|
| 72 |
x, |
|
| 73 |
row_facet = NULL, |
|
| 74 |
col_facet = NULL, |
|
| 75 |
coord_flip = FALSE, |
|
| 76 |
count_labels = TRUE, |
|
| 77 |
rotate_xaxis_labels = FALSE, |
|
| 78 |
freq = FALSE, |
|
| 79 |
plot_height = c(600, 400, 5000), |
|
| 80 |
plot_width = NULL, |
|
| 81 |
ggtheme = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
|
| 82 |
ggplot2_args = teal.widgets::ggplot2_args(), |
|
| 83 |
pre_output = NULL, |
|
| 84 |
post_output = NULL) {
|
|
| 85 | ! |
logger::log_info("Initializing tm_g_response")
|
| 86 | ! |
if (inherits(response, "data_extract_spec")) response <- list(response) |
| 87 | ! |
if (inherits(x, "data_extract_spec")) x <- list(x) |
| 88 | ! |
if (inherits(row_facet, "data_extract_spec")) row_facet <- list(row_facet) |
| 89 | ! |
if (inherits(col_facet, "data_extract_spec")) col_facet <- list(col_facet) |
| 90 | ! |
checkmate::assert_string(label) |
| 91 | ! |
ggtheme <- match.arg(ggtheme) |
| 92 | ! |
checkmate::assert_list(response, types = "data_extract_spec") |
| 93 | ! |
if (!all(vapply(response, function(x) !("" %in% x$select$choices), logical(1)))) {
|
| 94 | ! |
stop("'response' should not allow empty values")
|
| 95 |
} |
|
| 96 | ! |
if (!all(vapply(response, function(x) !x$select$multiple, logical(1)))) {
|
| 97 | ! |
stop("'response' should not allow multiple selection")
|
| 98 |
} |
|
| 99 | ! |
checkmate::assert_list(x, types = "data_extract_spec") |
| 100 | ! |
if (!all(vapply(x, function(x) !("" %in% x$select$choices), logical(1)))) {
|
| 101 | ! |
stop("'x' should not allow empty values")
|
| 102 |
} |
|
| 103 | ! |
if (!all(vapply(x, function(x) !x$select$multiple, logical(1)))) {
|
| 104 | ! |
stop("'x' should not allow multiple selection")
|
| 105 |
} |
|
| 106 | ! |
checkmate::assert_list(row_facet, types = "data_extract_spec", null.ok = TRUE) |
| 107 | ! |
checkmate::assert_list(col_facet, types = "data_extract_spec", null.ok = TRUE) |
| 108 | ! |
checkmate::assert_flag(coord_flip) |
| 109 | ! |
checkmate::assert_flag(count_labels) |
| 110 | ! |
checkmate::assert_flag(rotate_xaxis_labels) |
| 111 | ! |
checkmate::assert_flag(freq) |
| 112 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
| 113 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 114 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
| 115 | ! |
checkmate::assert_numeric( |
| 116 | ! |
plot_width[1], |
| 117 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 118 |
) |
|
| 119 | ||
| 120 | ! |
checkmate::assert_class(ggplot2_args, "ggplot2_args") |
| 121 | ||
| 122 | ! |
args <- as.list(environment()) |
| 123 | ||
| 124 | ! |
data_extract_list <- list( |
| 125 | ! |
response = response, |
| 126 | ! |
x = x, |
| 127 | ! |
row_facet = row_facet, |
| 128 | ! |
col_facet = col_facet |
| 129 |
) |
|
| 130 | ||
| 131 | ! |
module( |
| 132 | ! |
label = label, |
| 133 | ! |
server = srv_g_response, |
| 134 | ! |
ui = ui_g_response, |
| 135 | ! |
ui_args = args, |
| 136 | ! |
server_args = c( |
| 137 | ! |
data_extract_list, |
| 138 | ! |
list(plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args) |
| 139 |
), |
|
| 140 | ! |
datanames = teal.transform::get_extract_datanames(data_extract_list) |
| 141 |
) |
|
| 142 |
} |
|
| 143 | ||
| 144 |
ui_g_response <- function(id, ...) {
|
|
| 145 | ! |
ns <- NS(id) |
| 146 | ! |
args <- list(...) |
| 147 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset(args$response, args$x, args$row_facet, args$col_facet) |
| 148 | ||
| 149 | ! |
teal.widgets::standard_layout( |
| 150 | ! |
output = teal.widgets::white_small_well( |
| 151 | ! |
teal.widgets::plot_with_settings_ui(id = ns("myplot"))
|
| 152 |
), |
|
| 153 | ! |
encoding = div( |
| 154 |
### Reporter |
|
| 155 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 156 |
### |
|
| 157 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 158 | ! |
teal.transform::datanames_input(args[c("response", "x", "row_facet", "col_facet")]),
|
| 159 | ! |
teal.transform::data_extract_ui( |
| 160 | ! |
id = ns("response"),
|
| 161 | ! |
label = "Response variable", |
| 162 | ! |
data_extract_spec = args$response, |
| 163 | ! |
is_single_dataset = is_single_dataset_value |
| 164 |
), |
|
| 165 | ! |
teal.transform::data_extract_ui( |
| 166 | ! |
id = ns("x"),
|
| 167 | ! |
label = "X variable", |
| 168 | ! |
data_extract_spec = args$x, |
| 169 | ! |
is_single_dataset = is_single_dataset_value |
| 170 |
), |
|
| 171 | ! |
if (!is.null(args$row_facet)) {
|
| 172 | ! |
teal.transform::data_extract_ui( |
| 173 | ! |
id = ns("row_facet"),
|
| 174 | ! |
label = "Row facetting", |
| 175 | ! |
data_extract_spec = args$row_facet, |
| 176 | ! |
is_single_dataset = is_single_dataset_value |
| 177 |
) |
|
| 178 |
}, |
|
| 179 | ! |
if (!is.null(args$col_facet)) {
|
| 180 | ! |
teal.transform::data_extract_ui( |
| 181 | ! |
id = ns("col_facet"),
|
| 182 | ! |
label = "Column facetting", |
| 183 | ! |
data_extract_spec = args$col_facet, |
| 184 | ! |
is_single_dataset = is_single_dataset_value |
| 185 |
) |
|
| 186 |
}, |
|
| 187 | ! |
shinyWidgets::radioGroupButtons( |
| 188 | ! |
inputId = ns("freq"),
|
| 189 | ! |
label = NULL, |
| 190 | ! |
choices = c("frequency", "density"),
|
| 191 | ! |
selected = ifelse(args$freq, "frequency", "density"), |
| 192 | ! |
justified = TRUE |
| 193 |
), |
|
| 194 | ! |
teal.widgets::panel_group( |
| 195 | ! |
teal.widgets::panel_item( |
| 196 | ! |
title = "Plot settings", |
| 197 | ! |
checkboxInput(ns("count_labels"), "Add count labels", value = args$count_labels),
|
| 198 | ! |
checkboxInput(ns("coord_flip"), "Swap axes", value = args$coord_flip),
|
| 199 | ! |
checkboxInput(ns("rotate_xaxis_labels"), "Rotate X axis labels", value = args$rotate_xaxis_labels),
|
| 200 | ! |
selectInput( |
| 201 | ! |
inputId = ns("ggtheme"),
|
| 202 | ! |
label = "Theme (by ggplot):", |
| 203 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 204 | ! |
selected = args$ggtheme, |
| 205 | ! |
multiple = FALSE |
| 206 |
) |
|
| 207 |
) |
|
| 208 |
) |
|
| 209 |
), |
|
| 210 | ! |
forms = tagList( |
| 211 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), button_label = "Show Warnings"),
|
| 212 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 213 |
), |
|
| 214 | ! |
pre_output = args$pre_output, |
| 215 | ! |
post_output = args$post_output |
| 216 |
) |
|
| 217 |
} |
|
| 218 | ||
| 219 |
srv_g_response <- function(id, |
|
| 220 |
data, |
|
| 221 |
reporter, |
|
| 222 |
filter_panel_api, |
|
| 223 |
response, |
|
| 224 |
x, |
|
| 225 |
row_facet, |
|
| 226 |
col_facet, |
|
| 227 |
plot_height, |
|
| 228 |
plot_width, |
|
| 229 |
ggplot2_args) {
|
|
| 230 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 231 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 232 | ! |
checkmate::assert_class(data, "tdata") |
| 233 | ! |
moduleServer(id, function(input, output, session) {
|
| 234 | ! |
data_extract <- list(response = response, x = x, row_facet = row_facet, col_facet = col_facet) |
| 235 | ||
| 236 | ! |
rule_diff <- function(other) {
|
| 237 | ! |
function(value) {
|
| 238 | ! |
othervalue <- selector_list()[[other]]()[["select"]] |
| 239 | ! |
if (!is.null(othervalue)) {
|
| 240 | ! |
if (identical(value, othervalue)) {
|
| 241 | ! |
"Row and column facetting variables must be different." |
| 242 |
} |
|
| 243 |
} |
|
| 244 |
} |
|
| 245 |
} |
|
| 246 | ||
| 247 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 248 | ! |
data_extract = data_extract, |
| 249 | ! |
datasets = data, |
| 250 | ! |
select_validation_rule = list( |
| 251 | ! |
response = shinyvalidate::sv_required("Please define a column for the response variable"),
|
| 252 | ! |
x = shinyvalidate::sv_required("Please define a column for X variable"),
|
| 253 | ! |
row_facet = shinyvalidate::compose_rules( |
| 254 | ! |
shinyvalidate::sv_optional(), |
| 255 | ! |
~ if (length(.) > 1) "There must be 1 or no row facetting variable.", |
| 256 | ! |
rule_diff("col_facet")
|
| 257 |
), |
|
| 258 | ! |
col_facet = shinyvalidate::compose_rules( |
| 259 | ! |
shinyvalidate::sv_optional(), |
| 260 | ! |
~ if (length(.) > 1) "There must be 1 or no column facetting variable.", |
| 261 | ! |
rule_diff("row_facet")
|
| 262 |
) |
|
| 263 |
) |
|
| 264 |
) |
|
| 265 | ||
| 266 | ! |
iv_r <- reactive({
|
| 267 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 268 | ! |
iv$add_rule("ggtheme", shinyvalidate::sv_required("Please select a theme"))
|
| 269 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list) |
| 270 |
}) |
|
| 271 | ||
| 272 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 273 | ! |
selector_list = selector_list, |
| 274 | ! |
datasets = data, |
| 275 | ! |
join_keys = get_join_keys(data) |
| 276 |
) |
|
| 277 | ||
| 278 | ! |
anl_merged_q <- reactive({
|
| 279 | ! |
req(anl_merged_input()) |
| 280 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 281 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 282 |
}) |
|
| 283 | ||
| 284 | ! |
merged <- list( |
| 285 | ! |
anl_input_r = anl_merged_input, |
| 286 | ! |
anl_q_r = anl_merged_q |
| 287 |
) |
|
| 288 | ||
| 289 | ! |
output_q <- reactive({
|
| 290 | ! |
teal::validate_inputs(iv_r()) |
| 291 | ||
| 292 | ! |
qenv <- merged$anl_q_r() |
| 293 | ! |
ANL <- qenv[["ANL"]] # nolint |
| 294 | ! |
resp_var <- as.vector(merged$anl_input_r()$columns_source$response) |
| 295 | ! |
x <- as.vector(merged$anl_input_r()$columns_source$x) |
| 296 | ||
| 297 | ! |
validate(need(is.factor(ANL[[resp_var]]), "Please select a factor variable as the response.")) |
| 298 | ! |
validate(need(is.factor(ANL[[x]]), "Please select a factor variable as the X-Variable.")) |
| 299 | ! |
teal::validate_has_data(ANL, 10) |
| 300 | ! |
teal::validate_has_data(ANL[, c(resp_var, x)], 10, complete = TRUE, allow_inf = FALSE) |
| 301 | ||
| 302 | ! |
row_facet_name <- if (length(merged$anl_input_r()$columns_source$row_facet) == 0) {
|
| 303 | ! |
character(0) |
| 304 |
} else {
|
|
| 305 | ! |
as.vector(merged$anl_input_r()$columns_source$row_facet) |
| 306 |
} |
|
| 307 | ! |
col_facet_name <- if (length(merged$anl_input_r()$columns_source$col_facet) == 0) {
|
| 308 | ! |
character(0) |
| 309 |
} else {
|
|
| 310 | ! |
as.vector(merged$anl_input_r()$columns_source$col_facet) |
| 311 |
} |
|
| 312 | ||
| 313 | ! |
freq <- input$freq == "frequency" |
| 314 | ! |
swap_axes <- input$coord_flip |
| 315 | ! |
counts <- input$count_labels |
| 316 | ! |
rotate_xaxis_labels <- input$rotate_xaxis_labels |
| 317 | ! |
ggtheme <- input$ggtheme |
| 318 | ||
| 319 | ! |
arg_position <- if (freq) "stack" else "fill" # nolint |
| 320 | ||
| 321 | ! |
rowf <- if (length(row_facet_name) == 0) NULL else as.name(row_facet_name) # nolint |
| 322 | ! |
colf <- if (length(col_facet_name) == 0) NULL else as.name(col_facet_name) # nolint |
| 323 | ! |
resp_cl <- as.name(resp_var) # nolint |
| 324 | ! |
x_cl <- as.name(x) # nolint |
| 325 | ||
| 326 | ! |
if (swap_axes) {
|
| 327 | ! |
qenv <- teal.code::eval_code( |
| 328 | ! |
qenv, |
| 329 | ! |
substitute( |
| 330 | ! |
expr = ANL[[x]] <- with(ANL, forcats::fct_rev(x_cl)), # nolint |
| 331 | ! |
env = list(x = x, x_cl = x_cl) |
| 332 |
) |
|
| 333 |
) |
|
| 334 |
} |
|
| 335 | ||
| 336 | ! |
qenv <- teal.code::eval_code( |
| 337 | ! |
qenv, |
| 338 | ! |
substitute( |
| 339 | ! |
expr = ANL[[resp_var]] <- factor(ANL[[resp_var]]), # nolint |
| 340 | ! |
env = list(resp_var = resp_var) |
| 341 |
) |
|
| 342 |
) %>% |
|
| 343 |
# nolint start |
|
| 344 |
# rowf and colf will be a NULL if not set by a user |
|
| 345 | ! |
teal.code::eval_code( |
| 346 | ! |
substitute( |
| 347 | ! |
expr = ANL2 <- ANL %>% |
| 348 | ! |
dplyr::group_by_at(dplyr::vars(x_cl, resp_cl, rowf, colf)) %>% |
| 349 | ! |
dplyr::summarise(ns = dplyr::n()) %>% |
| 350 | ! |
dplyr::group_by_at(dplyr::vars(x_cl, rowf, colf)) %>% |
| 351 | ! |
dplyr::mutate(sums = sum(ns), percent = round(ns / sums * 100, 1)), |
| 352 | ! |
env = list(x_cl = x_cl, resp_cl = resp_cl, rowf = rowf, colf = colf) |
| 353 |
) |
|
| 354 |
) %>% |
|
| 355 | ! |
teal.code::eval_code( |
| 356 | ! |
substitute( |
| 357 | ! |
expr = ANL3 <- ANL %>% |
| 358 | ! |
dplyr::group_by_at(dplyr::vars(x_cl, rowf, colf)) %>% |
| 359 | ! |
dplyr::summarise(ns = dplyr::n()), |
| 360 | ! |
env = list(x_cl = x_cl, rowf = rowf, colf = colf) |
| 361 |
) |
|
| 362 |
) |
|
| 363 |
# nolint end |
|
| 364 | ||
| 365 | ! |
plot_call <- substitute( |
| 366 | ! |
expr = |
| 367 | ! |
ggplot(ANL2, aes(x = x_cl, y = ns)) + |
| 368 | ! |
geom_bar(aes(fill = resp_cl), stat = "identity", position = arg_position), |
| 369 | ! |
env = list( |
| 370 | ! |
x_cl = x_cl, |
| 371 | ! |
resp_cl = resp_cl, |
| 372 | ! |
arg_position = arg_position |
| 373 |
) |
|
| 374 |
) |
|
| 375 | ||
| 376 | ! |
if (!freq) plot_call <- substitute(plot_call + expand_limits(y = c(0, 1.1)), env = list(plot_call = plot_call)) |
| 377 | ||
| 378 | ! |
if (counts) {
|
| 379 | ! |
plot_call <- substitute( |
| 380 | ! |
expr = plot_call + |
| 381 | ! |
geom_text( |
| 382 | ! |
data = ANL2, |
| 383 | ! |
aes(label = ns, x = x_cl, y = ns, group = resp_cl), |
| 384 | ! |
col = "white", |
| 385 | ! |
vjust = "middle", |
| 386 | ! |
hjust = "middle", |
| 387 | ! |
position = position_anl2_value |
| 388 |
) + |
|
| 389 | ! |
geom_text( |
| 390 | ! |
data = ANL3, aes(label = ns, x = x_cl, y = anl3_y), |
| 391 | ! |
hjust = hjust_value, |
| 392 | ! |
vjust = vjust_value, |
| 393 | ! |
position = position_anl3_value |
| 394 |
), |
|
| 395 | ! |
env = list( |
| 396 | ! |
plot_call = plot_call, |
| 397 | ! |
x_cl = x_cl, |
| 398 | ! |
resp_cl = resp_cl, |
| 399 | ! |
hjust_value = if (swap_axes) "left" else "middle", |
| 400 | ! |
vjust_value = if (swap_axes) "middle" else -1, |
| 401 | ! |
position_anl2_value = if (!freq) quote(position_fill(0.5)) else quote(position_stack(0.5)), |
| 402 | ! |
anl3_y = if (!freq) 1.1 else as.name("ns"),
|
| 403 | ! |
position_anl3_value = if (!freq) "fill" else "stack" |
| 404 |
) |
|
| 405 |
) |
|
| 406 |
} |
|
| 407 | ||
| 408 | ! |
if (swap_axes) {
|
| 409 | ! |
plot_call <- substitute(plot_call + coord_flip(), env = list(plot_call = plot_call)) |
| 410 |
} |
|
| 411 | ||
| 412 | ! |
facet_cl <- facet_ggplot_call(row_facet_name, col_facet_name) |
| 413 | ||
| 414 | ! |
if (!is.null(facet_cl)) {
|
| 415 | ! |
plot_call <- substitute(expr = plot_call + facet_cl, env = list(plot_call = plot_call, facet_cl = facet_cl)) |
| 416 |
} |
|
| 417 | ||
| 418 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 419 | ! |
labs = list( |
| 420 | ! |
x = varname_w_label(x, ANL), |
| 421 | ! |
y = varname_w_label(resp_var, ANL, prefix = "Proportion of "), |
| 422 | ! |
fill = varname_w_label(resp_var, ANL) |
| 423 |
), |
|
| 424 | ! |
theme = list(legend.position = "bottom") |
| 425 |
) |
|
| 426 | ||
| 427 | ! |
if (rotate_xaxis_labels) {
|
| 428 | ! |
dev_ggplot2_args$theme[["axis.text.x"]] <- quote(element_text(angle = 45, hjust = 1)) # nolint |
| 429 |
} |
|
| 430 | ||
| 431 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 432 | ! |
user_plot = ggplot2_args, |
| 433 | ! |
module_plot = dev_ggplot2_args |
| 434 |
) |
|
| 435 | ||
| 436 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 437 | ! |
all_ggplot2_args, |
| 438 | ! |
ggtheme = ggtheme |
| 439 |
) |
|
| 440 | ||
| 441 | ! |
plot_call <- substitute(expr = {
|
| 442 | ! |
p <- plot_call + labs + ggthemes + themes |
| 443 | ! |
print(p) |
| 444 | ! |
}, env = list( |
| 445 | ! |
plot_call = plot_call, |
| 446 | ! |
labs = parsed_ggplot2_args$labs, |
| 447 | ! |
themes = parsed_ggplot2_args$theme, |
| 448 | ! |
ggthemes = parsed_ggplot2_args$ggtheme |
| 449 |
)) |
|
| 450 | ||
| 451 | ! |
teal.code::eval_code(qenv, plot_call) |
| 452 |
}) |
|
| 453 | ||
| 454 | ! |
plot_r <- reactive(output_q()[["p"]]) |
| 455 | ||
| 456 |
# Insert the plot into a plot_with_settings module from teal.widgets |
|
| 457 | ! |
pws <- teal.widgets::plot_with_settings_srv( |
| 458 | ! |
id = "myplot", |
| 459 | ! |
plot_r = plot_r, |
| 460 | ! |
height = plot_height, |
| 461 | ! |
width = plot_width |
| 462 |
) |
|
| 463 | ||
| 464 | ! |
teal.widgets::verbatim_popup_srv( |
| 465 | ! |
id = "warning", |
| 466 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 467 | ! |
title = "Warning", |
| 468 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 469 |
) |
|
| 470 | ||
| 471 | ! |
teal.widgets::verbatim_popup_srv( |
| 472 | ! |
id = "rcode", |
| 473 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 474 | ! |
title = "Show R Code for Response" |
| 475 |
) |
|
| 476 | ||
| 477 |
### REPORTER |
|
| 478 | ! |
if (with_reporter) {
|
| 479 | ! |
card_fun <- function(comment) {
|
| 480 | ! |
card <- teal::TealReportCard$new() |
| 481 | ! |
card$set_name("Response Plot")
|
| 482 | ! |
card$append_text("Response Plot", "header2")
|
| 483 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 484 | ! |
card$append_text("Plot", "header3")
|
| 485 | ! |
card$append_plot(plot_r(), dim = pws$dim()) |
| 486 | ! |
if (!comment == "") {
|
| 487 | ! |
card$append_text("Comment", "header3")
|
| 488 | ! |
card$append_text(comment) |
| 489 |
} |
|
| 490 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 491 | ! |
card |
| 492 |
} |
|
| 493 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 494 |
} |
|
| 495 |
### |
|
| 496 |
}) |
|
| 497 |
} |
| 1 |
#' Principal component analysis module |
|
| 2 |
#' @md |
|
| 3 |
#' |
|
| 4 |
#' @inheritParams teal::module |
|
| 5 |
#' @inheritParams shared_params |
|
| 6 |
#' @param dat (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 7 |
#' Columns used to compute PCA. |
|
| 8 |
#' @param alpha optional, (`numeric`) If scalar then the plot points will have a fixed opacity. If a |
|
| 9 |
#' slider should be presented to adjust the plot point opacity dynamically then it can be a vector of |
|
| 10 |
#' length three with `c(value, min, max)`. |
|
| 11 |
#' @param size optional, (`numeric`) If scalar then the plot point sizes will have a fixed size. |
|
| 12 |
#' If a slider should be presented to adjust the plot point sizes dynamically then it can be a |
|
| 13 |
#' vector of length three with `c(value, min, max)`. |
|
| 14 |
#' @param font_size optional, (`numeric`) font size control for title, x-axis label, y-axis label and legend. |
|
| 15 |
#' If scalar then the font size will have a fixed size. If a slider should be presented to adjust the plot |
|
| 16 |
#' point sizes dynamically then it can be a vector of length three with `c(value, min, max)`. |
|
| 17 |
#' |
|
| 18 |
#' @templateVar ggnames "Elbow plot", "Circle plot", "Biplot", "Eigenvector plot" |
|
| 19 |
#' @template ggplot2_args_multi |
|
| 20 |
#' |
|
| 21 |
#' @export |
|
| 22 |
#' |
|
| 23 |
#' @examples |
|
| 24 |
#' |
|
| 25 |
#' # ADSL example |
|
| 26 |
#' |
|
| 27 |
#' library(nestcolor) |
|
| 28 |
#' ADSL <- teal.modules.general::rADSL |
|
| 29 |
#' |
|
| 30 |
#' app <- teal::init( |
|
| 31 |
#' data = teal.data::cdisc_data( |
|
| 32 |
#' teal.data::cdisc_dataset( |
|
| 33 |
#' "ADSL", ADSL, |
|
| 34 |
#' code = "ADSL <- teal.modules.general::rADSL" |
|
| 35 |
#' ), |
|
| 36 |
#' check = TRUE |
|
| 37 |
#' ), |
|
| 38 |
#' modules = teal::modules( |
|
| 39 |
#' teal.modules.general::tm_a_pca( |
|
| 40 |
#' "PCA", |
|
| 41 |
#' dat = teal.transform::data_extract_spec( |
|
| 42 |
#' dataname = "ADSL", |
|
| 43 |
#' select = teal.transform::select_spec( |
|
| 44 |
#' choices = teal.transform::variable_choices(data = ADSL, c("BMRKR1", "AGE", "EOSDY")),
|
|
| 45 |
#' selected = c("BMRKR1", "AGE"),
|
|
| 46 |
#' multiple = TRUE |
|
| 47 |
#' ), |
|
| 48 |
#' filter = NULL |
|
| 49 |
#' ), |
|
| 50 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 51 |
#' labs = list(subtitle = "Plot generated by PCA Module") |
|
| 52 |
#' ) |
|
| 53 |
#' ) |
|
| 54 |
#' ) |
|
| 55 |
#' ) |
|
| 56 |
#' if (interactive()) {
|
|
| 57 |
#' shinyApp(app$ui, app$server) |
|
| 58 |
#' } |
|
| 59 |
#' |
|
| 60 |
tm_a_pca <- function(label = "Principal Component Analysis", |
|
| 61 |
dat, |
|
| 62 |
plot_height = c(600, 200, 2000), |
|
| 63 |
plot_width = NULL, |
|
| 64 |
ggtheme = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
|
| 65 |
ggplot2_args = teal.widgets::ggplot2_args(), |
|
| 66 |
rotate_xaxis_labels = FALSE, |
|
| 67 |
font_size = c(12, 8, 20), |
|
| 68 |
alpha = c(1, 0, 1), |
|
| 69 |
size = c(2, 1, 8), |
|
| 70 |
pre_output = NULL, |
|
| 71 |
post_output = NULL) {
|
|
| 72 | ! |
logger::log_info("Initializing tm_a_pca")
|
| 73 | ! |
if (inherits(dat, "data_extract_spec")) dat <- list(dat) |
| 74 | ! |
if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) |
| 75 | ||
| 76 | ! |
checkmate::assert_string(label) |
| 77 | ! |
checkmate::assert_list(dat, types = "data_extract_spec") |
| 78 | ! |
ggtheme <- match.arg(ggtheme) |
| 79 | ! |
checkmate::assert_flag(rotate_xaxis_labels) |
| 80 | ||
| 81 | ! |
if (length(alpha) == 1) {
|
| 82 | ! |
checkmate::assert_numeric(alpha, any.missing = FALSE, finite = TRUE, lower = 0, upper = 1) |
| 83 |
} else {
|
|
| 84 | ! |
checkmate::assert_numeric(alpha, len = 3, any.missing = FALSE, finite = TRUE, lower = 0, upper = 1) |
| 85 | ! |
checkmate::assert_numeric(alpha[1], lower = alpha[2], upper = alpha[3], .var.name = "alpha") |
| 86 |
} |
|
| 87 | ||
| 88 | ! |
if (length(size) == 1) {
|
| 89 | ! |
checkmate::assert_numeric(size, any.missing = FALSE, finite = TRUE, lower = 1, upper = 8) |
| 90 |
} else {
|
|
| 91 | ! |
checkmate::assert_numeric(size, len = 3, any.missing = FALSE, finite = TRUE, lower = 1, upper = 8) |
| 92 | ! |
checkmate::assert_numeric(size[1], lower = size[2], upper = size[3], .var.name = "size") |
| 93 |
} |
|
| 94 | ||
| 95 | ! |
if (length(font_size) == 1) {
|
| 96 | ! |
checkmate::assert_numeric(font_size, any.missing = FALSE, finite = TRUE, lower = 8, upper = 20) |
| 97 |
} else {
|
|
| 98 | ! |
checkmate::assert_numeric(font_size, len = 3, any.missing = FALSE, finite = TRUE, lower = 8, upper = 20) |
| 99 | ! |
checkmate::assert_numeric(font_size[1], lower = font_size[2], upper = font_size[3], .var.name = "font_size") |
| 100 |
} |
|
| 101 | ||
| 102 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
| 103 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 104 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
| 105 | ! |
checkmate::assert_numeric( |
| 106 | ! |
plot_width[1], |
| 107 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 108 |
) |
|
| 109 | ||
| 110 | ! |
plot_choices <- c("Elbow plot", "Circle plot", "Biplot", "Eigenvector plot")
|
| 111 | ! |
checkmate::assert_list(ggplot2_args, types = "ggplot2_args") |
| 112 | ! |
checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices))
|
| 113 | ||
| 114 | ! |
args <- as.list(environment()) |
| 115 | ||
| 116 | ! |
data_extract_list <- list(dat = dat) |
| 117 | ||
| 118 | ! |
module( |
| 119 | ! |
label = label, |
| 120 | ! |
server = srv_a_pca, |
| 121 | ! |
ui = ui_a_pca, |
| 122 | ! |
ui_args = args, |
| 123 | ! |
server_args = c( |
| 124 | ! |
data_extract_list, |
| 125 | ! |
list( |
| 126 | ! |
plot_height = plot_height, |
| 127 | ! |
plot_width = plot_width, |
| 128 | ! |
ggplot2_args = ggplot2_args |
| 129 |
) |
|
| 130 |
), |
|
| 131 | ! |
datanames = teal.transform::get_extract_datanames(data_extract_list) |
| 132 |
) |
|
| 133 |
} |
|
| 134 | ||
| 135 | ||
| 136 |
ui_a_pca <- function(id, ...) {
|
|
| 137 | ! |
ns <- NS(id) |
| 138 | ! |
args <- list(...) |
| 139 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset(args$dat) |
| 140 | ||
| 141 | ! |
color_selector <- args$dat |
| 142 | ! |
for (i in seq_along(color_selector)) {
|
| 143 | ! |
color_selector[[i]]$select$multiple <- FALSE |
| 144 | ! |
color_selector[[i]]$select$always_selected <- NULL |
| 145 | ! |
color_selector[[i]]$select$selected <- NULL |
| 146 |
} |
|
| 147 | ||
| 148 | ! |
shiny::tagList( |
| 149 | ! |
include_css_files("custom"),
|
| 150 | ! |
teal.widgets::standard_layout( |
| 151 | ! |
output = teal.widgets::white_small_well( |
| 152 | ! |
uiOutput(ns("all_plots"))
|
| 153 |
), |
|
| 154 | ! |
encoding = div( |
| 155 |
### Reporter |
|
| 156 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 157 |
### |
|
| 158 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 159 | ! |
teal.transform::datanames_input(args["dat"]), |
| 160 | ! |
teal.transform::data_extract_ui( |
| 161 | ! |
id = ns("dat"),
|
| 162 | ! |
label = "Data selection", |
| 163 | ! |
data_extract_spec = args$dat, |
| 164 | ! |
is_single_dataset = is_single_dataset_value |
| 165 |
), |
|
| 166 | ! |
teal.widgets::panel_group( |
| 167 | ! |
teal.widgets::panel_item( |
| 168 | ! |
title = "Display", |
| 169 | ! |
collapsed = FALSE, |
| 170 | ! |
checkboxGroupInput( |
| 171 | ! |
ns("tables_display"),
|
| 172 | ! |
"Tables display", |
| 173 | ! |
choices = c("PC importance" = "importance", "Eigenvectors" = "eigenvector"),
|
| 174 | ! |
selected = c("importance", "eigenvector")
|
| 175 |
), |
|
| 176 | ! |
radioButtons( |
| 177 | ! |
ns("plot_type"),
|
| 178 | ! |
label = "Plot type", |
| 179 | ! |
choices = args$plot_choices, |
| 180 | ! |
selected = args$plot_choices[1] |
| 181 |
) |
|
| 182 |
), |
|
| 183 | ! |
teal.widgets::panel_item( |
| 184 | ! |
title = "Pre-processing", |
| 185 | ! |
radioButtons( |
| 186 | ! |
ns("standardization"), "Standardization",
|
| 187 | ! |
choices = c("None" = "none", "Center" = "center", "Center & Scale" = "center_scale"),
|
| 188 | ! |
selected = "center_scale" |
| 189 |
), |
|
| 190 | ! |
radioButtons( |
| 191 | ! |
ns("na_action"), "NA action",
|
| 192 | ! |
choices = c("None" = "none", "Drop" = "drop"),
|
| 193 | ! |
selected = "none" |
| 194 |
) |
|
| 195 |
), |
|
| 196 | ! |
teal.widgets::panel_item( |
| 197 | ! |
title = "Selected plot specific settings", |
| 198 | ! |
collapsed = FALSE, |
| 199 | ! |
uiOutput(ns("plot_settings")),
|
| 200 | ! |
conditionalPanel( |
| 201 | ! |
condition = sprintf("input['%s'] == 'Biplot'", ns("plot_type")),
|
| 202 | ! |
list( |
| 203 | ! |
teal.transform::data_extract_ui( |
| 204 | ! |
id = ns("response"),
|
| 205 | ! |
label = "Color by", |
| 206 | ! |
data_extract_spec = color_selector, |
| 207 | ! |
is_single_dataset = is_single_dataset_value |
| 208 |
), |
|
| 209 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("alpha"), "Opacity:", args$alpha, ticks = FALSE),
|
| 210 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("size"), "Points size:", args$size, ticks = FALSE)
|
| 211 |
) |
|
| 212 |
) |
|
| 213 |
), |
|
| 214 | ! |
teal.widgets::panel_item( |
| 215 | ! |
title = "Plot settings", |
| 216 | ! |
collapsed = TRUE, |
| 217 | ! |
conditionalPanel( |
| 218 | ! |
condition = sprintf( |
| 219 | ! |
"input['%s'] == 'Elbow Plot' || input['%s'] == 'Eigenvector plot'", |
| 220 | ! |
ns("plot_type"),
|
| 221 | ! |
ns("plot_type")
|
| 222 |
), |
|
| 223 | ! |
list(checkboxInput(ns("rotate_xaxis_labels"), "Rotate X axis labels", value = args$rotate_xaxis_labels))
|
| 224 |
), |
|
| 225 | ! |
selectInput( |
| 226 | ! |
inputId = ns("ggtheme"),
|
| 227 | ! |
label = "Theme (by ggplot):", |
| 228 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 229 | ! |
selected = args$ggtheme, |
| 230 | ! |
multiple = FALSE |
| 231 |
), |
|
| 232 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("font_size"), "Font Size", args$font_size, ticks = FALSE)
|
| 233 |
) |
|
| 234 |
) |
|
| 235 |
), |
|
| 236 | ! |
forms = tagList( |
| 237 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"),
|
| 238 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 239 |
), |
|
| 240 | ! |
pre_output = args$pre_output, |
| 241 | ! |
post_output = args$post_output |
| 242 |
) |
|
| 243 |
) |
|
| 244 |
} |
|
| 245 | ||
| 246 |
srv_a_pca <- function(id, data, reporter, filter_panel_api, dat, plot_height, plot_width, ggplot2_args) {
|
|
| 247 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 248 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 249 | ! |
checkmate::assert_class(data, "tdata") |
| 250 | ! |
moduleServer(id, function(input, output, session) {
|
| 251 | ! |
response <- dat |
| 252 | ||
| 253 | ! |
for (i in seq_along(response)) {
|
| 254 | ! |
response[[i]]$select$multiple <- FALSE |
| 255 | ! |
response[[i]]$select$always_selected <- NULL |
| 256 | ! |
response[[i]]$select$selected <- NULL |
| 257 | ! |
response[[i]]$select$choices <- var_labels(data[[response[[i]]$dataname]]()) |
| 258 | ! |
response[[i]]$select$choices <- setdiff( |
| 259 | ! |
response[[i]]$select$choices, |
| 260 | ! |
unlist(get_join_keys(data)$get(response[[i]]$dataname)) |
| 261 |
) |
|
| 262 |
} |
|
| 263 | ||
| 264 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 265 | ! |
data_extract = list(dat = dat, response = response), |
| 266 | ! |
datasets = data, |
| 267 | ! |
select_validation_rule = list( |
| 268 | ! |
dat = ~ if (length(.) < 2L) "Please select more than 1 variable to perform PCA.", |
| 269 | ! |
response = shinyvalidate::compose_rules( |
| 270 | ! |
shinyvalidate::sv_optional(), |
| 271 | ! |
~ if (isTRUE(is.element(., selector_list()$dat()$select))) {
|
| 272 | ! |
"Response must not have been used for PCA." |
| 273 |
} |
|
| 274 |
) |
|
| 275 |
) |
|
| 276 |
) |
|
| 277 | ||
| 278 | ! |
iv_r <- reactive({
|
| 279 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 280 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list) |
| 281 |
}) |
|
| 282 | ||
| 283 | ! |
iv_extra <- shinyvalidate::InputValidator$new() |
| 284 | ! |
iv_extra$add_rule("x_axis", function(value) {
|
| 285 | ! |
if (isTRUE(input$plot_type %in% c("Circle plot", "Biplot"))) {
|
| 286 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 287 | ! |
"Need X axis" |
| 288 |
} |
|
| 289 |
} |
|
| 290 |
}) |
|
| 291 | ! |
iv_extra$add_rule("y_axis", function(value) {
|
| 292 | ! |
if (isTRUE(input$plot_type %in% c("Circle plot", "Biplot"))) {
|
| 293 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 294 | ! |
"Need Y axis" |
| 295 |
} |
|
| 296 |
} |
|
| 297 |
}) |
|
| 298 | ! |
rule_dupl <- function(...) {
|
| 299 | ! |
if (isTRUE(input$plot_type %in% c("Circle plot", "Biplot"))) {
|
| 300 | ! |
if (isTRUE(input$x_axis == input$y_axis)) {
|
| 301 | ! |
"Please choose different X and Y axes." |
| 302 |
} |
|
| 303 |
} |
|
| 304 |
} |
|
| 305 | ! |
iv_extra$add_rule("x_axis", rule_dupl)
|
| 306 | ! |
iv_extra$add_rule("y_axis", rule_dupl)
|
| 307 | ! |
iv_extra$add_rule("variables", function(value) {
|
| 308 | ! |
if (identical(input$plot_type, "Circle plot")) {
|
| 309 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 310 | ! |
"Need Original Coordinates" |
| 311 |
} |
|
| 312 |
} |
|
| 313 |
}) |
|
| 314 | ! |
iv_extra$add_rule("pc", function(value) {
|
| 315 | ! |
if (identical(input$plot_type, "Eigenvector plot")) {
|
| 316 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 317 | ! |
"Need PC" |
| 318 |
} |
|
| 319 |
} |
|
| 320 |
}) |
|
| 321 | ! |
iv_extra$enable() |
| 322 | ||
| 323 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 324 | ! |
selector_list = selector_list, |
| 325 | ! |
datasets = data, |
| 326 | ! |
join_keys = get_join_keys(data) |
| 327 |
) |
|
| 328 | ||
| 329 | ! |
anl_merged_q <- reactive({
|
| 330 | ! |
req(anl_merged_input()) |
| 331 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 332 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 333 |
}) |
|
| 334 | ||
| 335 | ! |
merged <- list( |
| 336 | ! |
anl_input_r = anl_merged_input, |
| 337 | ! |
anl_q_r = anl_merged_q |
| 338 |
) |
|
| 339 | ||
| 340 | ! |
validation <- reactive({
|
| 341 | ! |
req(merged$anl_q_r()) |
| 342 |
# inputs |
|
| 343 | ! |
keep_cols <- as.character(merged$anl_input_r()$columns_source$dat) |
| 344 | ! |
na_action <- input$na_action |
| 345 | ! |
standardization <- input$standardization |
| 346 | ! |
center <- standardization %in% c("center", "center_scale") # nolint
|
| 347 | ! |
scale <- standardization == "center_scale" |
| 348 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 349 | ||
| 350 | ! |
teal::validate_has_data(ANL, 10) |
| 351 | ! |
validate(need( |
| 352 | ! |
na_action != "none" | !anyNA(ANL[keep_cols]), |
| 353 | ! |
paste( |
| 354 | ! |
"There are NAs in the dataset. Please deal with them in preprocessing", |
| 355 | ! |
"or select \"Drop\" in the NA actions inside the encodings panel (left)." |
| 356 |
) |
|
| 357 |
)) |
|
| 358 | ! |
if (scale) {
|
| 359 | ! |
not_single <- vapply(ANL[keep_cols], function(column) length(unique(column)) != 1, FUN.VALUE = logical(1)) |
| 360 | ||
| 361 | ! |
msg <- paste0( |
| 362 | ! |
"You have selected `Center & Scale` under `Standardization` in the `Pre-processing` panel, ", |
| 363 | ! |
"but one or more of your columns has/have a variance value of zero, indicating all values are identical" |
| 364 |
) |
|
| 365 | ! |
validate(need(all(not_single), msg)) |
| 366 |
} |
|
| 367 |
}) |
|
| 368 | ||
| 369 |
# computation ---- |
|
| 370 | ! |
computation <- reactive({
|
| 371 | ! |
validation() |
| 372 | ||
| 373 |
# inputs |
|
| 374 | ! |
keep_cols <- as.character(merged$anl_input_r()$columns_source$dat) |
| 375 | ! |
na_action <- input$na_action |
| 376 | ! |
standardization <- input$standardization |
| 377 | ! |
center <- standardization %in% c("center", "center_scale") # nolint
|
| 378 | ! |
scale <- standardization == "center_scale" |
| 379 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 380 | ||
| 381 | ! |
qenv <- teal.code::eval_code( |
| 382 | ! |
merged$anl_q_r(), |
| 383 | ! |
substitute( |
| 384 | ! |
expr = keep_columns <- keep_cols, |
| 385 | ! |
env = list(keep_cols = keep_cols) |
| 386 |
) |
|
| 387 |
) |
|
| 388 | ||
| 389 | ! |
if (na_action == "drop") {
|
| 390 | ! |
qenv <- teal.code::eval_code( |
| 391 | ! |
qenv, |
| 392 | ! |
quote(ANL <- tidyr::drop_na(ANL, keep_columns)) # nolint |
| 393 |
) |
|
| 394 |
} |
|
| 395 | ||
| 396 | ! |
qenv <- teal.code::eval_code( |
| 397 | ! |
qenv, |
| 398 | ! |
substitute( |
| 399 | ! |
expr = pca <- summary(stats::prcomp(ANL[keep_columns], center = center, scale. = scale, retx = TRUE)), |
| 400 | ! |
env = list(center = center, scale = scale) |
| 401 |
) |
|
| 402 |
) |
|
| 403 | ||
| 404 | ! |
qenv <- teal.code::eval_code( |
| 405 | ! |
qenv, |
| 406 | ! |
quote({
|
| 407 | ! |
tbl_importance <- dplyr::as_tibble(pca$importance, rownames = "Metric") |
| 408 | ! |
tbl_importance |
| 409 |
}) |
|
| 410 |
) |
|
| 411 | ||
| 412 | ! |
teal.code::eval_code( |
| 413 | ! |
qenv, |
| 414 | ! |
quote({
|
| 415 | ! |
tbl_eigenvector <- dplyr::as_tibble(pca$rotation, rownames = "Variable") |
| 416 | ! |
tbl_eigenvector |
| 417 |
}) |
|
| 418 |
) |
|
| 419 |
}) |
|
| 420 | ||
| 421 |
# plot args ---- |
|
| 422 | ! |
output$plot_settings <- renderUI({
|
| 423 |
# reactivity triggers |
|
| 424 | ! |
req(iv_r()$is_valid()) |
| 425 | ! |
req(computation()) |
| 426 | ! |
qenv <- computation() |
| 427 | ||
| 428 | ! |
ns <- session$ns |
| 429 | ||
| 430 | ! |
pca <- qenv[["pca"]] |
| 431 | ! |
chcs_pcs <- colnames(pca$rotation) |
| 432 | ! |
chcs_vars <- qenv[["keep_columns"]] |
| 433 | ||
| 434 | ! |
tagList( |
| 435 | ! |
conditionalPanel( |
| 436 | ! |
condition = sprintf( |
| 437 | ! |
"input['%s'] == 'Biplot' || input['%s'] == 'Circle plot'", |
| 438 | ! |
ns("plot_type"), ns("plot_type")
|
| 439 |
), |
|
| 440 | ! |
list( |
| 441 | ! |
teal.widgets::optionalSelectInput(ns("x_axis"), "X axis", choices = chcs_pcs, selected = chcs_pcs[1]),
|
| 442 | ! |
teal.widgets::optionalSelectInput(ns("y_axis"), "Y axis", choices = chcs_pcs, selected = chcs_pcs[2]),
|
| 443 | ! |
teal.widgets::optionalSelectInput( |
| 444 | ! |
ns("variables"), "Original coordinates",
|
| 445 | ! |
choices = chcs_vars, selected = chcs_vars, |
| 446 | ! |
multiple = TRUE |
| 447 |
) |
|
| 448 |
) |
|
| 449 |
), |
|
| 450 | ! |
conditionalPanel( |
| 451 | ! |
condition = sprintf("input['%s'] == 'Elbow plot'", ns("plot_type")),
|
| 452 | ! |
helpText("No plot specific settings available.")
|
| 453 |
), |
|
| 454 | ! |
conditionalPanel( |
| 455 | ! |
condition = paste0("input['", ns("plot_type"), "'] == 'Eigenvector plot'"),
|
| 456 | ! |
teal.widgets::optionalSelectInput(ns("pc"), "PC", choices = chcs_pcs, selected = chcs_pcs[1])
|
| 457 |
) |
|
| 458 |
) |
|
| 459 |
}) |
|
| 460 | ||
| 461 |
# plot elbow ---- |
|
| 462 | ! |
plot_elbow <- function(base_q) {
|
| 463 | ! |
ggtheme <- input$ggtheme |
| 464 | ! |
rotate_xaxis_labels <- input$rotate_xaxis_labels # nolint |
| 465 | ! |
font_size <- input$font_size # nolint |
| 466 | ||
| 467 | ! |
angle_value <- ifelse(isTRUE(rotate_xaxis_labels), 45, 0) |
| 468 | ! |
hjust_value <- ifelse(isTRUE(rotate_xaxis_labels), 1, 0.5) |
| 469 | ||
| 470 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 471 | ! |
labs = list(x = "Principal component", y = "Proportion of variance explained", color = "", fill = "Legend"), |
| 472 | ! |
theme = list( |
| 473 | ! |
legend.position = "right", |
| 474 | ! |
legend.spacing.y = quote(grid::unit(-5, "pt")), |
| 475 | ! |
legend.title = quote(element_text(vjust = 25)), |
| 476 | ! |
axis.text.x = substitute( |
| 477 | ! |
element_text(angle = angle_value, hjust = hjust_value), |
| 478 | ! |
list(angle_value = angle_value, hjust_value = hjust_value) |
| 479 |
), |
|
| 480 | ! |
text = substitute(element_text(size = font_size), list(font_size = font_size)) |
| 481 |
) |
|
| 482 |
) |
|
| 483 | ||
| 484 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 485 | ! |
teal.widgets::resolve_ggplot2_args( |
| 486 | ! |
user_plot = ggplot2_args[["Elbow plot"]], |
| 487 | ! |
user_default = ggplot2_args$default, |
| 488 | ! |
module_plot = dev_ggplot2_args |
| 489 |
), |
|
| 490 | ! |
ggtheme = ggtheme |
| 491 |
) |
|
| 492 | ||
| 493 | ! |
teal.code::eval_code( |
| 494 | ! |
base_q, |
| 495 | ! |
substitute( |
| 496 | ! |
expr = {
|
| 497 | ! |
elb_dat <- pca$importance[c("Proportion of Variance", "Cumulative Proportion"), ] %>%
|
| 498 | ! |
dplyr::as_tibble(rownames = "metric") %>% |
| 499 | ! |
tidyr::gather("component", "value", -metric) %>%
|
| 500 | ! |
dplyr::mutate( |
| 501 | ! |
component = factor(component, levels = unique(stringr::str_sort(component, numeric = TRUE))) |
| 502 |
) |
|
| 503 | ||
| 504 | ! |
cols <- c(getOption("ggplot2.discrete.colour"), c("lightblue", "darkred", "black"))[1:3]
|
| 505 | ! |
g <- ggplot(mapping = aes_string(x = "component", y = "value")) + |
| 506 | ! |
geom_bar( |
| 507 | ! |
aes(fill = "Single variance"), |
| 508 | ! |
data = dplyr::filter(elb_dat, metric == "Proportion of Variance"), |
| 509 | ! |
color = "black", |
| 510 | ! |
stat = "identity" |
| 511 |
) + |
|
| 512 | ! |
geom_point( |
| 513 | ! |
aes(color = "Cumulative variance"), |
| 514 | ! |
data = dplyr::filter(elb_dat, metric == "Cumulative Proportion") |
| 515 |
) + |
|
| 516 | ! |
geom_line( |
| 517 | ! |
aes(group = 1, color = "Cumulative variance"), |
| 518 | ! |
data = dplyr::filter(elb_dat, metric == "Cumulative Proportion") |
| 519 |
) + |
|
| 520 | ! |
labs + |
| 521 | ! |
scale_color_manual(values = c("Cumulative variance" = cols[2], "Single variance" = cols[3])) +
|
| 522 | ! |
scale_fill_manual(values = c("Cumulative variance" = cols[2], "Single variance" = cols[1])) +
|
| 523 | ! |
ggthemes + |
| 524 | ! |
themes |
| 525 | ||
| 526 | ! |
print(g) |
| 527 |
}, |
|
| 528 | ! |
env = list( |
| 529 | ! |
ggthemes = parsed_ggplot2_args$ggtheme, |
| 530 | ! |
labs = parsed_ggplot2_args$labs, |
| 531 | ! |
themes = parsed_ggplot2_args$theme |
| 532 |
) |
|
| 533 |
) |
|
| 534 |
) |
|
| 535 |
} |
|
| 536 | ||
| 537 |
# plot circle ---- |
|
| 538 | ! |
plot_circle <- function(base_q) {
|
| 539 | ! |
x_axis <- input$x_axis # nolint |
| 540 | ! |
y_axis <- input$y_axis # nolint |
| 541 | ! |
variables <- input$variables # nolint |
| 542 | ! |
ggtheme <- input$ggtheme |
| 543 | ||
| 544 | ! |
rotate_xaxis_labels <- input$rotate_xaxis_labels # nolint |
| 545 | ! |
font_size <- input$font_size # nolint |
| 546 | ||
| 547 | ! |
angle <- ifelse(isTRUE(rotate_xaxis_labels), 45, 0) |
| 548 | ! |
hjust <- ifelse(isTRUE(rotate_xaxis_labels), 1, 0.5) |
| 549 | ||
| 550 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 551 | ! |
theme = list( |
| 552 | ! |
text = substitute(element_text(size = font_size), list(font_size = font_size)), |
| 553 | ! |
axis.text.x = substitute( |
| 554 | ! |
element_text(angle = angle_val, hjust = hjust_val), |
| 555 | ! |
list(angle_val = angle, hjust_val = hjust) |
| 556 |
) |
|
| 557 |
) |
|
| 558 |
) |
|
| 559 | ||
| 560 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 561 | ! |
user_plot = ggplot2_args[["Circle plot"]], |
| 562 | ! |
user_default = ggplot2_args$default, |
| 563 | ! |
module_plot = dev_ggplot2_args |
| 564 |
) |
|
| 565 | ||
| 566 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 567 | ! |
all_ggplot2_args, |
| 568 | ! |
ggtheme = ggtheme |
| 569 |
) |
|
| 570 | ||
| 571 | ! |
teal.code::eval_code( |
| 572 | ! |
base_q, |
| 573 | ! |
substitute( |
| 574 | ! |
expr = {
|
| 575 | ! |
pca_rot <- pca$rotation[, c(x_axis, y_axis)] %>% |
| 576 | ! |
dplyr::as_tibble(rownames = "label") %>% |
| 577 | ! |
dplyr::filter(label %in% variables) |
| 578 | ||
| 579 | ! |
circle_data <- data.frame( |
| 580 | ! |
x = cos(seq(0, 2 * pi, length.out = 100)), |
| 581 | ! |
y = sin(seq(0, 2 * pi, length.out = 100)) |
| 582 |
) |
|
| 583 | ||
| 584 | ! |
g <- ggplot(pca_rot) + |
| 585 | ! |
geom_point(aes_string(x = x_axis, y = y_axis)) + |
| 586 | ! |
geom_label( |
| 587 | ! |
aes_string(x = x_axis, y = y_axis, label = "label"), |
| 588 | ! |
nudge_x = 0.1, nudge_y = 0.05, |
| 589 | ! |
fontface = "bold" |
| 590 |
) + |
|
| 591 | ! |
geom_path(aes(x, y, group = 1), data = circle_data) + |
| 592 | ! |
geom_point(aes(x = x, y = y), data = data.frame(x = 0, y = 0), shape = "x", size = 5) + |
| 593 | ! |
labs + |
| 594 | ! |
ggthemes + |
| 595 | ! |
themes |
| 596 | ! |
print(g) |
| 597 |
}, |
|
| 598 | ! |
env = list( |
| 599 | ! |
x_axis = x_axis, |
| 600 | ! |
y_axis = y_axis, |
| 601 | ! |
variables = variables, |
| 602 | ! |
ggthemes = parsed_ggplot2_args$ggtheme, |
| 603 | ! |
labs = `if`(is.null(parsed_ggplot2_args$labs), quote(labs()), parsed_ggplot2_args$labs), |
| 604 | ! |
themes = parsed_ggplot2_args$theme |
| 605 |
) |
|
| 606 |
) |
|
| 607 |
) |
|
| 608 |
} |
|
| 609 | ||
| 610 |
# plot biplot ---- |
|
| 611 | ! |
plot_biplot <- function(base_q) {
|
| 612 | ! |
qenv <- base_q |
| 613 | ||
| 614 | ! |
ANL <- qenv[["ANL"]] # nolint |
| 615 | ||
| 616 | ! |
resp_col <- as.character(merged$anl_input_r()$columns_source$response) |
| 617 | ! |
dat_cols <- as.character(merged$anl_input_r()$columns_source$dat) |
| 618 | ! |
x_axis <- input$x_axis # nolint |
| 619 | ! |
y_axis <- input$y_axis # nolint |
| 620 | ! |
variables <- input$variables # nolint |
| 621 | ! |
pca <- qenv[["pca"]] |
| 622 | ||
| 623 | ! |
ggtheme <- input$ggtheme |
| 624 | ||
| 625 | ! |
rotate_xaxis_labels <- input$rotate_xaxis_labels # nolint |
| 626 | ! |
alpha <- input$alpha # nolint |
| 627 | ! |
size <- input$size # nolint |
| 628 | ! |
font_size <- input$font_size # nolint |
| 629 | ||
| 630 | ! |
qenv <- teal.code::eval_code( |
| 631 | ! |
qenv, |
| 632 | ! |
substitute( |
| 633 | ! |
expr = pca_rot <- dplyr::as_tibble(pca$x[, c(x_axis, y_axis)]), |
| 634 | ! |
env = list(x_axis = x_axis, y_axis = y_axis) |
| 635 |
) |
|
| 636 |
) |
|
| 637 | ||
| 638 |
# rot_vars = data frame that displays arrows in the plot, need to be scaled to data |
|
| 639 | ! |
if (!is.null(input$variables)) {
|
| 640 | ! |
qenv <- teal.code::eval_code( |
| 641 | ! |
qenv, |
| 642 | ! |
substitute( |
| 643 | ! |
expr = {
|
| 644 | ! |
r <- sqrt(qchisq(0.69, df = 2)) * prod(colMeans(pca_rot ^ 2)) ^ (1 / 4) # styler: off |
| 645 | ! |
v_scale <- rowSums(pca$rotation ^ 2) # styler: off |
| 646 | ||
| 647 | ! |
rot_vars <- pca$rotation[, c(x_axis, y_axis)] %>% |
| 648 | ! |
dplyr::as_tibble(rownames = "label") %>% |
| 649 | ! |
dplyr::mutate_at(vars(c(x_axis, y_axis)), function(x) r * x / sqrt(max(v_scale))) |
| 650 |
}, |
|
| 651 | ! |
env = list(x_axis = x_axis, y_axis = y_axis) |
| 652 |
) |
|
| 653 |
) %>% |
|
| 654 | ! |
teal.code::eval_code( |
| 655 | ! |
if (is.logical(pca$center) && !pca$center) {
|
| 656 | ! |
substitute( |
| 657 | ! |
expr = {
|
| 658 | ! |
rot_vars <- rot_vars %>% |
| 659 | ! |
tibble::column_to_rownames("label") %>%
|
| 660 | ! |
sweep(1, apply(ANL[keep_columns], 2, mean, na.rm = TRUE)) %>% |
| 661 | ! |
tibble::rownames_to_column("label") %>%
|
| 662 | ! |
dplyr::mutate( |
| 663 | ! |
xstart = mean(pca$x[, x_axis], na.rm = TRUE), |
| 664 | ! |
ystart = mean(pca$x[, y_axis], na.rm = TRUE) |
| 665 |
) |
|
| 666 |
}, |
|
| 667 | ! |
env = list(x_axis = x_axis, y_axis = y_axis) |
| 668 |
) |
|
| 669 |
} else {
|
|
| 670 | ! |
quote(rot_vars <- rot_vars %>% dplyr::mutate(xstart = 0, ystart = 0)) |
| 671 |
} |
|
| 672 |
) %>% |
|
| 673 | ! |
teal.code::eval_code( |
| 674 | ! |
substitute( |
| 675 | ! |
expr = rot_vars <- rot_vars %>% dplyr::filter(label %in% variables), |
| 676 | ! |
env = list(variables = variables) |
| 677 |
) |
|
| 678 |
) |
|
| 679 |
} |
|
| 680 | ||
| 681 | ! |
pca_plot_biplot_expr <- list(quote(ggplot())) |
| 682 | ||
| 683 | ! |
if (length(resp_col) == 0) {
|
| 684 | ! |
pca_plot_biplot_expr <- c( |
| 685 | ! |
pca_plot_biplot_expr, |
| 686 | ! |
substitute( |
| 687 | ! |
geom_point(aes_string(x = x_axis, y = y_axis), data = pca_rot, alpha = alpha, size = size), |
| 688 | ! |
list(x_axis = input$x_axis, y_axis = input$y_axis, alpha = input$alpha, size = input$size) |
| 689 |
) |
|
| 690 |
) |
|
| 691 | ! |
dev_labs <- list() |
| 692 |
} else {
|
|
| 693 | ! |
rp_keys <- setdiff( |
| 694 | ! |
colnames(ANL), |
| 695 | ! |
as.character(unlist(merged$anl_input_r()$columns_source)) |
| 696 | ! |
) # nolint |
| 697 | ||
| 698 | ! |
response <- ANL[[resp_col]] |
| 699 | ||
| 700 | ! |
aes_biplot <- substitute( |
| 701 | ! |
aes_string(x = x_axis, y = y_axis, color = "response"), |
| 702 | ! |
env = list(x_axis = x_axis, y_axis = y_axis) |
| 703 |
) |
|
| 704 | ||
| 705 | ! |
qenv <- teal.code::eval_code( |
| 706 | ! |
qenv, |
| 707 | ! |
substitute(response <- ANL[[resp_col]], env = list(resp_col = resp_col)) |
| 708 |
) |
|
| 709 | ||
| 710 | ! |
dev_labs <- list(color = varname_w_label(resp_col, ANL)) |
| 711 | ||
| 712 | ! |
scales_biplot <- |
| 713 | ! |
if (is.character(response) || is.factor(response) || (is.numeric(response) && length(unique(response)) <= 6)) { # nolint
|
| 714 | ! |
qenv <- teal.code::eval_code( |
| 715 | ! |
qenv, |
| 716 | ! |
quote(pca_rot$response <- as.factor(response)) |
| 717 |
) |
|
| 718 | ! |
quote(scale_color_brewer(palette = "Dark2")) |
| 719 | ! |
} else if (inherits(response, "Date")) {
|
| 720 | ! |
qenv <- teal.code::eval_code( |
| 721 | ! |
qenv, |
| 722 | ! |
quote(pca_rot$response <- numeric(response)) |
| 723 |
) |
|
| 724 | ||
| 725 | ! |
quote( |
| 726 | ! |
scale_color_gradient( |
| 727 | ! |
low = c(getOption("ggplot2.discrete.colour")[2], "darkred")[1],
|
| 728 | ! |
high = c(getOption("ggplot2.discrete.colour"), "lightblue")[1],
|
| 729 | ! |
labels = function(x) as.Date(x, origin = "1970-01-01") |
| 730 |
) |
|
| 731 |
) |
|
| 732 |
} else {
|
|
| 733 | ! |
qenv <- teal.code::eval_code( |
| 734 | ! |
qenv, |
| 735 | ! |
quote(pca_rot$response <- response) |
| 736 |
) |
|
| 737 | ! |
quote(scale_color_gradient( |
| 738 | ! |
low = c(getOption("ggplot2.discrete.colour")[2], "darkred")[1],
|
| 739 | ! |
high = c(getOption("ggplot2.discrete.colour"), "lightblue")[1]
|
| 740 |
)) |
|
| 741 |
} |
|
| 742 | ||
| 743 | ! |
pca_plot_biplot_expr <- c( |
| 744 | ! |
pca_plot_biplot_expr, |
| 745 | ! |
substitute( |
| 746 | ! |
geom_point(aes_biplot, data = pca_rot, alpha = alpha, size = size), |
| 747 | ! |
env = list(aes_biplot = aes_biplot, alpha = alpha, size = size) |
| 748 |
), |
|
| 749 | ! |
scales_biplot |
| 750 |
) |
|
| 751 |
} |
|
| 752 | ||
| 753 | ! |
if (!is.null(input$variables)) {
|
| 754 | ! |
pca_plot_biplot_expr <- c( |
| 755 | ! |
pca_plot_biplot_expr, |
| 756 | ! |
substitute( |
| 757 | ! |
geom_segment( |
| 758 | ! |
aes_string(x = "xstart", y = "ystart", xend = x_axis, yend = y_axis), |
| 759 | ! |
data = rot_vars, |
| 760 | ! |
lineend = "round", linejoin = "round", |
| 761 | ! |
arrow = grid::arrow(length = grid::unit(0.5, "cm")) |
| 762 |
), |
|
| 763 | ! |
env = list(x_axis = x_axis, y_axis = y_axis) |
| 764 |
), |
|
| 765 | ! |
substitute( |
| 766 | ! |
geom_label( |
| 767 | ! |
aes_string( |
| 768 | ! |
x = x_axis, |
| 769 | ! |
y = y_axis, |
| 770 | ! |
label = "label" |
| 771 |
), |
|
| 772 | ! |
data = rot_vars, |
| 773 | ! |
nudge_y = 0.1, |
| 774 | ! |
fontface = "bold" |
| 775 |
), |
|
| 776 | ! |
env = list(x_axis = x_axis, y_axis = y_axis) |
| 777 |
), |
|
| 778 | ! |
quote(geom_point(aes(x = xstart, y = ystart), data = rot_vars, shape = "x", size = 5)) |
| 779 |
) |
|
| 780 |
} |
|
| 781 | ||
| 782 | ! |
angle <- ifelse(isTRUE(rotate_xaxis_labels), 45, 0) |
| 783 | ! |
hjust <- ifelse(isTRUE(rotate_xaxis_labels), 1, 0.5) |
| 784 | ||
| 785 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 786 | ! |
labs = dev_labs, |
| 787 | ! |
theme = list( |
| 788 | ! |
text = substitute(element_text(size = font_size), list(font_size = font_size)), |
| 789 | ! |
axis.text.x = substitute( |
| 790 | ! |
element_text(angle = angle_val, hjust = hjust_val), |
| 791 | ! |
list(angle_val = angle, hjust_val = hjust) |
| 792 |
) |
|
| 793 |
) |
|
| 794 |
) |
|
| 795 | ||
| 796 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 797 | ! |
user_plot = ggplot2_args[["Biplot"]], |
| 798 | ! |
user_default = ggplot2_args$default, |
| 799 | ! |
module_plot = dev_ggplot2_args |
| 800 |
) |
|
| 801 | ||
| 802 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 803 | ! |
all_ggplot2_args, |
| 804 | ! |
ggtheme = ggtheme |
| 805 |
) |
|
| 806 | ||
| 807 | ! |
pca_plot_biplot_expr <- c( |
| 808 | ! |
pca_plot_biplot_expr, |
| 809 | ! |
parsed_ggplot2_args |
| 810 |
) |
|
| 811 | ||
| 812 | ! |
teal.code::eval_code( |
| 813 | ! |
qenv, |
| 814 | ! |
substitute( |
| 815 | ! |
expr = {
|
| 816 | ! |
g <- plot_call |
| 817 | ! |
print(g) |
| 818 |
}, |
|
| 819 | ! |
env = list( |
| 820 | ! |
plot_call = Reduce(function(x, y) call("+", x, y), pca_plot_biplot_expr)
|
| 821 |
) |
|
| 822 |
) |
|
| 823 |
) |
|
| 824 |
} |
|
| 825 | ||
| 826 |
# plot pc_var ---- |
|
| 827 | ! |
plot_pc_var <- function(base_q) {
|
| 828 | ! |
pc <- input$pc # nolint |
| 829 | ! |
ggtheme <- input$ggtheme |
| 830 | ||
| 831 | ! |
rotate_xaxis_labels <- input$rotate_xaxis_labels # nolint |
| 832 | ! |
font_size <- input$font_size # nolint |
| 833 | ||
| 834 | ! |
angle <- ifelse(rotate_xaxis_labels, 45, 0) |
| 835 | ! |
hjust <- ifelse(rotate_xaxis_labels, 1, 0.5) |
| 836 | ||
| 837 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 838 | ! |
theme = list( |
| 839 | ! |
text = substitute(element_text(size = font_size), list(font_size = font_size)), |
| 840 | ! |
axis.text.x = substitute( |
| 841 | ! |
element_text(angle = angle_val, hjust = hjust_val), |
| 842 | ! |
list(angle_val = angle, hjust_val = hjust) |
| 843 |
) |
|
| 844 |
) |
|
| 845 |
) |
|
| 846 | ||
| 847 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 848 | ! |
user_plot = ggplot2_args[["Eigenvector plot"]], |
| 849 | ! |
user_default = ggplot2_args$default, |
| 850 | ! |
module_plot = dev_ggplot2_args |
| 851 |
) |
|
| 852 | ||
| 853 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 854 | ! |
all_ggplot2_args, |
| 855 | ! |
ggtheme = ggtheme |
| 856 |
) |
|
| 857 | ||
| 858 | ! |
ggplot_exprs <- c( |
| 859 | ! |
list( |
| 860 | ! |
quote(ggplot(pca_rot)), |
| 861 | ! |
substitute( |
| 862 | ! |
geom_bar( |
| 863 | ! |
aes_string(x = "Variable", y = pc), |
| 864 | ! |
stat = "identity", |
| 865 | ! |
color = "black", |
| 866 | ! |
fill = c(getOption("ggplot2.discrete.colour"), "lightblue")[1]
|
| 867 |
), |
|
| 868 | ! |
env = list(pc = pc) |
| 869 |
), |
|
| 870 | ! |
substitute( |
| 871 | ! |
geom_text( |
| 872 | ! |
aes( |
| 873 | ! |
x = Variable, |
| 874 | ! |
y = pc_name, |
| 875 | ! |
label = round(pc_name, 3), |
| 876 | ! |
vjust = ifelse(pc_name > 0, -0.5, 1.3) |
| 877 |
) |
|
| 878 |
), |
|
| 879 | ! |
env = list(pc_name = as.name(pc)) |
| 880 |
) |
|
| 881 |
), |
|
| 882 | ! |
parsed_ggplot2_args$labs, |
| 883 | ! |
parsed_ggplot2_args$ggtheme, |
| 884 | ! |
parsed_ggplot2_args$theme |
| 885 |
) |
|
| 886 | ||
| 887 | ! |
teal.code::eval_code( |
| 888 | ! |
base_q, |
| 889 | ! |
substitute( |
| 890 | ! |
expr = {
|
| 891 | ! |
pca_rot <- pca$rotation[, pc, drop = FALSE] %>% |
| 892 | ! |
dplyr::as_tibble(rownames = "Variable") |
| 893 | ||
| 894 | ! |
g <- plot_call |
| 895 | ||
| 896 | ! |
print(g) |
| 897 |
}, |
|
| 898 | ! |
env = list( |
| 899 | ! |
pc = pc, |
| 900 | ! |
plot_call = Reduce(function(x, y) call("+", x, y), ggplot_exprs)
|
| 901 |
) |
|
| 902 |
) |
|
| 903 |
) |
|
| 904 |
} |
|
| 905 | ||
| 906 |
# plot final ---- |
|
| 907 | ! |
output_q <- reactive({
|
| 908 | ! |
req(computation()) |
| 909 | ! |
teal::validate_inputs(iv_r()) |
| 910 | ! |
teal::validate_inputs(iv_extra, header = "Plot settings are required") |
| 911 | ||
| 912 | ! |
switch(input$plot_type, |
| 913 | ! |
"Elbow plot" = plot_elbow(computation()), |
| 914 | ! |
"Circle plot" = plot_circle(computation()), |
| 915 | ! |
"Biplot" = plot_biplot(computation()), |
| 916 | ! |
"Eigenvector plot" = plot_pc_var(computation()), |
| 917 | ! |
stop("Unknown plot")
|
| 918 |
) |
|
| 919 |
}) |
|
| 920 | ||
| 921 | ! |
plot_r <- reactive({
|
| 922 | ! |
output_q()[["g"]] |
| 923 |
}) |
|
| 924 | ||
| 925 | ! |
pws <- teal.widgets::plot_with_settings_srv( |
| 926 | ! |
id = "pca_plot", |
| 927 | ! |
plot_r = plot_r, |
| 928 | ! |
height = plot_height, |
| 929 | ! |
width = plot_width, |
| 930 | ! |
graph_align = "center" |
| 931 |
) |
|
| 932 | ||
| 933 |
# tables ---- |
|
| 934 | ! |
output$tbl_importance <- renderTable( |
| 935 | ! |
expr = {
|
| 936 | ! |
req("importance" %in% input$tables_display, computation())
|
| 937 | ! |
computation()[["tbl_importance"]] |
| 938 |
}, |
|
| 939 | ! |
bordered = TRUE, |
| 940 | ! |
align = "c", |
| 941 | ! |
digits = 3 |
| 942 |
) |
|
| 943 | ||
| 944 | ! |
output$tbl_importance_ui <- renderUI({
|
| 945 | ! |
req("importance" %in% input$tables_display)
|
| 946 | ! |
div( |
| 947 | ! |
align = "center", |
| 948 | ! |
tags$h4("Principal components importance"),
|
| 949 | ! |
tableOutput(session$ns("tbl_importance")),
|
| 950 | ! |
hr() |
| 951 |
) |
|
| 952 |
}) |
|
| 953 | ||
| 954 | ! |
output$tbl_eigenvector <- renderTable( |
| 955 | ! |
expr = {
|
| 956 | ! |
req("eigenvector" %in% input$tables_display, req(computation()))
|
| 957 | ! |
computation()[["tbl_eigenvector"]] |
| 958 |
}, |
|
| 959 | ! |
bordered = TRUE, |
| 960 | ! |
align = "c", |
| 961 | ! |
digits = 3 |
| 962 |
) |
|
| 963 | ||
| 964 | ! |
output$tbl_eigenvector_ui <- renderUI({
|
| 965 | ! |
req("eigenvector" %in% input$tables_display)
|
| 966 | ! |
div( |
| 967 | ! |
align = "center", |
| 968 | ! |
tags$h4("Eigenvectors"),
|
| 969 | ! |
tableOutput(session$ns("tbl_eigenvector")),
|
| 970 | ! |
hr() |
| 971 |
) |
|
| 972 |
}) |
|
| 973 | ||
| 974 | ! |
output$all_plots <- renderUI({
|
| 975 | ! |
teal::validate_inputs(iv_r()) |
| 976 | ! |
teal::validate_inputs(iv_extra, header = "Plot settings are required") |
| 977 | ||
| 978 | ! |
validation() |
| 979 | ! |
tags$div( |
| 980 | ! |
class = "overflow-scroll", |
| 981 | ! |
uiOutput(session$ns("tbl_importance_ui")),
|
| 982 | ! |
uiOutput(session$ns("tbl_eigenvector_ui")),
|
| 983 | ! |
teal.widgets::plot_with_settings_ui(id = session$ns("pca_plot"))
|
| 984 |
) |
|
| 985 |
}) |
|
| 986 | ||
| 987 | ! |
teal.widgets::verbatim_popup_srv( |
| 988 | ! |
id = "warning", |
| 989 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 990 | ! |
title = "Warning", |
| 991 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 992 |
) |
|
| 993 | ||
| 994 | ! |
teal.widgets::verbatim_popup_srv( |
| 995 | ! |
id = "rcode", |
| 996 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 997 | ! |
title = "R Code for PCA" |
| 998 |
) |
|
| 999 | ||
| 1000 |
### REPORTER |
|
| 1001 | ! |
if (with_reporter) {
|
| 1002 | ! |
card_fun <- function(comment) {
|
| 1003 | ! |
card <- teal::TealReportCard$new() |
| 1004 | ! |
card$set_name("PCA Plot")
|
| 1005 | ! |
card$append_text("PCA Plot", "header2")
|
| 1006 | ! |
card$append_text("Principal Component Analysis Plot", "header3")
|
| 1007 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 1008 | ! |
card$append_text("Principal Components Table", "header3")
|
| 1009 | ! |
card$append_table(computation()[["tbl_importance"]]) |
| 1010 | ! |
card$append_text("Eigenvectors Table", "header3")
|
| 1011 | ! |
card$append_table(computation()[["tbl_eigenvector"]]) |
| 1012 | ! |
card$append_text("Plot", "header3")
|
| 1013 | ! |
card$append_plot(plot_r(), dim = pws$dim()) |
| 1014 | ! |
if (!comment == "") {
|
| 1015 | ! |
card$append_text("Comment", "header3")
|
| 1016 | ! |
card$append_text(comment) |
| 1017 |
} |
|
| 1018 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 1019 | ! |
card |
| 1020 |
} |
|
| 1021 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 1022 |
} |
|
| 1023 |
### |
|
| 1024 |
}) |
|
| 1025 |
} |
| 1 |
#' Distribution Module |
|
| 2 |
#' @md |
|
| 3 |
#' |
|
| 4 |
#' @details |
|
| 5 |
#' Module to analyze and explore univariate variable distribution |
|
| 6 |
#' |
|
| 7 |
#' @inheritParams teal::module |
|
| 8 |
#' @inheritParams teal.widgets::standard_layout |
|
| 9 |
#' @inheritParams shared_params |
|
| 10 |
#' |
|
| 11 |
#' @param dist_var (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 12 |
#' Variable to consider for the distribution analysis. |
|
| 13 |
#' @param strata_var (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 14 |
#' Categorical variable to split the selected distribution variable on. |
|
| 15 |
#' @param group_var optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 16 |
#' Which data columns to use for faceting rows. |
|
| 17 |
#' @param freq optional, (`logical`) Whether to display frequency (`TRUE`) or density (`FALSE`). |
|
| 18 |
#' Defaults to density (`FALSE`). |
|
| 19 |
#' @param bins optional, (`integer(1)` or `integer(3)`) If scalar then the histogram bins will have a fixed size. |
|
| 20 |
#' If a slider should be presented to adjust the number of histogram bins dynamically then it can be a |
|
| 21 |
#' vector of length three with `c(value, min, max)`. |
|
| 22 |
#' Defaults to `c(30L, 1L, 100L)`. |
|
| 23 |
#' |
|
| 24 |
#' @templateVar ggnames "Histogram", "QQplot" |
|
| 25 |
#' @template ggplot2_args_multi |
|
| 26 |
#' |
|
| 27 |
#' |
|
| 28 |
#' @export |
|
| 29 |
#' |
|
| 30 |
#' @examples |
|
| 31 |
#' # Example with non-clinical data |
|
| 32 |
#' app <- teal::init( |
|
| 33 |
#' data = teal_data(dataset("iris", iris)),
|
|
| 34 |
#' modules = list( |
|
| 35 |
#' teal.modules.general::tm_g_distribution( |
|
| 36 |
#' dist_var = teal.transform::data_extract_spec( |
|
| 37 |
#' dataname = "iris", |
|
| 38 |
#' select = teal.transform::select_spec(variable_choices("iris"), "Petal.Length")
|
|
| 39 |
#' ), |
|
| 40 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 41 |
#' labs = list(subtitle = "Plot generated by Distribution Module") |
|
| 42 |
#' ) |
|
| 43 |
#' ) |
|
| 44 |
#' ) |
|
| 45 |
#' ) |
|
| 46 |
#' if (interactive()) {
|
|
| 47 |
#' shinyApp(app$ui, app$server) |
|
| 48 |
#' } |
|
| 49 |
#' |
|
| 50 |
#' # Example with clinical data |
|
| 51 |
#' ADSL <- teal.modules.general::rADSL |
|
| 52 |
#' |
|
| 53 |
#' vars1 <- choices_selected(variable_choices(ADSL, c("ARM", "COUNTRY", "SEX")), selected = NULL)
|
|
| 54 |
#' |
|
| 55 |
#' app <- teal::init( |
|
| 56 |
#' data = teal.data::cdisc_data( |
|
| 57 |
#' teal.data::cdisc_dataset("ADSL", ADSL),
|
|
| 58 |
#' code = "ADSL <- teal.modules.general::rADSL", |
|
| 59 |
#' check = FALSE |
|
| 60 |
#' ), |
|
| 61 |
#' modules = teal::modules( |
|
| 62 |
#' teal.modules.general::tm_g_distribution( |
|
| 63 |
#' dist_var = teal.transform::data_extract_spec( |
|
| 64 |
#' dataname = "ADSL", |
|
| 65 |
#' select = teal.transform::select_spec( |
|
| 66 |
#' choices = teal.transform::variable_choices(ADSL, c("AGE", "BMRKR1")),
|
|
| 67 |
#' selected = "BMRKR1", |
|
| 68 |
#' multiple = FALSE, |
|
| 69 |
#' fixed = FALSE |
|
| 70 |
#' ) |
|
| 71 |
#' ), |
|
| 72 |
#' strata_var = teal.transform::data_extract_spec( |
|
| 73 |
#' dataname = "ADSL", |
|
| 74 |
#' filter = teal.transform::filter_spec( |
|
| 75 |
#' vars = vars1, |
|
| 76 |
#' multiple = TRUE |
|
| 77 |
#' ) |
|
| 78 |
#' ), |
|
| 79 |
#' group_var = teal.transform::data_extract_spec( |
|
| 80 |
#' dataname = "ADSL", |
|
| 81 |
#' filter = teal.transform::filter_spec( |
|
| 82 |
#' vars = vars1, |
|
| 83 |
#' multiple = TRUE |
|
| 84 |
#' ) |
|
| 85 |
#' ), |
|
| 86 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 87 |
#' labs = list(subtitle = "Plot generated by Distribution Module") |
|
| 88 |
#' ) |
|
| 89 |
#' ) |
|
| 90 |
#' ) |
|
| 91 |
#' ) |
|
| 92 |
#' if (interactive()) {
|
|
| 93 |
#' shinyApp(app$ui, app$server) |
|
| 94 |
#' } |
|
| 95 |
tm_g_distribution <- function(label = "Distribution Module", |
|
| 96 |
dist_var, |
|
| 97 |
strata_var = NULL, |
|
| 98 |
group_var = NULL, |
|
| 99 |
freq = FALSE, |
|
| 100 |
ggtheme = c( |
|
| 101 |
"gray", "bw", "linedraw", "light", "dark", |
|
| 102 |
"minimal", "classic", "void", "test" |
|
| 103 |
), |
|
| 104 |
ggplot2_args = teal.widgets::ggplot2_args(), |
|
| 105 |
bins = c(30L, 1L, 100L), |
|
| 106 |
plot_height = c(600, 200, 2000), |
|
| 107 |
plot_width = NULL, |
|
| 108 |
pre_output = NULL, |
|
| 109 |
post_output = NULL) {
|
|
| 110 | ! |
logger::log_info("Initializing tm_g_distribution")
|
| 111 | ||
| 112 | ! |
extra_packages <- c("ggpmisc", "ggpp", "goftest", "MASS", "broom")
|
| 113 | ! |
missing_packages <- Filter(function(x) !requireNamespace(x, quietly = TRUE), extra_packages) |
| 114 | ! |
if (length(missing_packages) > 0L) {
|
| 115 | ! |
stop(sprintf( |
| 116 | ! |
"Cannot load package(s): %s.\nInstall or restart your session.", |
| 117 | ! |
toString(missing_packages) |
| 118 |
)) |
|
| 119 |
} |
|
| 120 | ||
| 121 | ! |
if (inherits(dist_var, "data_extract_spec")) dist_var <- list(dist_var) |
| 122 | ! |
if (inherits(strata_var, "data_extract_spec")) strata_var <- list(strata_var) |
| 123 | ! |
if (inherits(group_var, "data_extract_spec")) group_var <- list(group_var) |
| 124 | ! |
if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) |
| 125 | ||
| 126 | ! |
ggtheme <- match.arg(ggtheme) |
| 127 | ! |
if (length(bins) == 1) {
|
| 128 | ! |
checkmate::assert_numeric(bins, any.missing = FALSE, lower = 1) |
| 129 |
} else {
|
|
| 130 | ! |
checkmate::assert_numeric(bins, len = 3, any.missing = FALSE, lower = 1) |
| 131 | ! |
checkmate::assert_numeric(bins[1], lower = bins[2], upper = bins[3], .var.name = "bins") |
| 132 |
} |
|
| 133 | ! |
checkmate::assert_string(label) |
| 134 | ! |
checkmate::assert_list(dist_var, "data_extract_spec") |
| 135 | ! |
checkmate::assert_false(dist_var[[1]]$select$multiple) |
| 136 | ! |
checkmate::assert_list(strata_var, types = "data_extract_spec", null.ok = TRUE) |
| 137 | ! |
checkmate::assert_list(group_var, types = "data_extract_spec", null.ok = TRUE) |
| 138 | ! |
checkmate::assert_flag(freq) |
| 139 | ! |
plot_choices <- c("Histogram", "QQplot")
|
| 140 | ! |
checkmate::assert_list(ggplot2_args, types = "ggplot2_args") |
| 141 | ! |
checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices))
|
| 142 | ||
| 143 | ! |
args <- as.list(environment()) |
| 144 | ||
| 145 | ! |
data_extract_list <- list( |
| 146 | ! |
dist_var = dist_var, |
| 147 | ! |
strata_var = strata_var, |
| 148 | ! |
group_var = group_var |
| 149 |
) |
|
| 150 | ||
| 151 | ! |
module( |
| 152 | ! |
label = label, |
| 153 | ! |
server = srv_distribution, |
| 154 | ! |
server_args = c( |
| 155 | ! |
data_extract_list, |
| 156 | ! |
list(plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args) |
| 157 |
), |
|
| 158 | ! |
ui = ui_distribution, |
| 159 | ! |
ui_args = args, |
| 160 | ! |
datanames = teal.transform::get_extract_datanames(data_extract_list) |
| 161 |
) |
|
| 162 |
} |
|
| 163 | ||
| 164 |
ui_distribution <- function(id, ...) {
|
|
| 165 | ! |
args <- list(...) |
| 166 | ! |
ns <- NS(id) |
| 167 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset(args$dist_var, args$strata_var, args$group_var) |
| 168 | ||
| 169 | ! |
teal.widgets::standard_layout( |
| 170 | ! |
output = tagList( |
| 171 | ! |
tabsetPanel( |
| 172 | ! |
id = ns("tabs"),
|
| 173 | ! |
tabPanel("Histogram", teal.widgets::plot_with_settings_ui(id = ns("hist_plot"))),
|
| 174 | ! |
tabPanel("QQplot", teal.widgets::plot_with_settings_ui(id = ns("qq_plot")))
|
| 175 |
), |
|
| 176 | ! |
h3("Statistics Table"),
|
| 177 | ! |
DT::dataTableOutput(ns("summary_table")),
|
| 178 | ! |
h3("Tests"),
|
| 179 | ! |
DT::dataTableOutput(ns("t_stats"))
|
| 180 |
), |
|
| 181 | ! |
encoding = div( |
| 182 |
### Reporter |
|
| 183 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 184 |
### |
|
| 185 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 186 | ! |
teal.transform::datanames_input(args[c("dist_var", "strata_var")]),
|
| 187 | ! |
teal.transform::data_extract_ui( |
| 188 | ! |
id = ns("dist_i"),
|
| 189 | ! |
label = "Variable", |
| 190 | ! |
data_extract_spec = args$dist_var, |
| 191 | ! |
is_single_dataset = is_single_dataset_value |
| 192 |
), |
|
| 193 | ! |
if (!is.null(args$group_var)) {
|
| 194 | ! |
tagList( |
| 195 | ! |
teal.transform::data_extract_ui( |
| 196 | ! |
id = ns("group_i"),
|
| 197 | ! |
label = "Group by", |
| 198 | ! |
data_extract_spec = args$group_var, |
| 199 | ! |
is_single_dataset = is_single_dataset_value |
| 200 |
), |
|
| 201 | ! |
uiOutput(ns("scales_types_ui"))
|
| 202 |
) |
|
| 203 |
}, |
|
| 204 | ! |
if (!is.null(args$strata_var)) {
|
| 205 | ! |
teal.transform::data_extract_ui( |
| 206 | ! |
id = ns("strata_i"),
|
| 207 | ! |
label = "Stratify by", |
| 208 | ! |
data_extract_spec = args$strata_var, |
| 209 | ! |
is_single_dataset = is_single_dataset_value |
| 210 |
) |
|
| 211 |
}, |
|
| 212 | ! |
teal.widgets::panel_group( |
| 213 | ! |
conditionalPanel( |
| 214 | ! |
condition = paste0("input['", ns("tabs"), "'] == 'Histogram'"),
|
| 215 | ! |
teal.widgets::panel_item( |
| 216 | ! |
"Histogram", |
| 217 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("bins"), "Bins", args$bins, ticks = FALSE, step = 1),
|
| 218 | ! |
shinyWidgets::prettyRadioButtons( |
| 219 | ! |
ns("main_type"),
|
| 220 | ! |
label = "Plot Type:", |
| 221 | ! |
choices = c("Density", "Frequency"),
|
| 222 | ! |
selected = if (!args$freq) "Density" else "Frequency", |
| 223 | ! |
bigger = FALSE, |
| 224 | ! |
inline = TRUE |
| 225 |
), |
|
| 226 | ! |
checkboxInput(ns("add_dens"), label = "Overlay Density", value = TRUE),
|
| 227 | ! |
collapsed = FALSE |
| 228 |
) |
|
| 229 |
), |
|
| 230 | ! |
conditionalPanel( |
| 231 | ! |
condition = paste0("input['", ns("tabs"), "'] == 'QQplot'"),
|
| 232 | ! |
teal.widgets::panel_item( |
| 233 | ! |
"QQ Plot", |
| 234 | ! |
checkboxInput(ns("qq_line"), label = "Add diagonal line(s)", TRUE),
|
| 235 | ! |
collapsed = FALSE |
| 236 |
) |
|
| 237 |
), |
|
| 238 | ! |
conditionalPanel( |
| 239 | ! |
condition = paste0("input['", ns("main_type"), "'] == 'Density'"),
|
| 240 | ! |
teal.widgets::panel_item( |
| 241 | ! |
"Theoretical Distribution", |
| 242 | ! |
teal.widgets::optionalSelectInput( |
| 243 | ! |
ns("t_dist"),
|
| 244 | ! |
div( |
| 245 | ! |
class = "teal-tooltip", |
| 246 | ! |
tagList( |
| 247 | ! |
"Distribution:", |
| 248 | ! |
icon("circle-info"),
|
| 249 | ! |
span( |
| 250 | ! |
class = "tooltiptext", |
| 251 | ! |
"Default parameters are optimized with MASS::fitdistr function." |
| 252 |
) |
|
| 253 |
) |
|
| 254 |
), |
|
| 255 | ! |
choices = c("normal", "lognormal", "gamma", "unif"),
|
| 256 | ! |
selected = NULL, |
| 257 | ! |
multiple = FALSE |
| 258 |
), |
|
| 259 | ! |
numericInput(ns("dist_param1"), label = "param1", value = NULL),
|
| 260 | ! |
numericInput(ns("dist_param2"), label = "param2", value = NULL),
|
| 261 | ! |
span(actionButton(ns("params_reset"), "Reset params")),
|
| 262 | ! |
collapsed = FALSE |
| 263 |
) |
|
| 264 |
) |
|
| 265 |
), |
|
| 266 | ! |
teal.widgets::panel_item( |
| 267 | ! |
"Tests", |
| 268 | ! |
teal.widgets::optionalSelectInput( |
| 269 | ! |
ns("dist_tests"),
|
| 270 | ! |
"Tests:", |
| 271 | ! |
choices = c( |
| 272 | ! |
"Shapiro-Wilk", |
| 273 | ! |
if (!is.null(args$strata_var)) "t-test (two-samples, not paired)", |
| 274 | ! |
if (!is.null(args$strata_var)) "one-way ANOVA", |
| 275 | ! |
if (!is.null(args$strata_var)) "Fligner-Killeen", |
| 276 | ! |
if (!is.null(args$strata_var)) "F-test", |
| 277 | ! |
"Kolmogorov-Smirnov (one-sample)", |
| 278 | ! |
"Anderson-Darling (one-sample)", |
| 279 | ! |
"Cramer-von Mises (one-sample)", |
| 280 | ! |
if (!is.null(args$strata_var)) "Kolmogorov-Smirnov (two-samples)" |
| 281 |
), |
|
| 282 | ! |
selected = NULL |
| 283 |
) |
|
| 284 |
), |
|
| 285 | ! |
teal.widgets::panel_item( |
| 286 | ! |
"Statistics Table", |
| 287 | ! |
sliderInput(ns("roundn"), "Round to n digits", min = 0, max = 10, value = 2)
|
| 288 |
), |
|
| 289 | ! |
teal.widgets::panel_item( |
| 290 | ! |
title = "Plot settings", |
| 291 | ! |
selectInput( |
| 292 | ! |
inputId = ns("ggtheme"),
|
| 293 | ! |
label = "Theme (by ggplot):", |
| 294 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 295 | ! |
selected = args$ggtheme, |
| 296 | ! |
multiple = FALSE |
| 297 |
) |
|
| 298 |
) |
|
| 299 |
), |
|
| 300 | ! |
forms = tagList( |
| 301 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"),
|
| 302 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 303 |
), |
|
| 304 | ! |
pre_output = args$pre_output, |
| 305 | ! |
post_output = args$post_output |
| 306 |
) |
|
| 307 |
} |
|
| 308 | ||
| 309 |
srv_distribution <- function(id, |
|
| 310 |
data, |
|
| 311 |
reporter, |
|
| 312 |
filter_panel_api, |
|
| 313 |
dist_var, |
|
| 314 |
strata_var, |
|
| 315 |
group_var, |
|
| 316 |
plot_height, |
|
| 317 |
plot_width, |
|
| 318 |
ggplot2_args) {
|
|
| 319 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 320 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 321 | ! |
checkmate::assert_class(data, "tdata") |
| 322 | ! |
moduleServer(id, function(input, output, session) {
|
| 323 | ! |
rule_req <- function(value) {
|
| 324 | ! |
if (isTRUE(input$dist_tests %in% c( |
| 325 | ! |
"Fligner-Killeen", |
| 326 | ! |
"t-test (two-samples, not paired)", |
| 327 | ! |
"F-test", |
| 328 | ! |
"Kolmogorov-Smirnov (two-samples)", |
| 329 | ! |
"one-way ANOVA" |
| 330 |
))) {
|
|
| 331 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 332 | ! |
"Please select stratify variable." |
| 333 |
} |
|
| 334 |
} |
|
| 335 |
} |
|
| 336 | ! |
rule_dupl <- function(...) {
|
| 337 | ! |
if (identical(input$dist_tests, "Fligner-Killeen")) {
|
| 338 | ! |
strata <- selector_list()$strata_i()$select |
| 339 | ! |
group <- selector_list()$group_i()$select |
| 340 | ! |
if (isTRUE(strata == group)) {
|
| 341 | ! |
"Please select different variables for strata and group." |
| 342 |
} |
|
| 343 |
} |
|
| 344 |
} |
|
| 345 | ||
| 346 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 347 | ! |
data_extract = list( |
| 348 | ! |
dist_i = dist_var, |
| 349 | ! |
strata_i = strata_var, |
| 350 | ! |
group_i = group_var |
| 351 |
), |
|
| 352 | ! |
data, |
| 353 | ! |
select_validation_rule = list( |
| 354 | ! |
dist_i = shinyvalidate::sv_required("Please select a variable")
|
| 355 |
), |
|
| 356 | ! |
filter_validation_rule = list( |
| 357 | ! |
strata_i = shinyvalidate::compose_rules( |
| 358 | ! |
rule_req, |
| 359 | ! |
rule_dupl |
| 360 |
), |
|
| 361 | ! |
group_i = rule_dupl |
| 362 |
) |
|
| 363 |
) |
|
| 364 | ||
| 365 | ! |
iv_r <- reactive({
|
| 366 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 367 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list, validator_names = "dist_i") |
| 368 |
}) |
|
| 369 | ||
| 370 | ! |
iv_r_dist <- reactive({
|
| 371 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 372 | ! |
teal.transform::compose_and_enable_validators( |
| 373 | ! |
iv, selector_list, |
| 374 | ! |
validator_names = c("strata_i", "group_i")
|
| 375 |
) |
|
| 376 |
}) |
|
| 377 | ! |
rule_dist_1 <- function(value) {
|
| 378 | ! |
if (!is.null(input$t_dist)) {
|
| 379 | ! |
switch(input$t_dist, |
| 380 | ! |
"normal" = if (!shinyvalidate::input_provided(value)) "mean is required", |
| 381 | ! |
"lognormal" = if (!shinyvalidate::input_provided(value)) "meanlog is required", |
| 382 | ! |
"gamma" = {
|
| 383 | ! |
if (!shinyvalidate::input_provided(value)) "shape is required" else if (value <= 0) "shape must be positive" |
| 384 |
}, |
|
| 385 | ! |
"unif" = NULL |
| 386 |
) |
|
| 387 |
} |
|
| 388 |
} |
|
| 389 | ! |
rule_dist_2 <- function(value) {
|
| 390 | ! |
if (!is.null(input$t_dist)) {
|
| 391 | ! |
switch(input$t_dist, |
| 392 | ! |
"normal" = {
|
| 393 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 394 | ! |
"sd is required" |
| 395 | ! |
} else if (value < 0) {
|
| 396 | ! |
"sd must be non-negative" |
| 397 |
} |
|
| 398 |
}, |
|
| 399 | ! |
"lognormal" = {
|
| 400 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 401 | ! |
"sdlog is required" |
| 402 | ! |
} else if (value < 0) {
|
| 403 | ! |
"sdlog must be non-negative" |
| 404 |
} |
|
| 405 |
}, |
|
| 406 | ! |
"gamma" = {
|
| 407 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 408 | ! |
"rate is required" |
| 409 | ! |
} else if (value <= 0) {
|
| 410 | ! |
"rate must be positive" |
| 411 |
} |
|
| 412 |
}, |
|
| 413 | ! |
"unif" = NULL |
| 414 |
) |
|
| 415 |
} |
|
| 416 |
} |
|
| 417 | ! |
rule_dist <- function(value) {
|
| 418 | ! |
if (isTRUE(input$tabs == "QQplot" || |
| 419 | ! |
input$dist_tests %in% c( |
| 420 | ! |
"Kolmogorov-Smirnov (one-sample)", |
| 421 | ! |
"Anderson-Darling (one-sample)", |
| 422 | ! |
"Cramer-von Mises (one-sample)" |
| 423 |
))) {
|
|
| 424 | ! |
if (!shinyvalidate::input_provided(value)) {
|
| 425 | ! |
"Please select the theoretical distribution." |
| 426 |
} |
|
| 427 |
} |
|
| 428 |
} |
|
| 429 | ! |
iv_dist <- shinyvalidate::InputValidator$new() |
| 430 | ! |
iv_dist$add_rule("t_dist", rule_dist)
|
| 431 | ! |
iv_dist$add_rule("dist_param1", rule_dist_1)
|
| 432 | ! |
iv_dist$add_rule("dist_param2", rule_dist_2)
|
| 433 | ! |
iv_dist$enable() |
| 434 | ||
| 435 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 436 | ! |
selector_list = selector_list, |
| 437 | ! |
datasets = data, |
| 438 | ! |
join_keys = get_join_keys(data) |
| 439 |
) |
|
| 440 | ||
| 441 | ! |
anl_merged_q <- reactive({
|
| 442 | ! |
req(anl_merged_input()) |
| 443 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 444 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 445 |
}) |
|
| 446 | ||
| 447 | ! |
merged <- list( |
| 448 | ! |
anl_input_r = anl_merged_input, |
| 449 | ! |
anl_q_r = anl_merged_q |
| 450 |
) |
|
| 451 | ||
| 452 | ! |
output$scales_types_ui <- renderUI({
|
| 453 | ! |
if ("group_i" %in% names(selector_list()) && length(selector_list()$group_i()$filters[[1]]$selected) > 0) {
|
| 454 | ! |
shinyWidgets::prettyRadioButtons( |
| 455 | ! |
session$ns("scales_type"),
|
| 456 | ! |
label = "Scales:", |
| 457 | ! |
choices = c("Fixed", "Free"),
|
| 458 | ! |
selected = "Fixed", |
| 459 | ! |
bigger = FALSE, |
| 460 | ! |
inline = TRUE |
| 461 |
) |
|
| 462 |
} |
|
| 463 |
}) |
|
| 464 | ||
| 465 | ! |
observeEvent( |
| 466 | ! |
eventExpr = list( |
| 467 | ! |
input$t_dist, |
| 468 | ! |
input$params_reset, |
| 469 | ! |
selector_list()$dist_i()$select |
| 470 |
), |
|
| 471 | ! |
handlerExpr = {
|
| 472 | ! |
if (length(input$t_dist) != 0) {
|
| 473 | ! |
dist_var2 <- as.vector(merged$anl_input_r()$columns_source$dist_i) |
| 474 | ||
| 475 | ! |
get_dist_params <- function(x, dist) {
|
| 476 | ! |
if (dist == "unif") {
|
| 477 | ! |
res <- as.list(range(x)) |
| 478 | ! |
names(res) <- c("min", "max")
|
| 479 | ! |
return(res) |
| 480 |
} |
|
| 481 | ! |
tryCatch( |
| 482 | ! |
as.list(MASS::fitdistr(x, densfun = dist)$estimate), |
| 483 | ! |
error = function(e) list(param1 = NA, param2 = NA) |
| 484 |
) |
|
| 485 |
} |
|
| 486 | ||
| 487 | ! |
ANL <- merged$anl_q_r()[[as.character(dist_var[[1]]$dataname)]] # nolint |
| 488 | ! |
params <- get_dist_params(as.numeric(stats::na.omit(ANL[[dist_var2]])), input$t_dist) |
| 489 | ! |
params_vec <- round(unname(unlist(params)), 2) |
| 490 | ! |
params_names <- names(params) |
| 491 | ||
| 492 | ! |
updateNumericInput(session, "dist_param1", label = params_names[1], value = params_vec[1]) |
| 493 | ! |
updateNumericInput(session, "dist_param2", label = params_names[2], value = params_vec[2]) |
| 494 |
} else {
|
|
| 495 | ! |
updateNumericInput(session, "dist_param1", label = "param1", value = NA) |
| 496 | ! |
updateNumericInput(session, "dist_param2", label = "param2", value = NA) |
| 497 |
} |
|
| 498 |
}, |
|
| 499 | ! |
ignoreInit = TRUE |
| 500 |
) |
|
| 501 | ||
| 502 | ! |
merge_vars <- reactive({
|
| 503 | ! |
teal::validate_inputs(iv_r()) |
| 504 | ||
| 505 | ! |
dist_var <- as.vector(merged$anl_input_r()$columns_source$dist_i) |
| 506 | ! |
s_var <- as.vector(merged$anl_input_r()$columns_source$strata_i) |
| 507 | ! |
g_var <- as.vector(merged$anl_input_r()$columns_source$group_i) |
| 508 | ||
| 509 | ! |
dist_var_name <- if (length(dist_var)) as.name(dist_var) else NULL |
| 510 | ! |
s_var_name <- if (length(s_var)) as.name(s_var) else NULL |
| 511 | ! |
g_var_name <- if (length(g_var)) as.name(g_var) else NULL |
| 512 | ||
| 513 | ! |
list( |
| 514 | ! |
dist_var = dist_var, |
| 515 | ! |
s_var = s_var, |
| 516 | ! |
g_var = g_var, |
| 517 | ! |
dist_var_name = dist_var_name, |
| 518 | ! |
s_var_name = s_var_name, |
| 519 | ! |
g_var_name = g_var_name |
| 520 |
) |
|
| 521 |
}) |
|
| 522 | ||
| 523 |
# common qenv |
|
| 524 | ! |
common_q <- reactive({
|
| 525 |
# Create a private stack for this function only. |
|
| 526 | ||
| 527 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 528 | ! |
dist_var <- merge_vars()$dist_var |
| 529 | ! |
s_var <- merge_vars()$s_var |
| 530 | ! |
g_var <- merge_vars()$g_var |
| 531 | ||
| 532 | ! |
dist_var_name <- merge_vars()$dist_var_name |
| 533 | ! |
s_var_name <- merge_vars()$s_var_name |
| 534 | ! |
g_var_name <- merge_vars()$g_var_name |
| 535 | ||
| 536 | ! |
roundn <- input$roundn |
| 537 | ! |
dist_param1 <- input$dist_param1 |
| 538 | ! |
dist_param2 <- input$dist_param2 |
| 539 |
# isolated as dist_param1/dist_param2 already triggered the reactivity |
|
| 540 | ! |
t_dist <- isolate(input$t_dist) |
| 541 | ||
| 542 | ! |
qenv <- merged$anl_q_r() |
| 543 | ||
| 544 | ! |
if (length(g_var) > 0) {
|
| 545 | ! |
validate( |
| 546 | ! |
need( |
| 547 | ! |
inherits(ANL[[g_var]], c("integer", "factor", "character")),
|
| 548 | ! |
"Group by variable must be `factor`, `character`, or `integer`" |
| 549 |
) |
|
| 550 |
) |
|
| 551 | ! |
qenv <- teal.code::eval_code( |
| 552 | ! |
qenv, |
| 553 | ! |
substitute( |
| 554 | ! |
expr = ANL[[g_var]] <- forcats::fct_na_value_to_level(as.factor(ANL[[g_var]]), "NA"), # nolint |
| 555 | ! |
env = list(g_var = g_var) |
| 556 |
) |
|
| 557 |
) |
|
| 558 |
} |
|
| 559 | ||
| 560 | ! |
if (length(s_var) > 0) {
|
| 561 | ! |
validate( |
| 562 | ! |
need( |
| 563 | ! |
inherits(ANL[[s_var]], c("integer", "factor", "character")),
|
| 564 | ! |
"Stratify by variable must be `factor`, `character`, or `integer`" |
| 565 |
) |
|
| 566 |
) |
|
| 567 | ! |
qenv <- teal.code::eval_code( |
| 568 | ! |
qenv, |
| 569 | ! |
substitute( |
| 570 | ! |
expr = ANL[[s_var]] <- forcats::fct_na_value_to_level(as.factor(ANL[[s_var]]), "NA"), # nolint |
| 571 | ! |
env = list(s_var = s_var) |
| 572 |
) |
|
| 573 |
) |
|
| 574 |
} |
|
| 575 | ||
| 576 | ! |
validate(need(is.numeric(ANL[[dist_var]]), "Please select a numeric variable.")) |
| 577 | ! |
teal::validate_has_data(ANL, 1, complete = TRUE) |
| 578 | ||
| 579 | ! |
if (length(t_dist) != 0) {
|
| 580 | ! |
map_distr_nams <- list( |
| 581 | ! |
normal = c("mean", "sd"),
|
| 582 | ! |
lognormal = c("meanlog", "sdlog"),
|
| 583 | ! |
gamma = c("shape", "rate"),
|
| 584 | ! |
unif = c("min", "max")
|
| 585 |
) |
|
| 586 | ! |
params_names_raw <- map_distr_nams[[t_dist]] |
| 587 | ||
| 588 | ! |
qenv <- teal.code::eval_code( |
| 589 | ! |
qenv, |
| 590 | ! |
substitute( |
| 591 | ! |
expr = {
|
| 592 | ! |
params <- as.list(c(dist_param1, dist_param2)) |
| 593 | ! |
names(params) <- params_names_raw |
| 594 |
}, |
|
| 595 | ! |
env = list( |
| 596 | ! |
dist_param1 = dist_param1, |
| 597 | ! |
dist_param2 = dist_param2, |
| 598 | ! |
params_names_raw = params_names_raw |
| 599 |
) |
|
| 600 |
) |
|
| 601 |
) |
|
| 602 |
} |
|
| 603 | ||
| 604 | ! |
if (length(s_var) == 0 && length(g_var) == 0) {
|
| 605 | ! |
qenv <- teal.code::eval_code( |
| 606 | ! |
qenv, |
| 607 | ! |
substitute( |
| 608 | ! |
expr = {
|
| 609 | ! |
summary_table <- ANL %>% |
| 610 | ! |
dplyr::summarise( |
| 611 | ! |
min = round(min(dist_var_name, na.rm = TRUE), roundn), |
| 612 | ! |
median = round(stats::median(dist_var_name, na.rm = TRUE), roundn), |
| 613 | ! |
mean = round(mean(dist_var_name, na.rm = TRUE), roundn), |
| 614 | ! |
max = round(max(dist_var_name, na.rm = TRUE), roundn), |
| 615 | ! |
sd = round(stats::sd(dist_var_name, na.rm = TRUE), roundn), |
| 616 | ! |
count = dplyr::n() |
| 617 |
) |
|
| 618 |
}, |
|
| 619 | ! |
env = list( |
| 620 | ! |
dist_var_name = as.name(dist_var), |
| 621 | ! |
roundn = roundn |
| 622 |
) |
|
| 623 |
) |
|
| 624 |
) |
|
| 625 |
} else {
|
|
| 626 | ! |
qenv <- teal.code::eval_code( |
| 627 | ! |
qenv, |
| 628 | ! |
substitute( |
| 629 | ! |
expr = {
|
| 630 | ! |
strata_vars <- strata_vars_raw |
| 631 | ! |
summary_table <- ANL %>% |
| 632 | ! |
dplyr::group_by_at(dplyr::vars(dplyr::any_of(strata_vars))) %>% |
| 633 | ! |
dplyr::summarise( |
| 634 | ! |
min = round(min(dist_var_name, na.rm = TRUE), roundn), |
| 635 | ! |
median = round(stats::median(dist_var_name, na.rm = TRUE), roundn), |
| 636 | ! |
mean = round(mean(dist_var_name, na.rm = TRUE), roundn), |
| 637 | ! |
max = round(max(dist_var_name, na.rm = TRUE), roundn), |
| 638 | ! |
sd = round(stats::sd(dist_var_name, na.rm = TRUE), roundn), |
| 639 | ! |
count = dplyr::n() |
| 640 |
) |
|
| 641 |
}, |
|
| 642 | ! |
env = list( |
| 643 | ! |
dist_var_name = dist_var_name, |
| 644 | ! |
strata_vars_raw = c(g_var, s_var), |
| 645 | ! |
roundn = roundn |
| 646 |
) |
|
| 647 |
) |
|
| 648 |
) |
|
| 649 |
} |
|
| 650 | ||
| 651 | ! |
qenv |
| 652 |
}) |
|
| 653 | ||
| 654 |
# distplot qenv ---- |
|
| 655 | ! |
dist_q <- eventReactive( |
| 656 | ! |
eventExpr = {
|
| 657 | ! |
common_q() |
| 658 | ! |
input$scales_type |
| 659 | ! |
input$main_type |
| 660 | ! |
input$bins |
| 661 | ! |
input$add_dens |
| 662 | ! |
is.null(input$ggtheme) |
| 663 |
}, |
|
| 664 | ! |
valueExpr = {
|
| 665 | ! |
dist_var <- merge_vars()$dist_var |
| 666 | ! |
s_var <- merge_vars()$s_var |
| 667 | ! |
g_var <- merge_vars()$g_var |
| 668 | ! |
dist_var_name <- merge_vars()$dist_var_name |
| 669 | ! |
s_var_name <- merge_vars()$s_var_name |
| 670 | ! |
g_var_name <- merge_vars()$g_var_name |
| 671 | ! |
t_dist <- input$t_dist |
| 672 | ! |
dist_param1 <- input$dist_param1 |
| 673 | ! |
dist_param2 <- input$dist_param2 |
| 674 | ||
| 675 | ! |
scales_type <- input$scales_type |
| 676 | ||
| 677 | ! |
ndensity <- 512 |
| 678 | ! |
main_type_var <- input$main_type |
| 679 | ! |
bins_var <- input$bins |
| 680 | ! |
add_dens_var <- input$add_dens |
| 681 | ! |
ggtheme <- input$ggtheme |
| 682 | ||
| 683 | ! |
teal::validate_inputs(iv_dist) |
| 684 | ||
| 685 | ! |
qenv <- common_q() |
| 686 | ||
| 687 | ! |
m_type <- if (main_type_var == "Density") "density" else "count" |
| 688 | ||
| 689 | ! |
plot_call <- if (length(s_var) == 0 && length(g_var) == 0) {
|
| 690 | ! |
substitute( |
| 691 | ! |
expr = ggplot(ANL, aes(dist_var_name)) + |
| 692 | ! |
geom_histogram( |
| 693 | ! |
position = "identity", aes(y = after_stat(m_type)), bins = bins_var, alpha = 0.3 |
| 694 |
), |
|
| 695 | ! |
env = list( |
| 696 | ! |
m_type = as.name(m_type), bins_var = bins_var, dist_var_name = as.name(dist_var) |
| 697 |
) |
|
| 698 |
) |
|
| 699 | ! |
} else if (length(s_var) != 0 && length(g_var) == 0) {
|
| 700 | ! |
substitute( |
| 701 | ! |
expr = ggplot(ANL, aes(dist_var_name, col = s_var_name)) + |
| 702 | ! |
geom_histogram( |
| 703 | ! |
position = "identity", aes(y = after_stat(m_type), fill = s_var), bins = bins_var, alpha = 0.3 |
| 704 |
), |
|
| 705 | ! |
env = list( |
| 706 | ! |
m_type = as.name(m_type), |
| 707 | ! |
bins_var = bins_var, |
| 708 | ! |
dist_var_name = dist_var_name, |
| 709 | ! |
s_var = as.name(s_var), |
| 710 | ! |
s_var_name = s_var_name |
| 711 |
) |
|
| 712 |
) |
|
| 713 | ! |
} else if (length(s_var) == 0 && length(g_var) != 0) {
|
| 714 | ! |
req(scales_type) |
| 715 | ! |
substitute( |
| 716 | ! |
expr = ggplot(ANL[ANL[[g_var]] != "NA", ], aes(dist_var_name)) + |
| 717 | ! |
geom_histogram( |
| 718 | ! |
position = "identity", aes(y = after_stat(m_type)), bins = bins_var, alpha = 0.3 |
| 719 |
) + |
|
| 720 | ! |
facet_wrap(~g_var_name, ncol = 1, scales = scales_raw), |
| 721 | ! |
env = list( |
| 722 | ! |
m_type = as.name(m_type), |
| 723 | ! |
bins_var = bins_var, |
| 724 | ! |
dist_var_name = dist_var_name, |
| 725 | ! |
g_var = g_var, |
| 726 | ! |
g_var_name = g_var_name, |
| 727 | ! |
scales_raw = tolower(scales_type) |
| 728 |
) |
|
| 729 |
) |
|
| 730 |
} else {
|
|
| 731 | ! |
req(scales_type) |
| 732 | ! |
substitute( |
| 733 | ! |
expr = ggplot(ANL[ANL[[g_var]] != "NA", ], aes(dist_var_name, col = s_var_name)) + |
| 734 | ! |
geom_histogram( |
| 735 | ! |
position = "identity", |
| 736 | ! |
aes(y = after_stat(m_type), fill = s_var), bins = bins_var, alpha = 0.3 |
| 737 |
) + |
|
| 738 | ! |
facet_wrap(~g_var_name, ncol = 1, scales = scales_raw), |
| 739 | ! |
env = list( |
| 740 | ! |
m_type = as.name(m_type), |
| 741 | ! |
bins_var = bins_var, |
| 742 | ! |
dist_var_name = dist_var_name, |
| 743 | ! |
g_var = g_var, |
| 744 | ! |
s_var = as.name(s_var), |
| 745 | ! |
g_var_name = g_var_name, |
| 746 | ! |
s_var_name = s_var_name, |
| 747 | ! |
scales_raw = tolower(scales_type) |
| 748 |
) |
|
| 749 |
) |
|
| 750 |
} |
|
| 751 | ||
| 752 | ! |
if (add_dens_var) {
|
| 753 | ! |
plot_call <- substitute( |
| 754 | ! |
expr = plot_call + |
| 755 | ! |
stat_density( |
| 756 | ! |
aes(y = after_stat(const * m_type2)), |
| 757 | ! |
geom = "line", |
| 758 | ! |
position = "identity", |
| 759 | ! |
alpha = 0.5, |
| 760 | ! |
size = 2, |
| 761 | ! |
n = ndensity |
| 762 |
), |
|
| 763 | ! |
env = list( |
| 764 | ! |
plot_call = plot_call, |
| 765 | ! |
const = if (main_type_var == "Density") {
|
| 766 | ! |
1 |
| 767 |
} else {
|
|
| 768 | ! |
diff(range(qenv[["ANL"]][[dist_var]], na.rm = TRUE)) / bins_var |
| 769 |
}, |
|
| 770 | ! |
m_type2 = if (main_type_var == "Density") as.name("density") else as.name("count"),
|
| 771 | ! |
ndensity = ndensity |
| 772 |
) |
|
| 773 |
) |
|
| 774 |
} |
|
| 775 | ||
| 776 | ! |
if (length(t_dist) != 0 && main_type_var == "Density" && length(g_var) == 0 && length(s_var) == 0) {
|
| 777 | ! |
qenv <- teal.code::eval_code( |
| 778 | ! |
qenv, |
| 779 | ! |
substitute( |
| 780 | ! |
df_params <- as.data.frame(append(params, list(name = t_dist))), |
| 781 | ! |
env = list(t_dist = t_dist) |
| 782 |
) |
|
| 783 |
) |
|
| 784 | ! |
datas <- quote(data.frame(x = 0.7, y = 1, tb = I(list(df_params = df_params)))) |
| 785 | ! |
label <- quote(tb) |
| 786 | ||
| 787 | ! |
plot_call <- substitute( |
| 788 | ! |
expr = plot_call + ggpp::geom_table_npc( |
| 789 | ! |
data = data, |
| 790 | ! |
aes(npcx = x, npcy = y, label = label), |
| 791 | ! |
hjust = 0, vjust = 1, size = 4 |
| 792 |
), |
|
| 793 | ! |
env = list(plot_call = plot_call, data = datas, label = label) |
| 794 |
) |
|
| 795 |
} |
|
| 796 | ||
| 797 | ! |
if (length(s_var) == 0 && length(g_var) == 0 && main_type_var == "Density" && |
| 798 | ! |
length(t_dist) != 0 && main_type_var == "Density") {
|
| 799 | ! |
map_dist <- stats::setNames( |
| 800 | ! |
c("dnorm", "dlnorm", "dgamma", "dunif"),
|
| 801 | ! |
c("normal", "lognormal", "gamma", "unif")
|
| 802 |
) |
|
| 803 | ! |
plot_call <- substitute( |
| 804 | ! |
expr = plot_call + stat_function( |
| 805 | ! |
data = data.frame(x = range(ANL[[dist_var]]), color = mapped_dist), |
| 806 | ! |
aes(x, color = color), |
| 807 | ! |
fun = mapped_dist_name, |
| 808 | ! |
n = ndensity, |
| 809 | ! |
size = 2, |
| 810 | ! |
args = params |
| 811 |
) + |
|
| 812 | ! |
scale_color_manual(values = stats::setNames("blue", mapped_dist), aesthetics = "color"),
|
| 813 | ! |
env = list( |
| 814 | ! |
plot_call = plot_call, |
| 815 | ! |
dist_var = dist_var, |
| 816 | ! |
ndensity = ndensity, |
| 817 | ! |
mapped_dist = unname(map_dist[t_dist]), |
| 818 | ! |
mapped_dist_name = as.name(unname(map_dist[t_dist])) |
| 819 |
) |
|
| 820 |
) |
|
| 821 |
} |
|
| 822 | ||
| 823 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 824 | ! |
user_plot = ggplot2_args[["Histogram"]], |
| 825 | ! |
user_default = ggplot2_args$default |
| 826 |
) |
|
| 827 | ||
| 828 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 829 | ! |
all_ggplot2_args, |
| 830 | ! |
ggtheme = ggtheme |
| 831 |
) |
|
| 832 | ||
| 833 | ! |
teal.code::eval_code( |
| 834 | ! |
qenv, |
| 835 | ! |
substitute( |
| 836 | ! |
expr = {
|
| 837 | ! |
g <- plot_call |
| 838 | ! |
print(g) |
| 839 |
}, |
|
| 840 | ! |
env = list(plot_call = Reduce(function(x, y) call("+", x, y), c(plot_call, parsed_ggplot2_args)))
|
| 841 |
) |
|
| 842 |
) |
|
| 843 |
} |
|
| 844 |
) |
|
| 845 | ||
| 846 |
# qqplot qenv ---- |
|
| 847 | ! |
qq_q <- eventReactive( |
| 848 | ! |
eventExpr = {
|
| 849 | ! |
common_q() |
| 850 | ! |
input$scales_type |
| 851 | ! |
input$qq_line |
| 852 | ! |
is.null(input$ggtheme) |
| 853 |
}, |
|
| 854 | ! |
valueExpr = {
|
| 855 | ! |
dist_var <- merge_vars()$dist_var |
| 856 | ! |
s_var <- merge_vars()$s_var |
| 857 | ! |
g_var <- merge_vars()$g_var |
| 858 | ! |
dist_var_name <- merge_vars()$dist_var_name |
| 859 | ! |
s_var_name <- merge_vars()$s_var_name |
| 860 | ! |
g_var_name <- merge_vars()$g_var_name |
| 861 | ! |
t_dist <- input$t_dist |
| 862 | ! |
dist_param1 <- input$dist_param1 |
| 863 | ! |
dist_param2 <- input$dist_param2 |
| 864 | ||
| 865 | ! |
scales_type <- input$scales_type |
| 866 | ! |
ggtheme <- input$ggtheme |
| 867 | ||
| 868 | ! |
teal::validate_inputs(iv_r_dist(), iv_dist) |
| 869 | ||
| 870 | ! |
qenv <- common_q() |
| 871 | ||
| 872 | ! |
plot_call <- if (length(s_var) == 0 && length(g_var) == 0) {
|
| 873 | ! |
substitute( |
| 874 | ! |
expr = ggplot(ANL, aes_string(sample = dist_var)), |
| 875 | ! |
env = list(dist_var = dist_var) |
| 876 |
) |
|
| 877 | ! |
} else if (length(s_var) != 0 && length(g_var) == 0) {
|
| 878 | ! |
substitute( |
| 879 | ! |
expr = ggplot(ANL, aes_string(sample = dist_var, color = s_var)), |
| 880 | ! |
env = list(dist_var = dist_var, s_var = s_var) |
| 881 |
) |
|
| 882 | ! |
} else if (length(s_var) == 0 && length(g_var) != 0) {
|
| 883 | ! |
substitute( |
| 884 | ! |
expr = ggplot(ANL[ANL[[g_var]] != "NA", ], aes_string(sample = dist_var)) + |
| 885 | ! |
facet_wrap(~g_var_name, ncol = 1, scales = scales_raw), |
| 886 | ! |
env = list( |
| 887 | ! |
dist_var = dist_var, |
| 888 | ! |
g_var = g_var, |
| 889 | ! |
g_var_name = g_var_name, |
| 890 | ! |
scales_raw = tolower(scales_type) |
| 891 |
) |
|
| 892 |
) |
|
| 893 |
} else {
|
|
| 894 | ! |
substitute( |
| 895 | ! |
expr = ggplot(ANL[ANL[[g_var]] != "NA", ], aes_string(sample = dist_var, color = s_var)) + |
| 896 | ! |
facet_wrap(~g_var_name, ncol = 1, scales = scales_raw), |
| 897 | ! |
env = list( |
| 898 | ! |
dist_var = dist_var, |
| 899 | ! |
g_var = g_var, |
| 900 | ! |
s_var = s_var, |
| 901 | ! |
g_var_name = g_var_name, |
| 902 | ! |
scales_raw = tolower(scales_type) |
| 903 |
) |
|
| 904 |
) |
|
| 905 |
} |
|
| 906 | ||
| 907 | ! |
map_dist <- stats::setNames( |
| 908 | ! |
c("qnorm", "qlnorm", "qgamma", "qunif"),
|
| 909 | ! |
c("normal", "lognormal", "gamma", "unif")
|
| 910 |
) |
|
| 911 | ||
| 912 | ! |
plot_call <- substitute( |
| 913 | ! |
expr = plot_call + |
| 914 | ! |
stat_qq(distribution = mapped_dist, dparams = params), |
| 915 | ! |
env = list(plot_call = plot_call, mapped_dist = as.name(unname(map_dist[t_dist]))) |
| 916 |
) |
|
| 917 | ||
| 918 | ! |
if (length(t_dist) != 0 && length(g_var) == 0 && length(s_var) == 0) {
|
| 919 | ! |
qenv <- teal.code::eval_code( |
| 920 | ! |
qenv, |
| 921 | ! |
substitute( |
| 922 | ! |
df_params <- as.data.frame(append(params, list(name = t_dist))), |
| 923 | ! |
env = list(t_dist = t_dist) |
| 924 |
) |
|
| 925 |
) |
|
| 926 | ! |
datas <- quote(data.frame(x = 0.7, y = 1, tb = I(list(df_params = df_params)))) |
| 927 | ! |
label <- quote(tb) |
| 928 | ||
| 929 | ! |
plot_call <- substitute( |
| 930 | ! |
expr = plot_call + |
| 931 | ! |
ggpp::geom_table_npc( |
| 932 | ! |
data = data, |
| 933 | ! |
aes(npcx = x, npcy = y, label = label), |
| 934 | ! |
hjust = 0, |
| 935 | ! |
vjust = 1, |
| 936 | ! |
size = 4 |
| 937 |
), |
|
| 938 | ! |
env = list( |
| 939 | ! |
plot_call = plot_call, |
| 940 | ! |
data = datas, |
| 941 | ! |
label = label |
| 942 |
) |
|
| 943 |
) |
|
| 944 |
} |
|
| 945 | ||
| 946 | ! |
if (isTRUE(input$qq_line)) {
|
| 947 | ! |
plot_call <- substitute( |
| 948 | ! |
expr = plot_call + |
| 949 | ! |
stat_qq_line(distribution = mapped_dist, dparams = params), |
| 950 | ! |
env = list(plot_call = plot_call, mapped_dist = as.name(unname(map_dist[t_dist]))) |
| 951 |
) |
|
| 952 |
} |
|
| 953 | ||
| 954 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 955 | ! |
user_plot = ggplot2_args[["QQplot"]], |
| 956 | ! |
user_default = ggplot2_args$default, |
| 957 | ! |
module_plot = teal.widgets::ggplot2_args(labs = list(x = "theoretical", y = "sample")) |
| 958 |
) |
|
| 959 | ||
| 960 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args( |
| 961 | ! |
all_ggplot2_args, |
| 962 | ! |
ggtheme = ggtheme |
| 963 |
) |
|
| 964 | ||
| 965 | ! |
teal.code::eval_code( |
| 966 | ! |
qenv, |
| 967 | ! |
substitute( |
| 968 | ! |
expr = {
|
| 969 | ! |
g <- plot_call |
| 970 | ! |
print(g) |
| 971 |
}, |
|
| 972 | ! |
env = list(plot_call = Reduce(function(x, y) call("+", x, y), c(plot_call, parsed_ggplot2_args)))
|
| 973 |
) |
|
| 974 |
) |
|
| 975 |
} |
|
| 976 |
) |
|
| 977 | ||
| 978 |
# test qenv ---- |
|
| 979 | ! |
test_q <- eventReactive( |
| 980 | ! |
ignoreNULL = FALSE, |
| 981 | ! |
eventExpr = {
|
| 982 | ! |
common_q() |
| 983 | ! |
input$dist_param1 |
| 984 | ! |
input$dist_param2 |
| 985 | ! |
input$dist_tests |
| 986 |
}, |
|
| 987 | ! |
valueExpr = {
|
| 988 |
# Create a private stack for this function only. |
|
| 989 | ! |
ANL <- common_q()[["ANL"]] # nolint |
| 990 | ||
| 991 | ! |
dist_var <- merge_vars()$dist_var |
| 992 | ! |
s_var <- merge_vars()$s_var |
| 993 | ! |
g_var <- merge_vars()$g_var |
| 994 | ||
| 995 | ! |
dist_var_name <- merge_vars()$dist_var_name |
| 996 | ! |
s_var_name <- merge_vars()$s_var_name |
| 997 | ! |
g_var_name <- merge_vars()$g_var_name |
| 998 | ||
| 999 | ! |
dist_param1 <- input$dist_param1 |
| 1000 | ! |
dist_param2 <- input$dist_param2 |
| 1001 | ! |
dist_tests <- input$dist_tests |
| 1002 | ! |
t_dist <- input$t_dist |
| 1003 | ||
| 1004 | ! |
validate(need(dist_tests, "Please select a test")) |
| 1005 | ||
| 1006 | ! |
teal::validate_inputs(iv_dist) |
| 1007 | ||
| 1008 | ! |
if (length(s_var) > 0 || length(g_var) > 0) {
|
| 1009 | ! |
counts <- ANL %>% |
| 1010 | ! |
dplyr::group_by_at(dplyr::vars(dplyr::any_of(c(s_var, g_var)))) %>% |
| 1011 | ! |
dplyr::summarise(n = dplyr::n()) |
| 1012 | ||
| 1013 | ! |
validate(need(all(counts$n > 5), "Please select strata*group with at least 5 observation each.")) |
| 1014 |
} |
|
| 1015 | ||
| 1016 | ||
| 1017 | ! |
if (dist_tests %in% c( |
| 1018 | ! |
"t-test (two-samples, not paired)", |
| 1019 | ! |
"F-test", |
| 1020 | ! |
"Kolmogorov-Smirnov (two-samples)" |
| 1021 |
)) {
|
|
| 1022 | ! |
if (length(g_var) == 0 && length(s_var) > 0) {
|
| 1023 | ! |
validate(need( |
| 1024 | ! |
length(unique(ANL[[s_var]])) == 2, |
| 1025 | ! |
"Please select stratify variable with 2 levels." |
| 1026 |
)) |
|
| 1027 |
} |
|
| 1028 | ! |
if (length(g_var) > 0 && length(s_var) > 0) {
|
| 1029 | ! |
validate(need( |
| 1030 | ! |
all(stats::na.omit(as.vector( |
| 1031 | ! |
tapply(ANL[[s_var]], list(ANL[[g_var]]), function(x) length(unique(x))) == 2 |
| 1032 |
))), |
|
| 1033 | ! |
"Please select stratify variable with 2 levels, per each group." |
| 1034 |
)) |
|
| 1035 |
} |
|
| 1036 |
} |
|
| 1037 | ||
| 1038 | ! |
map_dist <- stats::setNames( |
| 1039 | ! |
c("pnorm", "plnorm", "pgamma", "punif"),
|
| 1040 | ! |
c("normal", "lognormal", "gamma", "unif")
|
| 1041 |
) |
|
| 1042 | ! |
sks_args <- list( |
| 1043 | ! |
test = quote(stats::ks.test), |
| 1044 | ! |
args = bquote(append(list(.[[.(dist_var)]], .(map_dist[t_dist])), params)), |
| 1045 | ! |
groups = c(g_var, s_var) |
| 1046 |
) |
|
| 1047 | ! |
ssw_args <- list( |
| 1048 | ! |
test = quote(stats::shapiro.test), |
| 1049 | ! |
args = bquote(list(.[[.(dist_var)]])), |
| 1050 | ! |
groups = c(g_var, s_var) |
| 1051 |
) |
|
| 1052 | ! |
mfil_args <- list( |
| 1053 | ! |
test = quote(stats::fligner.test), |
| 1054 | ! |
args = bquote(list(.[[.(dist_var)]], .[[.(s_var)]])), |
| 1055 | ! |
groups = c(g_var) |
| 1056 |
) |
|
| 1057 | ! |
sad_args <- list( |
| 1058 | ! |
test = quote(goftest::ad.test), |
| 1059 | ! |
args = bquote(append(list(.[[.(dist_var)]], .(map_dist[t_dist])), params)), |
| 1060 | ! |
groups = c(g_var, s_var) |
| 1061 |
) |
|
| 1062 | ! |
scvm_args <- list( |
| 1063 | ! |
test = quote(goftest::cvm.test), |
| 1064 | ! |
args = bquote(append(list(.[[.(dist_var)]], .(map_dist[t_dist])), params)), |
| 1065 | ! |
groups = c(g_var, s_var) |
| 1066 |
) |
|
| 1067 | ! |
manov_args <- list( |
| 1068 | ! |
test = quote(stats::aov), |
| 1069 | ! |
args = bquote(list(stats::formula(.(dist_var_name) ~ .(s_var_name)), .)), |
| 1070 | ! |
groups = c(g_var) |
| 1071 |
) |
|
| 1072 | ! |
mt_args <- list( |
| 1073 | ! |
test = quote(stats::t.test), |
| 1074 | ! |
args = bquote(unname(split(.[[.(dist_var)]], .[[.(s_var)]], drop = TRUE))), |
| 1075 | ! |
groups = c(g_var) |
| 1076 |
) |
|
| 1077 | ! |
mv_args <- list( |
| 1078 | ! |
test = quote(stats::var.test), |
| 1079 | ! |
args = bquote(unname(split(.[[.(dist_var)]], .[[.(s_var)]], drop = TRUE))), |
| 1080 | ! |
groups = c(g_var) |
| 1081 |
) |
|
| 1082 | ! |
mks_args <- list( |
| 1083 | ! |
test = quote(stats::ks.test), |
| 1084 | ! |
args = bquote(unname(split(.[[.(dist_var)]], .[[.(s_var)]], drop = TRUE))), |
| 1085 | ! |
groups = c(g_var) |
| 1086 |
) |
|
| 1087 | ||
| 1088 | ! |
tests_base <- switch(dist_tests, |
| 1089 | ! |
"Kolmogorov-Smirnov (one-sample)" = sks_args, |
| 1090 | ! |
"Shapiro-Wilk" = ssw_args, |
| 1091 | ! |
"Fligner-Killeen" = mfil_args, |
| 1092 | ! |
"one-way ANOVA" = manov_args, |
| 1093 | ! |
"t-test (two-samples, not paired)" = mt_args, |
| 1094 | ! |
"F-test" = mv_args, |
| 1095 | ! |
"Kolmogorov-Smirnov (two-samples)" = mks_args, |
| 1096 | ! |
"Anderson-Darling (one-sample)" = sad_args, |
| 1097 | ! |
"Cramer-von Mises (one-sample)" = scvm_args |
| 1098 |
) |
|
| 1099 | ||
| 1100 | ! |
env <- list( |
| 1101 | ! |
t_test = t_dist, |
| 1102 | ! |
dist_var = dist_var, |
| 1103 | ! |
g_var = g_var, |
| 1104 | ! |
s_var = s_var, |
| 1105 | ! |
args = tests_base$args, |
| 1106 | ! |
groups = tests_base$groups, |
| 1107 | ! |
test = tests_base$test, |
| 1108 | ! |
dist_var_name = dist_var_name, |
| 1109 | ! |
g_var_name = g_var_name, |
| 1110 | ! |
s_var_name = s_var_name |
| 1111 |
) |
|
| 1112 | ||
| 1113 | ! |
qenv <- common_q() |
| 1114 | ||
| 1115 | ! |
if (length(s_var) == 0 && length(g_var) == 0) {
|
| 1116 | ! |
qenv <- teal.code::eval_code( |
| 1117 | ! |
qenv, |
| 1118 | ! |
substitute( |
| 1119 | ! |
expr = {
|
| 1120 | ! |
test_stats <- ANL %>% |
| 1121 | ! |
dplyr::select(dist_var) %>% |
| 1122 | ! |
with(., broom::glance(do.call(test, args))) %>% |
| 1123 | ! |
dplyr::mutate_if(is.numeric, round, 3) |
| 1124 |
}, |
|
| 1125 | ! |
env = env |
| 1126 |
) |
|
| 1127 |
) |
|
| 1128 |
} else {
|
|
| 1129 | ! |
qenv <- teal.code::eval_code( |
| 1130 | ! |
qenv, |
| 1131 | ! |
substitute( |
| 1132 | ! |
expr = {
|
| 1133 | ! |
test_stats <- ANL %>% |
| 1134 | ! |
dplyr::select(dist_var, s_var, g_var) %>% |
| 1135 | ! |
dplyr::group_by_at(dplyr::vars(dplyr::any_of(groups))) %>% |
| 1136 | ! |
dplyr::do(tests = broom::glance(do.call(test, args))) %>% |
| 1137 | ! |
tidyr::unnest(tests) %>% |
| 1138 | ! |
dplyr::mutate_if(is.numeric, round, 3) |
| 1139 |
}, |
|
| 1140 | ! |
env = env |
| 1141 |
) |
|
| 1142 |
) |
|
| 1143 |
} |
|
| 1144 | ! |
qenv |
| 1145 |
} |
|
| 1146 |
) |
|
| 1147 | ||
| 1148 |
# outputs ---- |
|
| 1149 |
## building main qenv |
|
| 1150 | ! |
output_q <- reactive({
|
| 1151 | ! |
tab <- input$tabs |
| 1152 | ! |
req(tab) # tab is NULL upon app launch, hence will crash without this statement |
| 1153 | ||
| 1154 | ! |
qenv_final <- common_q() |
| 1155 |
# wrapped in if since could lead into validate error - we do want to continue |
|
| 1156 | ! |
test_r_qenv_out <- try(test_q(), silent = TRUE) |
| 1157 | ! |
if (!inherits(test_r_qenv_out, c("try-error", "error"))) {
|
| 1158 | ! |
qenv_final <- teal.code::join(qenv_final, test_q()) |
| 1159 |
} |
|
| 1160 | ||
| 1161 | ! |
qenv_final <- if (tab == "Histogram") {
|
| 1162 | ! |
req(dist_q()) |
| 1163 | ! |
teal.code::join(qenv_final, dist_q()) |
| 1164 | ! |
} else if (tab == "QQplot") {
|
| 1165 | ! |
req(qq_q()) |
| 1166 | ! |
teal.code::join(qenv_final, qq_q()) |
| 1167 |
} |
|
| 1168 | ! |
qenv_final |
| 1169 |
}) |
|
| 1170 | ||
| 1171 | ! |
dist_r <- reactive(dist_q()[["g"]]) |
| 1172 | ||
| 1173 | ! |
qq_r <- reactive(qq_q()[["g"]]) |
| 1174 | ||
| 1175 | ! |
output$summary_table <- DT::renderDataTable( |
| 1176 | ! |
expr = if (iv_r()$is_valid()) common_q()[["summary_table"]] else NULL, |
| 1177 | ! |
options = list( |
| 1178 | ! |
autoWidth = TRUE, |
| 1179 | ! |
columnDefs = list(list(width = "200px", targets = "_all")) |
| 1180 |
), |
|
| 1181 | ! |
rownames = FALSE |
| 1182 |
) |
|
| 1183 | ||
| 1184 | ! |
tests_r <- reactive({
|
| 1185 | ! |
req(iv_r()$is_valid()) |
| 1186 | ! |
teal::validate_inputs(iv_r_dist()) |
| 1187 | ! |
test_q()[["test_stats"]] |
| 1188 |
}) |
|
| 1189 | ||
| 1190 | ! |
pws1 <- teal.widgets::plot_with_settings_srv( |
| 1191 | ! |
id = "hist_plot", |
| 1192 | ! |
plot_r = dist_r, |
| 1193 | ! |
height = plot_height, |
| 1194 | ! |
width = plot_width, |
| 1195 | ! |
brushing = FALSE |
| 1196 |
) |
|
| 1197 | ||
| 1198 | ! |
pws2 <- teal.widgets::plot_with_settings_srv( |
| 1199 | ! |
id = "qq_plot", |
| 1200 | ! |
plot_r = qq_r, |
| 1201 | ! |
height = plot_height, |
| 1202 | ! |
width = plot_width, |
| 1203 | ! |
brushing = FALSE |
| 1204 |
) |
|
| 1205 | ||
| 1206 | ! |
output$t_stats <- DT::renderDataTable( |
| 1207 | ! |
expr = tests_r(), |
| 1208 | ! |
options = list(scrollX = TRUE), |
| 1209 | ! |
rownames = FALSE |
| 1210 |
) |
|
| 1211 | ||
| 1212 | ! |
teal.widgets::verbatim_popup_srv( |
| 1213 | ! |
id = "warning", |
| 1214 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 1215 | ! |
title = "Warning", |
| 1216 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 1217 |
) |
|
| 1218 | ||
| 1219 | ! |
teal.widgets::verbatim_popup_srv( |
| 1220 | ! |
id = "rcode", |
| 1221 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 1222 | ! |
title = "R Code for distribution" |
| 1223 |
) |
|
| 1224 | ||
| 1225 |
### REPORTER |
|
| 1226 | ! |
if (with_reporter) {
|
| 1227 | ! |
card_fun <- function(comment) {
|
| 1228 | ! |
card <- teal::TealReportCard$new() |
| 1229 | ! |
card$set_name("Distribution Plot")
|
| 1230 | ! |
card$append_text("Distribution Plot", "header2")
|
| 1231 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 1232 | ! |
card$append_text("Plot", "header3")
|
| 1233 | ! |
if (input$tabs == "Histogram") {
|
| 1234 | ! |
card$append_plot(dist_r(), dim = pws1$dim()) |
| 1235 | ! |
} else if (input$tabs == "QQplot") {
|
| 1236 | ! |
card$append_plot(qq_r(), dim = pws2$dim()) |
| 1237 |
} |
|
| 1238 | ! |
card$append_text("Statistics table", "header3")
|
| 1239 | ||
| 1240 | ! |
card$append_table(common_q()[["summary_table"]]) |
| 1241 | ! |
tests_error <- tryCatch(expr = tests_r(), error = function(e) "error") |
| 1242 | ! |
if (inherits(tests_error, "data.frame")) {
|
| 1243 | ! |
card$append_text("Tests table", "header3")
|
| 1244 | ! |
card$append_table(tests_r()) |
| 1245 |
} |
|
| 1246 | ||
| 1247 | ! |
if (!comment == "") {
|
| 1248 | ! |
card$append_text("Comment", "header3")
|
| 1249 | ! |
card$append_text(comment) |
| 1250 |
} |
|
| 1251 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 1252 | ! |
card |
| 1253 |
} |
|
| 1254 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 1255 |
} |
|
| 1256 |
### |
|
| 1257 |
}) |
|
| 1258 |
} |
| 1 |
#' Create a scatterplot matrix |
|
| 2 |
#' |
|
| 3 |
#' The available datasets to choose from for each dataset selector is the same and |
|
| 4 |
#' determined by the argument `variables`. |
|
| 5 |
#' @md |
|
| 6 |
#' |
|
| 7 |
#' @inheritParams teal::module |
|
| 8 |
#' @inheritParams tm_g_scatterplot |
|
| 9 |
#' @inheritParams shared_params |
|
| 10 |
#' |
|
| 11 |
#' @param variables (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 12 |
#' Plotting variables from an incoming dataset with filtering and selecting. In case of |
|
| 13 |
#' `data_extract_spec` use `select_spec(..., ordered = TRUE)` if plot elements should be |
|
| 14 |
#' rendered according to selection order. |
|
| 15 |
#' |
|
| 16 |
#' @note For more examples, please see the vignette "Using scatterplot matrix" via |
|
| 17 |
#' \code{vignette("using-scatterplot-matrix", package = "teal.modules.general")}.
|
|
| 18 |
#' @export |
|
| 19 |
#' |
|
| 20 |
#' @examples |
|
| 21 |
#' # Scatterplot matrix of variables from ADSL dataset |
|
| 22 |
#' |
|
| 23 |
#' ADSL <- teal.modules.general::rADSL |
|
| 24 |
#' ADRS <- teal.modules.general::rADRS |
|
| 25 |
#' |
|
| 26 |
#' app <- teal::init( |
|
| 27 |
#' data = teal.data::cdisc_data( |
|
| 28 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 29 |
#' teal.data::cdisc_dataset("ADRS", ADRS, code = "ADRS <- teal.modules.general::rADRS"),
|
|
| 30 |
#' check = TRUE |
|
| 31 |
#' ), |
|
| 32 |
#' modules = teal::modules( |
|
| 33 |
#' teal.modules.general::tm_g_scatterplotmatrix( |
|
| 34 |
#' label = "Scatterplot matrix", |
|
| 35 |
#' variables = list( |
|
| 36 |
#' teal.transform::data_extract_spec( |
|
| 37 |
#' dataname = "ADSL", |
|
| 38 |
#' select = select_spec( |
|
| 39 |
#' label = "Select variables:", |
|
| 40 |
#' choices = variable_choices(ADSL), |
|
| 41 |
#' selected = c("AGE", "RACE", "SEX"),
|
|
| 42 |
#' multiple = TRUE, |
|
| 43 |
#' ordered = TRUE, |
|
| 44 |
#' fixed = FALSE |
|
| 45 |
#' ) |
|
| 46 |
#' ), |
|
| 47 |
#' teal.transform::data_extract_spec( |
|
| 48 |
#' dataname = "ADRS", |
|
| 49 |
#' filter = teal.transform::filter_spec( |
|
| 50 |
#' label = "Select endpoints:", |
|
| 51 |
#' vars = c("PARAMCD", "AVISIT"),
|
|
| 52 |
#' choices = value_choices(ADRS, c("PARAMCD", "AVISIT"), c("PARAM", "AVISIT")),
|
|
| 53 |
#' selected = "INVET - END OF INDUCTION", |
|
| 54 |
#' multiple = TRUE |
|
| 55 |
#' ), |
|
| 56 |
#' select = select_spec( |
|
| 57 |
#' label = "Select variables:", |
|
| 58 |
#' choices = variable_choices(ADRS), |
|
| 59 |
#' selected = c("AGE", "AVAL", "ADY"),
|
|
| 60 |
#' multiple = TRUE, |
|
| 61 |
#' ordered = TRUE, |
|
| 62 |
#' fixed = FALSE |
|
| 63 |
#' ) |
|
| 64 |
#' ) |
|
| 65 |
#' ) |
|
| 66 |
#' ) |
|
| 67 |
#' ) |
|
| 68 |
#' ) |
|
| 69 |
#' if (interactive()) {
|
|
| 70 |
#' shinyApp(app$ui, app$server) |
|
| 71 |
#' } |
|
| 72 |
tm_g_scatterplotmatrix <- function(label = "Scatterplot Matrix", |
|
| 73 |
variables, |
|
| 74 |
plot_height = c(600, 200, 2000), |
|
| 75 |
plot_width = NULL, |
|
| 76 |
pre_output = NULL, |
|
| 77 |
post_output = NULL) {
|
|
| 78 | ! |
logger::log_info("Initializing tm_g_scatterplotmatrix")
|
| 79 | ! |
if (!requireNamespace("lattice", quietly = TRUE)) {
|
| 80 | ! |
stop("Cannot load lattice - please install the package or restart your session.")
|
| 81 |
} |
|
| 82 | ! |
if (inherits(variables, "data_extract_spec")) variables <- list(variables) |
| 83 | ||
| 84 | ! |
checkmate::assert_string(label) |
| 85 | ! |
checkmate::assert_list(variables, types = "data_extract_spec") |
| 86 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
| 87 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 88 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
| 89 | ! |
checkmate::assert_numeric( |
| 90 | ! |
plot_width[1], |
| 91 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 92 |
) |
|
| 93 | ||
| 94 | ! |
args <- as.list(environment()) |
| 95 | ! |
module( |
| 96 | ! |
label = label, |
| 97 | ! |
server = srv_g_scatterplotmatrix, |
| 98 | ! |
ui = ui_g_scatterplotmatrix, |
| 99 | ! |
ui_args = args, |
| 100 | ! |
server_args = list(variables = variables, plot_height = plot_height, plot_width = plot_width), |
| 101 | ! |
datanames = teal.transform::get_extract_datanames(variables) |
| 102 |
) |
|
| 103 |
} |
|
| 104 | ||
| 105 |
ui_g_scatterplotmatrix <- function(id, ...) {
|
|
| 106 | ! |
args <- list(...) |
| 107 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset(args$variables) |
| 108 | ! |
ns <- NS(id) |
| 109 | ! |
teal.widgets::standard_layout( |
| 110 | ! |
output = teal.widgets::white_small_well( |
| 111 | ! |
textOutput(ns("message")),
|
| 112 | ! |
br(), |
| 113 | ! |
teal.widgets::plot_with_settings_ui(id = ns("myplot"))
|
| 114 |
), |
|
| 115 | ! |
encoding = div( |
| 116 |
### Reporter |
|
| 117 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 118 |
### |
|
| 119 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 120 | ! |
teal.transform::datanames_input(args$variables), |
| 121 | ! |
teal.transform::data_extract_ui( |
| 122 | ! |
id = ns("variables"),
|
| 123 | ! |
label = "Variables", |
| 124 | ! |
data_extract_spec = args$variables, |
| 125 | ! |
is_single_dataset = is_single_dataset_value |
| 126 |
), |
|
| 127 | ! |
hr(), |
| 128 | ! |
teal.widgets::panel_group( |
| 129 | ! |
teal.widgets::panel_item( |
| 130 | ! |
title = "Plot settings", |
| 131 | ! |
sliderInput( |
| 132 | ! |
ns("alpha"), "Opacity:",
|
| 133 | ! |
min = 0, max = 1, |
| 134 | ! |
step = .05, value = .5, ticks = FALSE |
| 135 |
), |
|
| 136 | ! |
sliderInput( |
| 137 | ! |
ns("cex"), "Points size:",
|
| 138 | ! |
min = 0.2, max = 3, |
| 139 | ! |
step = .05, value = .65, ticks = FALSE |
| 140 |
), |
|
| 141 | ! |
checkboxInput(ns("cor"), "Add Correlation", value = FALSE),
|
| 142 | ! |
radioButtons( |
| 143 | ! |
ns("cor_method"), "Select Correlation Method",
|
| 144 | ! |
choiceNames = c("Pearson", "Kendall", "Spearman"),
|
| 145 | ! |
choiceValues = c("pearson", "kendall", "spearman"),
|
| 146 | ! |
inline = TRUE |
| 147 |
), |
|
| 148 | ! |
checkboxInput(ns("cor_na_omit"), "Omit Missing Values", value = TRUE)
|
| 149 |
) |
|
| 150 |
) |
|
| 151 |
), |
|
| 152 | ! |
forms = tagList( |
| 153 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"),
|
| 154 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 155 |
), |
|
| 156 | ! |
pre_output = args$pre_output, |
| 157 | ! |
post_output = args$post_output |
| 158 |
) |
|
| 159 |
} |
|
| 160 | ||
| 161 |
srv_g_scatterplotmatrix <- function(id, data, reporter, filter_panel_api, variables, plot_height, plot_width) {
|
|
| 162 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 163 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 164 | ! |
checkmate::assert_class(data, "tdata") |
| 165 | ! |
moduleServer(id, function(input, output, session) {
|
| 166 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 167 | ! |
data_extract = list(variables = variables), |
| 168 | ! |
datasets = data, |
| 169 | ! |
select_validation_rule = list( |
| 170 | ! |
variables = ~ if (length(.) <= 1) "Please select at least 2 columns." |
| 171 |
) |
|
| 172 |
) |
|
| 173 | ||
| 174 | ! |
iv_r <- reactive({
|
| 175 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 176 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list) |
| 177 |
}) |
|
| 178 | ||
| 179 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 180 | ! |
datasets = data, |
| 181 | ! |
join_keys = get_join_keys(data), |
| 182 | ! |
selector_list = selector_list |
| 183 |
) |
|
| 184 | ||
| 185 | ! |
anl_merged_q <- reactive({
|
| 186 | ! |
req(anl_merged_input()) |
| 187 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 188 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 189 |
}) |
|
| 190 | ||
| 191 | ! |
merged <- list( |
| 192 | ! |
anl_input_r = anl_merged_input, |
| 193 | ! |
anl_q_r = anl_merged_q |
| 194 |
) |
|
| 195 | ||
| 196 |
# plot |
|
| 197 | ! |
output_q <- reactive({
|
| 198 | ! |
teal::validate_inputs(iv_r()) |
| 199 | ||
| 200 | ! |
qenv <- merged$anl_q_r() |
| 201 | ! |
ANL <- qenv[["ANL"]] # nolint |
| 202 | ||
| 203 | ! |
cols_names <- merged$anl_input_r()$columns_source$variables |
| 204 | ! |
alpha <- input$alpha # nolint |
| 205 | ! |
cex <- input$cex # nolint |
| 206 | ! |
add_cor <- input$cor # nolint |
| 207 | ! |
cor_method <- input$cor_method # nolint |
| 208 | ! |
cor_na_omit <- input$cor_na_omit # nolint |
| 209 | ||
| 210 | ! |
cor_na_action <- if (isTruthy(cor_na_omit)) {
|
| 211 | ! |
"na.omit" |
| 212 |
} else {
|
|
| 213 | ! |
"na.fail" |
| 214 |
} |
|
| 215 | ||
| 216 | ! |
teal::validate_has_data(ANL, 10) |
| 217 | ! |
teal::validate_has_data(ANL[, cols_names], 10, complete = TRUE, allow_inf = FALSE) |
| 218 | ||
| 219 |
# get labels and proper variable names |
|
| 220 | ! |
varnames <- varname_w_label(cols_names, ANL, wrap_width = 20) # nolint |
| 221 | ||
| 222 |
# check character columns. If any, then those are converted to factors |
|
| 223 | ! |
check_char <- vapply(ANL[, cols_names], is.character, logical(1)) |
| 224 | ! |
if (any(check_char)) {
|
| 225 | ! |
qenv <- teal.code::eval_code( |
| 226 | ! |
qenv, |
| 227 | ! |
substitute( |
| 228 | ! |
expr = ANL <- ANL[, cols_names] %>% # nolint |
| 229 | ! |
dplyr::mutate_if(is.character, as.factor) %>% |
| 230 | ! |
droplevels(), |
| 231 | ! |
env = list(cols_names = cols_names) |
| 232 |
) |
|
| 233 |
) |
|
| 234 |
} else {
|
|
| 235 | ! |
qenv <- teal.code::eval_code( |
| 236 | ! |
qenv, |
| 237 | ! |
substitute( |
| 238 | ! |
expr = ANL <- ANL[, cols_names] %>% # nolint |
| 239 | ! |
droplevels(), |
| 240 | ! |
env = list(cols_names = cols_names) |
| 241 |
) |
|
| 242 |
) |
|
| 243 |
} |
|
| 244 | ||
| 245 | ||
| 246 |
# create plot |
|
| 247 | ! |
if (add_cor) {
|
| 248 | ! |
shinyjs::show("cor_method")
|
| 249 | ! |
shinyjs::show("cor_use")
|
| 250 | ! |
shinyjs::show("cor_na_omit")
|
| 251 | ||
| 252 | ! |
qenv <- teal.code::eval_code( |
| 253 | ! |
qenv, |
| 254 | ! |
substitute( |
| 255 | ! |
expr = {
|
| 256 | ! |
g <- lattice::splom( |
| 257 | ! |
ANL, |
| 258 | ! |
varnames = varnames_value, |
| 259 | ! |
panel = function(x, y, ...) {
|
| 260 | ! |
lattice::panel.splom(x = x, y = y, ...) |
| 261 | ! |
cpl <- lattice::current.panel.limits() |
| 262 | ! |
lattice::panel.text( |
| 263 | ! |
mean(cpl$xlim), |
| 264 | ! |
mean(cpl$ylim), |
| 265 | ! |
get_scatterplotmatrix_stats( |
| 266 | ! |
x, |
| 267 | ! |
y, |
| 268 | ! |
.f = stats::cor.test, |
| 269 | ! |
.f_args = list(method = cor_method, na.action = cor_na_action) |
| 270 |
), |
|
| 271 | ! |
alpha = 0.6, |
| 272 | ! |
fontsize = 18, |
| 273 | ! |
fontface = "bold" |
| 274 |
) |
|
| 275 |
}, |
|
| 276 | ! |
pch = 16, |
| 277 | ! |
alpha = alpha_value, |
| 278 | ! |
cex = cex_value |
| 279 |
) |
|
| 280 | ! |
print(g) |
| 281 |
}, |
|
| 282 | ! |
env = list( |
| 283 | ! |
varnames_value = varnames, |
| 284 | ! |
cor_method = cor_method, |
| 285 | ! |
cor_na_action = cor_na_action, |
| 286 | ! |
alpha_value = alpha, |
| 287 | ! |
cex_value = cex |
| 288 |
) |
|
| 289 |
) |
|
| 290 |
) |
|
| 291 |
} else {
|
|
| 292 | ! |
shinyjs::hide("cor_method")
|
| 293 | ! |
shinyjs::hide("cor_use")
|
| 294 | ! |
shinyjs::hide("cor_na_omit")
|
| 295 | ! |
qenv <- teal.code::eval_code( |
| 296 | ! |
qenv, |
| 297 | ! |
substitute( |
| 298 | ! |
expr = {
|
| 299 | ! |
g <- lattice::splom(ANL, varnames = varnames_value, pch = 16, alpha = alpha_value, cex = cex_value) |
| 300 | ! |
g |
| 301 |
}, |
|
| 302 | ! |
env = list(varnames_value = varnames, alpha_value = alpha, cex_value = cex) |
| 303 |
) |
|
| 304 |
) |
|
| 305 |
} |
|
| 306 | ! |
qenv |
| 307 |
}) |
|
| 308 | ||
| 309 | ! |
plot_r <- reactive(output_q()[["g"]]) |
| 310 | ||
| 311 |
# Insert the plot into a plot_with_settings module |
|
| 312 | ! |
pws <- teal.widgets::plot_with_settings_srv( |
| 313 | ! |
id = "myplot", |
| 314 | ! |
plot_r = plot_r, |
| 315 | ! |
height = plot_height, |
| 316 | ! |
width = plot_width |
| 317 |
) |
|
| 318 | ||
| 319 |
# show a message if conversion to factors took place |
|
| 320 | ! |
output$message <- renderText({
|
| 321 | ! |
shiny::req(iv_r()$is_valid()) |
| 322 | ! |
req(selector_list()$variables()) |
| 323 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 324 | ! |
cols_names <- unique(unname(do.call(c, merged$anl_input_r()$columns_source))) |
| 325 | ! |
check_char <- vapply(ANL[, cols_names], is.character, logical(1)) |
| 326 | ! |
if (any(check_char)) {
|
| 327 | ! |
is_single <- sum(check_char) == 1 |
| 328 | ! |
paste( |
| 329 | ! |
"Character", |
| 330 | ! |
ifelse(is_single, "variable", "variables"), |
| 331 | ! |
paste0("(", paste(cols_names[check_char], collapse = ", "), ")"),
|
| 332 | ! |
ifelse(is_single, "was", "were"), |
| 333 | ! |
"converted to", |
| 334 | ! |
ifelse(is_single, "factor.", "factors.") |
| 335 |
) |
|
| 336 |
} else {
|
|
| 337 |
"" |
|
| 338 |
} |
|
| 339 |
}) |
|
| 340 | ||
| 341 | ! |
teal.widgets::verbatim_popup_srv( |
| 342 | ! |
id = "warning", |
| 343 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 344 | ! |
title = "Warning", |
| 345 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 346 |
) |
|
| 347 | ||
| 348 | ! |
teal.widgets::verbatim_popup_srv( |
| 349 | ! |
id = "rcode", |
| 350 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 351 | ! |
title = "Show R Code for Scatterplotmatrix" |
| 352 |
) |
|
| 353 | ||
| 354 |
### REPORTER |
|
| 355 | ! |
if (with_reporter) {
|
| 356 | ! |
card_fun <- function(comment) {
|
| 357 | ! |
card <- teal::TealReportCard$new() |
| 358 | ! |
card$set_name("Scatter Plot Matrix")
|
| 359 | ! |
card$append_text("Scatter Plot Matrix", "header2")
|
| 360 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 361 | ! |
card$append_text("Plot", "header3")
|
| 362 | ! |
card$append_plot(plot_r(), dim = pws$dim()) |
| 363 | ! |
if (!comment == "") {
|
| 364 | ! |
card$append_text("Comment", "header3")
|
| 365 | ! |
card$append_text(comment) |
| 366 |
} |
|
| 367 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 368 | ! |
card |
| 369 |
} |
|
| 370 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 371 |
} |
|
| 372 |
### |
|
| 373 |
}) |
|
| 374 |
} |
|
| 375 | ||
| 376 |
#' Get stats for x-y pairs in scatterplot matrix |
|
| 377 |
#' @description uses stats::cor.test per default for all numerical input variables and converts results |
|
| 378 |
#' to character vector. Could be extended if different stats for different variable |
|
| 379 |
#' types are needed. Meant to be called from \code{lattice::panel.text}.
|
|
| 380 |
#' @param x \code{numeric}
|
|
| 381 |
#' @param y \code{numeric}
|
|
| 382 |
#' @param .f \code{function}, function that accepts x and y as formula input \code{~ x + y}.
|
|
| 383 |
#' Default \code{stats::cor.test}
|
|
| 384 |
#' @param .f_args \code{list} of arguments to be passed to \code{.f}
|
|
| 385 |
#' @param round_stat \code{integer}
|
|
| 386 |
#' @param round_pval \code{integer}
|
|
| 387 |
#' @details presently we need to use a formula input for \code{stats::cor.test} because
|
|
| 388 |
#' \code{na.fail} only gets evaluated when a formula is passed (see below).
|
|
| 389 |
#' \preformatted{
|
|
| 390 |
#' x = c(1,3,5,7,NA) |
|
| 391 |
#' y = c(3,6,7,8,1) |
|
| 392 |
#' stats::cor.test(x, y, na.action = "na.fail") |
|
| 393 |
#' stats::cor.test(~ x + y, na.action = "na.fail") |
|
| 394 |
#' } |
|
| 395 |
#' @return \code{character} with stats. For \code{stats::cor.test} correlation coefficient and p-value.
|
|
| 396 |
#' @export |
|
| 397 |
#' @examples |
|
| 398 |
#' set.seed(1) |
|
| 399 |
#' x <- runif(25, 0, 1) |
|
| 400 |
#' y <- runif(25, 0, 1) |
|
| 401 |
#' x[c(3, 10, 18)] <- NA |
|
| 402 |
#' |
|
| 403 |
#' get_scatterplotmatrix_stats(x, y, .f = stats::cor.test, .f_args = list(method = "pearson")) |
|
| 404 |
#' get_scatterplotmatrix_stats(x, y, .f = stats::cor.test, .f_args = list( |
|
| 405 |
#' method = "pearson", |
|
| 406 |
#' na.action = na.fail |
|
| 407 |
#' )) |
|
| 408 |
get_scatterplotmatrix_stats <- function(x, y, |
|
| 409 |
.f = stats::cor.test, |
|
| 410 |
.f_args = list(), |
|
| 411 |
round_stat = 2, |
|
| 412 |
round_pval = 4) {
|
|
| 413 | 6x |
if (is.numeric(x) && is.numeric(y)) {
|
| 414 | 3x |
stat <- tryCatch(do.call(.f, c(list(~ x + y), .f_args)), error = function(e) NA) |
| 415 | ||
| 416 | 3x |
if (anyNA(stat)) {
|
| 417 | 1x |
return("NA")
|
| 418 | 2x |
} else if (all(c("estimate", "p.value") %in% names(stat))) {
|
| 419 | 2x |
return(paste( |
| 420 | 2x |
c( |
| 421 | 2x |
paste0(names(stat$estimate), ":", round(stat$estimate, round_stat)), |
| 422 | 2x |
paste0("P:", round(stat$p.value, round_pval))
|
| 423 |
), |
|
| 424 | 2x |
collapse = "\n" |
| 425 |
)) |
|
| 426 |
} else {
|
|
| 427 | ! |
stop("function not supported")
|
| 428 |
} |
|
| 429 |
} else {
|
|
| 430 | 3x |
if ("method" %in% names(.f_args)) {
|
| 431 | 3x |
if (.f_args$method == "pearson") {
|
| 432 | 1x |
return("cor:-")
|
| 433 |
} |
|
| 434 | 2x |
if (.f_args$method == "kendall") {
|
| 435 | 1x |
return("tau:-")
|
| 436 |
} |
|
| 437 | 1x |
if (.f_args$method == "spearman") {
|
| 438 | 1x |
return("rho:-")
|
| 439 |
} |
|
| 440 |
} |
|
| 441 | ! |
return("-")
|
| 442 |
} |
|
| 443 |
} |
| 1 |
#' Create a simple scatterplot |
|
| 2 |
#' |
|
| 3 |
#' Create a plot with the \code{\link{ggplot2}[geom_point]} function
|
|
| 4 |
#' @md |
|
| 5 |
#' |
|
| 6 |
#' @inheritParams teal::module |
|
| 7 |
#' @inheritParams shared_params |
|
| 8 |
#' @param x (`data_extract_spec` or `list` of multiple `data_extract_spec`) Variable |
|
| 9 |
#' names selected to plot along the x-axis by default. |
|
| 10 |
#' @param y (`data_extract_spec` or `list` of multiple `data_extract_spec`) Variable |
|
| 11 |
#' names selected to plot along the y-axis by default. |
|
| 12 |
#' @param color_by optional (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 13 |
#' Defines the color encoding. If `NULL` then no color encoding option will be displayed. |
|
| 14 |
#' @param size_by optional (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 15 |
#' Defines the point size encoding. If `NULL` then no size encoding option will be displayed. |
|
| 16 |
#' @param row_facet optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 17 |
#' Which data columns to use for faceting rows. |
|
| 18 |
#' @param col_facet optional, (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 19 |
#' Which data to use for faceting columns. |
|
| 20 |
#' @param alpha optional, (`numeric`) If scalar then the plot points will have a fixed opacity. If a |
|
| 21 |
#' slider should be presented to adjust the plot point opacity dynamically then it can be a vector of |
|
| 22 |
#' length three with `c(value, min, max)`. |
|
| 23 |
#' @param size optional, (`numeric`) If scalar then the plot point sizes will have a fixed size |
|
| 24 |
#' If a slider should be presented to adjust the plot point sizes dynamically then it can be a |
|
| 25 |
#' vector of length three with `c(value, min, max)`. |
|
| 26 |
#' @param shape optional, (`character`) A character vector with the English names of the |
|
| 27 |
#' shape, e.g. `c("triangle", "square", "circle")`. It defaults to `shape_names`. This is a complete list from
|
|
| 28 |
#' `vignette("ggplot2-specs", package="ggplot2")`.
|
|
| 29 |
#' @param max_deg optional, (`integer`) The maximum degree for the polynomial trend line. Must not be less than 1. |
|
| 30 |
#' @param table_dec optional, (`integer`) Number of decimal places used to round numeric values in the table. |
|
| 31 |
#' |
|
| 32 |
#' |
|
| 33 |
#' @note For more examples, please see the vignette "Using scatterplot" via |
|
| 34 |
#' `vignette("using-scatterplot", package = "teal.modules.general")`.
|
|
| 35 |
#' |
|
| 36 |
#' @export |
|
| 37 |
#' @examples |
|
| 38 |
#' # Scatterplot of variables from ADSL dataset |
|
| 39 |
#' library(nestcolor) |
|
| 40 |
#' |
|
| 41 |
#' ADSL <- teal.modules.general::rADSL |
|
| 42 |
#' |
|
| 43 |
#' app <- teal::init( |
|
| 44 |
#' data = teal.data::cdisc_data( |
|
| 45 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 46 |
#' check = TRUE |
|
| 47 |
#' ), |
|
| 48 |
#' modules = teal::modules( |
|
| 49 |
#' teal.modules.general::tm_g_scatterplot( |
|
| 50 |
#' label = "Scatterplot Choices", |
|
| 51 |
#' x = teal.transform::data_extract_spec( |
|
| 52 |
#' dataname = "ADSL", |
|
| 53 |
#' select = teal.transform::select_spec( |
|
| 54 |
#' label = "Select variable:", |
|
| 55 |
#' choices = teal.transform::variable_choices( |
|
| 56 |
#' ADSL, |
|
| 57 |
#' c("AGE", "BMRKR1", "BMRKR2")
|
|
| 58 |
#' ), |
|
| 59 |
#' selected = "AGE", |
|
| 60 |
#' multiple = FALSE, |
|
| 61 |
#' fixed = FALSE |
|
| 62 |
#' ) |
|
| 63 |
#' ), |
|
| 64 |
#' y = teal.transform::data_extract_spec( |
|
| 65 |
#' dataname = "ADSL", |
|
| 66 |
#' select = teal.transform::select_spec( |
|
| 67 |
#' label = "Select variable:", |
|
| 68 |
#' choices = teal.transform::variable_choices( |
|
| 69 |
#' ADSL, |
|
| 70 |
#' c("AGE", "BMRKR1", "BMRKR2")
|
|
| 71 |
#' ), |
|
| 72 |
#' selected = "BMRKR1", |
|
| 73 |
#' multiple = FALSE, |
|
| 74 |
#' fixed = FALSE |
|
| 75 |
#' ) |
|
| 76 |
#' ), |
|
| 77 |
#' color_by = teal.transform::data_extract_spec( |
|
| 78 |
#' dataname = "ADSL", |
|
| 79 |
#' select = teal.transform::select_spec( |
|
| 80 |
#' label = "Select variable:", |
|
| 81 |
#' choices = teal.transform::variable_choices( |
|
| 82 |
#' ADSL, |
|
| 83 |
#' c("AGE", "BMRKR1", "BMRKR2", "RACE", "REGION1")
|
|
| 84 |
#' ), |
|
| 85 |
#' selected = NULL, |
|
| 86 |
#' multiple = FALSE, |
|
| 87 |
#' fixed = FALSE |
|
| 88 |
#' ) |
|
| 89 |
#' ), |
|
| 90 |
#' size_by = teal.transform::data_extract_spec( |
|
| 91 |
#' dataname = "ADSL", |
|
| 92 |
#' select = teal.transform::select_spec( |
|
| 93 |
#' label = "Select variable:", |
|
| 94 |
#' choices = teal.transform::variable_choices( |
|
| 95 |
#' ADSL, |
|
| 96 |
#' c("AGE", "BMRKR1", "BMRKR2", "RACE", "REGION1")
|
|
| 97 |
#' ), |
|
| 98 |
#' selected = "AGE", |
|
| 99 |
#' multiple = FALSE, |
|
| 100 |
#' fixed = FALSE |
|
| 101 |
#' ) |
|
| 102 |
#' ), |
|
| 103 |
#' row_facet = teal.transform::data_extract_spec( |
|
| 104 |
#' dataname = "ADSL", |
|
| 105 |
#' select = teal.transform::select_spec( |
|
| 106 |
#' label = "Select variable:", |
|
| 107 |
#' choices = teal.transform::variable_choices( |
|
| 108 |
#' ADSL, |
|
| 109 |
#' c("BMRKR2", "RACE", "REGION1")
|
|
| 110 |
#' ), |
|
| 111 |
#' selected = NULL, |
|
| 112 |
#' multiple = FALSE, |
|
| 113 |
#' fixed = FALSE |
|
| 114 |
#' ) |
|
| 115 |
#' ), |
|
| 116 |
#' col_facet = teal.transform::data_extract_spec( |
|
| 117 |
#' dataname = "ADSL", |
|
| 118 |
#' select = teal.transform::select_spec( |
|
| 119 |
#' label = "Select variable:", |
|
| 120 |
#' choices = teal.transform::variable_choices( |
|
| 121 |
#' ADSL, |
|
| 122 |
#' c("BMRKR2", "RACE", "REGION1")
|
|
| 123 |
#' ), |
|
| 124 |
#' selected = NULL, |
|
| 125 |
#' multiple = FALSE, |
|
| 126 |
#' fixed = FALSE |
|
| 127 |
#' ) |
|
| 128 |
#' ), |
|
| 129 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 130 |
#' labs = list(subtitle = "Plot generated by Scatterplot Module") |
|
| 131 |
#' ) |
|
| 132 |
#' ) |
|
| 133 |
#' ) |
|
| 134 |
#' ) |
|
| 135 |
#' if (interactive()) {
|
|
| 136 |
#' shinyApp(app$ui, app$server) |
|
| 137 |
#' } |
|
| 138 |
tm_g_scatterplot <- function(label = "Scatterplot", |
|
| 139 |
x, |
|
| 140 |
y, |
|
| 141 |
color_by = NULL, |
|
| 142 |
size_by = NULL, |
|
| 143 |
row_facet = NULL, |
|
| 144 |
col_facet = NULL, |
|
| 145 |
plot_height = c(600, 200, 2000), |
|
| 146 |
plot_width = NULL, |
|
| 147 |
alpha = c(1, 0, 1), |
|
| 148 |
shape = shape_names, |
|
| 149 |
size = c(5, 1, 15), |
|
| 150 |
max_deg = 5L, |
|
| 151 |
rotate_xaxis_labels = FALSE, |
|
| 152 |
ggtheme = c( |
|
| 153 |
"gray", "bw", "linedraw", "light", "dark", |
|
| 154 |
"minimal", "classic", "void", "test" |
|
| 155 |
), |
|
| 156 |
pre_output = NULL, |
|
| 157 |
post_output = NULL, |
|
| 158 |
table_dec = 4, |
|
| 159 |
ggplot2_args = teal.widgets::ggplot2_args()) {
|
|
| 160 | ! |
logger::log_info("Initializing tm_g_scatterplot")
|
| 161 | ||
| 162 | ! |
extra_packages <- c("ggpmisc", "ggExtra", "colourpicker")
|
| 163 | ! |
missing_packages <- Filter(function(x) !requireNamespace(x, quietly = TRUE), extra_packages) |
| 164 | ! |
if (length(missing_packages) > 0L) {
|
| 165 | ! |
stop(sprintf( |
| 166 | ! |
"Cannot load package(s): %s.\nInstall or restart your session.", |
| 167 | ! |
toString(missing_packages) |
| 168 |
)) |
|
| 169 |
} |
|
| 170 | ||
| 171 | ! |
if (inherits(x, "data_extract_spec")) x <- list(x) |
| 172 | ! |
if (inherits(y, "data_extract_spec")) y <- list(y) |
| 173 | ! |
if (inherits(color_by, "data_extract_spec")) color_by <- list(color_by) |
| 174 | ! |
if (inherits(size_by, "data_extract_spec")) size_by <- list(size_by) |
| 175 | ! |
if (inherits(row_facet, "data_extract_spec")) row_facet <- list(row_facet) |
| 176 | ! |
if (inherits(col_facet, "data_extract_spec")) col_facet <- list(col_facet) |
| 177 | ! |
if (is.double(max_deg)) max_deg <- as.integer(max_deg) |
| 178 | ||
| 179 | ! |
ggtheme <- match.arg(ggtheme) |
| 180 | ! |
checkmate::assert_string(label) |
| 181 | ! |
checkmate::assert_list(x, types = "data_extract_spec") |
| 182 | ! |
checkmate::assert_list(y, types = "data_extract_spec") |
| 183 | ! |
checkmate::assert_list(color_by, types = "data_extract_spec", null.ok = TRUE) |
| 184 | ! |
checkmate::assert_list(size_by, types = "data_extract_spec", null.ok = TRUE) |
| 185 | ! |
checkmate::assert_list(row_facet, types = "data_extract_spec", null.ok = TRUE) |
| 186 | ! |
checkmate::assert_list(col_facet, types = "data_extract_spec", null.ok = TRUE) |
| 187 | ! |
checkmate::assert_list(row_facet, types = "data_extract_spec", null.ok = TRUE) |
| 188 | ! |
if (!all(vapply(row_facet, function(x) !x$select$multiple, logical(1)))) {
|
| 189 | ! |
stop("'row_facet' should not allow multiple selection")
|
| 190 |
} |
|
| 191 | ! |
if (!all(vapply(col_facet, function(x) !x$select$multiple, logical(1)))) {
|
| 192 | ! |
stop("'col_facet' should not allow multiple selection")
|
| 193 |
} |
|
| 194 | ! |
checkmate::assert_character(shape) |
| 195 | ||
| 196 | ! |
checkmate::assert_int(max_deg, lower = 1L) |
| 197 | ! |
checkmate::assert_scalar(table_dec) |
| 198 | ! |
checkmate::assert_flag(rotate_xaxis_labels) |
| 199 | ! |
if (length(alpha) == 1) {
|
| 200 | ! |
checkmate::assert_numeric(alpha, any.missing = FALSE, finite = TRUE) |
| 201 |
} else {
|
|
| 202 | ! |
checkmate::assert_numeric(alpha, len = 3, any.missing = FALSE, finite = TRUE) |
| 203 | ! |
checkmate::assert_numeric(alpha[1], lower = alpha[2], upper = alpha[3], .var.name = "alpha") |
| 204 |
} |
|
| 205 | ||
| 206 | ! |
if (length(size) == 1) {
|
| 207 | ! |
checkmate::assert_numeric(size, any.missing = FALSE, finite = TRUE) |
| 208 |
} else {
|
|
| 209 | ! |
checkmate::assert_numeric(size, len = 3, any.missing = FALSE, finite = TRUE) |
| 210 | ! |
checkmate::assert_numeric(size[1], lower = size[2], upper = size[3], .var.name = "size") |
| 211 |
} |
|
| 212 | ||
| 213 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
| 214 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 215 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
| 216 | ! |
checkmate::assert_numeric( |
| 217 | ! |
plot_width[1], |
| 218 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 219 |
) |
|
| 220 | ||
| 221 | ! |
checkmate::assert_class(ggplot2_args, "ggplot2_args") |
| 222 | ||
| 223 | ! |
args <- as.list(environment()) |
| 224 | ||
| 225 | ! |
data_extract_list <- list( |
| 226 | ! |
x = x, |
| 227 | ! |
y = y, |
| 228 | ! |
color_by = color_by, |
| 229 | ! |
size_by = size_by, |
| 230 | ! |
row_facet = row_facet, |
| 231 | ! |
col_facet = col_facet |
| 232 |
) |
|
| 233 | ||
| 234 | ! |
module( |
| 235 | ! |
label = label, |
| 236 | ! |
server = srv_g_scatterplot, |
| 237 | ! |
ui = ui_g_scatterplot, |
| 238 | ! |
ui_args = args, |
| 239 | ! |
server_args = c( |
| 240 | ! |
data_extract_list, |
| 241 | ! |
list(plot_height = plot_height, plot_width = plot_width, table_dec = table_dec, ggplot2_args = ggplot2_args) |
| 242 |
), |
|
| 243 | ! |
datanames = teal.transform::get_extract_datanames(data_extract_list) |
| 244 |
) |
|
| 245 |
} |
|
| 246 | ||
| 247 |
ui_g_scatterplot <- function(id, ...) {
|
|
| 248 | ! |
args <- list(...) |
| 249 | ! |
ns <- NS(id) |
| 250 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset( |
| 251 | ! |
args$x, args$y, args$color_by, args$size_by, args$row_facet, args$col_facet |
| 252 |
) |
|
| 253 | ||
| 254 | ! |
shiny::tagList( |
| 255 | ! |
include_css_files("custom"),
|
| 256 | ! |
teal.widgets::standard_layout( |
| 257 | ! |
output = teal.widgets::white_small_well( |
| 258 | ! |
teal.widgets::plot_with_settings_ui(id = ns("scatter_plot")),
|
| 259 | ! |
tags$h1(tags$strong("Selected points:"), class = "text-center font-150p"),
|
| 260 | ! |
teal.widgets::get_dt_rows(ns("data_table"), ns("data_table_rows")),
|
| 261 | ! |
DT::dataTableOutput(ns("data_table"), width = "100%")
|
| 262 |
), |
|
| 263 | ! |
encoding = div( |
| 264 |
### Reporter |
|
| 265 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 266 |
### |
|
| 267 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 268 | ! |
teal.transform::datanames_input(args[c("x", "y", "color_by", "size_by", "row_facet", "col_facet")]),
|
| 269 | ! |
teal.transform::data_extract_ui( |
| 270 | ! |
id = ns("x"),
|
| 271 | ! |
label = "X variable", |
| 272 | ! |
data_extract_spec = args$x, |
| 273 | ! |
is_single_dataset = is_single_dataset_value |
| 274 |
), |
|
| 275 | ! |
checkboxInput(ns("log_x"), "Use log transformation", value = FALSE),
|
| 276 | ! |
conditionalPanel( |
| 277 | ! |
condition = paste0("input['", ns("log_x"), "'] == true"),
|
| 278 | ! |
radioButtons( |
| 279 | ! |
ns("log_x_base"),
|
| 280 | ! |
label = NULL, |
| 281 | ! |
inline = TRUE, |
| 282 | ! |
choices = c("Natural" = "log", "Base 10" = "log10", "Base 2" = "log2")
|
| 283 |
) |
|
| 284 |
), |
|
| 285 | ! |
teal.transform::data_extract_ui( |
| 286 | ! |
id = ns("y"),
|
| 287 | ! |
label = "Y variable", |
| 288 | ! |
data_extract_spec = args$y, |
| 289 | ! |
is_single_dataset = is_single_dataset_value |
| 290 |
), |
|
| 291 | ! |
checkboxInput(ns("log_y"), "Use log transformation", value = FALSE),
|
| 292 | ! |
conditionalPanel( |
| 293 | ! |
condition = paste0("input['", ns("log_y"), "'] == true"),
|
| 294 | ! |
radioButtons( |
| 295 | ! |
ns("log_y_base"),
|
| 296 | ! |
label = NULL, |
| 297 | ! |
inline = TRUE, |
| 298 | ! |
choices = c("Natural" = "log", "Base 10" = "log10", "Base 2" = "log2")
|
| 299 |
) |
|
| 300 |
), |
|
| 301 | ! |
if (!is.null(args$color_by)) {
|
| 302 | ! |
teal.transform::data_extract_ui( |
| 303 | ! |
id = ns("color_by"),
|
| 304 | ! |
label = "Color by variable", |
| 305 | ! |
data_extract_spec = args$color_by, |
| 306 | ! |
is_single_dataset = is_single_dataset_value |
| 307 |
) |
|
| 308 |
}, |
|
| 309 | ! |
if (!is.null(args$size_by)) {
|
| 310 | ! |
teal.transform::data_extract_ui( |
| 311 | ! |
id = ns("size_by"),
|
| 312 | ! |
label = "Size by variable", |
| 313 | ! |
data_extract_spec = args$size_by, |
| 314 | ! |
is_single_dataset = is_single_dataset_value |
| 315 |
) |
|
| 316 |
}, |
|
| 317 | ! |
if (!is.null(args$row_facet)) {
|
| 318 | ! |
teal.transform::data_extract_ui( |
| 319 | ! |
id = ns("row_facet"),
|
| 320 | ! |
label = "Row facetting", |
| 321 | ! |
data_extract_spec = args$row_facet, |
| 322 | ! |
is_single_dataset = is_single_dataset_value |
| 323 |
) |
|
| 324 |
}, |
|
| 325 | ! |
if (!is.null(args$col_facet)) {
|
| 326 | ! |
teal.transform::data_extract_ui( |
| 327 | ! |
id = ns("col_facet"),
|
| 328 | ! |
label = "Column facetting", |
| 329 | ! |
data_extract_spec = args$col_facet, |
| 330 | ! |
is_single_dataset = is_single_dataset_value |
| 331 |
) |
|
| 332 |
}, |
|
| 333 | ! |
teal.widgets::panel_group( |
| 334 | ! |
teal.widgets::panel_item( |
| 335 | ! |
title = "Plot settings", |
| 336 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("alpha"), "Opacity:", args$alpha, ticks = FALSE),
|
| 337 | ! |
teal.widgets::optionalSelectInput( |
| 338 | ! |
inputId = ns("shape"),
|
| 339 | ! |
label = "Points shape:", |
| 340 | ! |
choices = args$shape, |
| 341 | ! |
selected = args$shape[1], |
| 342 | ! |
multiple = FALSE |
| 343 |
), |
|
| 344 | ! |
colourpicker::colourInput(ns("color"), "Points color:", "black"),
|
| 345 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("size"), "Points size:", args$size, ticks = FALSE, step = .1),
|
| 346 | ! |
checkboxInput(ns("rotate_xaxis_labels"), "Rotate X axis labels", value = args$rotate_xaxis_labels),
|
| 347 | ! |
checkboxInput(ns("add_density"), "Add marginal density", value = FALSE),
|
| 348 | ! |
checkboxInput(ns("rug_plot"), "Include rug plot", value = FALSE),
|
| 349 | ! |
checkboxInput(ns("show_count"), "Show N (number of observations)", value = FALSE),
|
| 350 | ! |
shinyjs::hidden(helpText(id = ns("line_msg"), "Trendline needs numeric X and Y variables")),
|
| 351 | ! |
teal.widgets::optionalSelectInput(ns("smoothing_degree"), "Smoothing degree", seq_len(args$max_deg)),
|
| 352 | ! |
shinyjs::hidden(teal.widgets::optionalSelectInput(ns("color_sub"), label = "", multiple = TRUE)),
|
| 353 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("ci"), "Confidence", c(.95, .8, .99), ticks = FALSE),
|
| 354 | ! |
shinyjs::hidden(checkboxInput(ns("show_form"), "Show formula", value = TRUE)),
|
| 355 | ! |
shinyjs::hidden(checkboxInput(ns("show_r2"), "Show adj-R Squared", value = TRUE)),
|
| 356 | ! |
uiOutput(ns("num_na_removed")),
|
| 357 | ! |
div( |
| 358 | ! |
id = ns("label_pos"),
|
| 359 | ! |
div(strong("Stats position")),
|
| 360 | ! |
div(class = "inline-block w-10", helpText("Left")),
|
| 361 | ! |
div( |
| 362 | ! |
class = "inline-block w-70", |
| 363 | ! |
teal.widgets::optionalSliderInput( |
| 364 | ! |
ns("pos"),
|
| 365 | ! |
label = NULL, |
| 366 | ! |
min = 0, max = 1, value = .99, ticks = FALSE, step = .01 |
| 367 |
) |
|
| 368 |
), |
|
| 369 | ! |
div(class = "inline-block w-10", helpText("Right"))
|
| 370 |
), |
|
| 371 | ! |
teal.widgets::optionalSliderInput( |
| 372 | ! |
ns("label_size"), "Stats font size",
|
| 373 | ! |
min = 3, max = 10, value = 5, ticks = FALSE, step = .1 |
| 374 |
), |
|
| 375 | ! |
if (!is.null(args$row_facet) || !is.null(args$col_facet)) {
|
| 376 | ! |
checkboxInput(ns("free_scales"), "Free scales", value = FALSE)
|
| 377 |
}, |
|
| 378 | ! |
selectInput( |
| 379 | ! |
inputId = ns("ggtheme"),
|
| 380 | ! |
label = "Theme (by ggplot):", |
| 381 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 382 | ! |
selected = args$ggtheme, |
| 383 | ! |
multiple = FALSE |
| 384 |
) |
|
| 385 |
) |
|
| 386 |
) |
|
| 387 |
), |
|
| 388 | ! |
forms = tagList( |
| 389 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), button_label = "Show Warnings"),
|
| 390 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 391 |
), |
|
| 392 | ! |
pre_output = args$pre_output, |
| 393 | ! |
post_output = args$post_output |
| 394 |
) |
|
| 395 |
) |
|
| 396 |
} |
|
| 397 | ||
| 398 |
srv_g_scatterplot <- function(id, |
|
| 399 |
data, |
|
| 400 |
reporter, |
|
| 401 |
filter_panel_api, |
|
| 402 |
x, |
|
| 403 |
y, |
|
| 404 |
color_by, |
|
| 405 |
size_by, |
|
| 406 |
row_facet, |
|
| 407 |
col_facet, |
|
| 408 |
plot_height, |
|
| 409 |
plot_width, |
|
| 410 |
table_dec, |
|
| 411 |
ggplot2_args) {
|
|
| 412 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 413 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 414 | ! |
checkmate::assert_class(data, "tdata") |
| 415 | ! |
moduleServer(id, function(input, output, session) {
|
| 416 | ! |
data_extract <- list( |
| 417 | ! |
x = x, |
| 418 | ! |
y = y, |
| 419 | ! |
color_by = color_by, |
| 420 | ! |
size_by = size_by, |
| 421 | ! |
row_facet = row_facet, |
| 422 | ! |
col_facet = col_facet |
| 423 |
) |
|
| 424 | ||
| 425 | ! |
rule_diff <- function(other) {
|
| 426 | ! |
function(value) {
|
| 427 | ! |
othervalue <- selector_list()[[other]]()[["select"]] |
| 428 | ! |
if (!is.null(othervalue)) {
|
| 429 | ! |
if (identical(value, othervalue)) {
|
| 430 | ! |
"Row and column facetting variables must be different." |
| 431 |
} |
|
| 432 |
} |
|
| 433 |
} |
|
| 434 |
} |
|
| 435 | ||
| 436 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 437 | ! |
data_extract = data_extract, |
| 438 | ! |
datasets = data, |
| 439 | ! |
select_validation_rule = list( |
| 440 | ! |
x = ~ if (length(.) != 1) "Please select exactly one x var.", |
| 441 | ! |
y = ~ if (length(.) != 1) "Please select exactly one y var.", |
| 442 | ! |
color_by = ~ if (length(.) > 1) "There cannot be more than 1 color variable.", |
| 443 | ! |
size_by = ~ if (length(.) > 1) "There cannot be more than 1 size variable.", |
| 444 | ! |
row_facet = shinyvalidate::compose_rules( |
| 445 | ! |
shinyvalidate::sv_optional(), |
| 446 | ! |
rule_diff("col_facet")
|
| 447 |
), |
|
| 448 | ! |
col_facet = shinyvalidate::compose_rules( |
| 449 | ! |
shinyvalidate::sv_optional(), |
| 450 | ! |
rule_diff("row_facet")
|
| 451 |
) |
|
| 452 |
) |
|
| 453 |
) |
|
| 454 | ||
| 455 | ! |
iv_r <- reactive({
|
| 456 | ! |
iv_facet <- shinyvalidate::InputValidator$new() |
| 457 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 458 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list) |
| 459 |
}) |
|
| 460 | ! |
iv_facet <- shinyvalidate::InputValidator$new() |
| 461 | ! |
iv_facet$add_rule("add_density", ~ if (isTRUE(.) &&
|
| 462 | ! |
(length(selector_list()$row_facet()$select) > 0L || |
| 463 | ! |
length(selector_list()$col_facet()$select) > 0L)) {
|
| 464 | ! |
"Cannot add marginal density when Row or Column facetting has been selected" |
| 465 |
}) |
|
| 466 | ! |
iv_facet$enable() |
| 467 | ||
| 468 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 469 | ! |
selector_list = selector_list, |
| 470 | ! |
datasets = data, |
| 471 | ! |
join_keys = get_join_keys(data), |
| 472 | ! |
merge_function = "dplyr::inner_join" |
| 473 |
) |
|
| 474 | ||
| 475 | ! |
anl_merged_q <- reactive({
|
| 476 | ! |
req(anl_merged_input()) |
| 477 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 478 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 479 |
}) |
|
| 480 | ||
| 481 | ! |
merged <- list( |
| 482 | ! |
anl_input_r = anl_merged_input, |
| 483 | ! |
anl_q_r = anl_merged_q |
| 484 |
) |
|
| 485 | ||
| 486 | ! |
trend_line_is_applicable <- reactive({
|
| 487 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 488 | ! |
x_var <- as.vector(merged$anl_input_r()$columns_source$x) |
| 489 | ! |
y_var <- as.vector(merged$anl_input_r()$columns_source$y) |
| 490 | ! |
length(x_var) > 0 && length(y_var) > 0 && is.numeric(ANL[[x_var]]) && is.numeric(ANL[[y_var]]) |
| 491 |
}) |
|
| 492 | ||
| 493 | ! |
add_trend_line <- reactive({
|
| 494 | ! |
smoothing_degree <- as.integer(input$smoothing_degree) |
| 495 | ! |
trend_line_is_applicable() && length(smoothing_degree) > 0 |
| 496 |
}) |
|
| 497 | ||
| 498 | ! |
if (!is.null(color_by)) {
|
| 499 | ! |
observeEvent( |
| 500 | ! |
eventExpr = merged$anl_input_r()$columns_source$color_by, |
| 501 | ! |
handlerExpr = {
|
| 502 | ! |
color_by_var <- as.vector(merged$anl_input_r()$columns_source$color_by) |
| 503 | ! |
if (length(color_by_var) > 0) {
|
| 504 | ! |
shinyjs::hide("color")
|
| 505 |
} else {
|
|
| 506 | ! |
shinyjs::show("color")
|
| 507 |
} |
|
| 508 |
} |
|
| 509 |
) |
|
| 510 |
} |
|
| 511 | ||
| 512 | ! |
output$num_na_removed <- renderUI({
|
| 513 | ! |
if (add_trend_line()) {
|
| 514 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 515 | ! |
x_var <- as.vector(merged$anl_input_r()$columns_source$x) |
| 516 | ! |
y_var <- as.vector(merged$anl_input_r()$columns_source$y) |
| 517 | ! |
if ((num_total_na <- nrow(ANL) - nrow(stats::na.omit(ANL[, c(x_var, y_var)]))) > 0) {
|
| 518 | ! |
shiny::tags$div(paste(num_total_na, "row(s) with missing values were removed"), shiny::tags$hr()) |
| 519 |
} |
|
| 520 |
} |
|
| 521 |
}) |
|
| 522 | ||
| 523 | ! |
observeEvent( |
| 524 | ! |
eventExpr = merged$anl_input_r()$columns_source[c("col_facet", "row_facet")],
|
| 525 | ! |
handlerExpr = {
|
| 526 | ! |
if (length(merged$anl_input_r()$columns_source$col_facet) == 0 && |
| 527 | ! |
length(merged$anl_input_r()$columns_source$row_facet) == 0) {
|
| 528 | ! |
shinyjs::hide("free_scales")
|
| 529 |
} else {
|
|
| 530 | ! |
shinyjs::show("free_scales")
|
| 531 |
} |
|
| 532 |
} |
|
| 533 |
) |
|
| 534 | ||
| 535 | ! |
output_q <- reactive({
|
| 536 | ! |
teal::validate_inputs(iv_r(), iv_facet) |
| 537 | ||
| 538 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 539 | ||
| 540 | ! |
x_var <- as.vector(merged$anl_input_r()$columns_source$x) |
| 541 | ! |
y_var <- as.vector(merged$anl_input_r()$columns_source$y) |
| 542 | ! |
color_by_var <- as.vector(merged$anl_input_r()$columns_source$color_by) |
| 543 | ! |
size_by_var <- as.vector(merged$anl_input_r()$columns_source$size_by) |
| 544 | ! |
row_facet_name <- if (length(merged$anl_input_r()$columns_source$row_facet) == 0) {
|
| 545 | ! |
character(0) |
| 546 |
} else {
|
|
| 547 | ! |
as.vector(merged$anl_input_r()$columns_source$row_facet) |
| 548 |
} |
|
| 549 | ! |
col_facet_name <- if (length(merged$anl_input_r()$columns_source$col_facet) == 0) {
|
| 550 | ! |
character(0) |
| 551 |
} else {
|
|
| 552 | ! |
as.vector(merged$anl_input_r()$columns_source$col_facet) |
| 553 |
} |
|
| 554 | ! |
alpha <- input$alpha # nolint |
| 555 | ! |
size <- input$size # nolint |
| 556 | ! |
rotate_xaxis_labels <- input$rotate_xaxis_labels # nolint |
| 557 | ! |
add_density <- input$add_density |
| 558 | ! |
ggtheme <- input$ggtheme |
| 559 | ! |
rug_plot <- input$rug_plot |
| 560 | ! |
color <- input$color # nolint |
| 561 | ! |
shape <- `if`(is.null(input$shape) || identical(input$shape, ""), "circle", input$shape) # nolint |
| 562 | ! |
smoothing_degree <- as.integer(input$smoothing_degree) |
| 563 | ! |
ci <- input$ci # nolint |
| 564 | ||
| 565 | ! |
log_x <- input$log_x |
| 566 | ! |
log_y <- input$log_y |
| 567 | ||
| 568 | ! |
validate(need( |
| 569 | ! |
length(row_facet_name) == 0 || inherits(ANL[[row_facet_name]], c("character", "factor", "Date", "integer")),
|
| 570 | ! |
"`Row facetting` variable must be of class `character`, `factor`, `Date`, or `integer`" |
| 571 |
)) |
|
| 572 | ! |
validate(need( |
| 573 | ! |
length(col_facet_name) == 0 || inherits(ANL[[col_facet_name]], c("character", "factor", "Date", "integer")),
|
| 574 | ! |
"`Column facetting` variable must be of class `character`, `factor`, `Date`, or `integer`" |
| 575 |
)) |
|
| 576 | ||
| 577 | ! |
if (add_density && length(color_by_var) > 0) {
|
| 578 | ! |
validate(need( |
| 579 | ! |
!is.numeric(ANL[[color_by_var]]), |
| 580 | ! |
"Marginal plots cannot be produced when the points are colored by numeric variables. |
| 581 | ! |
\n Uncheck the 'Add marginal density' checkbox to display the plot." |
| 582 |
)) |
|
| 583 | ! |
validate(need( |
| 584 | ! |
!(inherits(ANL[[color_by_var]], "Date") || |
| 585 | ! |
inherits(ANL[[color_by_var]], "POSIXct") || |
| 586 | ! |
inherits(ANL[[color_by_var]], "POSIXlt")), |
| 587 | ! |
"Marginal plots cannot be produced when the points are colored by Date or POSIX variables. |
| 588 | ! |
\n Uncheck the 'Add marginal density' checkbox to display the plot." |
| 589 |
)) |
|
| 590 |
} |
|
| 591 | ||
| 592 | ! |
teal::validate_has_data(ANL[, c(x_var, y_var)], 10, complete = TRUE, allow_inf = FALSE) |
| 593 | ||
| 594 | ! |
if (log_x) {
|
| 595 | ! |
validate( |
| 596 | ! |
need( |
| 597 | ! |
is.numeric(ANL[[x_var]]) && all( |
| 598 | ! |
ANL[[x_var]] > 0 | is.na(ANL[[x_var]]) |
| 599 |
), |
|
| 600 | ! |
"X variable can only be log transformed if variable is numeric and all values are positive." |
| 601 |
) |
|
| 602 |
) |
|
| 603 |
} |
|
| 604 | ! |
if (log_y) {
|
| 605 | ! |
validate( |
| 606 | ! |
need( |
| 607 | ! |
is.numeric(ANL[[y_var]]) && all( |
| 608 | ! |
ANL[[y_var]] > 0 | is.na(ANL[[y_var]]) |
| 609 |
), |
|
| 610 | ! |
"Y variable can only be log transformed if variable is numeric and all values are positive." |
| 611 |
) |
|
| 612 |
) |
|
| 613 |
} |
|
| 614 | ||
| 615 | ! |
facet_cl <- facet_ggplot_call( |
| 616 | ! |
row_facet_name, |
| 617 | ! |
col_facet_name, |
| 618 | ! |
free_x_scales = isTRUE(input$free_scales), |
| 619 | ! |
free_y_scales = isTRUE(input$free_scales) |
| 620 |
) |
|
| 621 | ||
| 622 | ! |
point_sizes <- if (length(size_by_var) > 0) {
|
| 623 | ! |
validate(need(is.numeric(ANL[[size_by_var]]), "Variable to size by must be numeric")) |
| 624 | ! |
substitute( |
| 625 | ! |
expr = size * ANL[[size_by_var]] / max(ANL[[size_by_var]], na.rm = TRUE), |
| 626 | ! |
env = list(size = size, size_by_var = size_by_var) |
| 627 |
) |
|
| 628 |
} else {
|
|
| 629 | ! |
size |
| 630 |
} |
|
| 631 | ||
| 632 | ! |
plot_q <- merged$anl_q_r() |
| 633 | ||
| 634 | ! |
if (log_x) {
|
| 635 | ! |
log_x_fn <- input$log_x_base |
| 636 | ! |
plot_q <- teal.code::eval_code( |
| 637 | ! |
object = plot_q, |
| 638 | ! |
code = substitute( |
| 639 | ! |
expr = ANL[, log_x_var] <- log_x_fn(ANL[, x_var]), # nolint |
| 640 | ! |
env = list( |
| 641 | ! |
x_var = x_var, |
| 642 | ! |
log_x_fn = as.name(log_x_fn), |
| 643 | ! |
log_x_var = paste0(log_x_fn, "_", x_var) |
| 644 |
) |
|
| 645 |
) |
|
| 646 |
) |
|
| 647 |
} |
|
| 648 | ||
| 649 | ! |
if (log_y) {
|
| 650 | ! |
log_y_fn <- input$log_y_base |
| 651 | ! |
plot_q <- teal.code::eval_code( |
| 652 | ! |
object = plot_q, |
| 653 | ! |
code = substitute( |
| 654 | ! |
expr = ANL[, log_y_var] <- log_y_fn(ANL[, y_var]), # nolint |
| 655 | ! |
env = list( |
| 656 | ! |
y_var = y_var, |
| 657 | ! |
log_y_fn = as.name(log_y_fn), |
| 658 | ! |
log_y_var = paste0(log_y_fn, "_", y_var) |
| 659 |
) |
|
| 660 |
) |
|
| 661 |
) |
|
| 662 |
} |
|
| 663 | ||
| 664 | ! |
pre_pro_anl <- if (input$show_count) {
|
| 665 | ! |
paste0( |
| 666 | ! |
"ANL %>% dplyr::group_by(",
|
| 667 | ! |
paste( |
| 668 | ! |
c( |
| 669 | ! |
if (length(color_by_var) > 0 && inherits(ANL[[color_by_var]], c("factor", "character"))) color_by_var,
|
| 670 | ! |
row_facet_name, |
| 671 | ! |
col_facet_name |
| 672 |
), |
|
| 673 | ! |
collapse = ", " |
| 674 |
), |
|
| 675 | ! |
") %>% dplyr::mutate(n = dplyr::n()) %>% dplyr::ungroup()" |
| 676 |
) |
|
| 677 |
} else {
|
|
| 678 | ! |
"ANL" |
| 679 |
} |
|
| 680 | ||
| 681 | ! |
plot_call <- substitute(expr = pre_pro_anl %>% ggplot(), env = list(pre_pro_anl = str2lang(pre_pro_anl))) |
| 682 | ||
| 683 | ! |
plot_call <- if (length(color_by_var) == 0) {
|
| 684 | ! |
substitute( |
| 685 | ! |
expr = plot_call + |
| 686 | ! |
ggplot2::aes(x = x_name, y = y_name) + |
| 687 | ! |
ggplot2::geom_point(alpha = alpha_value, size = point_sizes, shape = shape_value, color = color_value), |
| 688 | ! |
env = list( |
| 689 | ! |
plot_call = plot_call, |
| 690 | ! |
x_name = if (log_x) as.name(paste0(log_x_fn, "_", x_var)) else as.name(x_var), |
| 691 | ! |
y_name = if (log_y) as.name(paste0(log_y_fn, "_", y_var)) else as.name(y_var), |
| 692 | ! |
alpha_value = alpha, |
| 693 | ! |
point_sizes = point_sizes, |
| 694 | ! |
shape_value = shape, |
| 695 | ! |
color_value = color |
| 696 |
) |
|
| 697 |
) |
|
| 698 |
} else {
|
|
| 699 | ! |
substitute( |
| 700 | ! |
expr = plot_call + |
| 701 | ! |
ggplot2::aes(x = x_name, y = y_name, color = color_by_var_name) + |
| 702 | ! |
ggplot2::geom_point(alpha = alpha_value, size = point_sizes, shape = shape_value), |
| 703 | ! |
env = list( |
| 704 | ! |
plot_call = plot_call, |
| 705 | ! |
x_name = if (log_x) as.name(paste0(log_x_fn, "_", x_var)) else as.name(x_var), |
| 706 | ! |
y_name = if (log_y) as.name(paste0(log_y_fn, "_", y_var)) else as.name(y_var), |
| 707 | ! |
color_by_var_name = as.name(color_by_var), |
| 708 | ! |
alpha_value = alpha, |
| 709 | ! |
point_sizes = point_sizes, |
| 710 | ! |
shape_value = shape |
| 711 |
) |
|
| 712 |
) |
|
| 713 |
} |
|
| 714 | ||
| 715 | ! |
if (rug_plot) plot_call <- substitute(expr = plot_call + geom_rug(), env = list(plot_call = plot_call)) |
| 716 | ||
| 717 | ! |
plot_label_generator <- function(rhs_formula = quote(y ~ 1), |
| 718 | ! |
show_form = input$show_form, |
| 719 | ! |
show_r2 = input$show_r2, |
| 720 | ! |
show_count = input$show_count, |
| 721 | ! |
pos = input$pos, |
| 722 | ! |
label_size = input$label_size) {
|
| 723 | ! |
stopifnot(sum(show_form, show_r2, show_count) >= 1) |
| 724 | ! |
aes_label <- paste0( |
| 725 | ! |
"aes(",
|
| 726 | ! |
if (show_count) "n = n, ", |
| 727 | ! |
"label = ", |
| 728 | ! |
if (sum(show_form, show_r2, show_count) > 1) "paste(",
|
| 729 | ! |
paste( |
| 730 | ! |
c( |
| 731 | ! |
if (show_form) "stat(eq.label)", |
| 732 | ! |
if (show_r2) "stat(adj.rr.label)", |
| 733 | ! |
if (show_count) "paste('N ~`=`~', n)"
|
| 734 |
), |
|
| 735 | ! |
collapse = ", " |
| 736 |
), |
|
| 737 | ! |
if (sum(show_form, show_r2, show_count) > 1) ", sep = '*\", \"*'))" else ")" |
| 738 |
) |
|
| 739 | ! |
label_geom <- substitute( |
| 740 | ! |
expr = ggpmisc::stat_poly_eq( |
| 741 | ! |
mapping = aes_label, |
| 742 | ! |
formula = rhs_formula, |
| 743 | ! |
parse = TRUE, |
| 744 | ! |
label.x = pos, |
| 745 | ! |
size = label_size |
| 746 |
), |
|
| 747 | ! |
env = list( |
| 748 | ! |
rhs_formula = rhs_formula, |
| 749 | ! |
pos = pos, |
| 750 | ! |
aes_label = str2lang(aes_label), |
| 751 | ! |
label_size = label_size |
| 752 |
) |
|
| 753 |
) |
|
| 754 | ! |
substitute( |
| 755 | ! |
expr = plot_call + label_geom, |
| 756 | ! |
env = list( |
| 757 | ! |
plot_call = plot_call, |
| 758 | ! |
label_geom = label_geom |
| 759 |
) |
|
| 760 |
) |
|
| 761 |
} |
|
| 762 | ||
| 763 | ! |
if (trend_line_is_applicable()) {
|
| 764 | ! |
shinyjs::hide("line_msg")
|
| 765 | ! |
shinyjs::show("smoothing_degree")
|
| 766 | ! |
if (!add_trend_line()) {
|
| 767 | ! |
shinyjs::hide("ci")
|
| 768 | ! |
shinyjs::hide("color_sub")
|
| 769 | ! |
shinyjs::hide("show_form")
|
| 770 | ! |
shinyjs::hide("show_r2")
|
| 771 | ! |
if (input$show_count) {
|
| 772 | ! |
plot_call <- plot_label_generator(show_form = FALSE, show_r2 = FALSE) |
| 773 | ! |
shinyjs::show("label_pos")
|
| 774 | ! |
shinyjs::show("label_size")
|
| 775 |
} else {
|
|
| 776 | ! |
shinyjs::hide("label_pos")
|
| 777 | ! |
shinyjs::hide("label_size")
|
| 778 |
} |
|
| 779 |
} else {
|
|
| 780 | ! |
shinyjs::show("ci")
|
| 781 | ! |
shinyjs::show("show_form")
|
| 782 | ! |
shinyjs::show("show_r2")
|
| 783 | ! |
if (nrow(ANL) - nrow(stats::na.omit(ANL[, c(x_var, y_var)])) > 0) {
|
| 784 | ! |
plot_q <- teal.code::eval_code( |
| 785 | ! |
plot_q, |
| 786 | ! |
substitute( |
| 787 | ! |
expr = ANL <- dplyr::filter(ANL, !is.na(x_var) & !is.na(y_var)), # nolint |
| 788 | ! |
env = list(x_var = as.name(x_var), y_var = as.name(y_var)) |
| 789 |
) |
|
| 790 |
) |
|
| 791 |
} |
|
| 792 | ! |
rhs_formula <- substitute( |
| 793 | ! |
expr = y ~ poly(x, smoothing_degree, raw = TRUE), |
| 794 | ! |
env = list(smoothing_degree = smoothing_degree) |
| 795 |
) |
|
| 796 | ! |
if (input$show_form || input$show_r2 || input$show_count) {
|
| 797 | ! |
plot_call <- plot_label_generator(rhs_formula = rhs_formula) |
| 798 | ! |
shinyjs::show("label_pos")
|
| 799 | ! |
shinyjs::show("label_size")
|
| 800 |
} else {
|
|
| 801 | ! |
shinyjs::hide("label_pos")
|
| 802 | ! |
shinyjs::hide("label_size")
|
| 803 |
} |
|
| 804 | ! |
plot_call <- substitute( |
| 805 | ! |
expr = plot_call + ggplot2::geom_smooth(formula = rhs_formula, se = TRUE, level = ci, method = "lm"), |
| 806 | ! |
env = list(plot_call = plot_call, rhs_formula = rhs_formula, ci = ci) |
| 807 |
) |
|
| 808 |
} |
|
| 809 |
} else {
|
|
| 810 | ! |
shinyjs::hide("smoothing_degree")
|
| 811 | ! |
shinyjs::hide("ci")
|
| 812 | ! |
shinyjs::hide("color_sub")
|
| 813 | ! |
shinyjs::hide("show_form")
|
| 814 | ! |
shinyjs::hide("show_r2")
|
| 815 | ! |
if (input$show_count) {
|
| 816 | ! |
plot_call <- plot_label_generator(show_form = FALSE, show_r2 = FALSE) |
| 817 | ! |
shinyjs::show("label_pos")
|
| 818 | ! |
shinyjs::show("label_size")
|
| 819 |
} else {
|
|
| 820 | ! |
shinyjs::hide("label_pos")
|
| 821 | ! |
shinyjs::hide("label_size")
|
| 822 |
} |
|
| 823 | ! |
shinyjs::show("line_msg")
|
| 824 |
} |
|
| 825 | ||
| 826 | ! |
if (!is.null(facet_cl)) {
|
| 827 | ! |
plot_call <- substitute(expr = plot_call + facet_cl, env = list(plot_call = plot_call, facet_cl = facet_cl)) |
| 828 |
} |
|
| 829 | ||
| 830 | ! |
y_label <- varname_w_label( |
| 831 | ! |
y_var, |
| 832 | ! |
ANL, |
| 833 | ! |
prefix = if (log_y) paste(log_y_fn, "(") else NULL,
|
| 834 | ! |
suffix = if (log_y) ")" else NULL |
| 835 |
) |
|
| 836 | ! |
x_label <- varname_w_label( |
| 837 | ! |
x_var, |
| 838 | ! |
ANL, |
| 839 | ! |
prefix = if (log_x) paste(log_x_fn, "(") else NULL,
|
| 840 | ! |
suffix = if (log_x) ")" else NULL |
| 841 |
) |
|
| 842 | ||
| 843 | ! |
dev_ggplot2_args <- teal.widgets::ggplot2_args( |
| 844 | ! |
labs = list(y = y_label, x = x_label), |
| 845 | ! |
theme = list(legend.position = "bottom") |
| 846 |
) |
|
| 847 | ||
| 848 | ! |
if (rotate_xaxis_labels) {
|
| 849 | ! |
dev_ggplot2_args$theme[["axis.text.x"]] <- quote(element_text(angle = 45, hjust = 1)) # nolint |
| 850 |
} |
|
| 851 | ||
| 852 | ! |
all_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 853 | ! |
user_plot = ggplot2_args, |
| 854 | ! |
module_plot = dev_ggplot2_args |
| 855 |
) |
|
| 856 | ||
| 857 | ! |
parsed_ggplot2_args <- teal.widgets::parse_ggplot2_args(all_ggplot2_args, ggtheme = ggtheme) |
| 858 | ||
| 859 | ||
| 860 | ! |
if (add_density) {
|
| 861 | ! |
plot_call <- substitute( |
| 862 | ! |
expr = ggExtra::ggMarginal( |
| 863 | ! |
plot_call + labs + ggthemes + themes, |
| 864 | ! |
type = "density", |
| 865 | ! |
groupColour = group_colour |
| 866 |
), |
|
| 867 | ! |
env = list( |
| 868 | ! |
plot_call = plot_call, |
| 869 | ! |
group_colour = if (length(color_by_var) > 0) TRUE else FALSE, |
| 870 | ! |
labs = parsed_ggplot2_args$labs, |
| 871 | ! |
ggthemes = parsed_ggplot2_args$ggtheme, |
| 872 | ! |
themes = parsed_ggplot2_args$theme |
| 873 |
) |
|
| 874 |
) |
|
| 875 |
} else {
|
|
| 876 | ! |
plot_call <- substitute( |
| 877 | ! |
expr = plot_call + |
| 878 | ! |
labs + |
| 879 | ! |
ggthemes + |
| 880 | ! |
themes, |
| 881 | ! |
env = list( |
| 882 | ! |
plot_call = plot_call, |
| 883 | ! |
labs = parsed_ggplot2_args$labs, |
| 884 | ! |
ggthemes = parsed_ggplot2_args$ggtheme, |
| 885 | ! |
themes = parsed_ggplot2_args$theme |
| 886 |
) |
|
| 887 |
) |
|
| 888 |
} |
|
| 889 | ||
| 890 | ! |
plot_call <- substitute(expr = p <- plot_call, env = list(plot_call = plot_call)) |
| 891 | ||
| 892 | ! |
teal.code::eval_code(plot_q, plot_call) %>% |
| 893 | ! |
teal.code::eval_code(quote(print(p))) |
| 894 |
}) |
|
| 895 | ||
| 896 | ! |
plot_r <- reactive(output_q()[["p"]]) |
| 897 | ||
| 898 |
# Insert the plot into a plot_with_settings module from teal.widgets |
|
| 899 | ! |
pws <- teal.widgets::plot_with_settings_srv( |
| 900 | ! |
id = "scatter_plot", |
| 901 | ! |
plot_r = plot_r, |
| 902 | ! |
height = plot_height, |
| 903 | ! |
width = plot_width, |
| 904 | ! |
brushing = TRUE |
| 905 |
) |
|
| 906 | ||
| 907 | ! |
output$data_table <- DT::renderDataTable({
|
| 908 | ! |
plot_brush <- pws$brush() |
| 909 | ||
| 910 | ! |
if (!is.null(plot_brush)) {
|
| 911 | ! |
validate(need(!input$add_density, "Brushing feature is currently not supported when plot has marginal density")) |
| 912 |
} |
|
| 913 | ||
| 914 | ! |
merged_data <- isolate(teal.code::dev_suppress(output_q()[["ANL"]])) |
| 915 | ||
| 916 | ! |
brushed_df <- teal.widgets::clean_brushedPoints(merged_data, plot_brush) |
| 917 | ! |
numeric_cols <- names(brushed_df)[ |
| 918 | ! |
vapply(brushed_df, function(x) is.numeric(x) && !is.integer(x), FUN.VALUE = logical(1)) |
| 919 |
] |
|
| 920 | ||
| 921 | ! |
if (length(numeric_cols) > 0) {
|
| 922 | ! |
DT::formatRound( |
| 923 | ! |
DT::datatable(brushed_df, |
| 924 | ! |
rownames = FALSE, |
| 925 | ! |
options = list(scrollX = TRUE, pageLength = input$data_table_rows) |
| 926 |
), |
|
| 927 | ! |
numeric_cols, |
| 928 | ! |
table_dec |
| 929 |
) |
|
| 930 |
} else {
|
|
| 931 | ! |
DT::datatable(brushed_df, rownames = FALSE, options = list(scrollX = TRUE, pageLength = input$data_table_rows)) |
| 932 |
} |
|
| 933 |
}) |
|
| 934 | ||
| 935 | ! |
teal.widgets::verbatim_popup_srv( |
| 936 | ! |
id = "warning", |
| 937 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 938 | ! |
title = "Warning", |
| 939 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 940 |
) |
|
| 941 | ||
| 942 | ! |
teal.widgets::verbatim_popup_srv( |
| 943 | ! |
id = "rcode", |
| 944 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 945 | ! |
title = "R Code for scatterplot" |
| 946 |
) |
|
| 947 | ||
| 948 |
### REPORTER |
|
| 949 | ! |
if (with_reporter) {
|
| 950 | ! |
card_fun <- function(comment) {
|
| 951 | ! |
card <- teal::TealReportCard$new() |
| 952 | ! |
card$set_name("Scatter Plot")
|
| 953 | ! |
card$append_text("Scatter Plot", "header2")
|
| 954 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 955 | ! |
card$append_text("Plot", "header3")
|
| 956 | ! |
card$append_plot(plot_r(), dim = pws$dim()) |
| 957 | ! |
if (!comment == "") {
|
| 958 | ! |
card$append_text("Comment", "header3")
|
| 959 | ! |
card$append_text(comment) |
| 960 |
} |
|
| 961 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 962 | ! |
card |
| 963 |
} |
|
| 964 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 965 |
} |
|
| 966 |
### |
|
| 967 |
}) |
|
| 968 |
} |
| 1 |
#' Front page module |
|
| 2 |
#' |
|
| 3 |
#' @description This `teal` module creates a simple front page for `teal` applications |
|
| 4 |
#' |
|
| 5 |
#' @inheritParams teal::module |
|
| 6 |
#' @param header_text `character vector` text to be shown at the top of the module, for each |
|
| 7 |
#' element, if named the name is shown first in bold as a header followed by the value. The first |
|
| 8 |
#' element's header is displayed larger than the others |
|
| 9 |
#' @param tables `named list of dataframes` tables to be shown in the module |
|
| 10 |
#' @param additional_tags `shiny.tag.list` or `html` additional shiny tags or `html` to be included after the table, |
|
| 11 |
#' for example to include an image, `tagList(tags$img(src = "image.png"))` or to include further `html`, |
|
| 12 |
#' `HTML("html text here")`
|
|
| 13 |
#' @param footnotes `character vector` text to be shown at the bottom of the module, for each |
|
| 14 |
#' element, if named the name is shown first in bold, followed by the value |
|
| 15 |
#' @param show_metadata `logical` should the metadata of the datasets be available on the module? |
|
| 16 |
#' @return A `teal` module to be used in `teal` applications |
|
| 17 |
#' @export |
|
| 18 |
#' @examples |
|
| 19 |
#' |
|
| 20 |
#' table_1 <- data.frame(Info = c("A", "B"), Text = c("A", "B"))
|
|
| 21 |
#' table_2 <- data.frame(`Column 1` = c("C", "D"), `Column 2` = c(5.5, 6.6), `Column 3` = c("A", "B"))
|
|
| 22 |
#' table_3 <- data.frame(Info = c("E", "F"), Text = c("G", "H"))
|
|
| 23 |
#' |
|
| 24 |
#' table_input <- list( |
|
| 25 |
#' "Table 1" = table_1, |
|
| 26 |
#' "Table 2" = table_2, |
|
| 27 |
#' "Table 3" = table_3 |
|
| 28 |
#' ) |
|
| 29 |
#' |
|
| 30 |
#' ADSL <- teal.modules.general::rADSL |
|
| 31 |
#' app <- teal::init( |
|
| 32 |
#' data = teal.data::cdisc_data( |
|
| 33 |
#' teal.data::cdisc_dataset("ADSL", ADSL,
|
|
| 34 |
#' code = "ADSL <- teal.modules.general::rADSL", |
|
| 35 |
#' metadata = list("Author" = "NEST team", "data_source" = "synthetic data")
|
|
| 36 |
#' ), |
|
| 37 |
#' check = TRUE |
|
| 38 |
#' ), |
|
| 39 |
#' modules = teal::modules( |
|
| 40 |
#' teal.modules.general::tm_front_page( |
|
| 41 |
#' header_text = c( |
|
| 42 |
#' "Important information" = "It can go here.", |
|
| 43 |
#' "Other information" = "Can go here." |
|
| 44 |
#' ), |
|
| 45 |
#' tables = table_input, |
|
| 46 |
#' additional_tags = HTML("Additional HTML or shiny tags go here <br>"),
|
|
| 47 |
#' footnotes = c("X" = "is the first footnote", "Y is the second footnote"),
|
|
| 48 |
#' show_metadata = TRUE |
|
| 49 |
#' ) |
|
| 50 |
#' ), |
|
| 51 |
#' header = tags$h1("Sample Application"),
|
|
| 52 |
#' footer = tags$p("Application footer"),
|
|
| 53 |
#' ) |
|
| 54 |
#' if (interactive()) {
|
|
| 55 |
#' shinyApp(app$ui, app$server) |
|
| 56 |
#' } |
|
| 57 |
tm_front_page <- function(label = "Front page", |
|
| 58 |
header_text = character(0), |
|
| 59 |
tables = list(), |
|
| 60 |
additional_tags = tagList(), |
|
| 61 |
footnotes = character(0), |
|
| 62 |
show_metadata = FALSE) {
|
|
| 63 | ! |
checkmate::assert_string(label) |
| 64 | ! |
checkmate::assert_character(header_text, min.len = 0, any.missing = FALSE) |
| 65 | ! |
checkmate::assert_list(tables, types = "data.frame", names = "named", any.missing = FALSE) |
| 66 | ! |
checkmate::assert_multi_class(additional_tags, classes = c("shiny.tag.list", "html"))
|
| 67 | ! |
checkmate::assert_character(footnotes, min.len = 0, any.missing = FALSE) |
| 68 | ! |
checkmate::assert_flag(show_metadata) |
| 69 | ||
| 70 | ! |
logger::log_info("Initializing tm_front_page")
|
| 71 | ! |
args <- as.list(environment()) |
| 72 | ||
| 73 | ! |
module( |
| 74 | ! |
label = label, |
| 75 | ! |
server = srv_front_page, |
| 76 | ! |
ui = ui_front_page, |
| 77 | ! |
ui_args = args, |
| 78 | ! |
server_args = list(tables = tables, show_metadata = show_metadata), |
| 79 | ! |
datanames = if (show_metadata) "all" else NULL |
| 80 |
) |
|
| 81 |
} |
|
| 82 | ||
| 83 |
ui_front_page <- function(id, ...) {
|
|
| 84 | ! |
args <- list(...) |
| 85 | ! |
ns <- NS(id) |
| 86 | ||
| 87 | ! |
tagList( |
| 88 | ! |
include_css_files("custom"),
|
| 89 | ! |
tags$div( |
| 90 | ! |
id = "front_page_content", |
| 91 | ! |
class = "ml-8", |
| 92 | ! |
tags$div( |
| 93 | ! |
id = "front_page_headers", |
| 94 | ! |
get_header_tags(args$header_text) |
| 95 |
), |
|
| 96 | ! |
tags$div( |
| 97 | ! |
id = "front_page_tables", |
| 98 | ! |
class = "ml-4", |
| 99 | ! |
get_table_tags(args$tables, ns) |
| 100 |
), |
|
| 101 | ! |
tags$div( |
| 102 | ! |
id = "front_page_custom_html", |
| 103 | ! |
class = "my-4", |
| 104 | ! |
args$additional_tags |
| 105 |
), |
|
| 106 | ! |
if (args$show_metadata) {
|
| 107 | ! |
tags$div( |
| 108 | ! |
id = "front_page_metabutton", |
| 109 | ! |
class = "m-4", |
| 110 | ! |
actionButton(ns("metadata_button"), "Show metadata")
|
| 111 |
) |
|
| 112 |
}, |
|
| 113 | ! |
tags$footer( |
| 114 | ! |
class = ".small", |
| 115 | ! |
get_footer_tags(args$footnotes) |
| 116 |
) |
|
| 117 |
) |
|
| 118 |
) |
|
| 119 |
} |
|
| 120 | ||
| 121 |
get_header_tags <- function(header_text) {
|
|
| 122 | ! |
if (length(header_text) == 0) {
|
| 123 | ! |
return(list()) |
| 124 |
} |
|
| 125 | ||
| 126 | ! |
get_single_header_tags <- function(header_text, p_text, header_tag = tags$h4) {
|
| 127 | ! |
tagList( |
| 128 | ! |
tags$div( |
| 129 | ! |
if (!is.null(header_text) && nchar(header_text) > 0) header_tag(header_text), |
| 130 | ! |
tags$p(p_text) |
| 131 |
) |
|
| 132 |
) |
|
| 133 |
} |
|
| 134 | ||
| 135 | ! |
header_tags <- get_single_header_tags(names(header_text[1]), header_text[1], header_tag = tags$h3) |
| 136 | ! |
c(header_tags, mapply(get_single_header_tags, utils::tail(names(header_text), -1), utils::tail(header_text, -1))) |
| 137 |
} |
|
| 138 | ||
| 139 |
get_table_tags <- function(tables, ns) {
|
|
| 140 | ! |
if (length(tables) == 0) {
|
| 141 | ! |
return(list()) |
| 142 |
} |
|
| 143 | ! |
table_tags <- c(lapply(seq_along(tables), function(idx) {
|
| 144 | ! |
list( |
| 145 | ! |
tableOutput(ns(paste0("table_", idx)))
|
| 146 |
) |
|
| 147 |
})) |
|
| 148 | ! |
return(table_tags) |
| 149 |
} |
|
| 150 | ||
| 151 |
get_footer_tags <- function(footnotes) {
|
|
| 152 | ! |
if (length(footnotes) == 0) {
|
| 153 | ! |
return(list()) |
| 154 |
} |
|
| 155 | ! |
bold_texts <- if (is.null(names(footnotes))) rep("", length(footnotes)) else names(footnotes)
|
| 156 | ! |
footnote_tags <- mapply(function(bold_text, value) {
|
| 157 | ! |
list( |
| 158 | ! |
tags$div( |
| 159 | ! |
tags$b(bold_text), |
| 160 | ! |
value, |
| 161 | ! |
tags$br() |
| 162 |
) |
|
| 163 |
) |
|
| 164 | ! |
}, bold_text = bold_texts, value = footnotes) |
| 165 |
} |
|
| 166 | ||
| 167 |
srv_front_page <- function(id, data, tables, show_metadata) {
|
|
| 168 | ! |
checkmate::assert_class(data, "tdata") |
| 169 | ! |
moduleServer(id, function(input, output, session) {
|
| 170 | ! |
ns <- session$ns |
| 171 | ||
| 172 | ! |
lapply(seq_along(tables), function(idx) {
|
| 173 | ! |
output[[paste0("table_", idx)]] <- renderTable(
|
| 174 | ! |
tables[[idx]], |
| 175 | ! |
bordered = TRUE, |
| 176 | ! |
caption = names(tables)[idx], |
| 177 | ! |
caption.placement = "top" |
| 178 |
) |
|
| 179 |
}) |
|
| 180 | ||
| 181 | ! |
if (show_metadata) {
|
| 182 | ! |
observeEvent( |
| 183 | ! |
input$metadata_button, showModal( |
| 184 | ! |
modalDialog( |
| 185 | ! |
title = "Metadata", |
| 186 | ! |
dataTableOutput(ns("metadata_table")),
|
| 187 | ! |
size = "l", |
| 188 | ! |
easyClose = TRUE |
| 189 |
) |
|
| 190 |
) |
|
| 191 |
) |
|
| 192 | ||
| 193 | ! |
metadata_data_frame <- reactive({
|
| 194 | ! |
convert_metadata_to_dataframe( |
| 195 | ! |
lapply(names(data), function(dataname) get_metadata(data, dataname)), |
| 196 | ! |
names(data) |
| 197 |
) |
|
| 198 |
}) |
|
| 199 | ||
| 200 | ! |
output$metadata_table <- renderDataTable({
|
| 201 | ! |
validate(need(nrow(metadata_data_frame()) > 0, "The data has no associated metadata")) |
| 202 | ! |
metadata_data_frame() |
| 203 |
}) |
|
| 204 |
} |
|
| 205 |
}) |
|
| 206 |
} |
|
| 207 | ||
| 208 |
# take a list of metadata, one item per dataset (raw_metadata each element from datasets$get_metadata()) |
|
| 209 |
# and the corresponding datanames and output a data.frame with columns {Dataset, Name, Value}.
|
|
| 210 |
# which are, the Dataset the metadata came from, the metadata's name and value |
|
| 211 |
convert_metadata_to_dataframe <- function(raw_metadata, datanames) {
|
|
| 212 | 4x |
output <- mapply(function(metadata, dataname) {
|
| 213 | 6x |
if (is.null(metadata)) {
|
| 214 | 2x |
return(data.frame(Dataset = character(0), Name = character(0), Value = character(0))) |
| 215 |
} |
|
| 216 | 4x |
return(data.frame( |
| 217 | 4x |
Dataset = dataname, |
| 218 | 4x |
Name = names(metadata), |
| 219 | 4x |
Value = unname(unlist(lapply(metadata, as.character))) |
| 220 |
)) |
|
| 221 | 4x |
}, raw_metadata, datanames, SIMPLIFY = FALSE) |
| 222 | 4x |
do.call(rbind, output) |
| 223 |
} |
| 1 |
#' File Viewer Teal Module |
|
| 2 |
#' |
|
| 3 |
#' The file viewer module provides a tool to view static files. |
|
| 4 |
#' Supported formats include text formats, \code{PDF}, \code{PNG}, \code{APNG},
|
|
| 5 |
#' \code{JPEG}, \code{SVG}, \code{WEBP}, \code{GIF} and \code{BMP}.
|
|
| 6 |
#' |
|
| 7 |
#' @inheritParams teal::module |
|
| 8 |
#' @inheritParams shared_params |
|
| 9 |
#' @param input_path optional, (`list`) of the input paths to either: specific files of accepted formats, |
|
| 10 |
#' a directory or a URL. The paths can be specified as absolute paths or relative to the running |
|
| 11 |
#' directory of the application. Will default to current working directory if not supplied. |
|
| 12 |
#' |
|
| 13 |
#' @export |
|
| 14 |
#' |
|
| 15 |
#' @examples |
|
| 16 |
#' data <- data.frame(1) |
|
| 17 |
#' app <- teal::init( |
|
| 18 |
#' data = teal_data( |
|
| 19 |
#' dataset("data", data)
|
|
| 20 |
#' ), |
|
| 21 |
#' modules = teal::modules( |
|
| 22 |
#' teal.modules.general::tm_file_viewer( |
|
| 23 |
#' input_path = list( |
|
| 24 |
#' folder = system.file("sample_files", package = "teal.modules.general"),
|
|
| 25 |
#' png = system.file("sample_files/sample_file.png", package = "teal.modules.general"),
|
|
| 26 |
#' txt = system.file("sample_files/sample_file.txt", package = "teal.modules.general"),
|
|
| 27 |
#' url = |
|
| 28 |
#' "https://www.fda.gov/files/drugs/published/Portable-Document-Format-Specifications.pdf" |
|
| 29 |
#' ) |
|
| 30 |
#' ) |
|
| 31 |
#' ) |
|
| 32 |
#' ) |
|
| 33 |
#' if (interactive()) {
|
|
| 34 |
#' shinyApp(app$ui, app$server) |
|
| 35 |
#' } |
|
| 36 |
#' |
|
| 37 |
tm_file_viewer <- function(label = "File Viewer Module", |
|
| 38 |
input_path = list("Current Working Directory" = ".")) {
|
|
| 39 | ! |
logger::log_info("Initializing tm_file_viewer")
|
| 40 | ! |
if (length(label) == 0 || identical(label, "")) {
|
| 41 | ! |
label <- " " |
| 42 |
} |
|
| 43 | ! |
if (length(input_path) == 0 || identical(input_path, "")) {
|
| 44 | ! |
input_path <- list() |
| 45 |
} |
|
| 46 | ||
| 47 | ! |
checkmate::assert_string(label) |
| 48 | ! |
checkmate::assert( |
| 49 | ! |
checkmate::check_list(input_path, types = "character", min.len = 0), |
| 50 | ! |
checkmate::check_character(input_path, min.len = 1) |
| 51 |
) |
|
| 52 | ||
| 53 | ! |
if (length(input_path) > 0) {
|
| 54 | ! |
valid_url <- function(url_input, timeout = 2) {
|
| 55 | ! |
con <- try(url(url_input), silent = TRUE) |
| 56 | ! |
check <- suppressWarnings(try(open.connection(con, open = "rt", timeout = timeout), silent = TRUE)[1]) |
| 57 | ! |
try(close.connection(con), silent = TRUE) |
| 58 | ! |
ifelse(is.null(check), TRUE, FALSE) |
| 59 |
} |
|
| 60 | ! |
idx <- vapply(input_path, function(x) file.exists(x) || valid_url(x), logical(1)) |
| 61 | ||
| 62 | ! |
if (!all(idx)) {
|
| 63 | ! |
warning( |
| 64 | ! |
paste0( |
| 65 | ! |
"Non-existent file or url path. Please provide valid paths for:\n", |
| 66 | ! |
paste0(input_path[!idx], collapse = "\n") |
| 67 |
) |
|
| 68 |
) |
|
| 69 |
} |
|
| 70 | ! |
input_path <- input_path[idx] |
| 71 |
} else {
|
|
| 72 | ! |
warning( |
| 73 | ! |
"No file or url paths were provided." |
| 74 |
) |
|
| 75 |
} |
|
| 76 | ||
| 77 | ||
| 78 | ! |
args <- as.list(environment()) |
| 79 | ||
| 80 | ! |
module( |
| 81 | ! |
label = label, |
| 82 | ! |
server = srv_viewer, |
| 83 | ! |
server_args = list(input_path = input_path), |
| 84 | ! |
ui = ui_viewer, |
| 85 | ! |
ui_args = args, |
| 86 | ! |
datanames = NULL |
| 87 |
) |
|
| 88 |
} |
|
| 89 | ||
| 90 |
ui_viewer <- function(id, ...) {
|
|
| 91 | ! |
args <- list(...) |
| 92 | ! |
ns <- NS(id) |
| 93 | ||
| 94 | ! |
shiny::tagList( |
| 95 | ! |
include_css_files("custom"),
|
| 96 | ! |
teal.widgets::standard_layout( |
| 97 | ! |
output = div( |
| 98 | ! |
uiOutput(ns("output"))
|
| 99 |
), |
|
| 100 | ! |
encoding = div( |
| 101 | ! |
class = "file_viewer_encoding", |
| 102 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 103 | ! |
shinyTree::shinyTree( |
| 104 | ! |
ns("tree"),
|
| 105 | ! |
dragAndDrop = FALSE, |
| 106 | ! |
sort = FALSE, |
| 107 | ! |
wholerow = TRUE, |
| 108 | ! |
theme = "proton", |
| 109 | ! |
multiple = FALSE |
| 110 |
) |
|
| 111 |
) |
|
| 112 |
) |
|
| 113 |
) |
|
| 114 |
} |
|
| 115 | ||
| 116 |
srv_viewer <- function(id, input_path) {
|
|
| 117 | ! |
moduleServer(id, function(input, output, session) {
|
| 118 | ! |
temp_dir <- tempfile() |
| 119 | ! |
if (!dir.exists(temp_dir)) {
|
| 120 | ! |
dir.create(temp_dir, recursive = TRUE) |
| 121 |
} |
|
| 122 | ! |
addResourcePath(basename(temp_dir), temp_dir) |
| 123 | ||
| 124 | ! |
test_path_text <- function(selected_path, type) {
|
| 125 | ! |
out <- tryCatch( |
| 126 | ! |
expr = {
|
| 127 | ! |
if (type != "url") {
|
| 128 | ! |
selected_path <- normalizePath(selected_path, winslash = "/") |
| 129 |
} |
|
| 130 | ! |
readLines(con = selected_path) |
| 131 |
}, |
|
| 132 | ! |
error = function(cond) FALSE, |
| 133 | ! |
warning = function(cond) {
|
| 134 | ! |
`if`(grepl("^incomplete final line found on", cond[[1]]), suppressWarnings(eval(cond[[2]])), FALSE)
|
| 135 |
} |
|
| 136 |
) |
|
| 137 |
} |
|
| 138 | ||
| 139 | ! |
handle_connection_type <- function(selected_path) {
|
| 140 | ! |
file_extension <- tools::file_ext(selected_path) |
| 141 | ! |
file_class <- suppressWarnings(file(selected_path)) |
| 142 | ! |
close(file_class) |
| 143 | ||
| 144 | ! |
output_text <- test_path_text(selected_path, type = class(file_class)[1]) |
| 145 | ||
| 146 | ! |
if (class(file_class)[1] == "url") {
|
| 147 | ! |
list(selected_path = selected_path, output_text = output_text) |
| 148 |
} else {
|
|
| 149 | ! |
file.copy(normalizePath(selected_path, winslash = "/"), temp_dir) |
| 150 | ! |
selected_path <- file.path(basename(temp_dir), basename(selected_path)) |
| 151 | ! |
list(selected_path = selected_path, output_text = output_text) |
| 152 |
} |
|
| 153 |
} |
|
| 154 | ||
| 155 | ! |
display_file <- function(selected_path) {
|
| 156 | ! |
con_type <- handle_connection_type(selected_path) |
| 157 | ! |
file_extension <- tools::file_ext(selected_path) |
| 158 | ! |
if (file_extension %in% c("png", "apng", "jpg", "jpeg", "svg", "gif", "webp", "bmp")) {
|
| 159 | ! |
tags$img(src = con_type$selected_path, alt = "file does not exist") |
| 160 | ! |
} else if (file_extension == "pdf") {
|
| 161 | ! |
tags$embed( |
| 162 | ! |
class = "embed_pdf", |
| 163 | ! |
src = con_type$selected_path |
| 164 |
) |
|
| 165 | ! |
} else if (!isFALSE(con_type$output_text[1])) {
|
| 166 | ! |
tags$pre(paste0(con_type$output_text, collapse = "\n")) |
| 167 |
} else {
|
|
| 168 | ! |
tags$p("Please select a supported format.")
|
| 169 |
} |
|
| 170 |
} |
|
| 171 | ||
| 172 | ! |
tree_list <- function(file_or_dir) {
|
| 173 | ! |
nested_list <- lapply(file_or_dir, function(path) {
|
| 174 | ! |
file_class <- suppressWarnings(file(path)) |
| 175 | ! |
close(file_class) |
| 176 | ! |
if (class(file_class)[[1]] != "url") {
|
| 177 | ! |
isdir <- file.info(path)$isdir |
| 178 | ! |
if (!isdir) {
|
| 179 | ! |
structure(path, ancestry = path, sticon = "file") |
| 180 |
} else {
|
|
| 181 | ! |
files <- list.files(path, full.names = TRUE, include.dirs = TRUE) |
| 182 | ! |
out <- lapply(files, function(x) tree_list(x)) |
| 183 | ! |
out <- unlist(out, recursive = FALSE) |
| 184 | ! |
if (length(files) > 0) names(out) <- basename(files) |
| 185 | ! |
out |
| 186 |
} |
|
| 187 |
} else {
|
|
| 188 | ! |
structure(path, ancestry = path, sticon = "file") |
| 189 |
} |
|
| 190 |
}) |
|
| 191 | ||
| 192 | ! |
missing_labels <- if (is.null(names(nested_list))) seq_along(nested_list) else which(names(nested_list) == "") |
| 193 | ! |
names(nested_list)[missing_labels] <- file_or_dir[missing_labels] |
| 194 | ! |
nested_list |
| 195 |
} |
|
| 196 | ||
| 197 | ! |
output$tree <- shinyTree::renderTree({
|
| 198 | ! |
if (length(input_path) > 0) {
|
| 199 | ! |
tree_list(input_path) |
| 200 |
} else {
|
|
| 201 | ! |
list("Empty Path" = NULL)
|
| 202 |
} |
|
| 203 |
}) |
|
| 204 | ||
| 205 | ! |
output$output <- renderUI({
|
| 206 | ! |
validate( |
| 207 | ! |
need( |
| 208 | ! |
length(shinyTree::get_selected(input$tree)) > 0, |
| 209 | ! |
"Please select a file." |
| 210 |
) |
|
| 211 |
) |
|
| 212 | ||
| 213 | ! |
obj <- shinyTree::get_selected(input$tree, format = "names")[[1]] |
| 214 | ! |
repo <- attr(obj, "ancestry") |
| 215 | ! |
repo_collapsed <- if (length(repo) > 1) paste0(repo, collapse = "/") else repo |
| 216 | ! |
is_not_named <- file.exists(file.path(c(repo_collapsed, obj[1])))[1] |
| 217 | ||
| 218 | ! |
if (is_not_named) {
|
| 219 | ! |
selected_path <- do.call("file.path", as.list(c(repo, obj[1])))
|
| 220 |
} else {
|
|
| 221 | ! |
if (length(repo) == 0) {
|
| 222 | ! |
selected_path <- do.call("file.path", as.list(attr(input$tree[[obj[1]]], "ancestry")))
|
| 223 |
} else {
|
|
| 224 | ! |
selected_path <- do.call("file.path", as.list(attr(input$tree[[repo]][[obj[1]]], "ancestry")))
|
| 225 |
} |
|
| 226 |
} |
|
| 227 | ||
| 228 | ! |
validate( |
| 229 | ! |
need( |
| 230 | ! |
!isTRUE(file.info(selected_path)$isdir) && length(selected_path) > 0, |
| 231 | ! |
"Please select a single file." |
| 232 |
) |
|
| 233 |
) |
|
| 234 | ! |
display_file(selected_path) |
| 235 |
}) |
|
| 236 | ||
| 237 | ! |
onStop(function() {
|
| 238 | ! |
removeResourcePath(basename(temp_dir)) |
| 239 | ! |
unlink(temp_dir) |
| 240 |
}) |
|
| 241 |
}) |
|
| 242 |
} |
| 1 |
#' Shared Parameters |
|
| 2 |
#' |
|
| 3 |
#' @description Contains arguments that are shared between multiple functions |
|
| 4 |
#' in the package to avoid repetition using `inheritParams`. |
|
| 5 |
#' |
|
| 6 |
#' @param plot_height optional, (`numeric`) A vector of length three with `c(value, min and max)` |
|
| 7 |
#' for a slider encoding the plot height. |
|
| 8 |
#' @param plot_width optional, (`numeric`) A vector of length three with `c(value, min and max)` |
|
| 9 |
#' for a slider encoding the plot width. |
|
| 10 |
#' @param rotate_xaxis_labels optional, (`logical`) Whether to rotate plot X axis labels. Does not |
|
| 11 |
#' rotate by default (`FALSE`). |
|
| 12 |
#' @param ggtheme optional, (`character`) `ggplot2` theme to be used by default. |
|
| 13 |
#' One of `c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test")`.
|
|
| 14 |
#' Each theme can be chosen by the user during the session. Defaults to `"gray"`. |
|
| 15 |
#' @param ggplot2_args (`ggplot2_args`) object created by [teal.widgets::ggplot2_args()] |
|
| 16 |
#' with settings for the module plot. |
|
| 17 |
#' The argument is merged with options variable `teal.ggplot2_args` and default module setup. |
|
| 18 |
#' |
|
| 19 |
#' For more details see the vignette: `vignette("custom-ggplot2-arguments", package = "teal.widgets")`
|
|
| 20 |
#' @param basic_table_args (`basic_table_args`) object created by [teal.widgets::basic_table_args()] |
|
| 21 |
#' with settings for the module table. |
|
| 22 |
#' The argument is merged with options variable `teal.basic_table_args` and default module setup. |
|
| 23 |
#' |
|
| 24 |
#' For more details see the vignette: `vignette("custom-basic-table-arguments", package = "teal.widgets")`
|
|
| 25 |
#' @param pre_output (`shiny.tag`, optional)\cr |
|
| 26 |
#' with text placed before the output to put the output into context. For example a title. |
|
| 27 |
#' @param post_output (`shiny.tag`, optional) with text placed after the output to put the output |
|
| 28 |
#' into context. For example the [shiny::helpText()] elements are useful. |
|
| 29 |
#' |
|
| 30 |
#' @name shared_params |
|
| 31 |
#' @keywords internal |
|
| 32 |
NULL |
|
| 33 | ||
| 34 |
#' Add axis labels that show facetting variable |
|
| 35 |
#' |
|
| 36 |
#' Add axis labels that show facetting variable |
|
| 37 |
#' |
|
| 38 |
#' @param p `ggplot2` object to add facet labels to |
|
| 39 |
#' @param xfacet_label label of facet along x axis (nothing created if NULL), |
|
| 40 |
#' if vector, will be concatenated with " & " |
|
| 41 |
#' @param yfacet_label label of facet along y axis (nothing created if NULL), |
|
| 42 |
#' if vector, will be concatenated with " & " |
|
| 43 |
#' |
|
| 44 |
#' @return grid grob object (to be drawn with \code{grid.draw})
|
|
| 45 |
#' |
|
| 46 |
#' @export |
|
| 47 |
#' |
|
| 48 |
#' @examples |
|
| 49 |
#' # we put donttest to avoid strictr error with seq along.with argument |
|
| 50 |
#' \donttest{
|
|
| 51 |
#' library(ggplot2) |
|
| 52 |
#' library(grid) |
|
| 53 |
#' |
|
| 54 |
#' p <- ggplot(mtcars) + |
|
| 55 |
#' aes(x = mpg, y = disp) + |
|
| 56 |
#' geom_point() + |
|
| 57 |
#' facet_grid(gear ~ cyl) |
|
| 58 |
#' p |
|
| 59 |
#' xfacet_label <- "cylinders" |
|
| 60 |
#' yfacet_label <- "gear" |
|
| 61 |
#' res <- add_facet_labels(p, xfacet_label, yfacet_label) |
|
| 62 |
#' grid.newpage() |
|
| 63 |
#' grid.draw(res) |
|
| 64 |
#' |
|
| 65 |
#' grid.newpage() |
|
| 66 |
#' grid.draw(add_facet_labels(p, xfacet_label = NULL, yfacet_label)) |
|
| 67 |
#' grid.newpage() |
|
| 68 |
#' grid.draw(add_facet_labels(p, xfacet_label, yfacet_label = NULL)) |
|
| 69 |
#' grid.newpage() |
|
| 70 |
#' grid.draw(add_facet_labels(p, xfacet_label = NULL, yfacet_label = NULL)) |
|
| 71 |
#' } |
|
| 72 |
#' |
|
| 73 |
add_facet_labels <- function(p, xfacet_label = NULL, yfacet_label = NULL) {
|
|
| 74 | ! |
checkmate::assert_class(p, classes = "ggplot") |
| 75 | ! |
checkmate::assert_character(xfacet_label, null.ok = TRUE, min.len = 1) |
| 76 | ! |
checkmate::assert_character(yfacet_label, null.ok = TRUE, min.len = 1) |
| 77 | ! |
if (is.null(xfacet_label) && is.null(yfacet_label)) {
|
| 78 | ! |
return(ggplotGrob(p)) |
| 79 |
} |
|
| 80 | ! |
grid::grid.grabExpr({
|
| 81 | ! |
g <- ggplotGrob(p) |
| 82 | ||
| 83 |
# we are going to replace these, so we make sure they have nothing in them |
|
| 84 | ! |
checkmate::assert_class(g$grobs[[grep("xlab-t", g$layout$name, fixed = TRUE)]], "zeroGrob")
|
| 85 | ! |
checkmate::assert_class(g$grobs[[grep("ylab-r", g$layout$name, fixed = TRUE)]], "zeroGrob")
|
| 86 | ||
| 87 | ! |
xaxis_label_grob <- g$grobs[[grep("xlab-b", g$layout$name, fixed = TRUE)]]
|
| 88 | ! |
xaxis_label_grob$children[[1]]$label <- paste(xfacet_label, collapse = " & ") |
| 89 | ! |
yaxis_label_grob <- g$grobs[[grep("ylab-l", g$layout$name, fixed = TRUE)]]
|
| 90 | ! |
yaxis_label_grob$children[[1]]$label <- paste(yfacet_label, collapse = " & ") |
| 91 | ! |
yaxis_label_grob$children[[1]]$rot <- 270 |
| 92 | ||
| 93 | ! |
top_height <- if (is.null(xfacet_label)) 0 else grid::unit(2, "line") |
| 94 | ! |
right_width <- if (is.null(yfacet_label)) 0 else grid::unit(2, "line") |
| 95 | ||
| 96 | ! |
grid::grid.newpage() |
| 97 | ! |
grid::pushViewport(grid::plotViewport(margins = c(0, 0, top_height, right_width), name = "ggplot")) |
| 98 | ! |
grid::grid.draw(g) |
| 99 | ! |
grid::upViewport(1) |
| 100 | ||
| 101 |
# draw x facet |
|
| 102 | ! |
if (!is.null(xfacet_label)) {
|
| 103 | ! |
grid::pushViewport(grid::viewport( |
| 104 | ! |
x = 0, y = grid::unit(1, "npc") - top_height, width = grid::unit(1, "npc"), |
| 105 | ! |
height = top_height, just = c("left", "bottom"), name = "topxaxis"
|
| 106 |
)) |
|
| 107 | ! |
grid::grid.draw(xaxis_label_grob) |
| 108 | ! |
grid::upViewport(1) |
| 109 |
} |
|
| 110 | ||
| 111 |
# draw y facet |
|
| 112 | ! |
if (!is.null(yfacet_label)) {
|
| 113 | ! |
grid::pushViewport(grid::viewport( |
| 114 | ! |
x = grid::unit(1, "npc") - grid::unit(as.numeric(right_width) / 2, "line"), y = 0, width = right_width, |
| 115 | ! |
height = grid::unit(1, "npc"), just = c("left", "bottom"), name = "rightyaxis"
|
| 116 |
)) |
|
| 117 | ! |
grid::grid.draw(yaxis_label_grob) |
| 118 | ! |
grid::upViewport(1) |
| 119 |
} |
|
| 120 |
}) |
|
| 121 |
} |
|
| 122 | ||
| 123 |
#' Call a function with a character vector for the \code{...} argument
|
|
| 124 |
#' |
|
| 125 |
#' @param fun (\code{character}) Name of a function where the \code{...} argument
|
|
| 126 |
#' shall be replaced by values from \code{str_args}.
|
|
| 127 |
#' @param str_args (\code{character}) A character vector that the function shall
|
|
| 128 |
#' be executed with |
|
| 129 |
#' |
|
| 130 |
#' @return: call (i.e. expression) of the function provided by \code{fun}
|
|
| 131 |
#' with arguments provided by \code{str_args}.
|
|
| 132 |
#' @keywords internal |
|
| 133 |
#' |
|
| 134 |
#' @examples |
|
| 135 |
#' \dontrun{
|
|
| 136 |
#' a <- 1 |
|
| 137 |
#' b <- 2 |
|
| 138 |
#' call_fun_dots("sum", c("a", "b"))
|
|
| 139 |
#' eval(call_fun_dots("sum", c("a", "b")))
|
|
| 140 |
#' } |
|
| 141 |
call_fun_dots <- function(fun, str_args) {
|
|
| 142 | ! |
do.call("call", c(list(fun), lapply(str_args, as.name)), quote = TRUE)
|
| 143 |
} |
|
| 144 | ||
| 145 |
#' Get variable name with label |
|
| 146 |
#' |
|
| 147 |
#' @param var_names (\code{character}) Name of variable to extract labels from.
|
|
| 148 |
#' @param dataset (\code{dataset}) Name of analysis dataset.
|
|
| 149 |
#' @param prefix (\code{character}) String to paste to the beginning of the
|
|
| 150 |
#' variable name with label. |
|
| 151 |
#' @param suffix (\code{character}) String to paste to the end of the variable
|
|
| 152 |
#' name with label. |
|
| 153 |
#' @param wrap_width (\code{numeric}) Number of characters to wrap original
|
|
| 154 |
#' label to. Defaults to 80. |
|
| 155 |
#' |
|
| 156 |
#' @return (\code{character}) String with variable name and label.
|
|
| 157 |
#' @keywords internal |
|
| 158 |
#' |
|
| 159 |
#' @examples |
|
| 160 |
#' \dontrun{
|
|
| 161 |
#' ADSL <- teal.modules.general::rADSL |
|
| 162 |
#' |
|
| 163 |
#' varname_w_label("AGE", ADSL)
|
|
| 164 |
#' } |
|
| 165 |
varname_w_label <- function(var_names, |
|
| 166 |
dataset, |
|
| 167 |
wrap_width = 80, |
|
| 168 |
prefix = NULL, |
|
| 169 |
suffix = NULL) {
|
|
| 170 | ! |
add_label <- function(var_names) {
|
| 171 | ! |
label <- vapply( |
| 172 | ! |
dataset[var_names], function(x) {
|
| 173 | ! |
attr_label <- attr(x, "label") |
| 174 | ! |
`if`(is.null(attr_label), "", attr_label) |
| 175 |
}, |
|
| 176 | ! |
character(1) |
| 177 |
) |
|
| 178 | ||
| 179 | ! |
if (length(label) == 1 && !is.na(label) && !identical(label, "")) {
|
| 180 | ! |
paste0(prefix, label, " [", var_names, "]", suffix) |
| 181 |
} else {
|
|
| 182 | ! |
var_names |
| 183 |
} |
|
| 184 |
} |
|
| 185 | ||
| 186 | ! |
if (length(var_names) < 1) {
|
| 187 | ! |
NULL |
| 188 | ! |
} else if (length(var_names) == 1) {
|
| 189 | ! |
stringr::str_wrap(add_label(var_names), width = wrap_width) |
| 190 | ! |
} else if (length(var_names) > 1) {
|
| 191 | ! |
stringr::str_wrap(vapply(var_names, add_label, character(1)), width = wrap_width) |
| 192 |
} |
|
| 193 |
} |
|
| 194 | ||
| 195 |
#' Extract html id for `data_extract_ui` |
|
| 196 |
#' @description The `data_extract_ui` is located under extended html id. |
|
| 197 |
#' We could not use \code{ns("original id")} for reference, as it is extended with specific suffixes.
|
|
| 198 |
#' @param varname character original html id. |
|
| 199 |
#' This will be mostly retrieved with \code{ns("original id")} in `ui` or
|
|
| 200 |
#' \code{session$ns("original id")} in server function.
|
|
| 201 |
#' @param dataname character \code{dataname} from data_extract input.
|
|
| 202 |
#' This might be retrieved like \code{teal.transform::data_extract_spec(...)[[1]]$dataname}.
|
|
| 203 |
#' @param filter logical if the connected \code{extract_data_spec} is used with \code{filter} option.
|
|
| 204 |
#' @keywords internal |
|
| 205 |
extract_input <- function(varname, dataname, filter = FALSE) {
|
|
| 206 | ! |
if (filter) {
|
| 207 | ! |
paste0(varname, "-dataset_", dataname, "_singleextract-filter1-vals") |
| 208 |
} else {
|
|
| 209 | ! |
paste0(varname, "-dataset_", dataname, "_singleextract-select") |
| 210 |
} |
|
| 211 |
} |
|
| 212 | ||
| 213 |
# see vignette("ggplot2-specs", package="ggplot2")
|
|
| 214 |
shape_names <- c( |
|
| 215 |
"circle", paste("circle", c("open", "filled", "cross", "plus", "small")), "bullet",
|
|
| 216 |
"square", paste("square", c("open", "filled", "cross", "plus", "triangle")),
|
|
| 217 |
"diamond", paste("diamond", c("open", "filled", "plus")),
|
|
| 218 |
"triangle", paste("triangle", c("open", "filled", "square")),
|
|
| 219 |
paste("triangle down", c("open", "filled")),
|
|
| 220 |
"plus", "cross", "asterisk" |
|
| 221 |
) |
|
| 222 | ||
| 223 |
#' Get icons to represent variable types in dataset |
|
| 224 |
#' |
|
| 225 |
#' @param var_type (`character`)\cr |
|
| 226 |
#' of R internal types (classes). |
|
| 227 |
#' |
|
| 228 |
#' @return (`character`)\cr |
|
| 229 |
#' vector of HTML icons corresponding to data type in each column. |
|
| 230 |
#' @keywords internal |
|
| 231 |
#' |
|
| 232 |
#' @examples |
|
| 233 |
#' teal.modules.general:::variable_type_icons(c( |
|
| 234 |
#' "integer", "numeric", "logical", "Date", "POSIXct", "POSIXlt", |
|
| 235 |
#' "factor", "character", "unknown", "" |
|
| 236 |
#' )) |
|
| 237 |
variable_type_icons <- function(var_type) {
|
|
| 238 | ! |
checkmate::assert_character(var_type, any.missing = FALSE) |
| 239 | ||
| 240 | ! |
class_to_icon <- list( |
| 241 | ! |
numeric = "arrow-up-1-9", |
| 242 | ! |
integer = "arrow-up-1-9", |
| 243 | ! |
logical = "pause", |
| 244 | ! |
Date = "calendar", |
| 245 | ! |
POSIXct = "calendar", |
| 246 | ! |
POSIXlt = "calendar", |
| 247 | ! |
factor = "chart-bar", |
| 248 | ! |
character = "keyboard", |
| 249 | ! |
primary_key = "key", |
| 250 | ! |
unknown = "circle-question" |
| 251 |
) |
|
| 252 | ! |
class_to_icon <- lapply(class_to_icon, function(icon_name) toString(icon(icon_name, lib = "font-awesome"))) |
| 253 | ||
| 254 | ! |
res <- unname(vapply( |
| 255 | ! |
var_type, |
| 256 | ! |
FUN.VALUE = character(1), |
| 257 | ! |
FUN = function(class) {
|
| 258 | ! |
if (class == "") {
|
| 259 | ! |
class |
| 260 | ! |
} else if (is.null(class_to_icon[[class]])) {
|
| 261 | ! |
class_to_icon[["unknown"]] |
| 262 |
} else {
|
|
| 263 | ! |
class_to_icon[[class]] |
| 264 |
} |
|
| 265 |
} |
|
| 266 |
)) |
|
| 267 | ||
| 268 | ! |
return(res) |
| 269 |
} |
|
| 270 | ||
| 271 |
#' Include `CSS` files from `/inst/css/` package directory to application header |
|
| 272 |
#' |
|
| 273 |
#' `system.file` should not be used to access files in other packages, it does |
|
| 274 |
#' not work with `devtools`. Therefore, we redefine this method in each package |
|
| 275 |
#' as needed. Thus, we do not export this method |
|
| 276 |
#' |
|
| 277 |
#' @param pattern (`character`) pattern of files to be included |
|
| 278 |
#' |
|
| 279 |
#' @return HTML code that includes `CSS` files |
|
| 280 |
#' @keywords internal |
|
| 281 |
include_css_files <- function(pattern = "*") {
|
|
| 282 | ! |
css_files <- list.files( |
| 283 | ! |
system.file("css", package = "teal.modules.general", mustWork = TRUE),
|
| 284 | ! |
pattern = pattern, full.names = TRUE |
| 285 |
) |
|
| 286 | ! |
if (length(css_files) == 0) {
|
| 287 | ! |
return(NULL) |
| 288 |
} |
|
| 289 | ! |
return(shiny::singleton(shiny::tags$head(lapply(css_files, shiny::includeCSS)))) |
| 290 |
} |
|
| 291 | ||
| 292 | ||
| 293 |
#' Get Label Attributes of Variables in a \code{data.frame}
|
|
| 294 |
#' |
|
| 295 |
#' Variable labels can be stored as a \code{label} attribute for each variable.
|
|
| 296 |
#' This functions returns a named character vector with the variable labels |
|
| 297 |
#' (empty sting if not specified) |
|
| 298 |
#' |
|
| 299 |
#' @param x a \code{data.frame} object
|
|
| 300 |
#' @param fill boolean in case the \code{label} attribute does not exist if
|
|
| 301 |
#' \code{TRUE} the variable names is returned, otherwise \code{NA}
|
|
| 302 |
#' |
|
| 303 |
#' @return a named character vector with the variable labels, the names |
|
| 304 |
#' correspond to the variable names |
|
| 305 |
#' |
|
| 306 |
#' @note the `formatters` package is the source of the function. |
|
| 307 |
#' |
|
| 308 |
#' @keywords internal |
|
| 309 |
var_labels <- function(x, fill = FALSE) {
|
|
| 310 | ! |
stopifnot(is.data.frame(x)) |
| 311 | ! |
if (NCOL(x) == 0) {
|
| 312 | ! |
return(character()) |
| 313 |
} |
|
| 314 | ||
| 315 | ! |
y <- Map(function(col, colname) {
|
| 316 | ! |
label <- attr(col, "label") |
| 317 | ||
| 318 | ! |
if (is.null(label)) {
|
| 319 | ! |
if (fill) {
|
| 320 | ! |
colname |
| 321 |
} else {
|
|
| 322 | ! |
NA_character_ |
| 323 |
} |
|
| 324 |
} else {
|
|
| 325 | ! |
if (!is.character(label) && !(length(label) == 1)) {
|
| 326 | ! |
stop("label for variable ", colname, "is not a character string")
|
| 327 |
} |
|
| 328 | ! |
as.vector(label) |
| 329 |
} |
|
| 330 | ! |
}, x, colnames(x)) |
| 331 | ||
| 332 | ! |
labels <- unlist(y, recursive = FALSE, use.names = TRUE) |
| 333 | ||
| 334 | ! |
if (!is.character(labels)) {
|
| 335 | ! |
stop("label extraction failed")
|
| 336 |
} |
|
| 337 | ||
| 338 | ! |
labels |
| 339 |
} |
|
| 340 | ||
| 341 |
#' Get a string with java-script code checking if the specific tab is clicked |
|
| 342 |
#' @description will be the input for `shiny::conditionalPanel()` |
|
| 343 |
#' @param id `character(1)` the id of the tab panel with tabs. |
|
| 344 |
#' @param name `character(1)` the name of the tab. |
|
| 345 |
#' @keywords internal |
|
| 346 |
is_tab_active_js <- function(id, name) {
|
|
| 347 |
# supporting the bs3 and higher version at the same time |
|
| 348 | ! |
sprintf( |
| 349 | ! |
"$(\"#%1$s > li.active\").text().trim() == '%2$s' || $(\"#%1$s > li a.active\").text().trim() == '%2$s'", |
| 350 | ! |
id, name |
| 351 |
) |
|
| 352 |
} |
| 1 |
#' Stack Plots of variables and show association with reference variable |
|
| 2 |
#' @md |
|
| 3 |
#' |
|
| 4 |
#' @inheritParams teal::module |
|
| 5 |
#' @inheritParams shared_params |
|
| 6 |
#' @param ref (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 7 |
#' reference variable, must set `multiple = FALSE`. |
|
| 8 |
#' @param vars (`data_extract_spec` or `list` of multiple `data_extract_spec`) |
|
| 9 |
#' associated variables. |
|
| 10 |
#' @param show_association optional, (`logical`) Whether show association of `vars` |
|
| 11 |
#' with reference variable. Defaults to `TRUE`. |
|
| 12 |
#' @param distribution_theme optional, (`character`) `ggplot2` theme to be used by default. |
|
| 13 |
#' One of `c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test")`.
|
|
| 14 |
#' Each theme can be chosen by the user during the session. Defaults to `"gray"`. |
|
| 15 |
#' @param association_theme optional, (`character`) `ggplot2` theme to be used by default. |
|
| 16 |
#' One of `c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test")`.
|
|
| 17 |
#' Each theme can be chosen by the user during the session. Defaults to `"gray"`. |
|
| 18 |
#' |
|
| 19 |
#' @templateVar ggnames "Bivariate1", "Bivariate2" |
|
| 20 |
#' @template ggplot2_args_multi |
|
| 21 |
#' |
|
| 22 |
#' @note For more examples, please see the vignette "Using association plot" via |
|
| 23 |
#' \code{vignette("using-association-plot", package = "teal.modules.general")}.
|
|
| 24 |
#' @export |
|
| 25 |
#' @examples |
|
| 26 |
#' # Association plot of selected reference variable (SEX) |
|
| 27 |
#' # against other selected variables (BMRKR1) |
|
| 28 |
#' library(nestcolor) |
|
| 29 |
#' ADSL <- teal.modules.general::rADSL |
|
| 30 |
#' |
|
| 31 |
#' app <- teal::init( |
|
| 32 |
#' data = teal.data::cdisc_data( |
|
| 33 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 34 |
#' check = TRUE |
|
| 35 |
#' ), |
|
| 36 |
#' modules = teal::modules( |
|
| 37 |
#' teal.modules.general::tm_g_association( |
|
| 38 |
#' ref = teal.transform::data_extract_spec( |
|
| 39 |
#' dataname = "ADSL", |
|
| 40 |
#' select = teal.transform::select_spec( |
|
| 41 |
#' label = "Select variable:", |
|
| 42 |
#' choices = teal.transform::variable_choices( |
|
| 43 |
#' ADSL, |
|
| 44 |
#' c("SEX", "RACE", "COUNTRY", "ARM", "STRATA1", "STRATA2", "ITTFL", "BMRKR2")
|
|
| 45 |
#' ), |
|
| 46 |
#' selected = "RACE", |
|
| 47 |
#' fixed = FALSE |
|
| 48 |
#' ) |
|
| 49 |
#' ), |
|
| 50 |
#' vars = teal.transform::data_extract_spec( |
|
| 51 |
#' dataname = "ADSL", |
|
| 52 |
#' select = teal.transform::select_spec( |
|
| 53 |
#' label = "Select variables:", |
|
| 54 |
#' choices = teal.transform::variable_choices( |
|
| 55 |
#' ADSL, |
|
| 56 |
#' c("SEX", "RACE", "COUNTRY", "ARM", "STRATA1", "STRATA2", "ITTFL", "BMRKR2")
|
|
| 57 |
#' ), |
|
| 58 |
#' selected = "BMRKR2", |
|
| 59 |
#' multiple = TRUE, |
|
| 60 |
#' fixed = FALSE |
|
| 61 |
#' ) |
|
| 62 |
#' ), |
|
| 63 |
#' ggplot2_args = teal.widgets::ggplot2_args( |
|
| 64 |
#' labs = list(subtitle = "Plot generated by Association Module") |
|
| 65 |
#' ) |
|
| 66 |
#' ) |
|
| 67 |
#' ) |
|
| 68 |
#' ) |
|
| 69 |
#' if (interactive()) {
|
|
| 70 |
#' shinyApp(app$ui, app$server) |
|
| 71 |
#' } |
|
| 72 |
tm_g_association <- function(label = "Association", |
|
| 73 |
ref, |
|
| 74 |
vars, |
|
| 75 |
show_association = TRUE, |
|
| 76 |
plot_height = c(600, 400, 5000), |
|
| 77 |
plot_width = NULL, |
|
| 78 |
distribution_theme = c( |
|
| 79 |
"gray", "bw", "linedraw", "light", "dark", |
|
| 80 |
"minimal", "classic", "void", "test" |
|
| 81 |
), |
|
| 82 |
association_theme = c( |
|
| 83 |
"gray", "bw", "linedraw", "light", "dark", |
|
| 84 |
"minimal", "classic", "void", "test" |
|
| 85 |
), |
|
| 86 |
pre_output = NULL, |
|
| 87 |
post_output = NULL, |
|
| 88 |
ggplot2_args = teal.widgets::ggplot2_args()) {
|
|
| 89 | ! |
logger::log_info("Initializing tm_g_association")
|
| 90 | ! |
if (inherits(ref, "data_extract_spec")) ref <- list(ref) |
| 91 | ! |
if (inherits(vars, "data_extract_spec")) vars <- list(vars) |
| 92 | ! |
if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) |
| 93 | ||
| 94 | ! |
checkmate::assert_string(label) |
| 95 | ! |
checkmate::assert_list(ref, types = "data_extract_spec") |
| 96 | ! |
if (!all(vapply(ref, function(x) !x$select$multiple, logical(1)))) {
|
| 97 | ! |
stop("'ref' should not allow multiple selection")
|
| 98 |
} |
|
| 99 | ! |
checkmate::assert_list(vars, types = "data_extract_spec") |
| 100 | ! |
checkmate::assert_flag(show_association) |
| 101 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
| 102 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
| 103 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
| 104 | ! |
checkmate::assert_numeric( |
| 105 | ! |
plot_width[1], |
| 106 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
| 107 |
) |
|
| 108 | ! |
distribution_theme <- match.arg(distribution_theme) |
| 109 | ! |
association_theme <- match.arg(association_theme) |
| 110 | ! |
plot_choices <- c("Bivariate1", "Bivariate2")
|
| 111 | ! |
checkmate::assert_list(ggplot2_args, types = "ggplot2_args") |
| 112 | ! |
checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices))
|
| 113 | ||
| 114 | ! |
args <- as.list(environment()) |
| 115 | ||
| 116 | ! |
data_extract_list <- list( |
| 117 | ! |
ref = ref, |
| 118 | ! |
vars = vars |
| 119 |
) |
|
| 120 | ||
| 121 | ! |
module( |
| 122 | ! |
label = label, |
| 123 | ! |
server = srv_tm_g_association, |
| 124 | ! |
ui = ui_tm_g_association, |
| 125 | ! |
ui_args = args, |
| 126 | ! |
server_args = c( |
| 127 | ! |
data_extract_list, |
| 128 | ! |
list(plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args) |
| 129 |
), |
|
| 130 | ! |
datanames = teal.transform::get_extract_datanames(data_extract_list) |
| 131 |
) |
|
| 132 |
} |
|
| 133 | ||
| 134 |
ui_tm_g_association <- function(id, ...) {
|
|
| 135 | ! |
ns <- NS(id) |
| 136 | ! |
args <- list(...) |
| 137 | ! |
is_single_dataset_value <- teal.transform::is_single_dataset(args$ref, args$vars) |
| 138 | ||
| 139 | ! |
teal.widgets::standard_layout( |
| 140 | ! |
output = teal.widgets::white_small_well( |
| 141 | ! |
textOutput(ns("title")),
|
| 142 | ! |
tags$br(), |
| 143 | ! |
teal.widgets::plot_with_settings_ui(id = ns("myplot"))
|
| 144 |
), |
|
| 145 | ! |
encoding = div( |
| 146 |
### Reporter |
|
| 147 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")),
|
| 148 |
### |
|
| 149 | ! |
tags$label("Encodings", class = "text-primary"),
|
| 150 | ! |
teal.transform::datanames_input(args[c("ref", "vars")]),
|
| 151 | ! |
teal.transform::data_extract_ui( |
| 152 | ! |
id = ns("ref"),
|
| 153 | ! |
label = "Reference variable", |
| 154 | ! |
data_extract_spec = args$ref, |
| 155 | ! |
is_single_dataset = is_single_dataset_value |
| 156 |
), |
|
| 157 | ! |
teal.transform::data_extract_ui( |
| 158 | ! |
id = ns("vars"),
|
| 159 | ! |
label = "Associated variables", |
| 160 | ! |
data_extract_spec = args$vars, |
| 161 | ! |
is_single_dataset = is_single_dataset_value |
| 162 |
), |
|
| 163 | ! |
checkboxInput( |
| 164 | ! |
ns("association"),
|
| 165 | ! |
"Association with reference variable", |
| 166 | ! |
value = args$show_association |
| 167 |
), |
|
| 168 | ! |
checkboxInput( |
| 169 | ! |
ns("show_dist"),
|
| 170 | ! |
"Scaled frequencies", |
| 171 | ! |
value = FALSE |
| 172 |
), |
|
| 173 | ! |
checkboxInput( |
| 174 | ! |
ns("log_transformation"),
|
| 175 | ! |
"Log transformed", |
| 176 | ! |
value = FALSE |
| 177 |
), |
|
| 178 | ! |
teal.widgets::panel_group( |
| 179 | ! |
teal.widgets::panel_item( |
| 180 | ! |
title = "Plot settings", |
| 181 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("alpha"), "Scatterplot opacity:", c(0.5, 0, 1), ticks = FALSE),
|
| 182 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("size"), "Scatterplot points size:", c(2, 1, 8), ticks = FALSE),
|
| 183 | ! |
checkboxInput(ns("swap_axes"), "Swap axes", value = FALSE),
|
| 184 | ! |
checkboxInput(ns("rotate_xaxis_labels"), "Rotate X axis labels", value = FALSE),
|
| 185 | ! |
selectInput( |
| 186 | ! |
inputId = ns("distribution_theme"),
|
| 187 | ! |
label = "Distribution theme (by ggplot):", |
| 188 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 189 | ! |
selected = args$distribution_theme, |
| 190 | ! |
multiple = FALSE |
| 191 |
), |
|
| 192 | ! |
selectInput( |
| 193 | ! |
inputId = ns("association_theme"),
|
| 194 | ! |
label = "Association theme (by ggplot):", |
| 195 | ! |
choices = c("gray", "bw", "linedraw", "light", "dark", "minimal", "classic", "void", "test"),
|
| 196 | ! |
selected = args$association_theme, |
| 197 | ! |
multiple = FALSE |
| 198 |
) |
|
| 199 |
) |
|
| 200 |
) |
|
| 201 |
), |
|
| 202 | ! |
forms = tagList( |
| 203 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"),
|
| 204 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code")
|
| 205 |
), |
|
| 206 | ! |
pre_output = args$pre_output, |
| 207 | ! |
post_output = args$post_output |
| 208 |
) |
|
| 209 |
} |
|
| 210 | ||
| 211 |
srv_tm_g_association <- function(id, |
|
| 212 |
data, |
|
| 213 |
reporter, |
|
| 214 |
filter_panel_api, |
|
| 215 |
ref, |
|
| 216 |
vars, |
|
| 217 |
plot_height, |
|
| 218 |
plot_width, |
|
| 219 |
ggplot2_args) {
|
|
| 220 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
| 221 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
| 222 | ! |
checkmate::assert_class(data, "tdata") |
| 223 | ! |
moduleServer(id, function(input, output, session) {
|
| 224 | ! |
selector_list <- teal.transform::data_extract_multiple_srv( |
| 225 | ! |
data_extract = list(ref = ref, vars = vars), |
| 226 | ! |
datasets = data, |
| 227 | ! |
select_validation_rule = list( |
| 228 | ! |
ref = shinyvalidate::compose_rules( |
| 229 | ! |
shinyvalidate::sv_required("A reference variable needs to be selected."),
|
| 230 | ! |
~ if ((.) %in% selector_list()$vars()$select) {
|
| 231 | ! |
"Associated variables and reference variable cannot overlap" |
| 232 |
} |
|
| 233 |
), |
|
| 234 | ! |
vars = shinyvalidate::compose_rules( |
| 235 | ! |
shinyvalidate::sv_required("An associated variable needs to be selected."),
|
| 236 | ! |
~ if (length(selector_list()$ref()$select) != 0 && selector_list()$ref()$select %in% (.)) {
|
| 237 | ! |
"Associated variables and reference variable cannot overlap" |
| 238 |
} |
|
| 239 |
) |
|
| 240 |
) |
|
| 241 |
) |
|
| 242 | ||
| 243 | ! |
iv_r <- reactive({
|
| 244 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 245 | ! |
teal.transform::compose_and_enable_validators(iv, selector_list) |
| 246 |
}) |
|
| 247 | ||
| 248 | ! |
anl_merged_input <- teal.transform::merge_expression_srv( |
| 249 | ! |
datasets = data, |
| 250 | ! |
selector_list = selector_list, |
| 251 | ! |
join_keys = get_join_keys(data) |
| 252 |
) |
|
| 253 | ||
| 254 | ! |
anl_merged_q <- reactive({
|
| 255 | ! |
req(anl_merged_input()) |
| 256 | ! |
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
| 257 | ! |
teal.code::eval_code(as.expression(anl_merged_input()$expr)) |
| 258 |
}) |
|
| 259 | ||
| 260 | ! |
merged <- list( |
| 261 | ! |
anl_input_r = anl_merged_input, |
| 262 | ! |
anl_q_r = anl_merged_q |
| 263 |
) |
|
| 264 | ||
| 265 | ! |
output_q <- reactive({
|
| 266 | ! |
teal::validate_inputs(iv_r()) |
| 267 | ||
| 268 | ! |
ANL <- merged$anl_q_r()[["ANL"]] # nolint |
| 269 | ! |
teal::validate_has_data(ANL, 3) |
| 270 | ||
| 271 | ! |
vars_names <- merged$anl_input_r()$columns_source$vars |
| 272 | ||
| 273 | ! |
ref_name <- as.vector(merged$anl_input_r()$columns_source$ref) |
| 274 | ! |
association <- input$association |
| 275 | ! |
show_dist <- input$show_dist |
| 276 | ! |
log_transformation <- input$log_transformation |
| 277 | ! |
rotate_xaxis_labels <- input$rotate_xaxis_labels |
| 278 | ! |
swap_axes <- input$swap_axes |
| 279 | ! |
distribution_theme <- input$distribution_theme |
| 280 | ! |
association_theme <- input$association_theme |
| 281 | ||
| 282 | ! |
is_scatterplot <- is.numeric(ANL[[ref_name]]) && any(vapply(ANL[vars_names], is.numeric, logical(1))) |
| 283 | ! |
if (is_scatterplot) {
|
| 284 | ! |
shinyjs::show("alpha")
|
| 285 | ! |
shinyjs::show("size")
|
| 286 | ! |
alpha <- input$alpha # nolint |
| 287 | ! |
size <- input$size |
| 288 |
} else {
|
|
| 289 | ! |
shinyjs::hide("alpha")
|
| 290 | ! |
shinyjs::hide("size")
|
| 291 | ! |
alpha <- 0.5 |
| 292 | ! |
size <- 2 |
| 293 |
} |
|
| 294 | ||
| 295 | ! |
teal::validate_has_data(ANL[, c(ref_name, vars_names)], 3, complete = TRUE, allow_inf = FALSE) |
| 296 | ||
| 297 |
# reference |
|
| 298 | ! |
ref_class <- class(ANL[[ref_name]]) |
| 299 | ! |
if (is.numeric(ANL[[ref_name]]) && log_transformation) {
|
| 300 |
# works for both integers and doubles |
|
| 301 | ! |
ref_cl_name <- call("log", as.name(ref_name))
|
| 302 | ! |
ref_cl_lbl <- varname_w_label(ref_name, ANL, prefix = "Log of ") |
| 303 |
} else {
|
|
| 304 |
# silently ignore when non-numeric even if `log` is selected because some |
|
| 305 |
# variables may be numeric and others not |
|
| 306 | ! |
ref_cl_name <- as.name(ref_name) |
| 307 | ! |
ref_cl_lbl <- varname_w_label(ref_name, ANL) |
| 308 |
} |
|
| 309 | ||
| 310 | ! |
user_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 311 | ! |
user_plot = ggplot2_args[["Bivariate1"]], |
| 312 | ! |
user_default = ggplot2_args$default |
| 313 |
) |
|
| 314 | ||
| 315 | ! |
ref_call <- bivariate_plot_call( |
| 316 | ! |
data_name = "ANL", |
| 317 | ! |
x = ref_cl_name, |
| 318 | ! |
x_class = ref_class, |
| 319 | ! |
x_label = ref_cl_lbl, |
| 320 | ! |
freq = !show_dist, |
| 321 | ! |
theme = distribution_theme, |
| 322 | ! |
rotate_xaxis_labels = rotate_xaxis_labels, |
| 323 | ! |
swap_axes = FALSE, |
| 324 | ! |
size = size, |
| 325 | ! |
alpha = alpha, |
| 326 | ! |
ggplot2_args = user_ggplot2_args |
| 327 |
) |
|
| 328 | ||
| 329 |
# association |
|
| 330 | ! |
ref_class_cov <- ifelse(association, ref_class, "NULL") |
| 331 | ||
| 332 | ! |
print_call <- quote(print(p)) |
| 333 | ||
| 334 | ! |
var_calls <- lapply(vars_names, function(var_i) {
|
| 335 | ! |
var_class <- class(ANL[[var_i]]) |
| 336 | ! |
if (is.numeric(ANL[[var_i]]) && log_transformation) {
|
| 337 |
# works for both integers and doubles |
|
| 338 | ! |
var_cl_name <- call("log", as.name(var_i))
|
| 339 | ! |
var_cl_lbl <- varname_w_label(var_i, ANL, prefix = "Log of ") |
| 340 |
} else {
|
|
| 341 |
# silently ignore when non-numeric even if `log` is selected because some |
|
| 342 |
# variables may be numeric and others not |
|
| 343 | ! |
var_cl_name <- as.name(var_i) |
| 344 | ! |
var_cl_lbl <- varname_w_label(var_i, ANL) |
| 345 |
} |
|
| 346 | ||
| 347 | ! |
user_ggplot2_args <- teal.widgets::resolve_ggplot2_args( |
| 348 | ! |
user_plot = ggplot2_args[["Bivariate2"]], |
| 349 | ! |
user_default = ggplot2_args$default |
| 350 |
) |
|
| 351 | ||
| 352 | ! |
bivariate_plot_call( |
| 353 | ! |
data_name = "ANL", |
| 354 | ! |
x = ref_cl_name, |
| 355 | ! |
y = var_cl_name, |
| 356 | ! |
x_class = ref_class_cov, |
| 357 | ! |
y_class = var_class, |
| 358 | ! |
x_label = ref_cl_lbl, |
| 359 | ! |
y_label = var_cl_lbl, |
| 360 | ! |
theme = association_theme, |
| 361 | ! |
freq = !show_dist, |
| 362 | ! |
rotate_xaxis_labels = rotate_xaxis_labels, |
| 363 | ! |
swap_axes = swap_axes, |
| 364 | ! |
alpha = alpha, |
| 365 | ! |
size = size, |
| 366 | ! |
ggplot2_args = user_ggplot2_args |
| 367 |
) |
|
| 368 |
}) |
|
| 369 | ||
| 370 |
# helper function to format variable name |
|
| 371 | ! |
format_varnames <- function(x) {
|
| 372 | ! |
if (is.numeric(ANL[[x]]) && log_transformation) {
|
| 373 | ! |
varname_w_label(x, ANL, prefix = "Log of ") |
| 374 |
} else {
|
|
| 375 | ! |
varname_w_label(x, ANL) |
| 376 |
} |
|
| 377 |
} |
|
| 378 | ! |
new_title <- |
| 379 | ! |
if (association) {
|
| 380 | ! |
switch(as.character(length(vars_names)), |
| 381 | ! |
"0" = sprintf("Value distribution for %s", ref_cl_lbl),
|
| 382 | ! |
"1" = sprintf( |
| 383 | ! |
"Association between %s and %s", |
| 384 | ! |
ref_cl_lbl, |
| 385 | ! |
format_varnames(vars_names) |
| 386 |
), |
|
| 387 | ! |
sprintf( |
| 388 | ! |
"Associations between %s and: %s", |
| 389 | ! |
ref_cl_lbl, |
| 390 | ! |
paste(lapply(vars_names, format_varnames), collapse = ", ") |
| 391 |
) |
|
| 392 |
) |
|
| 393 |
} else {
|
|
| 394 | ! |
switch(as.character(length(vars_names)), |
| 395 | ! |
"0" = sprintf("Value distribution for %s", ref_cl_lbl),
|
| 396 | ! |
sprintf( |
| 397 | ! |
"Value distributions for %s and %s", |
| 398 | ! |
ref_cl_lbl, |
| 399 | ! |
paste(lapply(vars_names, format_varnames), collapse = ", ") |
| 400 |
) |
|
| 401 |
) |
|
| 402 |
} |
|
| 403 | ||
| 404 | ! |
teal.code::eval_code( |
| 405 | ! |
merged$anl_q_r(), |
| 406 | ! |
substitute( |
| 407 | ! |
expr = title <- new_title, |
| 408 | ! |
env = list(new_title = new_title) |
| 409 |
) |
|
| 410 |
) %>% |
|
| 411 | ! |
teal.code::eval_code( |
| 412 | ! |
substitute( |
| 413 | ! |
expr = {
|
| 414 | ! |
plots <- plot_calls |
| 415 | ! |
p <- tern::stack_grobs(grobs = lapply(plots, ggplotGrob)) |
| 416 | ! |
grid::grid.newpage() |
| 417 | ! |
grid::grid.draw(p) |
| 418 |
}, |
|
| 419 | ! |
env = list( |
| 420 | ! |
plot_calls = do.call( |
| 421 | ! |
"call", |
| 422 | ! |
c(list("list", ref_call), var_calls),
|
| 423 | ! |
quote = TRUE |
| 424 |
) |
|
| 425 |
) |
|
| 426 |
) |
|
| 427 |
) |
|
| 428 |
}) |
|
| 429 | ||
| 430 | ! |
plot_r <- shiny::reactive({
|
| 431 | ! |
shiny::req(iv_r()$is_valid()) |
| 432 | ! |
output_q()[["p"]] |
| 433 |
}) |
|
| 434 | ||
| 435 | ! |
pws <- teal.widgets::plot_with_settings_srv( |
| 436 | ! |
id = "myplot", |
| 437 | ! |
plot_r = plot_r, |
| 438 | ! |
height = plot_height, |
| 439 | ! |
width = plot_width |
| 440 |
) |
|
| 441 | ||
| 442 | ! |
output$title <- renderText({
|
| 443 | ! |
teal.code::dev_suppress(output_q()[["title"]]) |
| 444 |
}) |
|
| 445 | ||
| 446 | ! |
teal.widgets::verbatim_popup_srv( |
| 447 | ! |
id = "warning", |
| 448 | ! |
verbatim_content = reactive(teal.code::get_warnings(output_q())), |
| 449 | ! |
title = "Warning", |
| 450 | ! |
disabled = reactive(is.null(teal.code::get_warnings(output_q()))) |
| 451 |
) |
|
| 452 | ||
| 453 | ! |
teal.widgets::verbatim_popup_srv( |
| 454 | ! |
id = "rcode", |
| 455 | ! |
verbatim_content = reactive(teal.code::get_code(output_q())), |
| 456 | ! |
title = "Association Plot" |
| 457 |
) |
|
| 458 | ||
| 459 |
### REPORTER |
|
| 460 | ! |
if (with_reporter) {
|
| 461 | ! |
card_fun <- function(comment) {
|
| 462 | ! |
card <- teal::TealReportCard$new() |
| 463 | ! |
card$set_name("Association Plot")
|
| 464 | ! |
card$append_text("Association Plot", "header2")
|
| 465 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
| 466 | ! |
card$append_text("Plot", "header3")
|
| 467 | ! |
card$append_plot(plot_r(), dim = pws$dim()) |
| 468 | ! |
if (!comment == "") {
|
| 469 | ! |
card$append_text("Comment", "header3")
|
| 470 | ! |
card$append_text(comment) |
| 471 |
} |
|
| 472 | ! |
card$append_src(paste(teal.code::get_code(output_q()), collapse = "\n")) |
| 473 | ! |
card |
| 474 |
} |
|
| 475 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun)
|
| 476 |
} |
|
| 477 |
### |
|
| 478 |
}) |
|
| 479 |
} |
| 1 |
#' Data Table Viewer Teal Module |
|
| 2 |
#' |
|
| 3 |
#' A data table viewer shows the data using a paginated table. |
|
| 4 |
#' @md |
|
| 5 |
#' |
|
| 6 |
#' @inheritParams teal::module |
|
| 7 |
#' @inheritParams shared_params |
|
| 8 |
#' @param variables_selected (`list`) A named list of character vectors of the variables (i.e. columns) |
|
| 9 |
#' which should be initially shown for each dataset. Names of list elements should correspond to the names |
|
| 10 |
#' of the datasets available in the app. If no entry is specified for a dataset, the first six variables from that |
|
| 11 |
#' dataset will initially be shown. |
|
| 12 |
#' @param datasets_selected (`character`) A vector of datasets which should be |
|
| 13 |
#' shown and in what order. Names in the vector have to correspond with datasets names. |
|
| 14 |
#' If vector of length zero (default) then all datasets are shown. |
|
| 15 |
#' @param dt_args (named `list`) Additional arguments to be passed to `DT::datatable` |
|
| 16 |
#' (must not include `data` or `options`). |
|
| 17 |
#' @param dt_options (named `list`) The `options` argument to `DT::datatable`. By default |
|
| 18 |
#' `list(searching = FALSE, pageLength = 30, lengthMenu = c(5, 15, 30, 100), scrollX = TRUE)` |
|
| 19 |
#' @param server_rendering (`logical`) should the data table be rendered server side |
|
| 20 |
#' (see `server` argument of `DT::renderDataTable()`) |
|
| 21 |
#' @details |
|
| 22 |
#' The `DT` package has an option `DT.TOJSON_ARGS` to show `Inf` and `NA` in data tables. If this is something |
|
| 23 |
#' you require then set `options(DT.TOJSON_ARGS = list(na = "string"))` before running the module. |
|
| 24 |
#' Note though that sorting of numeric columns with `NA`/`Inf` will be lexicographic not numerical. |
|
| 25 |
#' @export |
|
| 26 |
#' @examples |
|
| 27 |
#' |
|
| 28 |
#' ADSL <- teal.modules.general::rADSL |
|
| 29 |
#' |
|
| 30 |
#' app <- teal::init( |
|
| 31 |
#' data = teal.data::cdisc_data( |
|
| 32 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- teal.modules.general::rADSL"),
|
|
| 33 |
#' check = TRUE |
|
| 34 |
#' ), |
|
| 35 |
#' modules = teal::modules( |
|
| 36 |
#' teal.modules.general::tm_data_table( |
|
| 37 |
#' variables_selected = list(ADSL = c("STUDYID", "USUBJID", "SUBJID", "SITEID", "AGE", "SEX")),
|
|
| 38 |
#' dt_args = list(caption = "ADSL Table Caption") |
|
| 39 |
#' ) |
|
| 40 |
#' ) |
|
| 41 |
#' ) |
|
| 42 |
#' if (interactive()) {
|
|
| 43 |
#' shinyApp(app$ui, app$server) |
|
| 44 |
#' } |
|
| 45 |
tm_data_table <- function(label = "Data Table", |
|
| 46 |
variables_selected = list(), |
|
| 47 |
datasets_selected = character(0), |
|
| 48 |
dt_args = list(), |
|
| 49 |
dt_options = list( |
|
| 50 |
searching = FALSE, |
|
| 51 |
pageLength = 30, |
|
| 52 |
lengthMenu = c(5, 15, 30, 100), |
|
| 53 |
scrollX = TRUE |
|
| 54 |
), |
|
| 55 |
server_rendering = FALSE, |
|
| 56 |
pre_output = NULL, |
|
| 57 |
post_output = NULL) {
|
|
| 58 | ! |
logger::log_info("Initializing tm_data_table")
|
| 59 | ! |
checkmate::assert_string(label) |
| 60 | ! |
checkmate::assert_list(variables_selected, min.len = 0, types = "character", names = "named") |
| 61 | ! |
if (length(variables_selected) > 0) {
|
| 62 | ! |
lapply(seq_along(variables_selected), function(i) {
|
| 63 | ! |
checkmate::assert_character(variables_selected[[i]], min.chars = 1, min.len = 1) |
| 64 | ! |
if (!is.null(names(variables_selected[[i]]))) {
|
| 65 | ! |
checkmate::assert_names(names(variables_selected[[i]])) |
| 66 |
} |
|
| 67 |
}) |
|
| 68 |
} |
|
| 69 | ! |
checkmate::assert_character(datasets_selected, min.len = 0, min.chars = 1) |
| 70 | ! |
checkmate::assert_list(dt_options, names = "named") |
| 71 | ! |
checkmate::assert( |
| 72 | ! |
checkmate::check_list(dt_args, len = 0), |
| 73 | ! |
checkmate::check_subset(names(dt_args), choices = names(formals(DT::datatable))) |
| 74 |
) |
|
| 75 | ||
| 76 | ! |
checkmate::assert_flag(server_rendering) |
| 77 | ||
| 78 | ! |
module( |
| 79 | ! |
label, |
| 80 | ! |
server = srv_page_data_table, |
| 81 | ! |
ui = ui_page_data_table, |
| 82 | ! |
datanames = if (length(datasets_selected) == 0) "all" else datasets_selected, |
| 83 | ! |
server_args = list( |
| 84 | ! |
datasets_selected = datasets_selected, |
| 85 | ! |
dt_args = dt_args, |
| 86 | ! |
dt_options = dt_options, |
| 87 | ! |
server_rendering = server_rendering |
| 88 |
), |
|
| 89 | ! |
ui_args = list( |
| 90 | ! |
selected = variables_selected, |
| 91 | ! |
datasets_selected = datasets_selected, |
| 92 | ! |
pre_output = pre_output, |
| 93 | ! |
post_output = post_output |
| 94 |
) |
|
| 95 |
) |
|
| 96 |
} |
|
| 97 | ||
| 98 | ||
| 99 |
# ui page module |
|
| 100 |
ui_page_data_table <- function(id, |
|
| 101 |
data, |
|
| 102 |
selected, |
|
| 103 |
datasets_selected, |
|
| 104 |
pre_output = NULL, |
|
| 105 |
post_output = NULL) {
|
|
| 106 | ! |
ns <- NS(id) |
| 107 | ||
| 108 | ! |
datanames <- names(data) |
| 109 | ||
| 110 | ! |
if (!identical(datasets_selected, character(0))) {
|
| 111 | ! |
stopifnot(all(datasets_selected %in% datanames)) |
| 112 | ! |
datanames <- datasets_selected |
| 113 |
} |
|
| 114 | ||
| 115 | ! |
shiny::tagList( |
| 116 | ! |
include_css_files("custom"),
|
| 117 | ! |
teal.widgets::standard_layout( |
| 118 | ! |
output = tagList( |
| 119 | ! |
fluidRow( |
| 120 | ! |
column( |
| 121 | ! |
width = 12, |
| 122 | ! |
checkboxInput( |
| 123 | ! |
ns("if_distinct"),
|
| 124 | ! |
"Show only distinct rows:", |
| 125 | ! |
value = FALSE |
| 126 |
) |
|
| 127 |
) |
|
| 128 |
), |
|
| 129 | ! |
fluidRow( |
| 130 | ! |
class = "mb-8", |
| 131 | ! |
column( |
| 132 | ! |
width = 12, |
| 133 | ! |
do.call( |
| 134 | ! |
tabsetPanel, |
| 135 | ! |
lapply( |
| 136 | ! |
datanames, |
| 137 | ! |
function(x) {
|
| 138 | ! |
dataset <- isolate(data[[x]]()) |
| 139 | ! |
choices <- names(dataset) |
| 140 | ! |
labels <- vapply( |
| 141 | ! |
dataset, |
| 142 | ! |
function(x) ifelse(is.null(attr(x, "label")), "", attr(x, "label")), |
| 143 | ! |
character(1) |
| 144 |
) |
|
| 145 | ! |
names(choices) <- ifelse( |
| 146 | ! |
is.na(labels) | labels == "", |
| 147 | ! |
choices, |
| 148 | ! |
paste(choices, labels, sep = ": ") |
| 149 |
) |
|
| 150 | ! |
selected <- if (!is.null(selected[[x]])) {
|
| 151 | ! |
selected[[x]] |
| 152 |
} else {
|
|
| 153 | ! |
utils::head(choices) |
| 154 |
} |
|
| 155 | ! |
tabPanel( |
| 156 | ! |
title = x, |
| 157 | ! |
column( |
| 158 | ! |
width = 12, |
| 159 | ! |
div( |
| 160 | ! |
class = "mt-4", |
| 161 | ! |
ui_data_table( |
| 162 | ! |
id = ns(x), |
| 163 | ! |
choices = choices, |
| 164 | ! |
selected = selected |
| 165 |
) |
|
| 166 |
) |
|
| 167 |
) |
|
| 168 |
) |
|
| 169 |
} |
|
| 170 |
) |
|
| 171 |
) |
|
| 172 |
) |
|
| 173 |
) |
|
| 174 |
), |
|
| 175 | ! |
pre_output = pre_output, |
| 176 | ! |
post_output = post_output |
| 177 |
) |
|
| 178 |
) |
|
| 179 |
} |
|
| 180 | ||
| 181 | ||
| 182 |
# server page module |
|
| 183 |
srv_page_data_table <- function(id, |
|
| 184 |
data, |
|
| 185 |
datasets_selected, |
|
| 186 |
dt_args, |
|
| 187 |
dt_options, |
|
| 188 |
server_rendering) {
|
|
| 189 | ! |
checkmate::assert_class(data, "tdata") |
| 190 | ! |
moduleServer(id, function(input, output, session) {
|
| 191 | ! |
if_filtered <- reactive(as.logical(input$if_filtered)) |
| 192 | ! |
if_distinct <- reactive(as.logical(input$if_distinct)) |
| 193 | ||
| 194 | ! |
datanames <- names(data) |
| 195 | ||
| 196 | ! |
lapply( |
| 197 | ! |
datanames, |
| 198 | ! |
function(x) {
|
| 199 | ! |
srv_data_table( |
| 200 | ! |
id = x, |
| 201 | ! |
data = data, |
| 202 | ! |
dataname = x, |
| 203 | ! |
if_filtered = if_filtered, |
| 204 | ! |
if_distinct = if_distinct, |
| 205 | ! |
dt_args = dt_args, |
| 206 | ! |
dt_options = dt_options, |
| 207 | ! |
server_rendering = server_rendering |
| 208 |
) |
|
| 209 |
} |
|
| 210 |
) |
|
| 211 |
}) |
|
| 212 |
} |
|
| 213 | ||
| 214 |
ui_data_table <- function(id, |
|
| 215 |
choices, |
|
| 216 |
selected) {
|
|
| 217 | ! |
ns <- NS(id) |
| 218 | ||
| 219 | ! |
if (!is.null(selected)) {
|
| 220 | ! |
all_choices <- choices |
| 221 | ! |
choices <- c(selected, setdiff(choices, selected)) |
| 222 | ! |
names(choices) <- names(all_choices)[match(choices, all_choices)] |
| 223 |
} |
|
| 224 | ||
| 225 | ! |
tagList( |
| 226 | ! |
teal.widgets::get_dt_rows(ns("data_table"), ns("dt_rows")),
|
| 227 | ! |
fluidRow( |
| 228 | ! |
teal.widgets::optionalSelectInput( |
| 229 | ! |
ns("variables"),
|
| 230 | ! |
"Select variables:", |
| 231 | ! |
choices = choices, |
| 232 | ! |
selected = selected, |
| 233 | ! |
multiple = TRUE, |
| 234 | ! |
width = "100%" |
| 235 |
) |
|
| 236 |
), |
|
| 237 | ! |
fluidRow( |
| 238 | ! |
DT::dataTableOutput(ns("data_table"), width = "100%")
|
| 239 |
) |
|
| 240 |
) |
|
| 241 |
} |
|
| 242 | ||
| 243 |
srv_data_table <- function(id, |
|
| 244 |
data, |
|
| 245 |
dataname, |
|
| 246 |
if_filtered, |
|
| 247 |
if_distinct, |
|
| 248 |
dt_args, |
|
| 249 |
dt_options, |
|
| 250 |
server_rendering) {
|
|
| 251 | ! |
moduleServer(id, function(input, output, session) {
|
| 252 | ! |
iv <- shinyvalidate::InputValidator$new() |
| 253 | ! |
iv$add_rule("variables", shinyvalidate::sv_required("Please select valid variable names"))
|
| 254 | ! |
iv$add_rule("variables", shinyvalidate::sv_in_set(
|
| 255 | ! |
set = names(data[[dataname]]()), message_fmt = "Not all selected variables exist in the data" |
| 256 |
)) |
|
| 257 | ! |
iv$enable() |
| 258 | ||
| 259 | ! |
output$data_table <- DT::renderDataTable(server = server_rendering, {
|
| 260 | ! |
teal::validate_inputs(iv) |
| 261 | ||
| 262 | ! |
df <- data[[dataname]]() |
| 263 | ! |
variables <- input$variables |
| 264 | ||
| 265 | ! |
teal::validate_has_data(df, min_nrow = 1L, msg = paste("data", dataname, "is empty"))
|
| 266 | ||
| 267 | ! |
dataframe_selected <- if (if_distinct()) {
|
| 268 | ! |
dplyr::count(df, dplyr::across(tidyselect::all_of(variables))) |
| 269 |
} else {
|
|
| 270 | ! |
df[variables] |
| 271 |
} |
|
| 272 | ||
| 273 | ! |
dt_args$options <- dt_options |
| 274 | ! |
if (!is.null(input$dt_rows)) {
|
| 275 | ! |
dt_args$options$pageLength <- input$dt_rows # nolint |
| 276 |
} |
|
| 277 | ! |
dt_args$data <- dataframe_selected |
| 278 | ||
| 279 | ! |
do.call(DT::datatable, dt_args) |
| 280 |
}) |
|
| 281 |
}) |
|
| 282 |
} |
| 1 |
.onLoad <- function(libname, pkgname) { # nolint
|
|
| 2 | ! |
teal.logger::register_logger(namespace = "teal.modules.general") |
| 3 |
} |