From 2c39313ffbb4a4741a96ec29119bc8bbf1d1683e Mon Sep 17 00:00:00 2001 From: Andreas Gammelgaard Damsbo Date: Thu, 2 Oct 2025 11:14:35 +0200 Subject: [PATCH 1/6] feat: correct labels in Euler diagrams --- R/helpers.R | 84 ++++++++++++++++++++++++++++++++++++++++++- R/plot_euler.R | 42 ++++++++++++++++------ R/regression-module.R | 58 ++++++++++++++++++++---------- R/regression_model.R | 81 ----------------------------------------- 4 files changed, 155 insertions(+), 110 deletions(-) diff --git a/R/helpers.R b/R/helpers.R index def92fa9..c038c5c1 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -154,7 +154,8 @@ dummy_Imports <- function() { parameters::ci(), DT::addRow(), bslib::accordion(), - NHANES::NHANES() + NHANES::NHANES(), + stRoke::add_padding() ) # https://github.com/hadley/r-pkgs/issues/828 } @@ -668,3 +669,84 @@ is_identical_to_previous <- function(data, no.name = TRUE) { simple_snake <- function(data){ gsub("[\\s+]","_",gsub("[^\\w\\s:-]", "", tolower(data), perl=TRUE), perl=TRUE) } + +#' Data type assessment. +#' +#' @description +#' These are more overall than the native typeof. This is used to assess a more +#' meaningful "clinical" data type. +#' +#' @param data vector or data.frame. if data frame, each column is evaluated. +#' +#' @returns outcome type +#' @export +#' +#' @examples +#' mtcars |> +#' default_parsing() |> +#' lapply(data_type) +#' mtcars |> +#' default_parsing() |> +#' data_type() +#' c(1, 2) |> data_type() +#' 1 |> data_type() +#' c(rep(NA, 10)) |> data_type() +#' sample(1:100, 50) |> data_type() +#' factor(letters[1:20]) |> data_type() +#' as.Date(1:20) |> data_type() +data_type <- function(data) { + if (is.data.frame(data)) { + sapply(data, data_type) + } else { + cl_d <- class(data) + l_unique <- length(unique(na.omit(data))) + if (all(is.na(data))) { + out <- "empty" + } else if (l_unique < 2) { + out <- "monotone" + } else if (any(c("factor", "logical") %in% cl_d) | l_unique == 2) { + if (identical("logical", cl_d) | l_unique == 2) { + out <- "dichotomous" + } else { + # if (is.ordered(data)) { + # out <- "ordinal" + # } else { + out <- "categorical" + # } + } + } else if (identical(cl_d, "character")) { + out <- "text" + } else if (any(c("hms", "Date", "POSIXct", "POSIXt") %in% cl_d)) { + out <- "datetime" + } else if (l_unique > 2) { + ## Previously had all thinkable classes + ## Now just assumes the class has not been defined above + ## any(c("numeric", "integer", "hms", "Date", "timediff") %in% cl_d) & + out <- "continuous" + } else { + out <- "unknown" + } + + out + } +} + +#' Recognised data types from data_type +#' +#' @returns vector +#' @export +#' +#' @examples +#' data_types() +data_types <- function() { + list( + "empty" = list(descr="Variable of all NAs",classes="Any class"), + "monotone" = list(descr="Variable with only one unique value",classes="Any class"), + "dichotomous" = list(descr="Variable with only two unique values",classes="Any class"), + "categorical"= list(descr="Factor variable",classes="factor (ordered or unordered)"), + "text"= list(descr="Character variable",classes="character"), + "datetime"= list(descr="Variable of time, date or datetime values",classes="hms, Date, POSIXct and POSIXt"), + "continuous"= list(descr="Numeric variable",classes="numeric, integer or double"), + "unknown"= list(descr="Anything not falling within the previous",classes="Any other class") + ) +} diff --git a/R/plot_euler.R b/R/plot_euler.R index e1e48aa8..17345020 100644 --- a/R/plot_euler.R +++ b/R/plot_euler.R @@ -1,7 +1,7 @@ #' Area proportional venn diagrams #' #' @description -#' THis is slightly modified from https://gist.github.com/danlooo/d23d8bcf8856c7dd8e86266097404ded +#' This is slightly modified from https://gist.github.com/danlooo/d23d8bcf8856c7dd8e86266097404ded #' #' This functions uses eulerr::euler to plot area proportional venn diagramms #' but plots it using ggplot2 @@ -11,18 +11,27 @@ #' @param show_quantities whether to show number of intersecting elements #' @param show_labels whether to show set names #' @param ... further arguments passed to eulerr::euler +#' +#' @include data_plots.R ggeulerr <- function( combinations, show_quantities = TRUE, show_labels = TRUE, ...) { - # browser() + + ## Extracting labels + labs <- sapply(names(combinations),\(.x){ + # browser() + get_label(combinations,.x) + }) + data <- - eulerr::euler(combinations = combinations, ...) |> + ## Set labels as variable names for nicer plotting + setNames(as.data.frame(combinations),labs) |> + eulerr::euler(...) |> plot(quantities = show_quantities) |> purrr::pluck("data") - tibble::as_tibble(data$ellipses, rownames = "Variables") |> ggplot2::ggplot() + ggforce::geom_ellipse( @@ -38,7 +47,8 @@ ggeulerr <- function( dplyr::mutate( label = labels |> purrr::map2(quantities, ~ { if (!is.na(.x) && !is.na(.y) && show_labels) { - paste0(.x, "\n", sprintf(.y, fmt = "%.2g")) + paste0(.x, "\n", sprintf(.y, fmt = "%.4g")) + # glue::glue("{.x}\n{round(.y,0)}") } else if (!is.na(.x) && show_labels) { .x } else if (!is.na(.y)) { @@ -77,6 +87,21 @@ ggeulerr <- function( #' ) |> plot_euler("A", c("B", "C"), "D", seed = 4) #' mtcars |> plot_euler("vs", "am", seed = 1) #' mtcars |> plot_euler("vs", "am", "cyl", seed = 1) +#' stRoke::trial |> +#' dplyr::mutate( +#' mfi_cut = cut(mfi_6, c(0, 12, max(mfi_6, na.rm = TRUE))), +#' mdi_cut = cut(mdi_6, c(0, 20, max(mdi_6, na.rm = TRUE))) +#' ) |> +#' purrr::map2( +#' c(sapply(stRoke::trial, \(.x)REDCapCAST::get_attr(.x, attr = "label")), "Fatigue", "Depression"), +#' \(.x, .y){ +#' REDCapCAST::set_attr(.x, .y, "label") +#' } +#' ) |> +#' dplyr::bind_cols() |> +#' plot_euler("mfi_cut", "mdi_cut") +#' stRoke::trial |> +#' plot_euler(pri="male", sec=c("hypertension")) plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103) { set.seed(seed = seed) if (!is.null(ter)) { @@ -84,16 +109,13 @@ plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103) { } else { ds <- list(data) } - out <- lapply(ds, \(.x){ .x[c(pri, sec)] |> - as.data.frame() |> na.omit() |> plot_euler_single() }) -# browser() - wrap_plot_list(out,title=glue::glue("Grouped by {get_label(data,ter)}")) - # patchwork::wrap_plots(out) + + wrap_plot_list(out, title = glue::glue(i18n$t("Grouped by {get_label(data,ter)}"))) } #' Easily plot single euler diagrams diff --git a/R/regression-module.R b/R/regression-module.R index d8ccc996..52898ce5 100644 --- a/R/regression-module.R +++ b/R/regression-module.R @@ -72,13 +72,15 @@ regression_ui <- function(id, ...) { shiny::radioButtons( inputId = ns("all"), label = i18n$t("Specify covariables"), - inline = TRUE, selected = 2, + inline = TRUE, + selected = 2, choiceNames = c( "Yes", "No" ), choiceValues = c(1, 2) ), + # shiny::uiOutput(outputId = ns("all")), shiny::conditionalPanel( condition = "input.all==1", shiny::uiOutput(outputId = ns("regression_vars")), @@ -131,7 +133,7 @@ regression_ui <- function(id, ...) { ) ), bslib::nav_panel( - title = "Coefficient plot", + title = i18n$t("Coefficient plot"), bslib::layout_sidebar( sidebar = bslib::sidebar( bslib::accordion( @@ -243,11 +245,6 @@ regression_server <- function(id, } }) - shiny::observe({ - bslib::accordion_panel_update(id = "acc_reg", target = "acc_pan_reg", title = i18n$t("Regression")) - bslib::accordion_panel_update(id = "acc_coef_plot", target = "acc_pan_coef_plot", title = i18n$t("Coefficients plot")) - bslib::accordion_panel_update(id = "acc_checks", target = "acc_pan_checks", title = i18n$t("Checks")) - }) output$data_info <- shiny::renderUI({ shiny::req(regression_vars()) @@ -255,6 +252,31 @@ regression_server <- function(id, data_description(data_r()[regression_vars()]) }) + ## Update on laguage change + + shiny::observe({ + bslib::accordion_panel_update(id = "acc_reg", target = "acc_pan_reg", title = i18n$t("Regression")) + bslib::accordion_panel_update(id = "acc_coef_plot", target = "acc_pan_coef_plot", title = i18n$t("Coefficients plot")) + bslib::accordion_panel_update(id = "acc_checks", target = "acc_pan_checks", title = i18n$t("Checks")) + }) + + # shiny::observe({ + # shiny::updateRadioButtons( + # session = session, + # inputId = "all", + # label = i18n$t("Specify covariables"), + # # inline = TRUE, + # # selected = 2, + # choiceNames = c( + # i18n$t("Yes"), + # i18n$t("No") + # ), + # choiceValues = c(1, 2) + # ) + # }) + + + ############################################################################## ######### ######### Input fields @@ -278,7 +300,7 @@ regression_server <- function(id, columnSelectInput( inputId = ns("outcome_var"), selected = NULL, - label = "Select outcome variable", + label = i18n$t("Select outcome variable"), data = data_r(), multiple = FALSE ) @@ -288,7 +310,7 @@ regression_server <- function(id, shiny::req(input$outcome_var) shiny::selectizeInput( inputId = ns("regression_type"), - label = "Choose regression analysis", + label = i18n$t("Choose regression analysis"), ## The below ifelse statement handles the case of loading a new dataset choices = possible_functions( data = dplyr::select( @@ -307,7 +329,7 @@ regression_server <- function(id, shiny::selectizeInput( inputId = ns("factor_vars"), selected = colnames(data_r())[sapply(data_r(), is.factor)], - label = "Covariables to format as categorical", + label = i18n$t("Covariables to format as categorical"), choices = colnames(data_r()), multiple = TRUE ) @@ -327,7 +349,7 @@ regression_server <- function(id, columnSelectInput( inputId = ns("strat_var"), selected = "none", - label = "Select variable to stratify baseline", + label = i18n$t("Select variable to stratify baseline"), data = data_r(), col_subset = c( "none", @@ -342,7 +364,7 @@ regression_server <- function(id, shiny::selectInput( inputId = ns("plot_model"), selected = 1, - label = "Select models to plot", + label = i18n$t("Select models to plot"), choices = names(rv$list$regression$tables), multiple = TRUE ) @@ -392,7 +414,7 @@ regression_server <- function(id, rv$list$regression$models <- model_lists }, error = function(err) { - showNotification(paste0("Creating regression models failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Creating regression models failed with the following error:"), err), type = "err") } ) } @@ -457,7 +479,7 @@ regression_server <- function(id, showNotification(paste0(warn), type = "warning") }, error = function(err) { - showNotification(paste0("Creating a regression table failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Creating a regression table failed with the following error:"), err), type = "err") } ) } @@ -558,7 +580,7 @@ regression_server <- function(id, output$download_plot <- shiny::downloadHandler( filename = paste0("regression_plot.", input$plot_type), content = function(file) { - shiny::withProgress(message = "Saving the plot. Hold on for a moment..", { + shiny::withProgress(message = i18n$t("Saving the plot. Hold on for a moment.."), { ggplot2::ggsave( filename = file, plot = rv$plot, @@ -595,7 +617,7 @@ regression_server <- function(id, # showNotification(paste0(warn), type = "warning") # }, error = function(err) { - showNotification(paste0("Running model assumptions checks failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Running model assumptions checks failed with the following error:"), err), type = "err") } ) } @@ -616,7 +638,7 @@ regression_server <- function(id, vectorSelectInput( inputId = ns("plot_checks"), selected = 1, - label = "Select checks to plot", + label = i18n$t("Select checks to plot"), choices = names, multiple = TRUE ) @@ -631,7 +653,7 @@ regression_server <- function(id, if (!is.null(rv$list$regression$tables)) { p <- rv$check_plot() + # patchwork::wrap_plots() + - patchwork::plot_annotation(title = "Multivariable regression model checks") + patchwork::plot_annotation(title = i18n$t("Multivariable regression model checks")) layout <- sapply(seq_len(length(p)), \(.x){ diff --git a/R/regression_model.R b/R/regression_model.R index df79cc16..f509f7d7 100644 --- a/R/regression_model.R +++ b/R/regression_model.R @@ -242,87 +242,6 @@ regression_model_uv <- function(data, ### HELPERS -#' Data type assessment. -#' -#' @description -#' These are more overall than the native typeof. This is used to assess a more -#' meaningful "clinical" data type. -#' -#' @param data vector or data.frame. if data frame, each column is evaluated. -#' -#' @returns outcome type -#' @export -#' -#' @examples -#' mtcars |> -#' default_parsing() |> -#' lapply(data_type) -#' mtcars |> -#' default_parsing() |> -#' data_type() -#' c(1, 2) |> data_type() -#' 1 |> data_type() -#' c(rep(NA, 10)) |> data_type() -#' sample(1:100, 50) |> data_type() -#' factor(letters[1:20]) |> data_type() -#' as.Date(1:20) |> data_type() -data_type <- function(data) { - if (is.data.frame(data)) { - sapply(data, data_type) - } else { - cl_d <- class(data) - l_unique <- length(unique(na.omit(data))) - if (all(is.na(data))) { - out <- "empty" - } else if (l_unique < 2) { - out <- "monotone" - } else if (any(c("factor", "logical") %in% cl_d) | l_unique == 2) { - if (identical("logical", cl_d) | l_unique == 2) { - out <- "dichotomous" - } else { - # if (is.ordered(data)) { - # out <- "ordinal" - # } else { - out <- "categorical" - # } - } - } else if (identical(cl_d, "character")) { - out <- "text" - } else if (any(c("hms", "Date", "POSIXct", "POSIXt") %in% cl_d)) { - out <- "datetime" - } else if (l_unique > 2) { - ## Previously had all thinkable classes - ## Now just assumes the class has not been defined above - ## any(c("numeric", "integer", "hms", "Date", "timediff") %in% cl_d) & - out <- "continuous" - } else { - out <- "unknown" - } - - out - } -} - -#' Recognised data types from data_type -#' -#' @returns vector -#' @export -#' -#' @examples -#' data_types() -data_types <- function() { - list( - "empty" = list(descr="Variable of all NAs",classes="Any class"), - "monotone" = list(descr="Variable with only one unique value",classes="Any class"), - "dichotomous" = list(descr="Variable with only two unique values",classes="Any class"), - "categorical"= list(descr="Factor variable",classes="factor (ordered or unordered)"), - "text"= list(descr="Character variable",classes="character"), - "datetime"= list(descr="Variable of time, date or datetime values",classes="hms, Date, POSIXct and POSIXt"), - "continuous"= list(descr="Numeric variable",classes="numeric, integer or double"), - "unknown"= list(descr="Anything not falling within the previous",classes="Any other class") - ) -} - #' Implemented functions #' From 9c1d6ed6309a5167cb72e0eaba18fb07669a90c1 Mon Sep 17 00:00:00 2001 From: Andreas Gammelgaard Damsbo Date: Thu, 2 Oct 2025 11:15:40 +0200 Subject: [PATCH 2/6] feat: loading of local data was internalised based on the datamods package --- R/import_globalenv-ext.R | 357 +++++++++++++++++++++++++++++++++++++++ R/regression_table.R | 34 ---- R/sysdata.rda | Bin 2713 -> 2781 bytes man/data_type.Rd | 2 +- man/data_types.Rd | 2 +- man/get_data_packages.Rd | 21 +++ man/ggeulerr.Rd | 2 +- man/import-globalenv.Rd | 50 ++++++ man/list_pkg_data.Rd | 21 +++ man/plot_euler.Rd | 15 ++ man/regression_table.Rd | 32 ---- 11 files changed, 467 insertions(+), 69 deletions(-) create mode 100644 R/import_globalenv-ext.R create mode 100644 man/get_data_packages.Rd create mode 100644 man/import-globalenv.Rd create mode 100644 man/list_pkg_data.Rd diff --git a/R/import_globalenv-ext.R b/R/import_globalenv-ext.R new file mode 100644 index 00000000..5368d45e --- /dev/null +++ b/R/import_globalenv-ext.R @@ -0,0 +1,357 @@ + +#' @title Import data from an Environment +#' +#' @description Let the user select a dataset from its own environment or from a package's environment. +#' Modified from datamods +#' +#' @param id Module's ID. +#' @param globalenv Search for data in Global environment. +#' @param packages Name of packages in which to search data. +#' @param title Module's title, if `TRUE` use the default title, +#' use `NULL` for no title or a `shiny.tag` for a custom one. +#' +#' @export +#' +#' @name import-globalenv +#' +import_globalenv_ui <- function(id, + globalenv = TRUE, + packages = datamods::get_data_packages(), + title = TRUE) { + + ns <- NS(id) + + choices <- list() + if (isTRUE(globalenv)) { + choices <- append(choices, "Global Environment") + } + if (!is.null(packages)) { + choices <- append(choices, list(Packages = as.character(packages))) + } + + if (isTRUE(globalenv)) { + selected <- "Global Environment" + } else { + selected <- packages[1] + } + + if (isTRUE(title)) { + title <- tags$h4( + i18n$t("Import a dataset from an environment"), + class = "datamods-title" + ) + } + + tags$div( + class = "datamods-import", + datamods:::html_dependency_datamods(), + title, + shinyWidgets::pickerInput( + inputId = ns("env"), + label = i18n$t("Select a data source:"), + choices = choices, + selected = selected, + width = "100%", + options = list( + "title" = i18n$t("Select source"), + "live-search" = TRUE, + "size" = 10 + ) + ), + shinyWidgets::pickerInput( + inputId = ns("data"), + label = i18n$t("Select a dataset:"), + # selected = character(0), + choices = NULL, + # options = list(title = i18n$t("List of datasets...")), + width = "100%" + ), + + tags$div( + id = ns("import-placeholder"), + shinyWidgets::alert( + id = ns("import-result"), + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a datasat from your environment or from the environment of a package."), + dismissible = TRUE + ) + ), + uiOutput( + outputId = ns("container_valid_btn"), + style = "margin-top: 20px;" + ) + ) +} + + + +#' @param btn_show_data Display or not a button to display data in a modal window if import is successful. +#' @param show_data_in Where to display data: in a `"popup"` or in a `"modal"` window. +#' @param trigger_return When to update selected data: +#' `"button"` (when user click on button) or +#' `"change"` (each time user select a dataset in the list). +#' @param return_class Class of returned data: `data.frame`, `data.table`, `tbl_df` (tibble) or `raw`. +#' @param reset A `reactive` function that when triggered resets the data. +#' +#' @export +#' +#' @importFrom shiny moduleServer reactiveValues observeEvent reactive removeUI is.reactive icon actionLink isTruthy +#' @importFrom htmltools tags tagList +#' @importFrom shinyWidgets updatePickerInput +#' +#' @rdname import-globalenv +import_globalenv_server <- function(id, + btn_show_data = TRUE, + show_data_in = c("popup", "modal"), + trigger_return = c("button", "change"), + return_class = c("data.frame", "data.table", "tbl_df", "raw"), + reset = reactive(NULL)) { + + trigger_return <- match.arg(trigger_return) + return_class <- match.arg(return_class) + + module <- function(input, output, session) { + + ns <- session$ns + imported_rv <- reactiveValues(data = NULL, name = NULL) + temporary_rv <- reactiveValues(data = NULL, name = NULL, status = NULL) + + observeEvent(reset(), { + temporary_rv$data <- NULL + temporary_rv$name <- NULL + temporary_rv$status <- NULL + }) + + output$container_valid_btn <- renderUI({ + if (identical(trigger_return, "button")) { + button_import() + } + }) + + observeEvent(input$env, { + if (identical(input$env, "Global Environment")) { + choices <- datamods:::search_obj("data.frame") + } else { + choices <- datamods:::list_pkg_data(input$env) + } + if (is.null(choices)) { + choices <- i18n$t("No dataset here...") + choicesOpt <- list(disabled = TRUE) + } else { + choicesOpt <- list( + subtext = datamods:::get_dimensions(choices) + ) + } + temporary_rv$package <- attr(choices, "package") + shinyWidgets::updatePickerInput( + session = session, + inputId = "data", + selected = character(0), + choices = choices, + choicesOpt = choicesOpt, + options = list(title = i18n$t("List of datasets...")) + ) + }) + + observe( + shinyWidgets::alert( + id = "import-result", + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a datasat from your environment or from the environment of a package."), + dismissible = TRUE + ) + ) + + + observeEvent(input$trigger, { + if (identical(trigger_return, "change")) { + datamods:::hideUI(selector = paste0("#", ns("container_valid_btn"))) + } + }) + + + + observeEvent(input$data, { + if (!isTruthy(input$data)) { + datamods:::toggle_widget(inputId = "confirm", enable = FALSE) + datamods:::insert_alert( + selector = ns("import"), + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a dataset from your environment or from the environment of a package.") + ) + } else { + name_df <- input$data + + if (!is.null(temporary_rv$package)) { + attr(name_df, "package") <- temporary_rv$package + } + + imported <- try(get_env_data(name_df), silent = TRUE) + + if (inherits(imported, "try-error") || NROW(imported) < 1) { + datamods:::toggle_widget(inputId = "confirm", enable = FALSE) + datamods:::insert_error(mssg = i18n$t(attr(imported, "condition")$message)) + temporary_rv$status <- "error" + temporary_rv$data <- NULL + temporary_rv$name <- NULL + } else { + datamods:::toggle_widget(inputId = "confirm", enable = TRUE) + datamods:::insert_alert( + selector = ns("import"), + status = "success", + datamods:::make_success_alert( + imported, + trigger_return = trigger_return, + btn_show_data = btn_show_data + ) + ) + pkg <- attr(name_df, "package") + if (!is.null(pkg)) { + name <- paste(pkg, input$data, sep = "::") + } else { + name <- input$data + } + name <- trimws(sub("\\(([^\\)]+)\\)", "", name)) + temporary_rv$status <- "success" + temporary_rv$data <- imported + temporary_rv$name <- name + } + } + }, ignoreInit = TRUE, ignoreNULL = FALSE) + + + observeEvent(input$see_data, { + show_data(temporary_rv$data, title = i18n$t("Imported data"), type = show_data_in) + }) + + observeEvent(input$confirm, { + imported_rv$data <- temporary_rv$data + imported_rv$name <- temporary_rv$name + }) + + + if (identical(trigger_return, "button")) { + return(list( + status = reactive(temporary_rv$status), + name = reactive(imported_rv$name), + data = reactive(datamods:::as_out(imported_rv$data, return_class)) + )) + } else { + return(list( + status = reactive(temporary_rv$status), + name = reactive(temporary_rv$name), + data = reactive(datamods:::as_out(temporary_rv$data, return_class)) + )) + } + } + + moduleServer( + id = id, + module = module + ) +} + + + + + + + +# utils ------------------------------------------------------------------- + + +#' Get packages containing datasets +#' +#' @return a character vector of packages names +#' @export +#' +#' @importFrom utils data +#' +#' @examples +#' if (interactive()) { +#' +#' get_data_packages() +#' +#' } +get_data_packages <- function() { + suppressWarnings({ + pkgs <- data(package = .packages(all.available = TRUE)) + }) + unique(pkgs$results[, 1]) +} + + +#' List dataset contained in a package +#' +#' @param pkg Name of the package, must be installed. +#' +#' @return a \code{character} vector or \code{NULL}. +#' @export +#' +#' @importFrom utils data +#' +#' @examples +#' +#' list_pkg_data("ggplot2") +list_pkg_data <- function(pkg) { + if (isTRUE(requireNamespace(pkg, quietly = TRUE))) { + list_data <- data(package = pkg, envir = environment())$results[, "Item"] + list_data <- sort(list_data) + attr(list_data, "package") <- pkg + if (length(list_data) < 1) { + NULL + } else { + unname(list_data) + } + } else { + NULL + } +} + +#' @importFrom utils data +get_env_data <- function(obj, env = globalenv()) { + pkg <- attr(obj, "package") + re <- regexpr(pattern = "\\(([^\\)]+)\\)", text = obj) + obj_ <- substr(x = obj, start = re + 1, stop = re + attr(re, "match.length") - 2) + obj <- gsub(pattern = "\\s.*", replacement = "", x = obj) + if (obj %in% ls(name = env)) { + get(x = obj, envir = env) + } else if (!is.null(pkg) && !identical(pkg, "")) { + res <- suppressWarnings(try( + get(utils::data(list = obj, package = pkg, envir = environment())), silent = TRUE + )) + if (!inherits(res, "try-error")) + return(res) + data(list = obj_, package = pkg, envir = environment()) + get(obj, envir = environment()) + } else { + NULL + } +} + + +get_dimensions <- function(objs) { + if (is.null(objs)) + return(NULL) + dataframes_dims <- Map( + f = function(name, pkg) { + attr(name, "package") <- pkg + tmp <- suppressWarnings(get_env_data(name)) + if (is.data.frame(tmp)) { + sprintf("%d obs. of %d variables", nrow(tmp), ncol(tmp)) + } else { + i18n$t("Not a data.frame") + } + }, + name = objs, + pkg = if (!is.null(attr(objs, "package"))) { + attr(objs, "package") + } else { + character(1) + } + ) + unlist(dataframes_dims) +} diff --git a/R/regression_table.R b/R/regression_table.R index 557359b3..ed512ab1 100644 --- a/R/regression_table.R +++ b/R/regression_table.R @@ -70,38 +70,6 @@ #' purrr::map(regression_table) |> #' tbl_merge() #' } -#' regression_table <- function(x, ...) { -#' UseMethod("regression_table") -#' } -#' -#' #' @rdname regression_table -#' #' @export -#' regression_table.list <- function(x, ...) { -#' x |> -#' purrr::map(\(.m){ -#' regression_table(x = .m, ...) |> -#' gtsummary::add_n() -#' }) |> -#' gtsummary::tbl_stack() -#' } -#' -#' #' @rdname regression_table -#' #' @export -#' regression_table.default <- function(x, ..., args.list = NULL, fun = "gtsummary::tbl_regression") { -#' # Stripping custom class -#' class(x) <- class(x)[class(x) != "freesearchr_model"] -#' -#' if (any(c(length(class(x)) != 1, class(x) != "lm"))) { -#' if (!"exponentiate" %in% names(args.list)) { -#' args.list <- c(args.list, list(exponentiate = TRUE)) -#' } -#' } -#' -#' out <- do.call(getfun(fun), c(list(x = x), args.list)) -#' out |> -#' gtsummary::add_glance_source_note() # |> -#' # gtsummary::bold_p() -#' } regression_table <- function(x, ...) { args <- list(...) @@ -179,5 +147,3 @@ tbl_merge <- function(data) { } } -# as_kable(tbl) |> write_lines(file=here::here("inst/apps/data_analysis_modules/www/_table1.md")) -# as_kable_extra(tbl)|> write_lines(file=here::here("inst/apps/data_analysis_modules/www/table1.md")) diff --git a/R/sysdata.rda b/R/sysdata.rda index 463120753399f65122208435ec9eca297cec6711..27b7821900ba46de2fefd1d7709702fc0d0f8722 100644 GIT binary patch literal 2781 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 Tfdv5unDZBMML1B9rJ>m>bJ^{P diff --git a/man/data_type.Rd b/man/data_type.Rd index cf287f28..cb49cf49 100644 --- a/man/data_type.Rd +++ b/man/data_type.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/regression_model.R +% Please edit documentation in R/helpers.R \name{data_type} \alias{data_type} \title{Data type assessment.} diff --git a/man/data_types.Rd b/man/data_types.Rd index b37a81b8..655c1ec5 100644 --- a/man/data_types.Rd +++ b/man/data_types.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/regression_model.R +% Please edit documentation in R/helpers.R \name{data_types} \alias{data_types} \title{Recognised data types from data_type} diff --git a/man/get_data_packages.Rd b/man/get_data_packages.Rd new file mode 100644 index 00000000..25371aa1 --- /dev/null +++ b/man/get_data_packages.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import_globalenv-ext.R +\name{get_data_packages} +\alias{get_data_packages} +\title{Get packages containing datasets} +\usage{ +get_data_packages() +} +\value{ +a character vector of packages names +} +\description{ +Get packages containing datasets +} +\examples{ +if (interactive()) { + + get_data_packages() + +} +} diff --git a/man/ggeulerr.Rd b/man/ggeulerr.Rd index 78fc3138..e9983218 100644 --- a/man/ggeulerr.Rd +++ b/man/ggeulerr.Rd @@ -17,7 +17,7 @@ data.frame(See \code{eulerr::euler})} \item{...}{further arguments passed to eulerr::euler} } \description{ -THis is slightly modified from https://gist.github.com/danlooo/d23d8bcf8856c7dd8e86266097404ded +This is slightly modified from https://gist.github.com/danlooo/d23d8bcf8856c7dd8e86266097404ded This functions uses eulerr::euler to plot area proportional venn diagramms but plots it using ggplot2 diff --git a/man/import-globalenv.Rd b/man/import-globalenv.Rd new file mode 100644 index 00000000..5c8f304c --- /dev/null +++ b/man/import-globalenv.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import_globalenv-ext.R +\name{import-globalenv} +\alias{import-globalenv} +\alias{import_globalenv_ui} +\alias{import_globalenv_server} +\title{Import data from an Environment} +\usage{ +import_globalenv_ui( + id, + globalenv = TRUE, + packages = datamods::get_data_packages(), + title = TRUE +) + +import_globalenv_server( + id, + btn_show_data = TRUE, + show_data_in = c("popup", "modal"), + trigger_return = c("button", "change"), + return_class = c("data.frame", "data.table", "tbl_df", "raw"), + reset = reactive(NULL) +) +} +\arguments{ +\item{id}{Module's ID.} + +\item{globalenv}{Search for data in Global environment.} + +\item{packages}{Name of packages in which to search data.} + +\item{title}{Module's title, if \code{TRUE} use the default title, +use \code{NULL} for no title or a \code{shiny.tag} for a custom one.} + +\item{btn_show_data}{Display or not a button to display data in a modal window if import is successful.} + +\item{show_data_in}{Where to display data: in a \code{"popup"} or in a \code{"modal"} window.} + +\item{trigger_return}{When to update selected data: +\code{"button"} (when user click on button) or +\code{"change"} (each time user select a dataset in the list).} + +\item{return_class}{Class of returned data: \code{data.frame}, \code{data.table}, \code{tbl_df} (tibble) or \code{raw}.} + +\item{reset}{A \code{reactive} function that when triggered resets the data.} +} +\description{ +Let the user select a dataset from its own environment or from a package's environment. +Modified from datamods +} diff --git a/man/list_pkg_data.Rd b/man/list_pkg_data.Rd new file mode 100644 index 00000000..de2567a9 --- /dev/null +++ b/man/list_pkg_data.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import_globalenv-ext.R +\name{list_pkg_data} +\alias{list_pkg_data} +\title{List dataset contained in a package} +\usage{ +list_pkg_data(pkg) +} +\arguments{ +\item{pkg}{Name of the package, must be installed.} +} +\value{ +a \code{character} vector or \code{NULL}. +} +\description{ +List dataset contained in a package +} +\examples{ + +list_pkg_data("ggplot2") +} diff --git a/man/plot_euler.Rd b/man/plot_euler.Rd index b4bc1b2d..4f387162 100644 --- a/man/plot_euler.Rd +++ b/man/plot_euler.Rd @@ -32,4 +32,19 @@ data.frame( ) |> plot_euler("A", c("B", "C"), "D", seed = 4) mtcars |> plot_euler("vs", "am", seed = 1) mtcars |> plot_euler("vs", "am", "cyl", seed = 1) +stRoke::trial |> + dplyr::mutate( + mfi_cut = cut(mfi_6, c(0, 12, max(mfi_6, na.rm = TRUE))), + mdi_cut = cut(mdi_6, c(0, 20, max(mdi_6, na.rm = TRUE))) + ) |> + purrr::map2( + c(sapply(stRoke::trial, \(.x)REDCapCAST::get_attr(.x, attr = "label")), "Fatigue", "Depression"), + \(.x, .y){ + REDCapCAST::set_attr(.x, .y, "label") + } + ) |> + dplyr::bind_cols() |> + plot_euler("mfi_cut", "mdi_cut") +stRoke::trial |> + plot_euler(pri="male", sec=c("hypertension")) } diff --git a/man/regression_table.Rd b/man/regression_table.Rd index d319247b..8a362df2 100644 --- a/man/regression_table.Rd +++ b/man/regression_table.Rd @@ -82,36 +82,4 @@ list( purrr::map(regression_table) |> tbl_merge() } -regression_table <- function(x, ...) { - UseMethod("regression_table") -} - -#' @rdname regression_table -#' @export -regression_table.list <- function(x, ...) { - x |> - purrr::map(\(.m){ - regression_table(x = .m, ...) |> - gtsummary::add_n() - }) |> - gtsummary::tbl_stack() -} - -#' @rdname regression_table -#' @export -regression_table.default <- function(x, ..., args.list = NULL, fun = "gtsummary::tbl_regression") { - # Stripping custom class - class(x) <- class(x)[class(x) != "freesearchr_model"] - - if (any(c(length(class(x)) != 1, class(x) != "lm"))) { - if (!"exponentiate" \%in\% names(args.list)) { - args.list <- c(args.list, list(exponentiate = TRUE)) - } - } - - out <- do.call(getfun(fun), c(list(x = x), args.list)) - out |> - gtsummary::add_glance_source_note() # |> - # gtsummary::bold_p() -} } From adcd4048137c954c8aad939868cdae4b28067ce3 Mon Sep 17 00:00:00 2001 From: Andreas Gammelgaard Damsbo Date: Thu, 2 Oct 2025 11:16:17 +0200 Subject: [PATCH 3/6] feat: missings table now features bold significant p-values for easier overview --- R/missings-module.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/missings-module.R b/R/missings-module.R index 562ae22c..7513185b 100644 --- a/R/missings-module.R +++ b/R/missings-module.R @@ -59,16 +59,19 @@ data_missings_server <- function(id, shiny::req(variabler) if (is.null(variabler()) || variabler() == "" || !variabler() %in% names(datar())) { + tbl <- rv$data() if (anyNA(datar())){ title <- i18n$t("No variable chosen for analysis") } else { title <- i18n$t("No missing observations") } } else { + tbl <- rv$data()|> + gtsummary::bold_p() title <- glue::glue(i18n$t("Missing vs non-missing observations in the variable **'{variabler()}'**")) } - out <- rv$data() |> + out <- tbl |> gtsummary::as_gt() |> gt::tab_header(title = gt::md(title)) From 0e0df73744d2762063b65bcc7c312e11086f0c91 Mon Sep 17 00:00:00 2001 From: Andreas Gammelgaard Damsbo Date: Thu, 2 Oct 2025 11:16:39 +0200 Subject: [PATCH 4/6] chore: new translation strings --- CITATION.cff | 2 +- DESCRIPTION | 46 ++++++++++++++- NAMESPACE | 9 +++ NEWS.md | 6 ++ R/app_version.R | 2 +- R/hosted_version.R | 2 +- R/html_dependency_freesearchr.R | 1 + R/ui_elements.R | 12 ++-- SESSION.md | 15 ++++- inst/translations/translation_da.csv | 27 +++++++-- inst/translations/translation_sw.csv | 15 +++++ renv.lock | 83 ++++++++++++++++++++++++++++ 12 files changed, 203 insertions(+), 17 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index d03d028a..64a2bfc9 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -8,7 +8,7 @@ message: 'To cite package "FreesearchR" in publications use:' type: software license: AGPL-3.0-or-later title: 'FreesearchR: Easy data analysis for clinicians' -version: 25.9.2 +version: 25.9.3 doi: 10.5281/zenodo.14527429 identifiers: - type: url diff --git a/DESCRIPTION b/DESCRIPTION index 011b5ffa..9ace2c09 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: FreesearchR Title: Easy data analysis for clinicians -Version: 25.9.2 +Version: 25.9.3 Authors@R: c( person("Andreas Gammelgaard", "Damsbo",email="agdamsbo@clin.au.dk", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-7559-1154")), @@ -67,7 +67,8 @@ Imports: emmeans, readxl, NHANES, - shiny.i18n + shiny.i18n, + stRoke Suggests: styler, devtools, @@ -91,3 +92,44 @@ Config/testthat/edition: 3 Depends: R (>= 3.5) LazyData: true +Collate: + 'app_version.R' + 'baseline_table.R' + 'contrast_text.R' + 'correlations-module.R' + 'create-column-mod.R' + 'custom_SelectInput.R' + 'cut-variable-dates.R' + 'data-summary.R' + 'data_plots.R' + 'datagrid-infos-mod.R' + 'helpers.R' + 'hosted_version.R' + 'html_dependency_freesearchr.R' + 'import-file-ext.R' + 'import_globalenv-ext.R' + 'launch_FreesearchR.R' + 'missings-module.R' + 'plot-download-module.R' + 'plot_box.R' + 'plot_euler.R' + 'plot_hbar.R' + 'plot_ridge.R' + 'plot_sankey.R' + 'plot_scatter.R' + 'plot_violin.R' + 'redcap_read_shiny_module.R' + 'regression-module.R' + 'regression_model.R' + 'regression_plot.R' + 'regression_table.R' + 'report.R' + 'syntax_highlight.R' + 'theme.R' + 'translate.R' + 'ui_elements.R' + 'update-factor-ext.R' + 'update-variables-ext.R' + 'validation.R' + 'visual_summary.R' + 'wide2long.R' diff --git a/NAMESPACE b/NAMESPACE index a1f0b9d0..03405295 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -51,6 +51,7 @@ export(expression_string) export(factorize) export(file_export) export(format_writer) +export(get_data_packages) export(get_fun_options) export(get_label) export(get_plot_options) @@ -64,6 +65,8 @@ export(import_delim) export(import_dta) export(import_file_server) export(import_file_ui) +export(import_globalenv_server) +export(import_globalenv_ui) export(import_ods) export(import_rds) export(import_xls) @@ -78,6 +81,7 @@ export(launch_FreesearchR) export(limit_log) export(line_break) export(list_allowed_operations) +export(list_pkg_data) export(m_redcap_readServer) export(m_redcap_readUI) export(make_validation) @@ -170,12 +174,14 @@ importFrom(rlang,sym) importFrom(rlang,syms) importFrom(shiny,NS) importFrom(shiny,actionButton) +importFrom(shiny,actionLink) importFrom(shiny,bindEvent) importFrom(shiny,checkboxInput) importFrom(shiny,column) importFrom(shiny,fluidRow) importFrom(shiny,getDefaultReactiveDomain) importFrom(shiny,icon) +importFrom(shiny,is.reactive) importFrom(shiny,isTruthy) importFrom(shiny,modalDialog) importFrom(shiny,moduleServer) @@ -184,6 +190,7 @@ importFrom(shiny,observeEvent) importFrom(shiny,plotOutput) importFrom(shiny,reactive) importFrom(shiny,reactiveValues) +importFrom(shiny,removeUI) importFrom(shiny,renderPlot) importFrom(shiny,req) importFrom(shiny,restoreInput) @@ -196,6 +203,7 @@ importFrom(shiny,updateActionButton) importFrom(shinyWidgets,WinBox) importFrom(shinyWidgets,noUiSliderInput) importFrom(shinyWidgets,prettyCheckbox) +importFrom(shinyWidgets,updatePickerInput) importFrom(shinyWidgets,updateVirtualSelect) importFrom(shinyWidgets,virtualSelectInput) importFrom(shinyWidgets,wbControls) @@ -208,4 +216,5 @@ importFrom(toastui,grid_colorbar) importFrom(toastui,grid_columns) importFrom(toastui,renderDatagrid) importFrom(toastui,renderDatagrid2) +importFrom(utils,data) importFrom(utils,type.convert) diff --git a/NEWS.md b/NEWS.md index 386366bf..22a08ac2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# FreesearchR 25.9.3 -DEV + +*NEW* Improvements to translations with more strings having been translated. + +*NEW* Sample data has been filtered to only include a few select packages (NHANES and stRoke). + # FreesearchR 25.9.2 *NEW* Improvements to translations with more strings having been translated. diff --git a/R/app_version.R b/R/app_version.R index 990115ff..4955a874 100644 --- a/R/app_version.R +++ b/R/app_version.R @@ -1 +1 @@ -app_version <- function()'25.9.2' +app_version <- function()'25.9.3' diff --git a/R/hosted_version.R b/R/hosted_version.R index 8c66342a..a6094623 100644 --- a/R/hosted_version.R +++ b/R/hosted_version.R @@ -1 +1 @@ -hosted_version <- function()'v25.9.2-250925' +hosted_version <- function()'v25.9.3-251002' diff --git a/R/html_dependency_freesearchr.R b/R/html_dependency_freesearchr.R index bf46e471..bb1af879 100644 --- a/R/html_dependency_freesearchr.R +++ b/R/html_dependency_freesearchr.R @@ -4,6 +4,7 @@ html_dependency_FreesearchR <- function() { version = packageVersion("FreesearchR"), src = list(href = "FreesearchR", file = "assets"), package = "FreesearchR", + script = "js/FreesearchR.js", stylesheet = "css/FreesearchR.css" ) } diff --git a/R/ui_elements.R b/R/ui_elements.R index 8465ec5e..4a5d5f3a 100644 --- a/R/ui_elements.R +++ b/R/ui_elements.R @@ -60,7 +60,7 @@ ui_elements <- function(selection) { # ), shiny::selectInput( inputId = "source", - label="", + label = "", selected = "file", choices = "file", width = "100%" @@ -96,7 +96,11 @@ ui_elements <- function(selection) { ), shiny::conditionalPanel( condition = "input.source=='env'", - import_globalenv_ui(id = "env", title = NULL) + import_globalenv_ui( + id = "env", + title = NULL, + packages = c("NHANES", "stRoke") + ) ), # shiny::conditionalPanel( # condition = "input.source=='redcap'", @@ -350,7 +354,7 @@ ui_elements <- function(selection) { sidebar = bslib::sidebar( shiny::uiOutput(outputId = "data_info_nochar", inline = TRUE), bslib::accordion( - id="acc_chars", + id = "acc_chars", open = "acc_chars", multiple = FALSE, bslib::accordion_panel( @@ -396,7 +400,7 @@ ui_elements <- function(selection) { sidebar = bslib::sidebar( # shiny::uiOutput(outputId = "data_info_nochar", inline = TRUE), bslib::accordion( - id="acc_cor", + id = "acc_cor", open = "acc_chars", multiple = FALSE, bslib::accordion_panel( diff --git a/SESSION.md b/SESSION.md index 3e7afd1e..803577dd 100644 --- a/SESSION.md +++ b/SESSION.md @@ -15,7 +15,7 @@ |rstudio |2025.05.0+496 Mariposa Orchid (desktop) | |pandoc |3.6.4 @ /opt/homebrew/bin/ (via rmarkdown) | |quarto |1.7.30 @ /usr/local/bin/quarto | -|FreesearchR |25.9.2.250925 | +|FreesearchR |25.9.3.250925 | -------------------------------------------------------------------------------- @@ -45,6 +45,7 @@ |cardx |0.2.5 |2025-07-03 |CRAN (R 4.4.1) | |caTools |1.18.3 |2024-09-04 |CRAN (R 4.4.1) | |cellranger |1.1.0 |2016-07-27 |CRAN (R 4.4.0) | +|cffr |1.2.0 |2025-01-25 |CRAN (R 4.4.1) | |checkmate |2.3.2 |2024-07-29 |CRAN (R 4.4.0) | |class |7.3-23 |2025-01-01 |CRAN (R 4.4.1) | |classInt |0.4-11 |2025-01-08 |CRAN (R 4.4.1) | @@ -54,6 +55,8 @@ |colorspace |2.1-1 |2024-07-26 |CRAN (R 4.4.1) | |commonmark |2.0.0 |2025-07-07 |CRAN (R 4.4.1) | |crayon |1.5.3 |2024-06-20 |CRAN (R 4.4.1) | +|credentials |2.0.2 |2024-10-04 |CRAN (R 4.4.1) | +|curl |6.4.0 |2025-06-22 |CRAN (R 4.4.1) | |data.table |1.17.8 |2025-07-10 |CRAN (R 4.4.1) | |datamods |1.5.3 |2024-10-02 |CRAN (R 4.4.1) | |datawizard |1.2.0 |2025-07-17 |CRAN (R 4.4.1) | @@ -85,16 +88,19 @@ |foreach |1.5.2 |2022-02-02 |CRAN (R 4.4.0) | |foreign |0.8-90 |2025-03-31 |CRAN (R 4.4.1) | |Formula |1.2-5 |2023-02-24 |CRAN (R 4.4.1) | -|FreesearchR |25.9.2 |NA |NA | +|FreesearchR |25.9.3 |NA |NA | |fs |1.6.6 |2025-04-12 |CRAN (R 4.4.1) | |gdtools |0.4.2 |2025-03-27 |CRAN (R 4.4.1) | |generics |0.1.4 |2025-05-09 |CRAN (R 4.4.1) | +|gert |2.1.5 |2025-03-25 |CRAN (R 4.4.1) | |ggalluvial |0.12.5 |2023-02-22 |CRAN (R 4.4.0) | |ggcorrplot |0.1.4.1 |2023-09-05 |CRAN (R 4.4.0) | |ggforce |0.5.0 |2025-06-18 |CRAN (R 4.4.1) | |ggplot2 |3.5.2 |2025-04-09 |CRAN (R 4.4.1) | |ggridges |0.5.6 |2024-01-23 |CRAN (R 4.4.0) | |ggstats |0.10.0 |2025-07-02 |CRAN (R 4.4.1) | +|gh |1.5.0 |2025-05-26 |CRAN (R 4.4.1) | +|gitcreds |0.1.2 |2022-09-08 |CRAN (R 4.4.1) | |glue |1.8.0 |2024-09-30 |CRAN (R 4.4.1) | |gridExtra |2.3 |2017-09-09 |CRAN (R 4.4.1) | |gt |1.0.0 |2025-04-05 |CRAN (R 4.4.1) | @@ -108,11 +114,13 @@ |htmltools |0.5.8.1 |2024-04-04 |CRAN (R 4.4.1) | |htmlwidgets |1.6.4 |2023-12-06 |CRAN (R 4.4.0) | |httpuv |1.6.16 |2025-04-16 |CRAN (R 4.4.1) | +|httr2 |1.2.1 |2025-07-22 |CRAN (R 4.4.1) | |IDEAFilter |0.2.1 |2025-07-29 |CRAN (R 4.4.1) | |insight |1.4.0 |2025-08-18 |CRAN (R 4.4.1) | |iterators |1.0.14 |2022-02-05 |CRAN (R 4.4.1) | |jquerylib |0.1.4 |2021-04-26 |CRAN (R 4.4.0) | |jsonlite |2.0.0 |2025-03-27 |CRAN (R 4.4.1) | +|jsonvalidate |1.5.0 |2025-02-07 |CRAN (R 4.4.1) | |KernSmooth |2.23-26 |2025-01-01 |CRAN (R 4.4.1) | |keyring |1.4.1 |2025-06-15 |CRAN (R 4.4.1) | |knitr |1.50 |2025-03-16 |CRAN (R 4.4.1) | @@ -164,6 +172,7 @@ |R6 |2.6.1 |2025-02-15 |CRAN (R 4.4.1) | |ragg |1.4.0 |2025-04-10 |CRAN (R 4.4.1) | |rankinPlot |1.1.0 |2023-01-30 |CRAN (R 4.4.0) | +|rappdirs |0.3.3 |2021-01-31 |CRAN (R 4.4.1) | |rbibutils |2.3 |2024-10-04 |CRAN (R 4.4.1) | |RColorBrewer |1.1-3 |2022-04-03 |CRAN (R 4.4.1) | |Rcpp |1.1.0 |2025-07-02 |CRAN (R 4.4.1) | @@ -202,6 +211,7 @@ |shinyWidgets |0.9.0 |2025-02-21 |CRAN (R 4.4.1) | |stringi |1.8.7 |2025-03-27 |CRAN (R 4.4.1) | |stringr |1.5.1 |2023-11-14 |CRAN (R 4.4.0) | +|sys |3.4.3 |2024-10-04 |CRAN (R 4.4.1) | |systemfonts |1.2.3 |2025-04-30 |CRAN (R 4.4.1) | |testthat |3.2.3 |2025-01-13 |CRAN (R 4.4.1) | |textshaping |1.0.1 |2025-05-01 |CRAN (R 4.4.1) | @@ -217,6 +227,7 @@ |urlchecker |1.0.1 |2021-11-30 |CRAN (R 4.4.1) | |usethis |3.1.0 |2024-11-26 |CRAN (R 4.4.1) | |uuid |1.2-1 |2024-07-29 |CRAN (R 4.4.1) | +|V8 |6.0.6 |2025-08-18 |CRAN (R 4.4.1) | |vctrs |0.6.5 |2023-12-01 |CRAN (R 4.4.0) | |vroom |1.6.5 |2023-12-05 |CRAN (R 4.4.0) | |withr |3.0.2 |2024-10-28 |CRAN (R 4.4.1) | diff --git a/inst/translations/translation_da.csv b/inst/translations/translation_da.csv index 68d560c3..13ab3733 100644 --- a/inst/translations/translation_da.csv +++ b/inst/translations/translation_da.csv @@ -220,12 +220,27 @@ "Click to see the imported data","Click to see the imported data" "Regression table","Regression table" "Import a dataset from an environment","Import a dataset from an environment" -"Select a dataset:","Select a dataset:" +"Select a dataset:","Vælg datasæt:" "List of datasets...","List of datasets..." -"No data selected!","No data selected!" +"No data selected!","Ingen data valgt!" "Use a datasat from your environment or from the environment of a package.","Use a datasat from your environment or from the environment of a package." -"No dataset here...","No dataset here..." +"No dataset here...","Ingen datasæt her..." "Use a dataset from your environment or from the environment of a package.","Use a dataset from your environment or from the environment of a package." -"Not a data.frame","Not a data.frame" -"Select source","Select source" -"Select a data source:","Select a data source:" +"Not a data.frame","Ikke en data.frame" +"Select source","Vælg datakilde" +"Select a data source:","Vælg datakilde:" +"Yes","Ja" +"No","Nej" +"Coefficient plot","Coefficient plot" +"Select outcome variable","Select outcome variable" +"Choose regression analysis","Choose regression analysis" +"Covariables to format as categorical","Covariables to format as categorical" +"Select variable to stratify baseline","Select variable to stratify baseline" +"Select models to plot","Select models to plot" +"Creating regression models failed with the following error:","Creating regression models failed with the following error:" +"Creating a regression table failed with the following error:","Creating a regression table failed with the following error:" +"Saving the plot. Hold on for a moment..","Saving the plot. Hold on for a moment.." +"Running model assumptions checks failed with the following error:","Running model assumptions checks failed with the following error:" +"Select checks to plot","Select checks to plot" +"Multivariable regression model checks","Multivariable regression model checks" +"Grouped by {get_label(data,ter)}","Grouped by {get_label(data,ter)}" diff --git a/inst/translations/translation_sw.csv b/inst/translations/translation_sw.csv index 3d12c21f..42613fca 100644 --- a/inst/translations/translation_sw.csv +++ b/inst/translations/translation_sw.csv @@ -229,3 +229,18 @@ "Not a data.frame","Not a data.frame" "Select source","Select source" "Select a data source:","Select a data source:" +"Yes","Yes" +"No","No" +"Coefficient plot","Coefficient plot" +"Select outcome variable","Select outcome variable" +"Choose regression analysis","Choose regression analysis" +"Covariables to format as categorical","Covariables to format as categorical" +"Select variable to stratify baseline","Select variable to stratify baseline" +"Select models to plot","Select models to plot" +"Creating regression models failed with the following error:","Creating regression models failed with the following error:" +"Creating a regression table failed with the following error:","Creating a regression table failed with the following error:" +"Saving the plot. Hold on for a moment..","Saving the plot. Hold on for a moment.." +"Running model assumptions checks failed with the following error:","Running model assumptions checks failed with the following error:" +"Select checks to plot","Select checks to plot" +"Multivariable regression model checks","Multivariable regression model checks" +"Grouped by {get_label(data,ter)}","Grouped by {get_label(data,ter)}" diff --git a/renv.lock b/renv.lock index fb494ff6..73215377 100644 --- a/renv.lock +++ b/renv.lock @@ -1588,6 +1588,39 @@ "Maintainer": "Winston Chang ", "Repository": "CRAN" }, + "calendar": { + "Package": "calendar", + "Version": "0.2.0", + "Source": "Repository", + "Title": "Create, Read, Write, and Work with 'iCalendar' Files, Calendars and Scheduling Data", + "Authors@R": "c(person(given = \"Robin\", family = \"Lovelace\", role = c(\"aut\", \"cre\"), email = \"rob00x@gmail.com\", comment = c(ORCID = \"0000-0001-5679-6536\")), person(given = \"Layik\", family = \"Hama\", role = \"aut\", email = \"layik.hama@gmail.com\", comment = c(ORCID = \"0000-0003-1912-4890\")), person(given = \"Ollie\", family = \"Lloyd\", role = \"ctb\", email = \"o.lloyd@doctors.org.uk\", comment = c(ORCID = \"0000-0002-9385-1634\")), person(given = \"Franco\", family = \"Scarafia\", role = \"ctb\", email = \"franco.scarafia@hotmail.com\", comment = c(ORCID = \"0009-0005-9822-169X\")), person(given = \"Serkan\", family = \"Korkmaz\", email = \"serkor1@duck.com\", role = c(\"ctb\"), comment = c(ORCID = \"0000-0002-5052-0982\")) )", + "Description": "Provides function to create, read, write, and work with 'iCalendar' files (which typically have '.ics' or '.ical' extensions), and the scheduling data, calendars and timelines of people, organisations and other entities that they represent. 'iCalendar' is an open standard for exchanging calendar and scheduling information between users and computers, described at .", + "License": "Apache License (>= 2.0)", + "URL": "https://github.com/atfutures/calendar, https://atfutures.github.io/calendar/, https://github.com/ATFutures/calendar", + "BugReports": "https://github.com/ATFutures/calendar/issues", + "Depends": [ + "R (>= 3.4.0)" + ], + "Imports": [ + "cli", + "lubridate", + "tibble" + ], + "Suggests": [ + "covr", + "knitr", + "rmarkdown", + "testthat" + ], + "VignetteBuilder": "knitr", + "Encoding": "UTF-8", + "LazyData": "true", + "RoxygenNote": "7.3.2", + "NeedsCompilation": "no", + "Author": "Robin Lovelace [aut, cre] (), Layik Hama [aut] (), Ollie Lloyd [ctb] (), Franco Scarafia [ctb] (), Serkan Korkmaz [ctb] ()", + "Maintainer": "Robin Lovelace ", + "Repository": "CRAN" + }, "cards": { "Package": "cards", "Version": "0.6.1", @@ -8257,6 +8290,56 @@ "NeedsCompilation": "yes", "Repository": "CRAN" }, + "stRoke": { + "Package": "stRoke", + "Version": "25.9.1", + "Source": "Repository", + "Title": "Clinical Stroke Research", + "Authors@R": "person(\"Andreas Gammelgaard\", \"Damsbo\", , \"agdamsbo@clin.au.dk\", role = c(\"aut\", \"cre\"), comment = c(ORCID = \"0000-0002-7559-1154\"))", + "Description": "A collection of tools for clinical trial data management and analysis in research and teaching. The package is mainly collected for personal use, but any use beyond that is encouraged. This package has migrated functions from 'agdamsbo/daDoctoR', and new functions has been added. Version follows months and year. See NEWS/Changelog for release notes. This package includes sampled data from the TALOS trial (Kraglund et al (2018) ). The win_prob() function is based on work by Zou et al (2022) . The age_calc() function is based on work by Becker (2020) .", + "URL": "https://agdamsbo.github.io/stRoke/, https://github.com/agdamsbo/stRoke", + "BugReports": "https://github.com/agdamsbo/stRoke/issues", + "License": "GPL-3", + "Encoding": "UTF-8", + "RoxygenNote": "7.3.2", + "LazyData": "true", + "Suggests": [ + "knitr", + "rmarkdown", + "testthat", + "here", + "spelling", + "usethis", + "pak", + "roxygen2", + "devtools" + ], + "Config/testthat/edition": "3", + "Imports": [ + "calendar", + "dplyr", + "ggplot2", + "grDevices", + "gtsummary", + "lubridate", + "MASS", + "rankinPlot", + "stats", + "tidyr", + "utils", + "tibble", + "tidyselect" + ], + "Depends": [ + "R (>= 4.1.0)" + ], + "VignetteBuilder": "knitr", + "Language": "en-US", + "NeedsCompilation": "no", + "Author": "Andreas Gammelgaard Damsbo [aut, cre] (ORCID: )", + "Maintainer": "Andreas Gammelgaard Damsbo ", + "Repository": "CRAN" + }, "stringi": { "Package": "stringi", "Version": "1.8.7", From 02fd53a35222e3333f79c45cba70b25e77f1272c Mon Sep 17 00:00:00 2001 From: Andreas Gammelgaard Damsbo Date: Thu, 2 Oct 2025 11:19:19 +0200 Subject: [PATCH 5/6] new version inbound --- DESCRIPTION | 2 +- NEWS.md | 4 +- R/app_version.R | 2 +- R/hosted_version.R | 2 +- app_docker/app.R | 690 ++++++++++++++++----- app_docker/translations/translation_da.csv | 25 + app_docker/translations/translation_sw.csv | 25 + inst/apps/FreesearchR/app.R | 690 ++++++++++++++++----- 8 files changed, 1130 insertions(+), 310 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 9ace2c09..9eeddb70 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: FreesearchR Title: Easy data analysis for clinicians -Version: 25.9.3 +Version: 25.10.1 Authors@R: c( person("Andreas Gammelgaard", "Damsbo",email="agdamsbo@clin.au.dk", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-7559-1154")), diff --git a/NEWS.md b/NEWS.md index 22a08ac2..893a5bb9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,11 @@ -# FreesearchR 25.9.3 -DEV +# FreesearchR 25.10.1 *NEW* Improvements to translations with more strings having been translated. *NEW* Sample data has been filtered to only include a few select packages (NHANES and stRoke). +*NEW* Missings evaluations slightly tweaked to include bold significant p-values for easier overview. + # FreesearchR 25.9.2 *NEW* Improvements to translations with more strings having been translated. diff --git a/R/app_version.R b/R/app_version.R index 4955a874..d394d1ea 100644 --- a/R/app_version.R +++ b/R/app_version.R @@ -1 +1 @@ -app_version <- function()'25.9.3' +app_version <- function()'25.10.1' diff --git a/R/hosted_version.R b/R/hosted_version.R index a6094623..f18af72a 100644 --- a/R/hosted_version.R +++ b/R/hosted_version.R @@ -1 +1 @@ -hosted_version <- function()'v25.9.3-251002' +hosted_version <- function()'v25.10.1-251002' diff --git a/app_docker/app.R b/app_docker/app.R index 06fb3a63..021bbcfa 100644 --- a/app_docker/app.R +++ b/app_docker/app.R @@ -1,7 +1,7 @@ ######## -#### Current file: /var/folders/9l/xbc19wxx0g79jdd2sf_0v291mhwh7f/T//Rtmpo2rU34/file15cb36d55cf55.R +#### Current file: /var/folders/9l/xbc19wxx0g79jdd2sf_0v291mhwh7f/T//RtmpWiu9wh/file1e994fcc5757.R ######## i18n_path <- here::here("translations") @@ -62,7 +62,7 @@ i18n$set_translation_language("en") #### Current file: /Users/au301842/FreesearchR/R//app_version.R ######## -app_version <- function()'25.9.2' +app_version <- function()'25.10.1' ######## @@ -3433,7 +3433,8 @@ dummy_Imports <- function() { parameters::ci(), DT::addRow(), bslib::accordion(), - NHANES::NHANES() + NHANES::NHANES(), + stRoke::add_padding() ) # https://github.com/hadley/r-pkgs/issues/828 } @@ -3948,12 +3949,93 @@ simple_snake <- function(data){ gsub("[\\s+]","_",gsub("[^\\w\\s:-]", "", tolower(data), perl=TRUE), perl=TRUE) } +#' Data type assessment. +#' +#' @description +#' These are more overall than the native typeof. This is used to assess a more +#' meaningful "clinical" data type. +#' +#' @param data vector or data.frame. if data frame, each column is evaluated. +#' +#' @returns outcome type +#' @export +#' +#' @examples +#' mtcars |> +#' default_parsing() |> +#' lapply(data_type) +#' mtcars |> +#' default_parsing() |> +#' data_type() +#' c(1, 2) |> data_type() +#' 1 |> data_type() +#' c(rep(NA, 10)) |> data_type() +#' sample(1:100, 50) |> data_type() +#' factor(letters[1:20]) |> data_type() +#' as.Date(1:20) |> data_type() +data_type <- function(data) { + if (is.data.frame(data)) { + sapply(data, data_type) + } else { + cl_d <- class(data) + l_unique <- length(unique(na.omit(data))) + if (all(is.na(data))) { + out <- "empty" + } else if (l_unique < 2) { + out <- "monotone" + } else if (any(c("factor", "logical") %in% cl_d) | l_unique == 2) { + if (identical("logical", cl_d) | l_unique == 2) { + out <- "dichotomous" + } else { + # if (is.ordered(data)) { + # out <- "ordinal" + # } else { + out <- "categorical" + # } + } + } else if (identical(cl_d, "character")) { + out <- "text" + } else if (any(c("hms", "Date", "POSIXct", "POSIXt") %in% cl_d)) { + out <- "datetime" + } else if (l_unique > 2) { + ## Previously had all thinkable classes + ## Now just assumes the class has not been defined above + ## any(c("numeric", "integer", "hms", "Date", "timediff") %in% cl_d) & + out <- "continuous" + } else { + out <- "unknown" + } + + out + } +} + +#' Recognised data types from data_type +#' +#' @returns vector +#' @export +#' +#' @examples +#' data_types() +data_types <- function() { + list( + "empty" = list(descr="Variable of all NAs",classes="Any class"), + "monotone" = list(descr="Variable with only one unique value",classes="Any class"), + "dichotomous" = list(descr="Variable with only two unique values",classes="Any class"), + "categorical"= list(descr="Factor variable",classes="factor (ordered or unordered)"), + "text"= list(descr="Character variable",classes="character"), + "datetime"= list(descr="Variable of time, date or datetime values",classes="hms, Date, POSIXct and POSIXt"), + "continuous"= list(descr="Numeric variable",classes="numeric, integer or double"), + "unknown"= list(descr="Anything not falling within the previous",classes="Any other class") + ) +} + ######## #### Current file: /Users/au301842/FreesearchR/R//hosted_version.R ######## -hosted_version <- function()'v25.9.2-250925' +hosted_version <- function()'v25.10.1-251002' ######## @@ -3966,11 +4048,375 @@ html_dependency_FreesearchR <- function() { version = packageVersion("FreesearchR"), src = list(href = "FreesearchR", file = "assets"), package = "FreesearchR", + script = "js/FreesearchR.js", stylesheet = "css/FreesearchR.css" ) } +######## +#### Current file: /Users/au301842/FreesearchR/R//import_globalenv-ext.R +######## + + +#' @title Import data from an Environment +#' +#' @description Let the user select a dataset from its own environment or from a package's environment. +#' Modified from datamods +#' +#' @param id Module's ID. +#' @param globalenv Search for data in Global environment. +#' @param packages Name of packages in which to search data. +#' @param title Module's title, if `TRUE` use the default title, +#' use `NULL` for no title or a `shiny.tag` for a custom one. +#' +#' @export +#' +#' @name import-globalenv +#' +import_globalenv_ui <- function(id, + globalenv = TRUE, + packages = datamods::get_data_packages(), + title = TRUE) { + + ns <- NS(id) + + choices <- list() + if (isTRUE(globalenv)) { + choices <- append(choices, "Global Environment") + } + if (!is.null(packages)) { + choices <- append(choices, list(Packages = as.character(packages))) + } + + if (isTRUE(globalenv)) { + selected <- "Global Environment" + } else { + selected <- packages[1] + } + + if (isTRUE(title)) { + title <- tags$h4( + i18n$t("Import a dataset from an environment"), + class = "datamods-title" + ) + } + + tags$div( + class = "datamods-import", + datamods:::html_dependency_datamods(), + title, + shinyWidgets::pickerInput( + inputId = ns("env"), + label = i18n$t("Select a data source:"), + choices = choices, + selected = selected, + width = "100%", + options = list( + "title" = i18n$t("Select source"), + "live-search" = TRUE, + "size" = 10 + ) + ), + shinyWidgets::pickerInput( + inputId = ns("data"), + label = i18n$t("Select a dataset:"), + # selected = character(0), + choices = NULL, + # options = list(title = i18n$t("List of datasets...")), + width = "100%" + ), + + tags$div( + id = ns("import-placeholder"), + shinyWidgets::alert( + id = ns("import-result"), + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a datasat from your environment or from the environment of a package."), + dismissible = TRUE + ) + ), + uiOutput( + outputId = ns("container_valid_btn"), + style = "margin-top: 20px;" + ) + ) +} + + + +#' @param btn_show_data Display or not a button to display data in a modal window if import is successful. +#' @param show_data_in Where to display data: in a `"popup"` or in a `"modal"` window. +#' @param trigger_return When to update selected data: +#' `"button"` (when user click on button) or +#' `"change"` (each time user select a dataset in the list). +#' @param return_class Class of returned data: `data.frame`, `data.table`, `tbl_df` (tibble) or `raw`. +#' @param reset A `reactive` function that when triggered resets the data. +#' +#' @export +#' +#' @importFrom shiny moduleServer reactiveValues observeEvent reactive removeUI is.reactive icon actionLink isTruthy +#' @importFrom htmltools tags tagList +#' @importFrom shinyWidgets updatePickerInput +#' +#' @rdname import-globalenv +import_globalenv_server <- function(id, + btn_show_data = TRUE, + show_data_in = c("popup", "modal"), + trigger_return = c("button", "change"), + return_class = c("data.frame", "data.table", "tbl_df", "raw"), + reset = reactive(NULL)) { + + trigger_return <- match.arg(trigger_return) + return_class <- match.arg(return_class) + + module <- function(input, output, session) { + + ns <- session$ns + imported_rv <- reactiveValues(data = NULL, name = NULL) + temporary_rv <- reactiveValues(data = NULL, name = NULL, status = NULL) + + observeEvent(reset(), { + temporary_rv$data <- NULL + temporary_rv$name <- NULL + temporary_rv$status <- NULL + }) + + output$container_valid_btn <- renderUI({ + if (identical(trigger_return, "button")) { + button_import() + } + }) + + observeEvent(input$env, { + if (identical(input$env, "Global Environment")) { + choices <- datamods:::search_obj("data.frame") + } else { + choices <- datamods:::list_pkg_data(input$env) + } + if (is.null(choices)) { + choices <- i18n$t("No dataset here...") + choicesOpt <- list(disabled = TRUE) + } else { + choicesOpt <- list( + subtext = datamods:::get_dimensions(choices) + ) + } + temporary_rv$package <- attr(choices, "package") + shinyWidgets::updatePickerInput( + session = session, + inputId = "data", + selected = character(0), + choices = choices, + choicesOpt = choicesOpt, + options = list(title = i18n$t("List of datasets...")) + ) + }) + + observe( + shinyWidgets::alert( + id = "import-result", + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a datasat from your environment or from the environment of a package."), + dismissible = TRUE + ) + ) + + + observeEvent(input$trigger, { + if (identical(trigger_return, "change")) { + datamods:::hideUI(selector = paste0("#", ns("container_valid_btn"))) + } + }) + + + + observeEvent(input$data, { + if (!isTruthy(input$data)) { + datamods:::toggle_widget(inputId = "confirm", enable = FALSE) + datamods:::insert_alert( + selector = ns("import"), + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a dataset from your environment or from the environment of a package.") + ) + } else { + name_df <- input$data + + if (!is.null(temporary_rv$package)) { + attr(name_df, "package") <- temporary_rv$package + } + + imported <- try(get_env_data(name_df), silent = TRUE) + + if (inherits(imported, "try-error") || NROW(imported) < 1) { + datamods:::toggle_widget(inputId = "confirm", enable = FALSE) + datamods:::insert_error(mssg = i18n$t(attr(imported, "condition")$message)) + temporary_rv$status <- "error" + temporary_rv$data <- NULL + temporary_rv$name <- NULL + } else { + datamods:::toggle_widget(inputId = "confirm", enable = TRUE) + datamods:::insert_alert( + selector = ns("import"), + status = "success", + datamods:::make_success_alert( + imported, + trigger_return = trigger_return, + btn_show_data = btn_show_data + ) + ) + pkg <- attr(name_df, "package") + if (!is.null(pkg)) { + name <- paste(pkg, input$data, sep = "::") + } else { + name <- input$data + } + name <- trimws(sub("\\(([^\\)]+)\\)", "", name)) + temporary_rv$status <- "success" + temporary_rv$data <- imported + temporary_rv$name <- name + } + } + }, ignoreInit = TRUE, ignoreNULL = FALSE) + + + observeEvent(input$see_data, { + show_data(temporary_rv$data, title = i18n$t("Imported data"), type = show_data_in) + }) + + observeEvent(input$confirm, { + imported_rv$data <- temporary_rv$data + imported_rv$name <- temporary_rv$name + }) + + + if (identical(trigger_return, "button")) { + return(list( + status = reactive(temporary_rv$status), + name = reactive(imported_rv$name), + data = reactive(datamods:::as_out(imported_rv$data, return_class)) + )) + } else { + return(list( + status = reactive(temporary_rv$status), + name = reactive(temporary_rv$name), + data = reactive(datamods:::as_out(temporary_rv$data, return_class)) + )) + } + } + + moduleServer( + id = id, + module = module + ) +} + + + + + + + +# utils ------------------------------------------------------------------- + + +#' Get packages containing datasets +#' +#' @return a character vector of packages names +#' @export +#' +#' @importFrom utils data +#' +#' @examples +#' if (interactive()) { +#' +#' get_data_packages() +#' +#' } +get_data_packages <- function() { + suppressWarnings({ + pkgs <- data(package = .packages(all.available = TRUE)) + }) + unique(pkgs$results[, 1]) +} + + +#' List dataset contained in a package +#' +#' @param pkg Name of the package, must be installed. +#' +#' @return a \code{character} vector or \code{NULL}. +#' @export +#' +#' @importFrom utils data +#' +#' @examples +#' +#' list_pkg_data("ggplot2") +list_pkg_data <- function(pkg) { + if (isTRUE(requireNamespace(pkg, quietly = TRUE))) { + list_data <- data(package = pkg, envir = environment())$results[, "Item"] + list_data <- sort(list_data) + attr(list_data, "package") <- pkg + if (length(list_data) < 1) { + NULL + } else { + unname(list_data) + } + } else { + NULL + } +} + +#' @importFrom utils data +get_env_data <- function(obj, env = globalenv()) { + pkg <- attr(obj, "package") + re <- regexpr(pattern = "\\(([^\\)]+)\\)", text = obj) + obj_ <- substr(x = obj, start = re + 1, stop = re + attr(re, "match.length") - 2) + obj <- gsub(pattern = "\\s.*", replacement = "", x = obj) + if (obj %in% ls(name = env)) { + get(x = obj, envir = env) + } else if (!is.null(pkg) && !identical(pkg, "")) { + res <- suppressWarnings(try( + get(utils::data(list = obj, package = pkg, envir = environment())), silent = TRUE + )) + if (!inherits(res, "try-error")) + return(res) + data(list = obj_, package = pkg, envir = environment()) + get(obj, envir = environment()) + } else { + NULL + } +} + + +get_dimensions <- function(objs) { + if (is.null(objs)) + return(NULL) + dataframes_dims <- Map( + f = function(name, pkg) { + attr(name, "package") <- pkg + tmp <- suppressWarnings(get_env_data(name)) + if (is.data.frame(tmp)) { + sprintf("%d obs. of %d variables", nrow(tmp), ncol(tmp)) + } else { + i18n$t("Not a data.frame") + } + }, + name = objs, + pkg = if (!is.null(attr(objs, "package"))) { + attr(objs, "package") + } else { + character(1) + } + ) + unlist(dataframes_dims) +} + + ######## #### Current file: /Users/au301842/FreesearchR/R//import-file-ext.R ######## @@ -4685,16 +5131,19 @@ data_missings_server <- function(id, shiny::req(variabler) if (is.null(variabler()) || variabler() == "" || !variabler() %in% names(datar())) { + tbl <- rv$data() if (anyNA(datar())){ title <- i18n$t("No variable chosen for analysis") } else { title <- i18n$t("No missing observations") } } else { + tbl <- rv$data()|> + gtsummary::bold_p() title <- glue::glue(i18n$t("Missing vs non-missing observations in the variable **'{variabler()}'**")) } - out <- rv$data() |> + out <- tbl |> gtsummary::as_gt() |> gt::tab_header(title = gt::md(title)) @@ -4875,7 +5324,7 @@ plot_box_single <- function(data, pri, sec=NULL, seed = 2103) { #' Area proportional venn diagrams #' #' @description -#' THis is slightly modified from https://gist.github.com/danlooo/d23d8bcf8856c7dd8e86266097404ded +#' This is slightly modified from https://gist.github.com/danlooo/d23d8bcf8856c7dd8e86266097404ded #' #' This functions uses eulerr::euler to plot area proportional venn diagramms #' but plots it using ggplot2 @@ -4885,18 +5334,27 @@ plot_box_single <- function(data, pri, sec=NULL, seed = 2103) { #' @param show_quantities whether to show number of intersecting elements #' @param show_labels whether to show set names #' @param ... further arguments passed to eulerr::euler +#' +#' @include data_plots.R ggeulerr <- function( combinations, show_quantities = TRUE, show_labels = TRUE, ...) { - # browser() + + ## Extracting labels + labs <- sapply(names(combinations),\(.x){ + # browser() + get_label(combinations,.x) + }) + data <- - eulerr::euler(combinations = combinations, ...) |> + ## Set labels as variable names for nicer plotting + setNames(as.data.frame(combinations),labs) |> + eulerr::euler(...) |> plot(quantities = show_quantities) |> purrr::pluck("data") - tibble::as_tibble(data$ellipses, rownames = "Variables") |> ggplot2::ggplot() + ggforce::geom_ellipse( @@ -4912,7 +5370,8 @@ ggeulerr <- function( dplyr::mutate( label = labels |> purrr::map2(quantities, ~ { if (!is.na(.x) && !is.na(.y) && show_labels) { - paste0(.x, "\n", sprintf(.y, fmt = "%.2g")) + paste0(.x, "\n", sprintf(.y, fmt = "%.4g")) + # glue::glue("{.x}\n{round(.y,0)}") } else if (!is.na(.x) && show_labels) { .x } else if (!is.na(.y)) { @@ -4951,6 +5410,21 @@ ggeulerr <- function( #' ) |> plot_euler("A", c("B", "C"), "D", seed = 4) #' mtcars |> plot_euler("vs", "am", seed = 1) #' mtcars |> plot_euler("vs", "am", "cyl", seed = 1) +#' stRoke::trial |> +#' dplyr::mutate( +#' mfi_cut = cut(mfi_6, c(0, 12, max(mfi_6, na.rm = TRUE))), +#' mdi_cut = cut(mdi_6, c(0, 20, max(mdi_6, na.rm = TRUE))) +#' ) |> +#' purrr::map2( +#' c(sapply(stRoke::trial, \(.x)REDCapCAST::get_attr(.x, attr = "label")), "Fatigue", "Depression"), +#' \(.x, .y){ +#' REDCapCAST::set_attr(.x, .y, "label") +#' } +#' ) |> +#' dplyr::bind_cols() |> +#' plot_euler("mfi_cut", "mdi_cut") +#' stRoke::trial |> +#' plot_euler(pri="male", sec=c("hypertension")) plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103) { set.seed(seed = seed) if (!is.null(ter)) { @@ -4958,16 +5432,13 @@ plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103) { } else { ds <- list(data) } - out <- lapply(ds, \(.x){ .x[c(pri, sec)] |> - as.data.frame() |> na.omit() |> plot_euler_single() }) -# browser() - wrap_plot_list(out,title=glue::glue("Grouped by {get_label(data,ter)}")) - # patchwork::wrap_plots(out) + + wrap_plot_list(out, title = glue::glue(i18n$t("Grouped by {get_label(data,ter)}"))) } #' Easily plot single euler diagrams @@ -6475,87 +6946,6 @@ regression_model_uv <- function(data, ### HELPERS -#' Data type assessment. -#' -#' @description -#' These are more overall than the native typeof. This is used to assess a more -#' meaningful "clinical" data type. -#' -#' @param data vector or data.frame. if data frame, each column is evaluated. -#' -#' @returns outcome type -#' @export -#' -#' @examples -#' mtcars |> -#' default_parsing() |> -#' lapply(data_type) -#' mtcars |> -#' default_parsing() |> -#' data_type() -#' c(1, 2) |> data_type() -#' 1 |> data_type() -#' c(rep(NA, 10)) |> data_type() -#' sample(1:100, 50) |> data_type() -#' factor(letters[1:20]) |> data_type() -#' as.Date(1:20) |> data_type() -data_type <- function(data) { - if (is.data.frame(data)) { - sapply(data, data_type) - } else { - cl_d <- class(data) - l_unique <- length(unique(na.omit(data))) - if (all(is.na(data))) { - out <- "empty" - } else if (l_unique < 2) { - out <- "monotone" - } else if (any(c("factor", "logical") %in% cl_d) | l_unique == 2) { - if (identical("logical", cl_d) | l_unique == 2) { - out <- "dichotomous" - } else { - # if (is.ordered(data)) { - # out <- "ordinal" - # } else { - out <- "categorical" - # } - } - } else if (identical(cl_d, "character")) { - out <- "text" - } else if (any(c("hms", "Date", "POSIXct", "POSIXt") %in% cl_d)) { - out <- "datetime" - } else if (l_unique > 2) { - ## Previously had all thinkable classes - ## Now just assumes the class has not been defined above - ## any(c("numeric", "integer", "hms", "Date", "timediff") %in% cl_d) & - out <- "continuous" - } else { - out <- "unknown" - } - - out - } -} - -#' Recognised data types from data_type -#' -#' @returns vector -#' @export -#' -#' @examples -#' data_types() -data_types <- function() { - list( - "empty" = list(descr="Variable of all NAs",classes="Any class"), - "monotone" = list(descr="Variable with only one unique value",classes="Any class"), - "dichotomous" = list(descr="Variable with only two unique values",classes="Any class"), - "categorical"= list(descr="Factor variable",classes="factor (ordered or unordered)"), - "text"= list(descr="Character variable",classes="character"), - "datetime"= list(descr="Variable of time, date or datetime values",classes="hms, Date, POSIXct and POSIXt"), - "continuous"= list(descr="Numeric variable",classes="numeric, integer or double"), - "unknown"= list(descr="Anything not falling within the previous",classes="Any other class") - ) -} - #' Implemented functions #' @@ -7196,38 +7586,6 @@ symmetrical_scale_x_log10 <- function(plot, breaks = c(1, 2, 3, 5, 10), ...) { #' purrr::map(regression_table) |> #' tbl_merge() #' } -#' regression_table <- function(x, ...) { -#' UseMethod("regression_table") -#' } -#' -#' #' @rdname regression_table -#' #' @export -#' regression_table.list <- function(x, ...) { -#' x |> -#' purrr::map(\(.m){ -#' regression_table(x = .m, ...) |> -#' gtsummary::add_n() -#' }) |> -#' gtsummary::tbl_stack() -#' } -#' -#' #' @rdname regression_table -#' #' @export -#' regression_table.default <- function(x, ..., args.list = NULL, fun = "gtsummary::tbl_regression") { -#' # Stripping custom class -#' class(x) <- class(x)[class(x) != "freesearchr_model"] -#' -#' if (any(c(length(class(x)) != 1, class(x) != "lm"))) { -#' if (!"exponentiate" %in% names(args.list)) { -#' args.list <- c(args.list, list(exponentiate = TRUE)) -#' } -#' } -#' -#' out <- do.call(getfun(fun), c(list(x = x), args.list)) -#' out |> -#' gtsummary::add_glance_source_note() # |> -#' # gtsummary::bold_p() -#' } regression_table <- function(x, ...) { args <- list(...) @@ -7305,8 +7663,6 @@ tbl_merge <- function(data) { } } -# as_kable(tbl) |> write_lines(file=here::here("inst/apps/data_analysis_modules/www/_table1.md")) -# as_kable_extra(tbl)|> write_lines(file=here::here("inst/apps/data_analysis_modules/www/table1.md")) ######## @@ -7387,13 +7743,15 @@ regression_ui <- function(id, ...) { shiny::radioButtons( inputId = ns("all"), label = i18n$t("Specify covariables"), - inline = TRUE, selected = 2, + inline = TRUE, + selected = 2, choiceNames = c( "Yes", "No" ), choiceValues = c(1, 2) ), + # shiny::uiOutput(outputId = ns("all")), shiny::conditionalPanel( condition = "input.all==1", shiny::uiOutput(outputId = ns("regression_vars")), @@ -7446,7 +7804,7 @@ regression_ui <- function(id, ...) { ) ), bslib::nav_panel( - title = "Coefficient plot", + title = i18n$t("Coefficient plot"), bslib::layout_sidebar( sidebar = bslib::sidebar( bslib::accordion( @@ -7558,11 +7916,6 @@ regression_server <- function(id, } }) - shiny::observe({ - bslib::accordion_panel_update(id = "acc_reg", target = "acc_pan_reg", title = i18n$t("Regression")) - bslib::accordion_panel_update(id = "acc_coef_plot", target = "acc_pan_coef_plot", title = i18n$t("Coefficients plot")) - bslib::accordion_panel_update(id = "acc_checks", target = "acc_pan_checks", title = i18n$t("Checks")) - }) output$data_info <- shiny::renderUI({ shiny::req(regression_vars()) @@ -7570,6 +7923,31 @@ regression_server <- function(id, data_description(data_r()[regression_vars()]) }) + ## Update on laguage change + + shiny::observe({ + bslib::accordion_panel_update(id = "acc_reg", target = "acc_pan_reg", title = i18n$t("Regression")) + bslib::accordion_panel_update(id = "acc_coef_plot", target = "acc_pan_coef_plot", title = i18n$t("Coefficients plot")) + bslib::accordion_panel_update(id = "acc_checks", target = "acc_pan_checks", title = i18n$t("Checks")) + }) + + # shiny::observe({ + # shiny::updateRadioButtons( + # session = session, + # inputId = "all", + # label = i18n$t("Specify covariables"), + # # inline = TRUE, + # # selected = 2, + # choiceNames = c( + # i18n$t("Yes"), + # i18n$t("No") + # ), + # choiceValues = c(1, 2) + # ) + # }) + + + ############################################################################## ######### ######### Input fields @@ -7593,7 +7971,7 @@ regression_server <- function(id, columnSelectInput( inputId = ns("outcome_var"), selected = NULL, - label = "Select outcome variable", + label = i18n$t("Select outcome variable"), data = data_r(), multiple = FALSE ) @@ -7603,7 +7981,7 @@ regression_server <- function(id, shiny::req(input$outcome_var) shiny::selectizeInput( inputId = ns("regression_type"), - label = "Choose regression analysis", + label = i18n$t("Choose regression analysis"), ## The below ifelse statement handles the case of loading a new dataset choices = possible_functions( data = dplyr::select( @@ -7622,7 +8000,7 @@ regression_server <- function(id, shiny::selectizeInput( inputId = ns("factor_vars"), selected = colnames(data_r())[sapply(data_r(), is.factor)], - label = "Covariables to format as categorical", + label = i18n$t("Covariables to format as categorical"), choices = colnames(data_r()), multiple = TRUE ) @@ -7642,7 +8020,7 @@ regression_server <- function(id, columnSelectInput( inputId = ns("strat_var"), selected = "none", - label = "Select variable to stratify baseline", + label = i18n$t("Select variable to stratify baseline"), data = data_r(), col_subset = c( "none", @@ -7657,7 +8035,7 @@ regression_server <- function(id, shiny::selectInput( inputId = ns("plot_model"), selected = 1, - label = "Select models to plot", + label = i18n$t("Select models to plot"), choices = names(rv$list$regression$tables), multiple = TRUE ) @@ -7707,7 +8085,7 @@ regression_server <- function(id, rv$list$regression$models <- model_lists }, error = function(err) { - showNotification(paste0("Creating regression models failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Creating regression models failed with the following error:"), err), type = "err") } ) } @@ -7772,7 +8150,7 @@ regression_server <- function(id, showNotification(paste0(warn), type = "warning") }, error = function(err) { - showNotification(paste0("Creating a regression table failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Creating a regression table failed with the following error:"), err), type = "err") } ) } @@ -7873,7 +8251,7 @@ regression_server <- function(id, output$download_plot <- shiny::downloadHandler( filename = paste0("regression_plot.", input$plot_type), content = function(file) { - shiny::withProgress(message = "Saving the plot. Hold on for a moment..", { + shiny::withProgress(message = i18n$t("Saving the plot. Hold on for a moment.."), { ggplot2::ggsave( filename = file, plot = rv$plot, @@ -7910,7 +8288,7 @@ regression_server <- function(id, # showNotification(paste0(warn), type = "warning") # }, error = function(err) { - showNotification(paste0("Running model assumptions checks failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Running model assumptions checks failed with the following error:"), err), type = "err") } ) } @@ -7931,7 +8309,7 @@ regression_server <- function(id, vectorSelectInput( inputId = ns("plot_checks"), selected = 1, - label = "Select checks to plot", + label = i18n$t("Select checks to plot"), choices = names, multiple = TRUE ) @@ -7946,7 +8324,7 @@ regression_server <- function(id, if (!is.null(rv$list$regression$tables)) { p <- rv$check_plot() + # patchwork::wrap_plots() + - patchwork::plot_annotation(title = "Multivariable regression model checks") + patchwork::plot_annotation(title = i18n$t("Multivariable regression model checks")) layout <- sapply(seq_len(length(p)), \(.x){ @@ -8325,7 +8703,7 @@ ui_elements <- function(selection) { # ), shiny::selectInput( inputId = "source", - label="", + label = "", selected = "file", choices = "file", width = "100%" @@ -8361,7 +8739,11 @@ ui_elements <- function(selection) { ), shiny::conditionalPanel( condition = "input.source=='env'", - import_globalenv_ui(id = "env", title = NULL) + import_globalenv_ui( + id = "env", + title = NULL, + packages = c("NHANES", "stRoke") + ) ), # shiny::conditionalPanel( # condition = "input.source=='redcap'", @@ -8615,7 +8997,7 @@ ui_elements <- function(selection) { sidebar = bslib::sidebar( shiny::uiOutput(outputId = "data_info_nochar", inline = TRUE), bslib::accordion( - id="acc_chars", + id = "acc_chars", open = "acc_chars", multiple = FALSE, bslib::accordion_panel( @@ -8661,7 +9043,7 @@ ui_elements <- function(selection) { sidebar = bslib::sidebar( # shiny::uiOutput(outputId = "data_info_nochar", inline = TRUE), bslib::accordion( - id="acc_cor", + id = "acc_cor", open = "acc_chars", multiple = FALSE, bslib::accordion_panel( @@ -10979,6 +11361,7 @@ ui <- bslib::page_fixed( ## Code formatting dependencies prismDependencies, prismRDependency, + html_dependency_FreesearchR(), ## Version dependent header header_include(), ## This adds the actual favicon @@ -11264,7 +11647,8 @@ server <- function(input, output, session) { rv$code <- modifyList(x = rv$code, list(import = from_redcap$code())) }) - from_env <- datamods::import_globalenv_server( + # from_env <- datamods::import_globalenv_server( + from_env <- import_globalenv_server( id = "env", trigger_return = "change", btn_show_data = FALSE, diff --git a/app_docker/translations/translation_da.csv b/app_docker/translations/translation_da.csv index fa7a238e..13ab3733 100644 --- a/app_docker/translations/translation_da.csv +++ b/app_docker/translations/translation_da.csv @@ -219,3 +219,28 @@ "Data retrieved, but it looks like not all requested fields were retrieved from the server. Please check with your REDCap administrator that you have required permissions for data access.","Data retrieved, but it looks like not all requested fields were retrieved from the server. Please check with your REDCap administrator that you have required permissions for data access." "Click to see the imported data","Click to see the imported data" "Regression table","Regression table" +"Import a dataset from an environment","Import a dataset from an environment" +"Select a dataset:","Vælg datasæt:" +"List of datasets...","List of datasets..." +"No data selected!","Ingen data valgt!" +"Use a datasat from your environment or from the environment of a package.","Use a datasat from your environment or from the environment of a package." +"No dataset here...","Ingen datasæt her..." +"Use a dataset from your environment or from the environment of a package.","Use a dataset from your environment or from the environment of a package." +"Not a data.frame","Ikke en data.frame" +"Select source","Vælg datakilde" +"Select a data source:","Vælg datakilde:" +"Yes","Ja" +"No","Nej" +"Coefficient plot","Coefficient plot" +"Select outcome variable","Select outcome variable" +"Choose regression analysis","Choose regression analysis" +"Covariables to format as categorical","Covariables to format as categorical" +"Select variable to stratify baseline","Select variable to stratify baseline" +"Select models to plot","Select models to plot" +"Creating regression models failed with the following error:","Creating regression models failed with the following error:" +"Creating a regression table failed with the following error:","Creating a regression table failed with the following error:" +"Saving the plot. Hold on for a moment..","Saving the plot. Hold on for a moment.." +"Running model assumptions checks failed with the following error:","Running model assumptions checks failed with the following error:" +"Select checks to plot","Select checks to plot" +"Multivariable regression model checks","Multivariable regression model checks" +"Grouped by {get_label(data,ter)}","Grouped by {get_label(data,ter)}" diff --git a/app_docker/translations/translation_sw.csv b/app_docker/translations/translation_sw.csv index 543193ff..42613fca 100644 --- a/app_docker/translations/translation_sw.csv +++ b/app_docker/translations/translation_sw.csv @@ -219,3 +219,28 @@ "Data retrieved, but it looks like not all requested fields were retrieved from the server. Please check with your REDCap administrator that you have required permissions for data access.","Data retrieved, but it looks like not all requested fields were retrieved from the server. Please check with your REDCap administrator that you have required permissions for data access." "Click to see the imported data","Click to see the imported data" "Regression table","Regression table" +"Import a dataset from an environment","Import a dataset from an environment" +"Select a dataset:","Select a dataset:" +"List of datasets...","List of datasets..." +"No data selected!","No data selected!" +"Use a datasat from your environment or from the environment of a package.","Use a datasat from your environment or from the environment of a package." +"No dataset here...","No dataset here..." +"Use a dataset from your environment or from the environment of a package.","Use a dataset from your environment or from the environment of a package." +"Not a data.frame","Not a data.frame" +"Select source","Select source" +"Select a data source:","Select a data source:" +"Yes","Yes" +"No","No" +"Coefficient plot","Coefficient plot" +"Select outcome variable","Select outcome variable" +"Choose regression analysis","Choose regression analysis" +"Covariables to format as categorical","Covariables to format as categorical" +"Select variable to stratify baseline","Select variable to stratify baseline" +"Select models to plot","Select models to plot" +"Creating regression models failed with the following error:","Creating regression models failed with the following error:" +"Creating a regression table failed with the following error:","Creating a regression table failed with the following error:" +"Saving the plot. Hold on for a moment..","Saving the plot. Hold on for a moment.." +"Running model assumptions checks failed with the following error:","Running model assumptions checks failed with the following error:" +"Select checks to plot","Select checks to plot" +"Multivariable regression model checks","Multivariable regression model checks" +"Grouped by {get_label(data,ter)}","Grouped by {get_label(data,ter)}" diff --git a/inst/apps/FreesearchR/app.R b/inst/apps/FreesearchR/app.R index 111cd321..2de02a7a 100644 --- a/inst/apps/FreesearchR/app.R +++ b/inst/apps/FreesearchR/app.R @@ -1,7 +1,7 @@ ######## -#### Current file: /var/folders/9l/xbc19wxx0g79jdd2sf_0v291mhwh7f/T//Rtmpo2rU34/file15cb31846160a.R +#### Current file: /var/folders/9l/xbc19wxx0g79jdd2sf_0v291mhwh7f/T//RtmpWiu9wh/file1e99785ae783.R ######## i18n_path <- system.file("translations", package = "FreesearchR") @@ -62,7 +62,7 @@ i18n$set_translation_language("en") #### Current file: /Users/au301842/FreesearchR/R//app_version.R ######## -app_version <- function()'25.9.2' +app_version <- function()'25.10.1' ######## @@ -3433,7 +3433,8 @@ dummy_Imports <- function() { parameters::ci(), DT::addRow(), bslib::accordion(), - NHANES::NHANES() + NHANES::NHANES(), + stRoke::add_padding() ) # https://github.com/hadley/r-pkgs/issues/828 } @@ -3948,12 +3949,93 @@ simple_snake <- function(data){ gsub("[\\s+]","_",gsub("[^\\w\\s:-]", "", tolower(data), perl=TRUE), perl=TRUE) } +#' Data type assessment. +#' +#' @description +#' These are more overall than the native typeof. This is used to assess a more +#' meaningful "clinical" data type. +#' +#' @param data vector or data.frame. if data frame, each column is evaluated. +#' +#' @returns outcome type +#' @export +#' +#' @examples +#' mtcars |> +#' default_parsing() |> +#' lapply(data_type) +#' mtcars |> +#' default_parsing() |> +#' data_type() +#' c(1, 2) |> data_type() +#' 1 |> data_type() +#' c(rep(NA, 10)) |> data_type() +#' sample(1:100, 50) |> data_type() +#' factor(letters[1:20]) |> data_type() +#' as.Date(1:20) |> data_type() +data_type <- function(data) { + if (is.data.frame(data)) { + sapply(data, data_type) + } else { + cl_d <- class(data) + l_unique <- length(unique(na.omit(data))) + if (all(is.na(data))) { + out <- "empty" + } else if (l_unique < 2) { + out <- "monotone" + } else if (any(c("factor", "logical") %in% cl_d) | l_unique == 2) { + if (identical("logical", cl_d) | l_unique == 2) { + out <- "dichotomous" + } else { + # if (is.ordered(data)) { + # out <- "ordinal" + # } else { + out <- "categorical" + # } + } + } else if (identical(cl_d, "character")) { + out <- "text" + } else if (any(c("hms", "Date", "POSIXct", "POSIXt") %in% cl_d)) { + out <- "datetime" + } else if (l_unique > 2) { + ## Previously had all thinkable classes + ## Now just assumes the class has not been defined above + ## any(c("numeric", "integer", "hms", "Date", "timediff") %in% cl_d) & + out <- "continuous" + } else { + out <- "unknown" + } + + out + } +} + +#' Recognised data types from data_type +#' +#' @returns vector +#' @export +#' +#' @examples +#' data_types() +data_types <- function() { + list( + "empty" = list(descr="Variable of all NAs",classes="Any class"), + "monotone" = list(descr="Variable with only one unique value",classes="Any class"), + "dichotomous" = list(descr="Variable with only two unique values",classes="Any class"), + "categorical"= list(descr="Factor variable",classes="factor (ordered or unordered)"), + "text"= list(descr="Character variable",classes="character"), + "datetime"= list(descr="Variable of time, date or datetime values",classes="hms, Date, POSIXct and POSIXt"), + "continuous"= list(descr="Numeric variable",classes="numeric, integer or double"), + "unknown"= list(descr="Anything not falling within the previous",classes="Any other class") + ) +} + ######## #### Current file: /Users/au301842/FreesearchR/R//hosted_version.R ######## -hosted_version <- function()'v25.9.2-250925' +hosted_version <- function()'v25.10.1-251002' ######## @@ -3966,11 +4048,375 @@ html_dependency_FreesearchR <- function() { version = packageVersion("FreesearchR"), src = list(href = "FreesearchR", file = "assets"), package = "FreesearchR", + script = "js/FreesearchR.js", stylesheet = "css/FreesearchR.css" ) } +######## +#### Current file: /Users/au301842/FreesearchR/R//import_globalenv-ext.R +######## + + +#' @title Import data from an Environment +#' +#' @description Let the user select a dataset from its own environment or from a package's environment. +#' Modified from datamods +#' +#' @param id Module's ID. +#' @param globalenv Search for data in Global environment. +#' @param packages Name of packages in which to search data. +#' @param title Module's title, if `TRUE` use the default title, +#' use `NULL` for no title or a `shiny.tag` for a custom one. +#' +#' @export +#' +#' @name import-globalenv +#' +import_globalenv_ui <- function(id, + globalenv = TRUE, + packages = datamods::get_data_packages(), + title = TRUE) { + + ns <- NS(id) + + choices <- list() + if (isTRUE(globalenv)) { + choices <- append(choices, "Global Environment") + } + if (!is.null(packages)) { + choices <- append(choices, list(Packages = as.character(packages))) + } + + if (isTRUE(globalenv)) { + selected <- "Global Environment" + } else { + selected <- packages[1] + } + + if (isTRUE(title)) { + title <- tags$h4( + i18n$t("Import a dataset from an environment"), + class = "datamods-title" + ) + } + + tags$div( + class = "datamods-import", + datamods:::html_dependency_datamods(), + title, + shinyWidgets::pickerInput( + inputId = ns("env"), + label = i18n$t("Select a data source:"), + choices = choices, + selected = selected, + width = "100%", + options = list( + "title" = i18n$t("Select source"), + "live-search" = TRUE, + "size" = 10 + ) + ), + shinyWidgets::pickerInput( + inputId = ns("data"), + label = i18n$t("Select a dataset:"), + # selected = character(0), + choices = NULL, + # options = list(title = i18n$t("List of datasets...")), + width = "100%" + ), + + tags$div( + id = ns("import-placeholder"), + shinyWidgets::alert( + id = ns("import-result"), + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a datasat from your environment or from the environment of a package."), + dismissible = TRUE + ) + ), + uiOutput( + outputId = ns("container_valid_btn"), + style = "margin-top: 20px;" + ) + ) +} + + + +#' @param btn_show_data Display or not a button to display data in a modal window if import is successful. +#' @param show_data_in Where to display data: in a `"popup"` or in a `"modal"` window. +#' @param trigger_return When to update selected data: +#' `"button"` (when user click on button) or +#' `"change"` (each time user select a dataset in the list). +#' @param return_class Class of returned data: `data.frame`, `data.table`, `tbl_df` (tibble) or `raw`. +#' @param reset A `reactive` function that when triggered resets the data. +#' +#' @export +#' +#' @importFrom shiny moduleServer reactiveValues observeEvent reactive removeUI is.reactive icon actionLink isTruthy +#' @importFrom htmltools tags tagList +#' @importFrom shinyWidgets updatePickerInput +#' +#' @rdname import-globalenv +import_globalenv_server <- function(id, + btn_show_data = TRUE, + show_data_in = c("popup", "modal"), + trigger_return = c("button", "change"), + return_class = c("data.frame", "data.table", "tbl_df", "raw"), + reset = reactive(NULL)) { + + trigger_return <- match.arg(trigger_return) + return_class <- match.arg(return_class) + + module <- function(input, output, session) { + + ns <- session$ns + imported_rv <- reactiveValues(data = NULL, name = NULL) + temporary_rv <- reactiveValues(data = NULL, name = NULL, status = NULL) + + observeEvent(reset(), { + temporary_rv$data <- NULL + temporary_rv$name <- NULL + temporary_rv$status <- NULL + }) + + output$container_valid_btn <- renderUI({ + if (identical(trigger_return, "button")) { + button_import() + } + }) + + observeEvent(input$env, { + if (identical(input$env, "Global Environment")) { + choices <- datamods:::search_obj("data.frame") + } else { + choices <- datamods:::list_pkg_data(input$env) + } + if (is.null(choices)) { + choices <- i18n$t("No dataset here...") + choicesOpt <- list(disabled = TRUE) + } else { + choicesOpt <- list( + subtext = datamods:::get_dimensions(choices) + ) + } + temporary_rv$package <- attr(choices, "package") + shinyWidgets::updatePickerInput( + session = session, + inputId = "data", + selected = character(0), + choices = choices, + choicesOpt = choicesOpt, + options = list(title = i18n$t("List of datasets...")) + ) + }) + + observe( + shinyWidgets::alert( + id = "import-result", + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a datasat from your environment or from the environment of a package."), + dismissible = TRUE + ) + ) + + + observeEvent(input$trigger, { + if (identical(trigger_return, "change")) { + datamods:::hideUI(selector = paste0("#", ns("container_valid_btn"))) + } + }) + + + + observeEvent(input$data, { + if (!isTruthy(input$data)) { + datamods:::toggle_widget(inputId = "confirm", enable = FALSE) + datamods:::insert_alert( + selector = ns("import"), + status = "info", + tags$b(i18n$t("No data selected!")), + i18n$t("Use a dataset from your environment or from the environment of a package.") + ) + } else { + name_df <- input$data + + if (!is.null(temporary_rv$package)) { + attr(name_df, "package") <- temporary_rv$package + } + + imported <- try(get_env_data(name_df), silent = TRUE) + + if (inherits(imported, "try-error") || NROW(imported) < 1) { + datamods:::toggle_widget(inputId = "confirm", enable = FALSE) + datamods:::insert_error(mssg = i18n$t(attr(imported, "condition")$message)) + temporary_rv$status <- "error" + temporary_rv$data <- NULL + temporary_rv$name <- NULL + } else { + datamods:::toggle_widget(inputId = "confirm", enable = TRUE) + datamods:::insert_alert( + selector = ns("import"), + status = "success", + datamods:::make_success_alert( + imported, + trigger_return = trigger_return, + btn_show_data = btn_show_data + ) + ) + pkg <- attr(name_df, "package") + if (!is.null(pkg)) { + name <- paste(pkg, input$data, sep = "::") + } else { + name <- input$data + } + name <- trimws(sub("\\(([^\\)]+)\\)", "", name)) + temporary_rv$status <- "success" + temporary_rv$data <- imported + temporary_rv$name <- name + } + } + }, ignoreInit = TRUE, ignoreNULL = FALSE) + + + observeEvent(input$see_data, { + show_data(temporary_rv$data, title = i18n$t("Imported data"), type = show_data_in) + }) + + observeEvent(input$confirm, { + imported_rv$data <- temporary_rv$data + imported_rv$name <- temporary_rv$name + }) + + + if (identical(trigger_return, "button")) { + return(list( + status = reactive(temporary_rv$status), + name = reactive(imported_rv$name), + data = reactive(datamods:::as_out(imported_rv$data, return_class)) + )) + } else { + return(list( + status = reactive(temporary_rv$status), + name = reactive(temporary_rv$name), + data = reactive(datamods:::as_out(temporary_rv$data, return_class)) + )) + } + } + + moduleServer( + id = id, + module = module + ) +} + + + + + + + +# utils ------------------------------------------------------------------- + + +#' Get packages containing datasets +#' +#' @return a character vector of packages names +#' @export +#' +#' @importFrom utils data +#' +#' @examples +#' if (interactive()) { +#' +#' get_data_packages() +#' +#' } +get_data_packages <- function() { + suppressWarnings({ + pkgs <- data(package = .packages(all.available = TRUE)) + }) + unique(pkgs$results[, 1]) +} + + +#' List dataset contained in a package +#' +#' @param pkg Name of the package, must be installed. +#' +#' @return a \code{character} vector or \code{NULL}. +#' @export +#' +#' @importFrom utils data +#' +#' @examples +#' +#' list_pkg_data("ggplot2") +list_pkg_data <- function(pkg) { + if (isTRUE(requireNamespace(pkg, quietly = TRUE))) { + list_data <- data(package = pkg, envir = environment())$results[, "Item"] + list_data <- sort(list_data) + attr(list_data, "package") <- pkg + if (length(list_data) < 1) { + NULL + } else { + unname(list_data) + } + } else { + NULL + } +} + +#' @importFrom utils data +get_env_data <- function(obj, env = globalenv()) { + pkg <- attr(obj, "package") + re <- regexpr(pattern = "\\(([^\\)]+)\\)", text = obj) + obj_ <- substr(x = obj, start = re + 1, stop = re + attr(re, "match.length") - 2) + obj <- gsub(pattern = "\\s.*", replacement = "", x = obj) + if (obj %in% ls(name = env)) { + get(x = obj, envir = env) + } else if (!is.null(pkg) && !identical(pkg, "")) { + res <- suppressWarnings(try( + get(utils::data(list = obj, package = pkg, envir = environment())), silent = TRUE + )) + if (!inherits(res, "try-error")) + return(res) + data(list = obj_, package = pkg, envir = environment()) + get(obj, envir = environment()) + } else { + NULL + } +} + + +get_dimensions <- function(objs) { + if (is.null(objs)) + return(NULL) + dataframes_dims <- Map( + f = function(name, pkg) { + attr(name, "package") <- pkg + tmp <- suppressWarnings(get_env_data(name)) + if (is.data.frame(tmp)) { + sprintf("%d obs. of %d variables", nrow(tmp), ncol(tmp)) + } else { + i18n$t("Not a data.frame") + } + }, + name = objs, + pkg = if (!is.null(attr(objs, "package"))) { + attr(objs, "package") + } else { + character(1) + } + ) + unlist(dataframes_dims) +} + + ######## #### Current file: /Users/au301842/FreesearchR/R//import-file-ext.R ######## @@ -4685,16 +5131,19 @@ data_missings_server <- function(id, shiny::req(variabler) if (is.null(variabler()) || variabler() == "" || !variabler() %in% names(datar())) { + tbl <- rv$data() if (anyNA(datar())){ title <- i18n$t("No variable chosen for analysis") } else { title <- i18n$t("No missing observations") } } else { + tbl <- rv$data()|> + gtsummary::bold_p() title <- glue::glue(i18n$t("Missing vs non-missing observations in the variable **'{variabler()}'**")) } - out <- rv$data() |> + out <- tbl |> gtsummary::as_gt() |> gt::tab_header(title = gt::md(title)) @@ -4875,7 +5324,7 @@ plot_box_single <- function(data, pri, sec=NULL, seed = 2103) { #' Area proportional venn diagrams #' #' @description -#' THis is slightly modified from https://gist.github.com/danlooo/d23d8bcf8856c7dd8e86266097404ded +#' This is slightly modified from https://gist.github.com/danlooo/d23d8bcf8856c7dd8e86266097404ded #' #' This functions uses eulerr::euler to plot area proportional venn diagramms #' but plots it using ggplot2 @@ -4885,18 +5334,27 @@ plot_box_single <- function(data, pri, sec=NULL, seed = 2103) { #' @param show_quantities whether to show number of intersecting elements #' @param show_labels whether to show set names #' @param ... further arguments passed to eulerr::euler +#' +#' @include data_plots.R ggeulerr <- function( combinations, show_quantities = TRUE, show_labels = TRUE, ...) { - # browser() + + ## Extracting labels + labs <- sapply(names(combinations),\(.x){ + # browser() + get_label(combinations,.x) + }) + data <- - eulerr::euler(combinations = combinations, ...) |> + ## Set labels as variable names for nicer plotting + setNames(as.data.frame(combinations),labs) |> + eulerr::euler(...) |> plot(quantities = show_quantities) |> purrr::pluck("data") - tibble::as_tibble(data$ellipses, rownames = "Variables") |> ggplot2::ggplot() + ggforce::geom_ellipse( @@ -4912,7 +5370,8 @@ ggeulerr <- function( dplyr::mutate( label = labels |> purrr::map2(quantities, ~ { if (!is.na(.x) && !is.na(.y) && show_labels) { - paste0(.x, "\n", sprintf(.y, fmt = "%.2g")) + paste0(.x, "\n", sprintf(.y, fmt = "%.4g")) + # glue::glue("{.x}\n{round(.y,0)}") } else if (!is.na(.x) && show_labels) { .x } else if (!is.na(.y)) { @@ -4951,6 +5410,21 @@ ggeulerr <- function( #' ) |> plot_euler("A", c("B", "C"), "D", seed = 4) #' mtcars |> plot_euler("vs", "am", seed = 1) #' mtcars |> plot_euler("vs", "am", "cyl", seed = 1) +#' stRoke::trial |> +#' dplyr::mutate( +#' mfi_cut = cut(mfi_6, c(0, 12, max(mfi_6, na.rm = TRUE))), +#' mdi_cut = cut(mdi_6, c(0, 20, max(mdi_6, na.rm = TRUE))) +#' ) |> +#' purrr::map2( +#' c(sapply(stRoke::trial, \(.x)REDCapCAST::get_attr(.x, attr = "label")), "Fatigue", "Depression"), +#' \(.x, .y){ +#' REDCapCAST::set_attr(.x, .y, "label") +#' } +#' ) |> +#' dplyr::bind_cols() |> +#' plot_euler("mfi_cut", "mdi_cut") +#' stRoke::trial |> +#' plot_euler(pri="male", sec=c("hypertension")) plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103) { set.seed(seed = seed) if (!is.null(ter)) { @@ -4958,16 +5432,13 @@ plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103) { } else { ds <- list(data) } - out <- lapply(ds, \(.x){ .x[c(pri, sec)] |> - as.data.frame() |> na.omit() |> plot_euler_single() }) -# browser() - wrap_plot_list(out,title=glue::glue("Grouped by {get_label(data,ter)}")) - # patchwork::wrap_plots(out) + + wrap_plot_list(out, title = glue::glue(i18n$t("Grouped by {get_label(data,ter)}"))) } #' Easily plot single euler diagrams @@ -6475,87 +6946,6 @@ regression_model_uv <- function(data, ### HELPERS -#' Data type assessment. -#' -#' @description -#' These are more overall than the native typeof. This is used to assess a more -#' meaningful "clinical" data type. -#' -#' @param data vector or data.frame. if data frame, each column is evaluated. -#' -#' @returns outcome type -#' @export -#' -#' @examples -#' mtcars |> -#' default_parsing() |> -#' lapply(data_type) -#' mtcars |> -#' default_parsing() |> -#' data_type() -#' c(1, 2) |> data_type() -#' 1 |> data_type() -#' c(rep(NA, 10)) |> data_type() -#' sample(1:100, 50) |> data_type() -#' factor(letters[1:20]) |> data_type() -#' as.Date(1:20) |> data_type() -data_type <- function(data) { - if (is.data.frame(data)) { - sapply(data, data_type) - } else { - cl_d <- class(data) - l_unique <- length(unique(na.omit(data))) - if (all(is.na(data))) { - out <- "empty" - } else if (l_unique < 2) { - out <- "monotone" - } else if (any(c("factor", "logical") %in% cl_d) | l_unique == 2) { - if (identical("logical", cl_d) | l_unique == 2) { - out <- "dichotomous" - } else { - # if (is.ordered(data)) { - # out <- "ordinal" - # } else { - out <- "categorical" - # } - } - } else if (identical(cl_d, "character")) { - out <- "text" - } else if (any(c("hms", "Date", "POSIXct", "POSIXt") %in% cl_d)) { - out <- "datetime" - } else if (l_unique > 2) { - ## Previously had all thinkable classes - ## Now just assumes the class has not been defined above - ## any(c("numeric", "integer", "hms", "Date", "timediff") %in% cl_d) & - out <- "continuous" - } else { - out <- "unknown" - } - - out - } -} - -#' Recognised data types from data_type -#' -#' @returns vector -#' @export -#' -#' @examples -#' data_types() -data_types <- function() { - list( - "empty" = list(descr="Variable of all NAs",classes="Any class"), - "monotone" = list(descr="Variable with only one unique value",classes="Any class"), - "dichotomous" = list(descr="Variable with only two unique values",classes="Any class"), - "categorical"= list(descr="Factor variable",classes="factor (ordered or unordered)"), - "text"= list(descr="Character variable",classes="character"), - "datetime"= list(descr="Variable of time, date or datetime values",classes="hms, Date, POSIXct and POSIXt"), - "continuous"= list(descr="Numeric variable",classes="numeric, integer or double"), - "unknown"= list(descr="Anything not falling within the previous",classes="Any other class") - ) -} - #' Implemented functions #' @@ -7196,38 +7586,6 @@ symmetrical_scale_x_log10 <- function(plot, breaks = c(1, 2, 3, 5, 10), ...) { #' purrr::map(regression_table) |> #' tbl_merge() #' } -#' regression_table <- function(x, ...) { -#' UseMethod("regression_table") -#' } -#' -#' #' @rdname regression_table -#' #' @export -#' regression_table.list <- function(x, ...) { -#' x |> -#' purrr::map(\(.m){ -#' regression_table(x = .m, ...) |> -#' gtsummary::add_n() -#' }) |> -#' gtsummary::tbl_stack() -#' } -#' -#' #' @rdname regression_table -#' #' @export -#' regression_table.default <- function(x, ..., args.list = NULL, fun = "gtsummary::tbl_regression") { -#' # Stripping custom class -#' class(x) <- class(x)[class(x) != "freesearchr_model"] -#' -#' if (any(c(length(class(x)) != 1, class(x) != "lm"))) { -#' if (!"exponentiate" %in% names(args.list)) { -#' args.list <- c(args.list, list(exponentiate = TRUE)) -#' } -#' } -#' -#' out <- do.call(getfun(fun), c(list(x = x), args.list)) -#' out |> -#' gtsummary::add_glance_source_note() # |> -#' # gtsummary::bold_p() -#' } regression_table <- function(x, ...) { args <- list(...) @@ -7305,8 +7663,6 @@ tbl_merge <- function(data) { } } -# as_kable(tbl) |> write_lines(file=here::here("inst/apps/data_analysis_modules/www/_table1.md")) -# as_kable_extra(tbl)|> write_lines(file=here::here("inst/apps/data_analysis_modules/www/table1.md")) ######## @@ -7387,13 +7743,15 @@ regression_ui <- function(id, ...) { shiny::radioButtons( inputId = ns("all"), label = i18n$t("Specify covariables"), - inline = TRUE, selected = 2, + inline = TRUE, + selected = 2, choiceNames = c( "Yes", "No" ), choiceValues = c(1, 2) ), + # shiny::uiOutput(outputId = ns("all")), shiny::conditionalPanel( condition = "input.all==1", shiny::uiOutput(outputId = ns("regression_vars")), @@ -7446,7 +7804,7 @@ regression_ui <- function(id, ...) { ) ), bslib::nav_panel( - title = "Coefficient plot", + title = i18n$t("Coefficient plot"), bslib::layout_sidebar( sidebar = bslib::sidebar( bslib::accordion( @@ -7558,11 +7916,6 @@ regression_server <- function(id, } }) - shiny::observe({ - bslib::accordion_panel_update(id = "acc_reg", target = "acc_pan_reg", title = i18n$t("Regression")) - bslib::accordion_panel_update(id = "acc_coef_plot", target = "acc_pan_coef_plot", title = i18n$t("Coefficients plot")) - bslib::accordion_panel_update(id = "acc_checks", target = "acc_pan_checks", title = i18n$t("Checks")) - }) output$data_info <- shiny::renderUI({ shiny::req(regression_vars()) @@ -7570,6 +7923,31 @@ regression_server <- function(id, data_description(data_r()[regression_vars()]) }) + ## Update on laguage change + + shiny::observe({ + bslib::accordion_panel_update(id = "acc_reg", target = "acc_pan_reg", title = i18n$t("Regression")) + bslib::accordion_panel_update(id = "acc_coef_plot", target = "acc_pan_coef_plot", title = i18n$t("Coefficients plot")) + bslib::accordion_panel_update(id = "acc_checks", target = "acc_pan_checks", title = i18n$t("Checks")) + }) + + # shiny::observe({ + # shiny::updateRadioButtons( + # session = session, + # inputId = "all", + # label = i18n$t("Specify covariables"), + # # inline = TRUE, + # # selected = 2, + # choiceNames = c( + # i18n$t("Yes"), + # i18n$t("No") + # ), + # choiceValues = c(1, 2) + # ) + # }) + + + ############################################################################## ######### ######### Input fields @@ -7593,7 +7971,7 @@ regression_server <- function(id, columnSelectInput( inputId = ns("outcome_var"), selected = NULL, - label = "Select outcome variable", + label = i18n$t("Select outcome variable"), data = data_r(), multiple = FALSE ) @@ -7603,7 +7981,7 @@ regression_server <- function(id, shiny::req(input$outcome_var) shiny::selectizeInput( inputId = ns("regression_type"), - label = "Choose regression analysis", + label = i18n$t("Choose regression analysis"), ## The below ifelse statement handles the case of loading a new dataset choices = possible_functions( data = dplyr::select( @@ -7622,7 +8000,7 @@ regression_server <- function(id, shiny::selectizeInput( inputId = ns("factor_vars"), selected = colnames(data_r())[sapply(data_r(), is.factor)], - label = "Covariables to format as categorical", + label = i18n$t("Covariables to format as categorical"), choices = colnames(data_r()), multiple = TRUE ) @@ -7642,7 +8020,7 @@ regression_server <- function(id, columnSelectInput( inputId = ns("strat_var"), selected = "none", - label = "Select variable to stratify baseline", + label = i18n$t("Select variable to stratify baseline"), data = data_r(), col_subset = c( "none", @@ -7657,7 +8035,7 @@ regression_server <- function(id, shiny::selectInput( inputId = ns("plot_model"), selected = 1, - label = "Select models to plot", + label = i18n$t("Select models to plot"), choices = names(rv$list$regression$tables), multiple = TRUE ) @@ -7707,7 +8085,7 @@ regression_server <- function(id, rv$list$regression$models <- model_lists }, error = function(err) { - showNotification(paste0("Creating regression models failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Creating regression models failed with the following error:"), err), type = "err") } ) } @@ -7772,7 +8150,7 @@ regression_server <- function(id, showNotification(paste0(warn), type = "warning") }, error = function(err) { - showNotification(paste0("Creating a regression table failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Creating a regression table failed with the following error:"), err), type = "err") } ) } @@ -7873,7 +8251,7 @@ regression_server <- function(id, output$download_plot <- shiny::downloadHandler( filename = paste0("regression_plot.", input$plot_type), content = function(file) { - shiny::withProgress(message = "Saving the plot. Hold on for a moment..", { + shiny::withProgress(message = i18n$t("Saving the plot. Hold on for a moment.."), { ggplot2::ggsave( filename = file, plot = rv$plot, @@ -7910,7 +8288,7 @@ regression_server <- function(id, # showNotification(paste0(warn), type = "warning") # }, error = function(err) { - showNotification(paste0("Running model assumptions checks failed with the following error: ", err), type = "err") + showNotification(paste(i18n$t("Running model assumptions checks failed with the following error:"), err), type = "err") } ) } @@ -7931,7 +8309,7 @@ regression_server <- function(id, vectorSelectInput( inputId = ns("plot_checks"), selected = 1, - label = "Select checks to plot", + label = i18n$t("Select checks to plot"), choices = names, multiple = TRUE ) @@ -7946,7 +8324,7 @@ regression_server <- function(id, if (!is.null(rv$list$regression$tables)) { p <- rv$check_plot() + # patchwork::wrap_plots() + - patchwork::plot_annotation(title = "Multivariable regression model checks") + patchwork::plot_annotation(title = i18n$t("Multivariable regression model checks")) layout <- sapply(seq_len(length(p)), \(.x){ @@ -8325,7 +8703,7 @@ ui_elements <- function(selection) { # ), shiny::selectInput( inputId = "source", - label="", + label = "", selected = "file", choices = "file", width = "100%" @@ -8361,7 +8739,11 @@ ui_elements <- function(selection) { ), shiny::conditionalPanel( condition = "input.source=='env'", - import_globalenv_ui(id = "env", title = NULL) + import_globalenv_ui( + id = "env", + title = NULL, + packages = c("NHANES", "stRoke") + ) ), # shiny::conditionalPanel( # condition = "input.source=='redcap'", @@ -8615,7 +8997,7 @@ ui_elements <- function(selection) { sidebar = bslib::sidebar( shiny::uiOutput(outputId = "data_info_nochar", inline = TRUE), bslib::accordion( - id="acc_chars", + id = "acc_chars", open = "acc_chars", multiple = FALSE, bslib::accordion_panel( @@ -8661,7 +9043,7 @@ ui_elements <- function(selection) { sidebar = bslib::sidebar( # shiny::uiOutput(outputId = "data_info_nochar", inline = TRUE), bslib::accordion( - id="acc_cor", + id = "acc_cor", open = "acc_chars", multiple = FALSE, bslib::accordion_panel( @@ -10979,6 +11361,7 @@ ui <- bslib::page_fixed( ## Code formatting dependencies prismDependencies, prismRDependency, + html_dependency_FreesearchR(), ## Version dependent header header_include(), ## This adds the actual favicon @@ -11264,7 +11647,8 @@ server <- function(input, output, session) { rv$code <- modifyList(x = rv$code, list(import = from_redcap$code())) }) - from_env <- datamods::import_globalenv_server( + # from_env <- datamods::import_globalenv_server( + from_env <- import_globalenv_server( id = "env", trigger_return = "change", btn_show_data = FALSE, From 498ce5256575dba0f3be7db963f83ea05a6b2e16 Mon Sep 17 00:00:00 2001 From: Andreas Gammelgaard Damsbo Date: Thu, 2 Oct 2025 13:40:22 +0200 Subject: [PATCH 6/6] new docker --- CITATION.cff | 15 +++++++- R/sysdata.rda | Bin 2781 -> 2804 bytes SESSION.md | 30 ++++++++-------- app_docker/app.R | 4 +-- app_docker/renv.lock | 83 +++++++++++++++++++++++++++++++++++++++++++ renv.lock | 2 +- 6 files changed, 114 insertions(+), 20 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 64a2bfc9..be088f29 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -8,7 +8,7 @@ message: 'To cite package "FreesearchR" in publications use:' type: software license: AGPL-3.0-or-later title: 'FreesearchR: Easy data analysis for clinicians' -version: 25.9.3 +version: 25.10.1 doi: 10.5281/zenodo.14527429 identifiers: - type: url @@ -1050,6 +1050,19 @@ references: email: jakub.sobolewski@appsilon.com year: '2025' doi: 10.32614/CRAN.package.shiny.i18n +- type: software + title: stRoke + abstract: 'stRoke: Clinical Stroke Research' + notes: Imports + url: https://agdamsbo.github.io/stRoke/ + repository: https://CRAN.R-project.org/package=stRoke + authors: + - family-names: Damsbo + given-names: Andreas Gammelgaard + email: agdamsbo@clin.au.dk + orcid: https://orcid.org/0000-0002-7559-1154 + year: '2025' + doi: 10.32614/CRAN.package.stRoke - type: software title: styler abstract: 'styler: Non-Invasive Pretty Printing of R Code' diff --git a/R/sysdata.rda b/R/sysdata.rda index 27b7821900ba46de2fefd1d7709702fc0d0f8722..799e56842342a5d191975673894bfb9e32972c1a 100644 GIT binary patch literal 2804 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", "Repository": "CRAN" }, + "calendar": { + "Package": "calendar", + "Version": "0.2.0", + "Source": "Repository", + "Title": "Create, Read, Write, and Work with 'iCalendar' Files, Calendars and Scheduling Data", + "Authors@R": "c(person(given = \"Robin\", family = \"Lovelace\", role = c(\"aut\", \"cre\"), email = \"rob00x@gmail.com\", comment = c(ORCID = \"0000-0001-5679-6536\")), person(given = \"Layik\", family = \"Hama\", role = \"aut\", email = \"layik.hama@gmail.com\", comment = c(ORCID = \"0000-0003-1912-4890\")), person(given = \"Ollie\", family = \"Lloyd\", role = \"ctb\", email = \"o.lloyd@doctors.org.uk\", comment = c(ORCID = \"0000-0002-9385-1634\")), person(given = \"Franco\", family = \"Scarafia\", role = \"ctb\", email = \"franco.scarafia@hotmail.com\", comment = c(ORCID = \"0009-0005-9822-169X\")), person(given = \"Serkan\", family = \"Korkmaz\", email = \"serkor1@duck.com\", role = c(\"ctb\"), comment = c(ORCID = \"0000-0002-5052-0982\")) )", + "Description": "Provides function to create, read, write, and work with 'iCalendar' files (which typically have '.ics' or '.ical' extensions), and the scheduling data, calendars and timelines of people, organisations and other entities that they represent. 'iCalendar' is an open standard for exchanging calendar and scheduling information between users and computers, described at .", + "License": "Apache License (>= 2.0)", + "URL": "https://github.com/atfutures/calendar, https://atfutures.github.io/calendar/, https://github.com/ATFutures/calendar", + "BugReports": "https://github.com/ATFutures/calendar/issues", + "Depends": [ + "R (>= 3.4.0)" + ], + "Imports": [ + "cli", + "lubridate", + "tibble" + ], + "Suggests": [ + "covr", + "knitr", + "rmarkdown", + "testthat" + ], + "VignetteBuilder": "knitr", + "Encoding": "UTF-8", + "LazyData": "true", + "RoxygenNote": "7.3.2", + "NeedsCompilation": "no", + "Author": "Robin Lovelace [aut, cre] (), Layik Hama [aut] (), Ollie Lloyd [ctb] (), Franco Scarafia [ctb] (), Serkan Korkmaz [ctb] ()", + "Maintainer": "Robin Lovelace ", + "Repository": "CRAN" + }, "cards": { "Package": "cards", "Version": "0.6.1", @@ -8257,6 +8290,56 @@ "NeedsCompilation": "yes", "Repository": "CRAN" }, + "stRoke": { + "Package": "stRoke", + "Version": "25.9.2", + "Source": "Repository", + "Title": "Clinical Stroke Research", + "Authors@R": "person(\"Andreas Gammelgaard\", \"Damsbo\", , \"agdamsbo@clin.au.dk\", role = c(\"aut\", \"cre\"), comment = c(ORCID = \"0000-0002-7559-1154\"))", + "Description": "A collection of tools for clinical trial data management and analysis in research and teaching. The package is mainly collected for personal use, but any use beyond that is encouraged. This package has migrated functions from 'agdamsbo/daDoctoR', and new functions has been added. Version follows months and year. See NEWS/Changelog for release notes. This package includes sampled data from the TALOS trial (Kraglund et al (2018) ). The win_prob() function is based on work by Zou et al (2022) . The age_calc() function is based on work by Becker (2020) .", + "URL": "https://agdamsbo.github.io/stRoke/, https://github.com/agdamsbo/stRoke", + "BugReports": "https://github.com/agdamsbo/stRoke/issues", + "License": "GPL-3", + "Encoding": "UTF-8", + "RoxygenNote": "7.3.2", + "LazyData": "true", + "Suggests": [ + "knitr", + "rmarkdown", + "testthat", + "here", + "spelling", + "usethis", + "pak", + "roxygen2", + "devtools" + ], + "Config/testthat/edition": "3", + "Imports": [ + "calendar", + "dplyr", + "ggplot2", + "grDevices", + "gtsummary", + "lubridate", + "MASS", + "rankinPlot", + "stats", + "tidyr", + "utils", + "tibble", + "tidyselect" + ], + "Depends": [ + "R (>= 4.1.0)" + ], + "VignetteBuilder": "knitr", + "Language": "en-US", + "NeedsCompilation": "no", + "Author": "Andreas Gammelgaard Damsbo [aut, cre] (ORCID: )", + "Maintainer": "Andreas Gammelgaard Damsbo ", + "Repository": "CRAN" + }, "stringi": { "Package": "stringi", "Version": "1.8.7", diff --git a/renv.lock b/renv.lock index 73215377..49bb63e6 100644 --- a/renv.lock +++ b/renv.lock @@ -8292,7 +8292,7 @@ }, "stRoke": { "Package": "stRoke", - "Version": "25.9.1", + "Version": "25.9.2", "Source": "Repository", "Title": "Clinical Stroke Research", "Authors@R": "person(\"Andreas Gammelgaard\", \"Damsbo\", , \"agdamsbo@clin.au.dk\", role = c(\"aut\", \"cre\"), comment = c(ORCID = \"0000-0002-7559-1154\"))",