2024-12-18 10:37:37 +01:00
########
2025-04-30 13:02:26 +02:00
#### Current file: /Users/au301842/FreesearchR/app/functions.R
2024-12-18 10:37:37 +01:00
########
2025-01-27 14:09:08 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//app_version.R
2025-01-27 14:09:08 +01:00
########
2025-05-08 10:12:49 +02:00
app_version <- function ( ) ' 25.5.2'
2025-01-27 14:09:08 +01:00
2024-12-18 10:37:37 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//baseline_table.R
2024-12-18 10:37:37 +01:00
########
2025-01-16 11:24:26 +01:00
#' Print a flexible baseline characteristics table
#'
#' @param data data set
#' @param fun.args list of arguments passed to
#' @param fun function to
#' @param vars character vector of variables to include
#'
#' @return object of standard class for fun
#' @export
#'
#' @examples
#' mtcars |> baseline_table()
#' mtcars |> baseline_table(fun.args = list(by = "gear"))
2024-12-18 10:37:37 +01:00
baseline_table <- function ( data , fun.args = NULL , fun = gtsummary :: tbl_summary , vars = NULL ) {
out <- do.call ( fun , c ( list ( data = data ) , fun.args ) )
return ( out )
}
2025-03-24 14:40:30 +01:00
#' Create a baseline table
#'
#' @param data data
#' @param ... passed as fun.arg to baseline_table()
#' @param strat.var grouping/strat variable
#' @param add.p add comparison/p-value
#' @param add.overall add overall column
#'
#' @returns gtsummary table list object
#' @export
#'
#' @examples
2025-04-02 11:31:04 +02:00
#' mtcars |> create_baseline(by.var = "gear", add.p = "yes" == "yes")
2025-04-15 08:55:35 +02:00
#' create_baseline(default_parsing(mtcars), by.var = "am", add.p = FALSE, add.overall = FALSE, theme = "lancet")
create_baseline <- function ( data , ... , by.var , add.p = FALSE , add.overall = FALSE , theme = c ( " jama" , " lancet" , " nejm" , " qjecon" ) ) {
2025-04-02 11:31:04 +02:00
theme <- match.arg ( theme )
2025-03-24 14:40:30 +01:00
if ( by.var == " none" | ! by.var %in% names ( data ) ) {
by.var <- NULL
}
## These steps are to handle logicals/booleans, that messes up the order of columns
2025-04-02 11:31:04 +02:00
## Has been reported and should be fixed soon (02042025)
2025-03-24 14:40:30 +01:00
if ( ! is.null ( by.var ) ) {
2025-04-02 11:31:04 +02:00
if ( identical ( " logical" , class ( data [ [by.var ] ] ) ) ) {
2025-03-24 14:40:30 +01:00
data [by.var ] <- as.character ( data [ [by.var ] ] )
}
}
2025-04-15 16:14:03 +02:00
suppressMessages ( gtsummary :: theme_gtsummary_journal ( journal = theme ) )
2025-04-02 11:31:04 +02:00
2025-04-15 08:55:35 +02:00
args <- list ( ... )
parameters <- list (
data = data ,
fun.args = list ( by = by.var , ... )
)
out <- do.call (
baseline_table ,
parameters
)
2025-03-24 14:40:30 +01:00
if ( ! is.null ( by.var ) ) {
2025-04-02 11:31:04 +02:00
if ( isTRUE ( add.overall ) ) {
out <- out | > gtsummary :: add_overall ( )
2025-03-24 14:40:30 +01:00
}
if ( isTRUE ( add.p ) ) {
out <- out | >
gtsummary :: add_p ( ) | >
gtsummary :: bold_p ( )
}
2025-04-02 11:31:04 +02:00
}
2025-03-24 14:40:30 +01:00
out
}
2025-02-25 09:51:42 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//contrast_text.R
2025-02-25 09:51:42 +01:00
########
#' @title Contrast Text Color
#' @description Calculates the best contrast text color for a given
#' background color.
#' @param background A hex/named color value that represents the background.
#' @param light_text A hex/named color value that represents the light text
#' color.
#' @param dark_text A hex/named color value that represents the dark text color.
#' @param threshold A numeric value between 0 and 1 that is used to determine
#' the luminance threshold of the background color for text color.
#' @param method A character string that specifies the method for calculating
#' the luminance. Three different methods are available:
#' c("relative","perceived","perceived_2")
#' @param ... parameter overflow. Ignored.
#' @details
#' This function aids in deciding the font color to print on a given background.
#' The function is based on the example provided by teppo:
#' https://stackoverflow.com/a/66669838/21019325.
#' The different methods provided are based on the methods outlined in the
#' StackOverflow thread:
#' https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color
#' @return A character string that contains the best contrast text color.
#' @examples
#' contrast_text(c("#F2F2F2", "blue"))
#'
#' contrast_text(c("#F2F2F2", "blue"), method="relative")
#' @export
#'
#' @importFrom grDevices col2rgb
#'
contrast_text <- function ( background ,
light_text = ' white' ,
dark_text = ' black' ,
threshold = 0.5 ,
method = " perceived_2" ,
... ) {
if ( method == " relative" ) {
luminance <-
c ( c ( .2126 , .7152 , .0722 ) %*% grDevices :: col2rgb ( background ) / 255 )
} else if ( method == " perceived" ) {
luminance <-
c ( c ( .299 , .587 , .114 ) %*% grDevices :: col2rgb ( background ) / 255 )
} else if ( method == " perceived_2" ) {
luminance <- c ( sqrt ( colSums ( (
c ( .299 , .587 , .114 ) * grDevices :: col2rgb ( background )
) ^ 2 ) ) / 255 )
}
ifelse ( luminance < threshold ,
light_text ,
dark_text )
}
2025-02-07 16:24:09 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//correlations-module.R
2025-02-07 16:24:09 +01:00
########
#' Data correlations evaluation module
#'
#' @param id Module id. (Use 'ns("id")')
#'
#' @name data-correlations
#' @returns Shiny ui module
#' @export
data_correlations_ui <- function ( id , ... ) {
2025-02-25 09:51:42 +01:00
ns <- shiny :: NS ( id )
2025-02-07 16:24:09 +01:00
shiny :: tagList (
shiny :: textOutput ( outputId = ns ( " suggest" ) ) ,
shiny :: plotOutput ( outputId = ns ( " correlation_plot" ) , ... )
)
}
#'
#' @param data data
#' @param color.main main color
#' @param color.sec secondary color
#' @param ... arguments passed to toastui::datagrid
#'
#' @name data-correlations
#' @returns shiny server module
#' @export
data_correlations_server <- function ( id ,
data ,
include.class = NULL ,
cutoff = .7 ,
... ) {
shiny :: moduleServer (
id = id ,
module = function ( input , output , session ) {
# ns <- session$ns
rv <- shiny :: reactiveValues (
data = NULL
)
rv $ data <- shiny :: reactive ( {
shiny :: req ( data )
if ( ! is.null ( include.class ) ) {
filter <- sapply ( data ( ) , class ) %in% include.class
out <- data ( ) [filter ]
} else {
out <- data ( )
}
2025-04-15 16:14:03 +02:00
# out |> dplyr::mutate(dplyr::across(tidyselect::everything(),as.numeric))
2025-04-15 16:27:43 +02:00
sapply ( out , as.numeric )
2025-03-13 14:13:18 +01:00
# as.numeric()
2025-02-07 16:24:09 +01:00
} )
# rv <- list()
# rv$data <- mtcars
output $ suggest <- shiny :: renderPrint ( {
shiny :: req ( rv $ data )
shiny :: req ( cutoff )
pairs <- correlation_pairs ( rv $ data ( ) , threshold = cutoff ( ) )
more <- ifelse ( nrow ( pairs ) > 1 , " from each pair " , " " )
if ( nrow ( pairs ) == 0 ) {
out <- glue :: glue ( " No variables have a correlation measure above the threshold." )
} else {
out <- pairs | >
apply ( 1 , \ ( .x ) {
glue :: glue ( " '{.x[1]}'x'{.x[2]}'({round(as.numeric(.x[3]),2)})" )
} ) | >
( \ ( .x ) {
glue :: glue ( " The following variable pairs are highly correlated: {sentence_paste(.x)}.\nConsider excluding one {more}from the dataset to ensure variables are independent." )
} ) ( )
}
out
} )
output $ correlation_plot <- shiny :: renderPlot ( {
2025-03-13 14:13:18 +01:00
ggcorrplot :: ggcorrplot ( cor ( rv $ data ( ) ) ) +
# ggplot2::theme_void() +
ggplot2 :: theme (
# legend.position = "none",
legend.title = ggplot2 :: element_text ( size = 20 ) ,
legend.text = ggplot2 :: element_text ( size = 14 ) ,
# panel.grid.major = element_blank(),
# panel.grid.minor = element_blank(),
# axis.text.y = element_blank(),
# axis.title.y = element_blank(),
axis.text.x = ggplot2 :: element_text ( size = 20 ) ,
axis.text.y = ggplot2 :: element_text ( size = 20 ) ,
# text = element_text(size = 5),
# plot.title = element_blank(),
# panel.background = ggplot2::element_rect(fill = "white"),
# plot.background = ggplot2::element_rect(fill = "white"),
panel.border = ggplot2 :: element_blank ( )
)
# psych::pairs.panels(rv$data())
2025-02-07 16:24:09 +01:00
} )
}
)
}
correlation_pairs <- function ( data , threshold = .8 ) {
2025-04-15 16:14:03 +02:00
data <- as.data.frame ( data ) [ ! sapply ( as.data.frame ( data ) , is.character ) ]
data <- sapply ( data , \ ( .x ) if ( is.factor ( .x ) ) as.numeric ( .x ) else .x ) | > as.data.frame ( )
# data <- data |> dplyr::mutate(dplyr::across(dplyr::where(is.factor), as.numeric))
2025-02-07 16:24:09 +01:00
cor <- Hmisc :: rcorr ( as.matrix ( data ) )
r <- cor $ r %>% as.table ( )
d <- r | >
as.data.frame ( ) | >
dplyr :: filter ( abs ( Freq ) > threshold , Freq != 1 )
d [1 : 2 ] | >
apply ( 1 , \ ( .x ) {
sort ( unname ( .x ) )
} ,
simplify = logical ( 1 )
) | >
duplicated ( ) | >
( \ ( .x ) {
d [ ! .x , ]
} ) ( ) | >
setNames ( c ( " var1" , " var2" , " cor" ) )
}
sentence_paste <- function ( data , and.str = " and" ) {
and.str <- gsub ( " " , " " , and.str )
if ( length ( data ) < 2 ) {
data
} else if ( length ( data ) == 2 ) {
paste ( data , collapse = glue :: glue ( " {and.str} " ) )
} else if ( length ( data ) > 2 ) {
2025-04-03 14:31:34 +02:00
paste ( paste ( data [ - length ( data ) ] , collapse = " , " ) , data [length ( data ) ] , sep = glue :: glue ( " {and.str} " ) )
2025-02-07 16:24:09 +01:00
}
}
2025-04-02 11:31:04 +02:00
2025-02-07 16:24:09 +01:00
2025-04-24 12:53:47 +02:00
########
#### Current file: /Users/au301842/FreesearchR/R//create-column-mod.R
########
#' @title Create new column
#'
#' @description
#' This module allow to enter an expression to create a new column in a `data.frame`.
#'
#'
#' @param id Module's ID.
#'
#' @return A [shiny::reactive()] function returning the data.
#'
#' @note User can only use a subset of function: `r paste(list_allowed_operations(), collapse=", ")`.
#' You can add more operations using the `allowed_operations` argument, for example if you want to allow to use package lubridate, you can do:
#' ```r
#' c(list_allowed_operations(), getNamespaceExports("lubridate"))
#' ```
#'
#' @export
#'
#' @importFrom htmltools tagList tags css
#' @importFrom shiny NS textInput textAreaInput uiOutput actionButton
#' @importFrom phosphoricons ph
#' @importFrom shinyWidgets virtualSelectInput
#'
#' @name create-column
#'
2025-04-28 08:31:23 +02:00
#' @example examples/create_column_module_demo.R
2025-04-24 12:53:47 +02:00
create_column_ui <- function ( id ) {
ns <- NS ( id )
tagList (
# datamods:::html_dependency_datamods(),
# html_dependency_FreesearchR(),
tags $ head (
tags $ link ( rel = " stylesheet" , type = " text/css" , href = " FreesearchR/inst/assets/css/FreesearchR.css" )
) ,
# tags$head(
# # Note the wrapping of the string in HTML()
# tags$style(HTML("
# /* modified from esquisse for data types */
# .btn-column-categorical {
# background-color: #EF562D;
# color: #FFFFFF;
# }
# .btn-column-continuous {
# background-color: #0C4C8A;
# color: #FFFFFF;
# }
# .btn-column-dichotomous {
# background-color: #97D5E0;
# color: #FFFFFF;
# }
# .btn-column-datetime {
# background-color: #97D5E0;
# color: #FFFFFF;
# }
# .btn-column-id {
# background-color: #848484;
# color: #FFFFFF;
# }
# .btn-column-text {
# background-color: #2E2E2E;
# color: #FFFFFF;
# }"))
# ),
fluidRow (
column (
width = 6 ,
textInput (
inputId = ns ( " new_column" ) ,
label = i18n ( " New column name:" ) ,
value = " new_column1" ,
width = " 100%"
)
) ,
column (
width = 6 ,
shinyWidgets :: virtualSelectInput (
inputId = ns ( " group_by" ) ,
label = i18n ( " Group calculation by:" ) ,
choices = NULL ,
multiple = TRUE ,
disableSelectAll = TRUE ,
hasOptionDescription = TRUE ,
width = " 100%"
)
)
) ,
textAreaInput (
inputId = ns ( " expression" ) ,
label = i18n ( " Enter an expression to define new column:" ) ,
value = " " ,
width = " 100%" ,
rows = 6
) ,
tags $ i (
class = " d-block" ,
phosphoricons :: ph ( " info" ) ,
datamods :: i18n ( " Click on a column name to add it to the expression:" )
) ,
uiOutput ( outputId = ns ( " columns" ) ) ,
uiOutput ( outputId = ns ( " feedback" ) ) ,
tags $ div (
style = css (
display = " grid" ,
gridTemplateColumns = " 3fr 1fr" ,
columnGap = " 10px" ,
margin = " 10px 0"
) ,
actionButton (
inputId = ns ( " compute" ) ,
label = tagList (
phosphoricons :: ph ( " gear" ) , i18n ( " Create column" )
) ,
class = " btn-outline-primary" ,
width = " 100%"
) ,
actionButton (
inputId = ns ( " remove" ) ,
label = tagList (
phosphoricons :: ph ( " trash" )
) ,
class = " btn-outline-danger" ,
width = " 100%"
)
)
)
}
#' @param data_r A [shiny::reactive()] function returning a `data.frame`.
#' @param allowed_operations A `list` of allowed operations, see below for details.
#'
#' @export
#'
#' @rdname create-column
#'
#' @importFrom shiny moduleServer reactiveValues observeEvent renderUI req
#' updateTextAreaInput reactive bindEvent observe
#' @importFrom shinyWidgets alert updateVirtualSelect
create_column_server <- function ( id ,
data_r = reactive ( NULL ) ,
allowed_operations = list_allowed_operations ( ) ) {
moduleServer (
id ,
function ( input , output , session ) {
ns <- session $ ns
info_alert <- shinyWidgets :: alert (
status = " info" ,
phosphoricons :: ph ( " question" ) ,
datamods :: i18n ( " Choose a name for the column to be created or modified," ) ,
datamods :: i18n ( " then enter an expression before clicking on the button above to validate or on " ) ,
phosphoricons :: ph ( " trash" ) , datamods :: i18n ( " to delete it." )
)
rv <- reactiveValues (
data = NULL ,
feedback = info_alert
)
observeEvent ( input $ hidden , rv $ feedback <- info_alert )
bindEvent ( observe ( {
data <- data_r ( )
shinyWidgets :: updateVirtualSelect (
inputId = " group_by" ,
choices = make_choices_with_infos ( data )
)
} ) , data_r ( ) , input $ hidden )
observeEvent ( data_r ( ) , rv $ data <- data_r ( ) )
output $ feedback <- renderUI ( rv $ feedback )
output $ columns <- renderUI ( {
data <- req ( rv $ data )
mapply (
label = names ( data ) ,
data = data ,
FUN = btn_column ,
MoreArgs = list ( inputId = ns ( " add_column" ) ) ,
SIMPLIFY = FALSE
)
} )
observeEvent ( input $ add_column , {
updateTextAreaInput (
session = session ,
inputId = " expression" ,
value = paste0 ( input $ expression , input $ add_column )
)
} )
observeEvent ( input $ new_column , {
if ( input $ new_column == " " ) {
rv $ feedback <- shinyWidgets :: alert (
status = " warning" ,
ph ( " warning" ) , datamods :: i18n ( " New column name cannot be empty" )
)
}
} )
observeEvent ( input $ remove , {
rv $ data [ [input $ new_column ] ] <- NULL
} )
observeEvent ( input $ compute , {
rv $ feedback <- try_compute_column (
expression = input $ expression ,
name = input $ new_column ,
rv = rv ,
allowed_operations = allowed_operations ,
by = input $ group_by
)
} )
return ( reactive ( rv $ data ) )
}
)
}
#' @export
#'
#' @rdname create-column
# @importFrom methods getGroupMembers
list_allowed_operations <- function ( ) {
c (
" (" , " c" ,
# getGroupMembers("Arith"),
c ( " +" , " -" , " *" , " ^" , " %%" , " %/%" , " /" ) ,
# getGroupMembers("Compare"),
c ( " ==" , " >" , " <" , " !=" , " <=" , " >=" ) ,
# getGroupMembers("Logic"),
c ( " &" , " |" ) ,
# getGroupMembers("Math"),
c (
" abs" , " sign" , " sqrt" , " ceiling" , " floor" , " trunc" , " cummax" ,
" cummin" , " cumprod" , " cumsum" , " exp" , " expm1" , " log" , " log10" ,
" log2" , " log1p" , " cos" , " cosh" , " sin" , " sinh" , " tan" , " tanh" ,
" acos" , " acosh" , " asin" , " asinh" , " atan" , " atanh" , " cospi" , " sinpi" ,
" tanpi" , " gamma" , " lgamma" , " digamma" , " trigamma"
) ,
# getGroupMembers("Math2"),
c ( " round" , " signif" ) ,
# getGroupMembers("Summary"),
c ( " max" , " min" , " range" , " prod" , " sum" , " any" , " all" ) ,
" pmin" , " pmax" , " mean" ,
" paste" , " paste0" , " substr" , " nchar" , " trimws" ,
" gsub" , " sub" , " grepl" , " ifelse" , " length" ,
" as.numeric" , " as.character" , " as.integer" , " as.Date" , " as.POSIXct" ,
" as.factor" , " factor"
)
}
#' @inheritParams shiny::modalDialog
#' @export
#'
#' @importFrom shiny showModal modalDialog textInput
#' @importFrom htmltools tagList
#'
#' @rdname create-column
modal_create_column <- function ( id ,
title = i18n ( " Create a new column" ) ,
easyClose = TRUE ,
size = " l" ,
footer = NULL ) {
ns <- NS ( id )
showModal ( modalDialog (
title = tagList ( title , datamods ::: button_close_modal ( ) ) ,
create_column_ui ( id ) ,
tags $ div (
style = " display: none;" ,
textInput ( inputId = ns ( " hidden" ) , label = NULL , value = datamods ::: genId ( ) )
) ,
easyClose = easyClose ,
size = size ,
footer = footer
) )
}
#' @inheritParams shinyWidgets::WinBox
#' @export
#'
#' @importFrom shinyWidgets WinBox wbOptions wbControls
#' @importFrom htmltools tagList
#' @rdname create-column
winbox_create_column <- function ( id ,
title = i18n ( " Create a new column" ) ,
options = shinyWidgets :: wbOptions ( ) ,
controls = shinyWidgets :: wbControls ( ) ) {
ns <- NS ( id )
WinBox (
title = title ,
ui = tagList (
create_column_ui ( id ) ,
tags $ div (
style = " display: none;" ,
textInput ( inputId = ns ( " hidden" ) , label = NULL , value = datamods ::: genId ( ) )
)
) ,
options = modifyList (
shinyWidgets :: wbOptions ( height = " 550px" , modal = TRUE ) ,
options
) ,
controls = controls ,
auto_height = FALSE
)
}
try_compute_column <- function ( expression ,
name ,
rv ,
allowed_operations ,
by = NULL ) {
parsed <- try ( parse ( text = expression , keep.source = FALSE ) , silent = TRUE )
if ( inherits ( parsed , " try-error" ) ) {
return ( datamods ::: alert_error ( attr ( parsed , " condition" ) $ message ) )
}
funs <- unlist ( c ( extract_calls ( parsed ) , lapply ( parsed , extract_calls ) ) , recursive = TRUE )
if ( ! are_allowed_operations ( funs , allowed_operations ) ) {
return ( datamods ::: alert_error ( datamods :: i18n ( " Some operations are not allowed" ) ) )
}
if ( ! isTruthy ( by ) ) {
result <- try (
rlang :: eval_tidy ( rlang :: parse_expr ( expression ) , data = rv $ data ) ,
silent = TRUE
)
} else {
result <- try (
{
dt <- as.data.table ( rv $ data )
new_col <- NULL
dt [ , new_col : = rlang :: eval_tidy ( rlang :: parse_expr ( expression ) , data = .SD ) , by = by ]
dt $ new_col
} ,
silent = TRUE
)
}
if ( inherits ( result , " try-error" ) ) {
return ( alert_error ( attr ( result , " condition" ) $ message ) )
}
adding_col <- try ( rv $ data [ [name ] ] <- result , silent = TRUE )
if ( inherits ( adding_col , " try-error" ) ) {
return ( alert_error ( attr ( adding_col , " condition" ) $ message ) )
}
code <- if ( ! isTruthy ( by ) ) {
rlang :: call2 ( " mutate" , ! ! ! rlang :: set_names ( list ( rlang :: parse_expr ( expression ) ) , name ) )
} else {
rlang :: call2 (
" mutate" ,
! ! ! rlang :: set_names ( list ( rlang :: parse_expr ( expression ) ) , name ) ,
! ! ! list ( .by = rlang :: expr ( c ( ! ! ! rlang :: syms ( by ) ) ) )
)
}
attr ( rv $ data , " code" ) <- Reduce (
f = function ( x , y ) rlang :: expr ( ! ! x %>% ! ! y ) ,
x = c ( attr ( rv $ data , " code" ) , code )
)
shinyWidgets :: alert (
status = " success" ,
ph ( " check" ) , datamods :: i18n ( " Column added!" )
)
}
are_allowed_operations <- function ( x , allowed_operations ) {
all (
x %in% allowed_operations
)
}
extract_calls <- function ( exp ) {
if ( is.call ( exp ) ) {
return ( list (
as.character ( exp [ [1L ] ] ) ,
lapply ( exp [ -1L ] , extract_calls )
) )
}
}
alert_error <- function ( text ) {
alert (
status = " danger" ,
ph ( " bug" ) , text
)
}
btn_column <- function ( label , data , inputId ) {
icon <- get_var_icon ( data , " class" )
type <- data_type ( data )
tags $ button (
type = " button" ,
class = paste0 ( " btn btn-column-" , type ) ,
style = css (
" --bs-btn-padding-y" = " .25rem" ,
" --bs-btn-padding-x" = " .5rem" ,
" --bs-btn-font-size" = " .75rem" ,
" margin-bottom" = " 5px"
) ,
if ( ! is.null ( icon ) ) icon ,
label ,
onclick = sprintf (
" Shiny.setInputValue('%s', '%s', {priority: 'event'})" ,
inputId , label
)
)
}
make_choices_with_infos <- function ( data ) {
lapply (
X = seq_along ( data ) ,
FUN = function ( i ) {
nm <- names ( data ) [i ]
values <- data [ [nm ] ]
icon <- get_var_icon ( values , " class" )
# icon <- if (inherits(values, "character")) {
# phosphoricons::ph("text-aa")
# } else if (inherits(values, "factor")) {
# phosphoricons::ph("list-bullets")
# } else if (inherits(values, c("numeric", "integer"))) {
# phosphoricons::ph("hash")
# } else if (inherits(values, c("Date"))) {
# phosphoricons::ph("calendar")
# } else if (inherits(values, c("POSIXt"))) {
# phosphoricons::ph("clock")
# } else {
# NULL
# }
description <- if ( is.atomic ( values ) ) {
paste ( i18n ( " Unique values:" ) , data.table :: uniqueN ( values ) )
} else {
" "
}
list (
label = htmltools :: doRenderTags ( tagList (
icon , nm
) ) ,
value = nm ,
description = description
)
}
)
}
2025-03-07 14:55:08 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//custom_SelectInput.R
2025-03-07 14:55:08 +01:00
########
#' A selectizeInput customized for data frames with column labels
#'
#' @description
#' Copied and modified from the IDEAFilter package
#' Adds the option to select "none" which is handled later
#'
#' @param inputId passed to \code{\link[shiny]{selectizeInput}}
#' @param label passed to \code{\link[shiny]{selectizeInput}}
#' @param data \code{data.frame} object from which fields should be populated
#' @param selected default selection
#' @param ... passed to \code{\link[shiny]{selectizeInput}}
#' @param col_subset a \code{vector} containing the list of allowable columns to select
#' @param placeholder passed to \code{\link[shiny]{selectizeInput}} options
#' @param onInitialize passed to \code{\link[shiny]{selectizeInput}} options
#' @param none_label label for "none" item
2025-03-12 18:27:46 +01:00
#' @param maxItems max number of items
2025-03-07 14:55:08 +01:00
#'
#' @return a \code{\link[shiny]{selectizeInput}} dropdown element
#'
#' @importFrom shiny selectizeInput
#' @export
#'
columnSelectInput <- function ( inputId , label , data , selected = " " , ... ,
2025-03-12 18:27:46 +01:00
col_subset = NULL , placeholder = " " , onInitialize , none_label = " No variable selected" , maxItems = NULL ) {
2025-03-07 14:55:08 +01:00
datar <- if ( is.reactive ( data ) ) data else reactive ( data )
col_subsetr <- if ( is.reactive ( col_subset ) ) col_subset else reactive ( col_subset )
labels <- Map ( function ( col ) {
json <- sprintf (
IDEAFilter ::: strip_leading_ws ( '
{
" name" : " %s" ,
" label" : " %s" ,
2025-03-20 11:46:02 +01:00
" dataclass" : " %s" ,
2025-03-07 14:55:08 +01:00
" datatype" : " %s"
} ' ) ,
col ,
attr ( datar ( ) [ [col ] ] , " label" ) %||% " " ,
2025-03-20 11:46:02 +01:00
IDEAFilter ::: get_dataFilter_class ( datar ( ) [ [col ] ] ) ,
data_type ( datar ( ) [ [col ] ] )
2025-03-07 14:55:08 +01:00
)
} , col = names ( datar ( ) ) )
if ( ! " none" %in% names ( datar ( ) ) ) {
2025-03-20 11:46:02 +01:00
labels <- c ( " none" = list ( sprintf ( ' \n {\n \"name\": \"none\",\n \"label\": \"%s\",\n \"dataclass\": \"\",\n \"datatype\": \"\"\n }' , none_label ) ) , labels )
2025-03-07 14:55:08 +01:00
choices <- setNames ( names ( labels ) , labels )
choices <- choices [match ( if ( length ( col_subsetr ( ) ) == 0 || isTRUE ( col_subsetr ( ) == " " ) ) names ( datar ( ) ) else col_subsetr ( ) , choices ) ]
} else {
choices <- setNames ( names ( datar ( ) ) , labels )
choices <- choices [match ( if ( length ( col_subsetr ( ) ) == 0 || isTRUE ( col_subsetr ( ) == " " ) ) choices else col_subsetr ( ) , choices ) ]
}
shiny :: selectizeInput (
inputId = inputId ,
label = label ,
choices = choices ,
selected = selected ,
... ,
options = c (
list ( render = I ( " {
/ / format the way that options are rendered
option : function ( item , escape ) {
item.data = JSON.parse ( item.label ) ;
return ' <div style=\"padding: 3px 12px\">' +
' <div><strong>' +
escape ( item.data.name ) + ' ' +
2025-03-20 13:13:14 +01:00
' </strong>' +
( item.data.dataclass != ' ' ?
2025-03-20 11:46:02 +01:00
' <span style=\"opacity: 0.9;\"><code style=\"color: black;\"> ' +
item.data.dataclass +
2025-03-20 13:13:14 +01:00
' </code></span>' : ' ' ) + ' ' +
( item.data.datatype != ' ' ?
2025-03-20 11:46:02 +01:00
' <span style=\"opacity: 0.9;\"><code style=\"color: black;\"> ' +
2025-03-07 14:55:08 +01:00
item.data.datatype +
2025-03-20 13:13:14 +01:00
' </code></span>' : ' ' ) +
' </div>' +
2025-03-07 14:55:08 +01:00
( item.data.label != ' ' ? ' <div style=\"line-height: 1em;\"><small>' + escape ( item.data.label ) + ' </small></div>' : ' ' ) +
' </div>' ;
} ,
/ / avoid data vomit splashing on screen when an option is selected
item : function ( item , escape ) {
item.data = JSON.parse ( item.label ) ;
return ' <div>' +
escape ( item.data.name ) +
' </div>' ;
}
2025-03-12 18:27:46 +01:00
} " ) ) ,
if ( ! is.null ( maxItems ) ) list ( maxItems = maxItems )
2025-03-07 14:55:08 +01:00
)
)
}
#' A selectizeInput customized for named vectors
#'
#' @param inputId passed to \code{\link[shiny]{selectizeInput}}
#' @param label passed to \code{\link[shiny]{selectizeInput}}
2025-03-11 13:42:57 +01:00
#' @param choices A named \code{vector} from which fields should be populated
2025-03-07 14:55:08 +01:00
#' @param selected default selection
#' @param ... passed to \code{\link[shiny]{selectizeInput}}
#' @param placeholder passed to \code{\link[shiny]{selectizeInput}} options
#' @param onInitialize passed to \code{\link[shiny]{selectizeInput}} options
#'
#' @returns a \code{\link[shiny]{selectizeInput}} dropdown element
#' @export
#'
#' @examples
#' if (shiny::interactive()) {
#' shinyApp(
#' ui = fluidPage(
#' shiny::uiOutput("select"),
#' tableOutput("data")
#' ),
#' server = function(input, output) {
#' output$select <- shiny::renderUI({
#' vectorSelectInput(
#' inputId = "variable", label = "Variable:",
#' data = c(
#' "Cylinders" = "cyl",
#' "Transmission" = "am",
#' "Gears" = "gear"
#' )
#' )
#' })
#'
#' output$data <- renderTable(
#' {
#' mtcars[, c("mpg", input$variable), drop = FALSE]
#' },
#' rownames = TRUE
#' )
#' }
#' )
#' }
vectorSelectInput <- function ( inputId ,
label ,
2025-03-11 13:42:57 +01:00
choices ,
2025-03-07 14:55:08 +01:00
selected = " " ,
... ,
placeholder = " " ,
onInitialize ) {
2025-03-11 13:42:57 +01:00
datar <- if ( shiny :: is.reactive ( choices ) ) data else shiny :: reactive ( choices )
2025-03-07 14:55:08 +01:00
labels <- sprintf (
IDEAFilter ::: strip_leading_ws ( '
{
" name" : " %s" ,
" label" : " %s"
} ' ) ,
datar ( ) ,
names ( datar ( ) ) %||% " "
)
2025-03-11 13:42:57 +01:00
choices_new <- stats :: setNames ( datar ( ) , labels )
2025-03-07 14:55:08 +01:00
shiny :: selectizeInput (
inputId = inputId ,
label = label ,
2025-03-11 13:42:57 +01:00
choices = choices_new ,
2025-03-07 14:55:08 +01:00
selected = selected ,
... ,
options = c (
list ( render = I ( " {
/ / format the way that options are rendered
option : function ( item , escape ) {
item.data = JSON.parse ( item.label ) ;
return ' <div style=\"padding: 3px 12px\">' +
' <div><strong>' +
escape ( item.data.name ) + ' ' +
' </strong></div>' +
( item.data.label != ' ' ? ' <div style=\"line-height: 1em;\"><small>' + escape ( item.data.label ) + ' </small></div>' : ' ' ) +
' </div>' ;
} ,
/ / avoid data vomit splashing on screen when an option is selected
item : function ( item , escape ) {
item.data = JSON.parse ( item.label ) ;
return ' <div>' +
escape ( item.data.name ) +
' </div>' ;
}
} " ) )
)
)
}
2024-12-18 10:37:37 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//cut-variable-dates.R
2024-12-18 10:37:37 +01:00
########
library ( datamods )
library ( toastui )
library ( phosphoricons )
library ( rlang )
library ( shiny )
2025-04-11 13:23:18 +02:00
#' Extended cutting function with fall-back to the native base::cut
2025-01-16 11:24:26 +01:00
#'
#' @param x an object inheriting from class "hms"
#' @param ... passed on
#'
2025-04-11 13:23:18 +02:00
#' @export
#' @name cut_var
cut_var <- function ( x , ... ) {
UseMethod ( " cut_var" )
}
#' @export
#' @name cut_var
cut_var.default <- function ( x , ... ) {
2025-04-15 16:14:03 +02:00
base :: cut ( x , ... )
2025-04-11 13:23:18 +02:00
}
#' @name cut_var
2025-01-16 11:24:26 +01:00
#'
#' @return factor
#' @export
#'
#' @examples
2025-04-11 13:23:18 +02:00
#' readr::parse_time(c("01:00:20", "03:00:20", "01:20:20", "08:20:20", "21:20:20", "03:02:20")) |> cut_var(2)
#' readr::parse_time(c("01:00:20", "03:00:20", "01:20:20", "08:20:20", "21:20:20", "03:02:20")) |> cut_var("min")
#' readr::parse_time(c("01:00:20", "03:00:20", "01:20:20", "08:20:20", "21:20:20", "03:02:20")) |> cut_var(breaks = "hour")
#' readr::parse_time(c("01:00:20", "03:00:20", "01:20:20", "08:20:20", "21:20:20", "03:02:20")) |> cut_var(breaks = hms::as_hms(c("01:00:00", "03:01:20", "9:20:20")))
2025-01-16 11:24:26 +01:00
#' d_t <- readr::parse_time(c("01:00:20", "03:00:20", "01:20:20", "03:02:20", NA))
2025-04-11 13:23:18 +02:00
#' f <- d_t |> cut_var(2)
#' readr::parse_time(c("01:00:20", "03:00:20", "01:20:20", "03:02:20", NA)) |> cut_var(breaks = lubridate::as_datetime(c(hms::as_hms(levels(f)), hms::as_hms(max(d_t, na.rm = TRUE) + 1))), right = FALSE)
cut_var.hms <- function ( x , breaks , ... ) {
2025-03-19 13:10:56 +01:00
## as_hms keeps returning warnings on tz(); ignored
suppressWarnings ( {
2025-04-10 15:46:42 +02:00
if ( hms :: is_hms ( breaks ) ) {
breaks <- lubridate :: as_datetime ( breaks )
}
x <- lubridate :: as_datetime ( x )
2025-04-11 13:23:18 +02:00
out <- cut_var.POSIXt ( x , breaks = breaks , ... )
2025-04-10 15:46:42 +02:00
attr ( out , which = " brks" ) <- hms :: as_hms ( lubridate :: as_datetime ( attr ( out , which = " brks" ) ) )
attr ( out , which = " levels" ) <- as.character ( hms :: as_hms ( lubridate :: as_datetime ( attr ( out , which = " levels" ) ) ) )
2025-03-19 13:10:56 +01:00
} )
2024-12-18 10:37:37 +01:00
out
}
2025-04-11 13:23:18 +02:00
#' @name cut_var
2025-01-16 11:24:26 +01:00
#' @param x an object inheriting from class "POSIXt" or "Date"
#'
#' @examples
2025-04-11 13:23:18 +02:00
#' readr::parse_datetime(c("1992-02-01 01:00:20", "1992-02-06 03:00:20", "1992-05-01 01:20:20", "1992-09-01 08:20:20", "1999-02-01 21:20:20", "1992-12-01 03:02:20")) |> cut_var(2)
#' readr::parse_datetime(c("1992-02-01 01:00:20", "1992-02-06 03:00:20", "1992-05-01 01:20:20", "1992-09-01 08:20:20", "1999-02-01 21:20:20", "1992-12-01 03:02:20")) |> cut_var(breaks = "weekday")
#' readr::parse_datetime(c("1992-02-01 01:00:20", "1992-02-06 03:00:20", "1992-05-01 01:20:20", "1992-09-01 08:20:20", "1999-02-01 21:20:20", "1992-12-01 03:02:20")) |> cut_var(breaks = "month_only")
2025-04-15 08:55:35 +02:00
#' readr::parse_datetime(c("1992-02-01 01:00:20", "1992-02-06 03:00:20", "1992-05-01 01:20:20", "1992-09-01 08:20:20", "1999-02-01 21:20:20", "1992-12-01 03:02:20")) |> cut_var(breaks=NULL,format = "%A-%H")
2025-04-24 11:00:56 +02:00
#' readr::parse_datetime(c("1992-02-01 01:00:20", "1992-02-06 03:00:20", "1992-05-01 01:20:20", "1992-09-01 08:20:20", "1999-02-01 21:20:20", "1992-12-01 03:02:20")) |> cut_var(breaks=NULL,format = "%W")
2025-04-11 13:23:18 +02:00
cut_var.POSIXt <- function ( x , breaks , right = FALSE , include.lowest = TRUE , start.on.monday = TRUE , ... ) {
2024-12-18 10:37:37 +01:00
breaks_o <- breaks
2025-04-15 08:55:35 +02:00
args <- list ( ... )
2024-12-18 10:37:37 +01:00
# browser()
if ( is.numeric ( breaks ) ) {
breaks <- quantile (
x ,
probs = seq ( 0 , 1 , 1 / breaks ) ,
right = right ,
include.lowest = include.lowest ,
2025-04-10 15:46:42 +02:00
na.rm = TRUE
2024-12-18 10:37:37 +01:00
)
}
2025-04-15 08:55:35 +02:00
if ( " format" %in% names ( args ) ) {
assertthat :: assert_that ( is.character ( args $ format ) )
out <- forcats :: as_factor ( format ( x , format = args $ format ) )
} else if ( identical ( breaks , " weekday" ) ) {
## This is
ds <- as.Date ( 1 : 7 ) | >
( \ ( .x ) {
sort_by ( format ( .x , " %A" ) , as.numeric ( format ( .x , " %w" ) ) )
} ) ( )
if ( start.on.monday ) {
ds <- ds [c ( 7 , 1 : 6 ) ]
2024-12-18 10:37:37 +01:00
}
2025-04-15 08:55:35 +02:00
out <- factor ( weekdays ( x ) , levels = ds ) | > forcats :: fct_drop ( )
2025-04-10 15:46:42 +02:00
} else if ( identical ( breaks , " month_only" ) ) {
2025-04-15 08:55:35 +02:00
## Simplest way to create a vector of all months in order
## which will also follow the locale of the machine
2025-04-10 15:46:42 +02:00
ms <- paste0 ( " 1970-" , 1 : 12 , " -01" ) | >
as.Date ( ) | >
months ( )
2024-12-19 11:34:25 +01:00
2025-04-10 15:46:42 +02:00
out <- factor ( months ( x ) , levels = ms ) | > forcats :: fct_drop ( )
2024-12-18 10:37:37 +01:00
} else {
2025-04-10 15:46:42 +02:00
## Doesn't really work very well for breaks other than the special character cases as right border is excluded
out <- base :: cut.POSIXt ( x , breaks = breaks , right = right , ... ) | > forcats :: fct_drop ( )
# browser()
}
2024-12-18 10:37:37 +01:00
l <- levels ( out )
if ( is.numeric ( breaks_o ) ) {
l <- breaks
2025-04-10 15:46:42 +02:00
} else if ( is.character ( breaks ) && length ( breaks ) == 1 && ! ( identical ( breaks , " weekday" ) | identical ( breaks , " month_only" ) ) ) {
2024-12-18 10:37:37 +01:00
if ( include.lowest ) {
if ( right ) {
l <- c ( l , min ( as.character ( x ) ) )
} else {
l <- c ( l , max ( as.character ( x ) ) )
}
}
} else if ( length ( l ) < length ( breaks_o ) ) {
l <- breaks_o
}
attr ( out , which = " brks" ) <- l
out
}
2025-04-11 13:23:18 +02:00
#' @name cut_var
2025-01-16 11:24:26 +01:00
#' @param x an object inheriting from class "POSIXct"
2025-04-11 13:23:18 +02:00
cut_var.POSIXct <- cut_var.POSIXt
2024-12-18 10:37:37 +01:00
2025-04-11 13:23:18 +02:00
#' @name cut_var
2025-01-16 11:24:26 +01:00
#' @param x an object inheriting from class "POSIXct"
#'
#' @examples
2025-04-11 13:23:18 +02:00
#' as.Date(c("1992-02-01 01:00:20", "1992-02-06 03:00:20", "1992-05-01 01:20:20", "1992-09-01 08:20:20", "1999-02-01 21:20:20", "1992-12-01 03:02:20")) |> cut_var(2)
#' as.Date(c("1992-02-01 01:00:20", "1992-02-06 03:00:20", "1992-05-01 01:20:20", "1992-09-01 08:20:20", "1999-02-01 21:20:20", "1992-12-01 03:02:20")) |> cut_var(breaks = "weekday")
2025-04-24 11:00:56 +02:00
#' as.Date(c("1992-02-01 01:00:20", "1992-02-06 03:00:20", "1992-05-01 01:20:20", "1992-09-01 08:20:20", "1999-02-01 21:20:20", "1992-12-01 03:02:20")) |> cut_var(format = "%W")
cut_var.Date <- function ( x , breaks = NULL , start.on.monday = TRUE , ... ) {
args <- list ( ... )
2025-04-15 08:55:35 +02:00
if ( " format" %in% names ( args ) ) {
assertthat :: assert_that ( is.character ( args $ format ) )
out <- forcats :: as_factor ( format ( x , format = args $ format ) )
} else if ( identical ( breaks , " weekday" ) ) {
ds <- as.Date ( 1 : 7 ) | >
( \ ( .x ) {
sort_by ( format ( .x , " %A" ) , as.numeric ( format ( .x , " %w" ) ) )
} ) ( )
if ( start.on.monday ) {
ds <- ds [c ( 7 , 1 : 6 ) ]
2024-12-18 10:37:37 +01:00
}
2025-04-15 08:55:35 +02:00
out <- factor ( weekdays ( x ) , levels = ds ) | > forcats :: fct_drop ( )
2025-04-10 15:46:42 +02:00
} else if ( identical ( breaks , " month_only" ) ) {
ms <- paste0 ( " 1970-" , 1 : 12 , " -01" ) | >
as.Date ( ) | >
months ( )
2024-12-19 11:34:25 +01:00
2025-04-10 15:46:42 +02:00
out <- factor ( months ( x ) , levels = ms ) | > forcats :: fct_drop ( )
2024-12-18 10:37:37 +01:00
} else {
## Doesn't really work very well for breaks other than the special character cases as right border is excluded
2025-04-10 15:46:42 +02:00
out <- base :: cut.Date ( x , breaks = breaks , ... ) | > forcats :: fct_drop ( )
2024-12-18 10:37:37 +01:00
# browser()
}
out
}
2025-01-16 11:24:26 +01:00
#' Test class
#'
#' @param data data
#' @param class.vec vector of class names to test
#'
#' @return factor
#' @export
#'
#' @examples
#' \dontrun{
#' vapply(REDCapCAST::redcapcast_data, \(.x){
#' is_any_class(.x, c("hms", "Date", "POSIXct", "POSIXt"))
#' }, logical(1))
#' }
2024-12-18 10:37:37 +01:00
is_any_class <- function ( data , class.vec ) {
any ( class ( data ) %in% class.vec )
}
2025-01-16 11:24:26 +01:00
#' Test is date/datetime/time
#'
#' @param data data
#'
#' @return factor
#' @export
#'
#' @examples
#' vapply(REDCapCAST::redcapcast_data, is_datetime, logical(1))
2024-12-18 10:37:37 +01:00
is_datetime <- function ( data ) {
is_any_class ( data , class.vec = c ( " hms" , " Date" , " POSIXct" , " POSIXt" ) )
}
2025-01-16 11:24:26 +01:00
#' @title Module to Convert Numeric to Factor
#'
#' @description
#' This module contain an interface to cut a numeric into several intervals.
#'
#'
#' @param id Module ID.
#'
#' @return A [shiny::reactive()] function returning the data.
#' @export
#'
#' @importFrom shiny NS fluidRow column numericInput checkboxInput checkboxInput plotOutput uiOutput
#' @importFrom shinyWidgets virtualSelectInput
#' @importFrom toastui datagridOutput2
#'
#' @name cut-variable
#'
2024-12-18 10:37:37 +01:00
cut_variable_ui <- function ( id ) {
ns <- NS ( id )
tagList (
shiny :: fluidRow (
column (
width = 3 ,
virtualSelectInput (
inputId = ns ( " variable" ) ,
label = i18n ( " Variable to cut:" ) ,
choices = NULL ,
width = " 100%"
)
) ,
column (
width = 3 ,
shiny :: uiOutput ( ns ( " cut_method" ) )
) ,
column (
width = 3 ,
numericInput (
inputId = ns ( " n_breaks" ) ,
label = i18n ( " Number of breaks:" ) ,
2024-12-18 15:46:02 +01:00
value = 3 ,
2024-12-18 10:37:37 +01:00
min = 2 ,
max = 12 ,
width = " 100%"
)
) ,
column (
width = 3 ,
checkboxInput (
inputId = ns ( " right" ) ,
label = i18n ( " Close intervals on the right" ) ,
value = TRUE
) ,
checkboxInput (
inputId = ns ( " include_lowest" ) ,
label = i18n ( " Include lowest value" ) ,
value = TRUE
)
)
) ,
conditionalPanel (
condition = " input.method == 'fixed'" ,
ns = ns ,
uiOutput ( outputId = ns ( " slider_fixed" ) )
) ,
plotOutput ( outputId = ns ( " plot" ) , width = " 100%" , height = " 270px" ) ,
datagridOutput2 ( outputId = ns ( " count" ) ) ,
actionButton (
inputId = ns ( " create" ) ,
label = tagList ( ph ( " scissors" ) , i18n ( " Create factor variable" ) ) ,
class = " btn-outline-primary float-end"
) ,
tags $ div ( class = " clearfix" )
)
}
2025-01-16 11:24:26 +01:00
#' @param data_r A [shiny::reactive()] function returning a `data.frame`.
#'
#' @export
#'
#' @importFrom shiny moduleServer observeEvent reactive req bindEvent renderPlot
#' @importFrom shinyWidgets updateVirtualSelect noUiSliderInput
#' @importFrom toastui renderDatagrid2 datagrid grid_colorbar
#' @importFrom rlang %||% call2 set_names expr syms
#' @importFrom classInt classIntervals
#'
#' @rdname cut-variable
2024-12-18 10:37:37 +01:00
cut_variable_server <- function ( id , data_r = reactive ( NULL ) ) {
moduleServer (
id ,
function ( input , output , session ) {
2025-04-11 13:23:18 +02:00
rv <- reactiveValues ( data = NULL , new_var_name = NULL )
2024-12-18 10:37:37 +01:00
bindEvent ( observe ( {
data <- data_r ( )
rv $ data <- data
vars_num <- vapply ( data , \ ( .x ) {
is.numeric ( .x ) || is_datetime ( .x )
} , logical ( 1 ) )
vars_num <- names ( vars_num ) [vars_num ]
updateVirtualSelect (
inputId = " variable" ,
choices = vars_num ,
selected = if ( isTruthy ( input $ variable ) ) input $ variable else vars_num [1 ]
)
} ) , data_r ( ) , input $ hidden )
output $ slider_fixed <- renderUI ( {
data <- req ( data_r ( ) )
variable <- req ( input $ variable )
req ( hasName ( data , variable ) )
if ( is_datetime ( data [ [variable ] ] ) ) {
2025-04-11 13:23:18 +02:00
brks <- cut_var ( data [ [variable ] ] ,
2024-12-18 10:37:37 +01:00
breaks = input $ n_breaks
) $ brks
} else {
brks <- classInt :: classIntervals (
var = data [ [variable ] ] ,
n = input $ n_breaks ,
style = " quantile"
) $ brks
}
if ( is_datetime ( data [ [variable ] ] ) ) {
lower <- min ( data [ [variable ] ] , na.rm = TRUE )
} else {
lower <- floor ( min ( data [ [variable ] ] , na.rm = TRUE ) )
}
if ( is_datetime ( data [ [variable ] ] ) ) {
upper <- max ( data [ [variable ] ] , na.rm = TRUE )
} else {
upper <- ceiling ( max ( data [ [variable ] ] , na.rm = TRUE ) )
}
noUiSliderInput (
inputId = session $ ns ( " fixed_brks" ) ,
label = i18n ( " Fixed breaks:" ) ,
min = lower ,
max = upper ,
value = brks ,
color = datamods ::: get_primary_color ( ) ,
width = " 100%"
)
} )
output $ cut_method <- renderUI ( {
data <- req ( data_r ( ) )
variable <- req ( input $ variable )
choices <- c (
2025-04-24 11:00:56 +02:00
# "fixed",
2024-12-18 10:37:37 +01:00
# "quantile"
2025-04-10 15:46:42 +02:00
)
2024-12-18 10:37:37 +01:00
2025-04-24 11:00:56 +02:00
if ( any ( c ( " hms" , " POSIXct" ) %in% class ( data [ [variable ] ] ) ) ) {
2024-12-18 10:37:37 +01:00
choices <- c ( choices , " hour" )
2025-04-10 15:46:42 +02:00
} else if ( any ( c ( " POSIXt" , " Date" ) %in% class ( data [ [variable ] ] ) ) ) {
2024-12-18 10:37:37 +01:00
choices <- c (
choices ,
" day" ,
" weekday" ,
" week" ,
2025-04-24 11:00:56 +02:00
# "week_only",
2024-12-18 10:37:37 +01:00
" month" ,
2024-12-19 11:34:25 +01:00
" month_only" ,
2024-12-18 10:37:37 +01:00
" quarter" ,
" year"
)
} else {
choices <- c (
choices ,
" fixed" ,
" quantile" ,
# "sd",
# "equal",
# "pretty",
# "kmeans",
# "hclust",
# "bclust",
# "fisher",
# "jenks",
" headtails" # ,
# "maximum",
# "box"
)
}
2025-04-24 11:00:56 +02:00
choices <- unique ( choices )
2024-12-18 10:37:37 +01:00
shinyWidgets :: virtualSelectInput (
inputId = session $ ns ( " method" ) ,
label = i18n ( " Method:" ) ,
choices = choices ,
2024-12-19 11:34:25 +01:00
selected = NULL ,
2024-12-18 10:37:37 +01:00
width = " 100%"
)
} )
breaks_r <- reactive ( {
data <- req ( data_r ( ) )
variable <- req ( input $ variable )
req ( hasName ( data , variable ) )
req ( input $ n_breaks , input $ method )
if ( input $ method == " fixed" ) {
req ( input $ fixed_brks )
2025-04-24 11:00:56 +02:00
if ( any ( c ( " hms" , " POSIXct" ) %in% class ( data [ [variable ] ] ) ) ) {
2025-04-11 13:23:18 +02:00
# cut.POSIXct <- cut.POSIXt
f <- cut_var ( data [ [variable ] ] , breaks = input $ fixed_brks )
2024-12-18 10:37:37 +01:00
list ( var = f , brks = levels ( f ) )
} else {
classInt :: classIntervals (
var = as.numeric ( data [ [variable ] ] ) ,
n = input $ n_breaks ,
style = " fixed" ,
fixedBreaks = input $ fixed_brks
)
}
} else if ( input $ method == " quantile" ) {
req ( input $ fixed_brks )
if ( any ( c ( " hms" , " POSIXt" ) %in% class ( data [ [variable ] ] ) ) ) {
2025-04-11 13:23:18 +02:00
# cut.POSIXct <- cut.POSIXt
f <- cut_var ( data [ [variable ] ] , breaks = input $ n_breaks )
2024-12-18 10:37:37 +01:00
list ( var = f , brks = levels ( f ) )
} else {
classInt :: classIntervals (
var = as.numeric ( data [ [variable ] ] ) ,
n = input $ n_breaks ,
style = " quantile"
)
}
} else if ( input $ method %in% c (
" day" ,
" weekday" ,
" week" ,
" month" ,
2024-12-19 11:34:25 +01:00
" month_only" ,
2024-12-18 10:37:37 +01:00
" quarter" ,
" year"
) ) {
# To enable datetime cutting
2025-04-11 13:23:18 +02:00
# cut.POSIXct <- cut.POSIXt
f <- cut_var ( data [ [variable ] ] , breaks = input $ method )
2024-12-18 10:37:37 +01:00
list ( var = f , brks = levels ( f ) )
} else if ( input $ method %in% c ( " hour" ) ) {
# To enable datetime cutting
2025-04-11 13:23:18 +02:00
# cut.POSIXct <- cut.POSIXt
f <- cut_var ( data [ [variable ] ] , breaks = " hour" )
2024-12-18 10:37:37 +01:00
list ( var = f , brks = levels ( f ) )
2025-04-24 11:00:56 +02:00
# } else if (input$method %in% c("week_only")) {
# # As a proof of concept a single option to use "format" parameter
# # https://www.stat.berkeley.edu/~s133/dates.html
# f <- cut_var(data[[variable]], format = "%W")
# list(var = f, brks = levels(f))
2024-12-18 10:37:37 +01:00
} else {
classInt :: classIntervals (
var = as.numeric ( data [ [variable ] ] ) ,
n = input $ n_breaks ,
style = input $ method
)
}
} )
output $ plot <- renderPlot ( {
data <- req ( data_r ( ) )
variable <- req ( input $ variable )
plot_histogram ( data , variable , breaks = breaks_r ( ) $ brks , color = datamods ::: get_primary_color ( ) )
2025-04-24 11:00:56 +02:00
# plot_histogram(data = breaks_r()$var, breaks = breaks_r()$brks, color = datamods:::get_primary_color())
2024-12-18 10:37:37 +01:00
} )
data_cutted_r <- reactive ( {
2025-04-11 13:23:18 +02:00
req ( input $ method )
2024-12-18 10:37:37 +01:00
data <- req ( data_r ( ) )
variable <- req ( input $ variable )
2025-04-10 15:46:42 +02:00
2025-04-11 13:23:18 +02:00
if ( input $ method %in% c ( " day" , " weekday" , " week" , " month" , " month_only" , " quarter" , " year" , " hour" ) ) {
breaks <- input $ method
} else {
breaks <- breaks_r ( ) $ brks
}
parameters <- list (
2024-12-18 10:37:37 +01:00
x = data [ [variable ] ] ,
2025-04-11 13:23:18 +02:00
breaks = breaks ,
2024-12-18 10:37:37 +01:00
include.lowest = input $ include_lowest ,
right = input $ right
2025-04-11 13:23:18 +02:00
)
2025-04-10 15:46:42 +02:00
2025-04-11 13:23:18 +02:00
new_variable <- tryCatch (
{
rlang :: exec ( cut_var , ! ! ! parameters )
} ,
error = function ( err ) {
showNotification ( paste0 ( " We encountered the following error creating your report: " , err ) , type = " err" )
}
2024-12-18 10:37:37 +01:00
)
2025-04-11 13:23:18 +02:00
# new_variable <- do.call(
# cut,
# parameters
# )
data <- append_column ( data , column = new_variable , name = paste0 ( variable , " _cut" ) , index = " right" )
# setNames(paste0(variable, "_cut"))
#
# data <- dplyr::bind_cols(data, new_variable, .name_repair = "unique_quiet")
# rv$new_var_name <- names(data)[length(data)]
# browser()
# browser()
code <- rlang :: call2 (
" append_column" ,
! ! ! list (
column = rlang :: call2 ( " cut_var" ,
! ! ! modifyList ( parameters , list ( x = as.symbol ( paste0 ( " data$" , variable ) ) ) ) ,
.ns = " FreesearchR" ) ,
name = paste0 ( variable , " _cut" ) , index = " right"
) ,
.ns = " FreesearchR"
2024-12-18 10:37:37 +01:00
)
2025-04-11 13:23:18 +02:00
attr ( data , " code" ) <- code
# attr(data, "code") <- Reduce(
# f = function(x, y) expr(!!x %>% !!y),
# x = c(attr(data, "code"), code)
# )
2024-12-18 10:37:37 +01:00
data
} )
output $ count <- renderDatagrid2 ( {
2025-04-11 13:23:18 +02:00
# shiny::req(rv$new_var_name)
2024-12-18 10:37:37 +01:00
data <- req ( data_cutted_r ( ) )
2025-04-11 13:23:18 +02:00
# variable <- req(input$variable)
2024-12-18 10:37:37 +01:00
count_data <- as.data.frame (
table (
2025-04-11 13:23:18 +02:00
breaks = data [ [length ( data ) ] ] ,
2024-12-18 10:37:37 +01:00
useNA = " ifany"
) ,
responseName = " count"
)
gridTheme <- getOption ( " datagrid.theme" )
if ( length ( gridTheme ) < 1 ) {
datamods ::: apply_grid_theme ( )
}
on.exit ( toastui :: reset_grid_theme ( ) )
grid <- datagrid (
data = count_data ,
colwidths = " guess" ,
theme = " default" ,
bodyHeight = " auto"
)
grid <- toastui :: grid_columns ( grid , className = " font-monospace" )
grid_colorbar (
grid ,
column = " count" ,
label_outside = TRUE ,
label_width = " 40px" ,
bar_bg = datamods ::: get_primary_color ( ) ,
from = c ( 0 , max ( count_data $ count ) + 1 )
)
} )
data_returned_r <- observeEvent ( input $ create , {
rv $ data <- data_cutted_r ( )
} )
return ( reactive ( rv $ data ) )
}
)
}
2025-01-16 11:24:26 +01:00
#' @inheritParams shiny::modalDialog
#' @export
#'
#' @importFrom shiny showModal modalDialog textInput
#' @importFrom htmltools tagList
#'
#' @rdname cut-variable
2024-12-18 10:37:37 +01:00
modal_cut_variable <- function ( id ,
title = i18n ( " Convert Numeric to Factor" ) ,
easyClose = TRUE ,
size = " l" ,
footer = NULL ) {
ns <- NS ( id )
showModal ( modalDialog (
title = tagList ( title , datamods ::: button_close_modal ( ) ) ,
cut_variable_ui ( id ) ,
tags $ div (
style = " display: none;" ,
textInput ( inputId = ns ( " hidden" ) , label = NULL , value = datamods ::: genId ( ) )
) ,
easyClose = easyClose ,
size = size ,
footer = footer
) )
}
2025-02-25 09:51:42 +01:00
#' @importFrom graphics abline axis hist par plot.new plot.window
2025-04-24 11:00:56 +02:00
plot_histogram <- function ( data , column = NULL , bins = 30 , breaks = NULL , color = " #112466" ) {
if ( is.vector ( data ) ) {
x <- data
} else {
2025-02-25 09:51:42 +01:00
x <- data [ [column ] ]
2025-04-24 11:00:56 +02:00
}
2025-02-25 09:51:42 +01:00
x <- as.numeric ( x )
op <- par ( mar = rep ( 1.5 , 4 ) )
on.exit ( par ( op ) )
plot.new ( )
plot.window ( xlim = range ( pretty ( x ) ) , ylim = range ( pretty ( hist ( x , breaks = bins , plot = FALSE ) $ counts ) ) )
abline ( v = pretty ( x ) , col = " #D8D8D8" )
abline ( h = pretty ( hist ( x , breaks = bins , plot = FALSE ) $ counts ) , col = " #D8D8D8" )
hist ( x , breaks = bins , xlim = range ( pretty ( x ) ) , xaxs = " i" , yaxs = " i" , col = color , add = TRUE )
axis ( side = 1 , at = pretty ( x ) , pos = 0 )
axis ( side = 2 , at = pretty ( hist ( x , breaks = bins , plot = FALSE ) $ counts ) , pos = min ( pretty ( x ) ) )
abline ( v = breaks , col = " #FFFFFF" , lty = 1 , lwd = 1.5 )
abline ( v = breaks , col = " #2E2E2E" , lty = 2 , lwd = 1.5 )
}
2025-04-15 16:14:03 +02:00
2025-02-25 09:51:42 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//data_plots.R
2025-02-25 09:51:42 +01:00
########
# source(here::here("functions.R"))
#' Data correlations evaluation module
#'
#' @param id Module id. (Use 'ns("id")')
#'
2025-03-13 12:41:50 +01:00
#' @name data-plots
2025-02-25 09:51:42 +01:00
#' @returns Shiny ui module
#' @export
#'
2025-03-05 21:13:06 +01:00
data_visuals_ui <- function ( id , tab_title = " Plots" , ... ) {
2025-02-25 09:51:42 +01:00
ns <- shiny :: NS ( id )
# bslib::navset_bar(
list (
# Sidebar with a slider input
sidebar = bslib :: sidebar (
bslib :: accordion (
multiple = FALSE ,
bslib :: accordion_panel (
title = " Creating plot" ,
icon = bsicons :: bs_icon ( " graph-up" ) ,
shiny :: uiOutput ( outputId = ns ( " primary" ) ) ,
2025-04-11 13:23:18 +02:00
shiny :: helpText ( ' Only non-text variables are available for plotting. Go the "Data" to reclass data to plot.' ) ,
2025-04-14 10:10:33 +02:00
shiny :: tags $ br ( ) ,
2025-02-25 09:51:42 +01:00
shiny :: uiOutput ( outputId = ns ( " type" ) ) ,
shiny :: uiOutput ( outputId = ns ( " secondary" ) ) ,
2025-03-13 12:41:50 +01:00
shiny :: uiOutput ( outputId = ns ( " tertiary" ) ) ,
shiny :: br ( ) ,
shiny :: actionButton (
inputId = ns ( " act_plot" ) ,
label = " Plot" ,
width = " 100%" ,
icon = shiny :: icon ( " palette" ) ,
disabled = FALSE
) ,
shiny :: helpText ( ' Adjust settings, then press "Plot".' )
2025-03-05 21:13:06 +01:00
) ,
2025-03-13 12:41:50 +01:00
# bslib::accordion_panel(
# title = "Advanced",
# icon = bsicons::bs_icon("gear")
# ),
2025-02-25 09:51:42 +01:00
bslib :: accordion_panel (
title = " Download" ,
icon = bsicons :: bs_icon ( " download" ) ,
shinyWidgets :: noUiSliderInput (
2025-05-05 14:45:07 +02:00
inputId = ns ( " height_slide" ) ,
2025-02-25 09:51:42 +01:00
label = " Plot height (mm)" ,
min = 50 ,
max = 300 ,
value = 100 ,
step = 1 ,
2025-03-05 21:13:06 +01:00
format = shinyWidgets :: wNumbFormat ( decimals = 0 ) ,
2025-05-05 14:45:07 +02:00
color = datamods ::: get_primary_color ( ) ,
inline = TRUE
2025-02-25 09:51:42 +01:00
) ,
2025-05-05 14:45:07 +02:00
# shiny::numericInput(
# inputId = ns("height_numeric"),
# label = "Plot height (mm)",
# min = 50,
# max = 300,
# value = 100
# ),
2025-02-25 09:51:42 +01:00
shinyWidgets :: noUiSliderInput (
inputId = ns ( " width" ) ,
label = " Plot width (mm)" ,
min = 50 ,
max = 300 ,
value = 100 ,
step = 1 ,
2025-03-05 21:13:06 +01:00
format = shinyWidgets :: wNumbFormat ( decimals = 0 ) ,
2025-02-25 09:51:42 +01:00
color = datamods ::: get_primary_color ( )
) ,
shiny :: selectInput (
inputId = ns ( " plot_type" ) ,
label = " File format" ,
choices = list (
" png" ,
" tiff" ,
" eps" ,
" pdf" ,
" jpeg" ,
" svg"
)
) ,
shiny :: br ( ) ,
# Button
shiny :: downloadButton (
outputId = ns ( " download_plot" ) ,
label = " Download plot" ,
icon = shiny :: icon ( " download" )
)
)
)
) ,
bslib :: nav_panel (
title = tab_title ,
2025-04-15 16:14:03 +02:00
shiny :: plotOutput ( ns ( " plot" ) , height = " 70vh" ) ,
2025-04-09 12:31:08 +02:00
shiny :: tags $ br ( ) ,
2025-04-11 13:23:18 +02:00
shiny :: tags $ br ( ) ,
shiny :: htmlOutput ( outputId = ns ( " code_plot" ) )
2025-02-25 09:51:42 +01:00
)
)
}
#'
#' @param data data
#' @param ... ignored
#'
2025-03-13 12:41:50 +01:00
#' @name data-plots
2025-02-25 09:51:42 +01:00
#' @returns shiny server module
#' @export
data_visuals_server <- function ( id ,
data ,
... ) {
shiny :: moduleServer (
id = id ,
module = function ( input , output , session ) {
ns <- session $ ns
rv <- shiny :: reactiveValues (
plot.params = NULL ,
2025-04-09 12:31:08 +02:00
plot = NULL ,
2025-04-15 16:14:03 +02:00
code = NULL
2025-02-25 09:51:42 +01:00
)
2025-03-19 13:10:56 +01:00
# ## --- New attempt
#
# rv$plot.params <- shiny::reactive({
# get_plot_options(input$type) |> purrr::pluck(1)
# })
#
# c(output,
# list(shiny::renderUI({
# columnSelectInput(
# inputId = ns("primary"),
# data = data,
# placeholder = "Select variable",
# label = "Response variable",
# multiple = FALSE
# )
# }),
# shiny::renderUI({
# shiny::req(input$primary)
# # browser()
#
# if (!input$primary %in% names(data())) {
# plot_data <- data()[1]
# } else {
# plot_data <- data()[input$primary]
# }
#
# plots <- possible_plots(
# data = plot_data
# )
#
# plots_named <- get_plot_options(plots) |>
# lapply(\(.x){
# stats::setNames(.x$descr, .x$note)
# })
#
# vectorSelectInput(
# inputId = ns("type"),
# selected = NULL,
# label = shiny::h4("Plot type"),
# choices = Reduce(c, plots_named),
# multiple = FALSE
# )
# }),
# shiny::renderUI({
# shiny::req(input$type)
#
# cols <- c(
# rv$plot.params()[["secondary.extra"]],
# all_but(
# colnames(subset_types(
# data(),
# rv$plot.params()[["secondary.type"]]
# )),
# input$primary
# )
# )
#
# columnSelectInput(
# inputId = ns("secondary"),
# data = data,
# selected = cols[1],
# placeholder = "Please select",
# label = if (isTRUE(rv$plot.params()[["secondary.multi"]])) "Additional variables" else "Secondary variable",
# multiple = rv$plot.params()[["secondary.multi"]],
# maxItems = rv$plot.params()[["secondary.max"]],
# col_subset = cols,
# none_label = "No variable"
# )
# }),
# shiny::renderUI({
# shiny::req(input$type)
# columnSelectInput(
# inputId = ns("tertiary"),
# data = data,
# placeholder = "Please select",
# label = "Grouping variable",
# multiple = FALSE,
# col_subset = c(
# "none",
# all_but(
# colnames(subset_types(
# data(),
# rv$plot.params()[["tertiary.type"]]
# )),
# input$primary,
# input$secondary
# )
# ),
# none_label = "No stratification"
# )
# })
# )|> setNames(c("primary","type","secondary","tertiary")),keep.null = TRUE)
2025-04-11 13:23:18 +02:00
2025-02-25 09:51:42 +01:00
output $ primary <- shiny :: renderUI ( {
2025-04-11 13:23:18 +02:00
shiny :: req ( data ( ) )
2025-02-25 09:51:42 +01:00
columnSelectInput (
inputId = ns ( " primary" ) ,
2025-04-15 16:14:03 +02:00
col_subset = names ( data ( ) ) [sapply ( data ( ) , data_type ) != " text" ] ,
2025-02-25 09:51:42 +01:00
data = data ,
placeholder = " Select variable" ,
label = " Response variable" ,
multiple = FALSE
)
} )
2025-04-11 13:23:18 +02:00
# shiny::observeEvent(data, {
# if (is.null(data()) | NROW(data()) == 0) {
# shiny::updateActionButton(inputId = ns("act_plot"), disabled = TRUE)
# } else {
# shiny::updateActionButton(inputId = ns("act_plot"), disabled = FALSE)
# }
# })
2025-02-25 09:51:42 +01:00
output $ type <- shiny :: renderUI ( {
shiny :: req ( input $ primary )
2025-04-11 13:23:18 +02:00
shiny :: req ( data ( ) )
2025-02-25 09:51:42 +01:00
# browser()
if ( ! input $ primary %in% names ( data ( ) ) ) {
plot_data <- data ( ) [1 ]
} else {
plot_data <- data ( ) [input $ primary ]
}
plots <- possible_plots (
data = plot_data
)
2025-03-12 18:27:46 +01:00
plots_named <- get_plot_options ( plots ) | >
lapply ( \ ( .x ) {
2025-03-13 12:41:50 +01:00
stats :: setNames ( .x $ descr , .x $ note )
2025-03-12 18:27:46 +01:00
} )
vectorSelectInput (
2025-02-25 09:51:42 +01:00
inputId = ns ( " type" ) ,
selected = NULL ,
label = shiny :: h4 ( " Plot type" ) ,
2025-03-13 12:41:50 +01:00
choices = Reduce ( c , plots_named ) ,
2025-02-25 09:51:42 +01:00
multiple = FALSE
)
} )
rv $ plot.params <- shiny :: reactive ( {
2025-03-12 18:27:46 +01:00
get_plot_options ( input $ type ) | > purrr :: pluck ( 1 )
2025-02-25 09:51:42 +01:00
} )
output $ secondary <- shiny :: renderUI ( {
shiny :: req ( input $ type )
2025-03-12 18:27:46 +01:00
cols <- c (
rv $ plot.params ( ) [ [ " secondary.extra" ] ] ,
all_but (
colnames ( subset_types (
data ( ) ,
rv $ plot.params ( ) [ [ " secondary.type" ] ]
) ) ,
input $ primary
)
)
2025-02-25 09:51:42 +01:00
columnSelectInput (
inputId = ns ( " secondary" ) ,
data = data ,
2025-03-13 12:41:50 +01:00
selected = cols [1 ] ,
placeholder = " Please select" ,
label = if ( isTRUE ( rv $ plot.params ( ) [ [ " secondary.multi" ] ] ) ) " Additional variables" else " Secondary variable" ,
2025-03-12 18:27:46 +01:00
multiple = rv $ plot.params ( ) [ [ " secondary.multi" ] ] ,
maxItems = rv $ plot.params ( ) [ [ " secondary.max" ] ] ,
col_subset = cols ,
2025-02-25 09:51:42 +01:00
none_label = " No variable"
)
} )
output $ tertiary <- shiny :: renderUI ( {
shiny :: req ( input $ type )
columnSelectInput (
inputId = ns ( " tertiary" ) ,
data = data ,
2025-03-13 12:41:50 +01:00
placeholder = " Please select" ,
label = " Grouping variable" ,
2025-02-25 09:51:42 +01:00
multiple = FALSE ,
col_subset = c (
" none" ,
all_but (
colnames ( subset_types (
data ( ) ,
2025-03-12 18:27:46 +01:00
rv $ plot.params ( ) [ [ " tertiary.type" ] ]
2025-02-25 09:51:42 +01:00
) ) ,
input $ primary ,
input $ secondary
)
) ,
none_label = " No stratification"
)
} )
2025-03-13 12:41:50 +01:00
shiny :: observeEvent ( input $ act_plot ,
{
2025-04-15 16:14:03 +02:00
if ( NROW ( data ( ) ) > 0 ) {
tryCatch (
{
parameters <- list (
type = rv $ plot.params ( ) [ [ " fun" ] ] ,
pri = input $ primary ,
sec = input $ secondary ,
ter = input $ tertiary
)
shiny :: withProgress ( message = " Drawing the plot. Hold tight for a moment.." , {
rv $ plot <- rlang :: exec ( create_plot , ! ! ! append_list ( data ( ) , parameters , " data" ) )
} )
rv $ code <- glue :: glue ( " FreesearchR::create_plot(data,{list2str(parameters)})" )
} ,
# warning = function(warn) {
# showNotification(paste0(warn), type = "warning")
# },
error = function ( err ) {
showNotification ( paste0 ( err ) , type = " err" )
}
)
}
2025-03-13 12:41:50 +01:00
} ,
ignoreInit = TRUE
)
2025-02-25 09:51:42 +01:00
2025-04-11 13:23:18 +02:00
output $ code_plot <- shiny :: renderUI ( {
shiny :: req ( rv $ code )
prismCodeBlock ( paste0 ( " #Plotting\n" , rv $ code ) )
2025-04-09 12:31:08 +02:00
} )
2025-04-30 10:02:29 +02:00
shiny :: observeEvent (
list (
data ( )
) ,
{
shiny :: req ( data ( ) )
rv $ plot <- NULL
}
)
2025-02-25 09:51:42 +01:00
output $ plot <- shiny :: renderPlot ( {
2025-04-30 10:02:29 +02:00
# shiny::req(rv$plot)
# rv$plot
if ( ! is.null ( rv $ plot ) ) {
rv $ plot
} else {
return ( NULL )
}
2025-02-25 09:51:42 +01:00
} )
2025-05-05 14:45:07 +02:00
# shiny::observeEvent(input$height_numeric, {
# shinyWidgets::updateNoUiSliderInput(session, ns("height_slide"), value = input$height_numeric)
# }, ignoreInit = TRUE)
# shiny::observeEvent(input$height_slide, {
# shiny::updateNumericInput(session, ns("height_numeric"), value = input$height_slide)
# }, ignoreInit = TRUE)
2025-02-25 09:51:42 +01:00
output $ download_plot <- shiny :: downloadHandler (
filename = shiny :: reactive ( {
paste0 ( " plot." , input $ plot_type )
} ) ,
content = function ( file ) {
2025-05-05 14:45:07 +02:00
if ( inherits ( rv $ plot , " patchwork" ) ) {
plot <- rv $ plot
2025-05-08 10:12:49 +02:00
} else if ( inherits ( rv $ plot , " ggplot" ) ) {
plot <- rv $ plot
} else {
2025-05-05 14:45:07 +02:00
plot <- rv $ plot [ [1 ] ]
}
# browser()
2025-02-25 09:51:42 +01:00
shiny :: withProgress ( message = " Drawing the plot. Hold on for a moment.." , {
2025-03-05 21:13:06 +01:00
ggplot2 :: ggsave (
filename = file ,
2025-05-05 14:45:07 +02:00
plot = plot ,
2025-03-05 21:13:06 +01:00
width = input $ width ,
2025-05-05 14:45:07 +02:00
height = input $ height_slide ,
2025-03-05 21:13:06 +01:00
dpi = 300 ,
units = " mm" , scale = 2
)
2025-02-25 09:51:42 +01:00
} )
}
)
shiny :: observe (
return ( rv $ plot )
)
}
)
}
#' Select all from vector but
#'
#' @param data vector
#' @param ... exclude
#'
2025-03-05 21:13:06 +01:00
#' @returns vector
2025-02-25 09:51:42 +01:00
#' @export
#'
#' @examples
#' all_but(1:10, c(2, 3), 11, 5)
all_but <- function ( data , ... ) {
data [ ! data %in% c ( ... ) ]
}
#' Easily subset by data type function
#'
#' @param data data
#' @param types desired types
#' @param type.fun function to get type. Default is outcome_type
#'
2025-03-05 21:13:06 +01:00
#' @returns vector
2025-02-25 09:51:42 +01:00
#' @export
#'
#' @examples
#' default_parsing(mtcars) |> subset_types("ordinal")
2025-04-22 10:02:12 +02:00
#' default_parsing(mtcars) |> subset_types(c("dichotomous", "categorical"))
2025-02-25 09:51:42 +01:00
#' #' default_parsing(mtcars) |> subset_types("factor",class)
2025-03-20 11:46:02 +01:00
subset_types <- function ( data , types , type.fun = data_type ) {
2025-02-25 09:51:42 +01:00
data [sapply ( data , type.fun ) %in% types ]
}
#' Implemented functions
#'
#' @description
#' Library of supported functions. The list name and "descr" element should be
#' unique for each element on list.
#'
#' - descr: Plot description
#'
#' - primary.type: Primary variable data type (continuous, dichotomous or ordinal)
#'
#' - secondary.type: Secondary variable data type (continuous, dichotomous or ordinal)
#'
#' - secondary.extra: "none" or NULL to have option to choose none.
#'
#' - tertiary.type: Tertiary variable data type (continuous, dichotomous or ordinal)
#'
#'
#' @returns list
#' @export
#'
#' @examples
#' supported_plots() |> str()
supported_plots <- function ( ) {
list (
plot_hbars = list (
2025-03-12 18:27:46 +01:00
fun = " plot_hbars" ,
2025-03-05 21:13:06 +01:00
descr = " Stacked horizontal bars" ,
note = " A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" ,
2025-04-22 10:02:12 +02:00
primary.type = c ( " dichotomous" , " categorical" ) ,
secondary.type = c ( " dichotomous" , " categorical" ) ,
2025-03-12 18:27:46 +01:00
secondary.multi = FALSE ,
2025-04-22 10:02:12 +02:00
tertiary.type = c ( " dichotomous" , " categorical" ) ,
2025-02-25 09:51:42 +01:00
secondary.extra = " none"
) ,
plot_violin = list (
2025-03-12 18:27:46 +01:00
fun = " plot_violin" ,
2025-02-25 09:51:42 +01:00
descr = " Violin plot" ,
2025-03-05 21:13:06 +01:00
note = " A modern alternative to the classic boxplot to visualise data distribution" ,
2025-04-22 10:02:12 +02:00
primary.type = c ( " datatime" , " continuous" , " dichotomous" , " categorical" ) ,
secondary.type = c ( " dichotomous" , " categorical" ) ,
2025-03-12 18:27:46 +01:00
secondary.multi = FALSE ,
secondary.extra = " none" ,
2025-04-22 10:02:12 +02:00
tertiary.type = c ( " dichotomous" , " categorical" )
2025-02-25 09:51:42 +01:00
) ,
2025-03-11 13:42:57 +01:00
# plot_ridge = list(
# descr = "Ridge plot",
# note = "An alternative option to visualise data distribution",
# primary.type = "continuous",
2025-04-22 10:02:12 +02:00
# secondary.type = c("dichotomous" ,"categorical"),
# tertiary.type = c("dichotomous" ,"categorical"),
2025-03-11 13:42:57 +01:00
# secondary.extra = NULL
# ),
2025-03-05 21:13:06 +01:00
plot_sankey = list (
2025-03-12 18:27:46 +01:00
fun = " plot_sankey" ,
2025-03-05 21:13:06 +01:00
descr = " Sankey plot" ,
note = " A way of visualising change between groups" ,
2025-04-22 10:02:12 +02:00
primary.type = c ( " dichotomous" , " categorical" ) ,
secondary.type = c ( " dichotomous" , " categorical" ) ,
2025-03-12 18:27:46 +01:00
secondary.multi = FALSE ,
secondary.extra = NULL ,
2025-04-22 10:02:12 +02:00
tertiary.type = c ( " dichotomous" , " categorical" )
2025-03-05 21:13:06 +01:00
) ,
2025-02-25 09:51:42 +01:00
plot_scatter = list (
2025-03-12 18:27:46 +01:00
fun = " plot_scatter" ,
2025-02-25 09:51:42 +01:00
descr = " Scatter plot" ,
2025-03-05 21:13:06 +01:00
note = " A classic way of showing the association between to variables" ,
2025-04-15 16:14:03 +02:00
primary.type = c ( " datatime" , " continuous" ) ,
2025-04-22 10:02:12 +02:00
secondary.type = c ( " datatime" , " continuous" , " categorical" ) ,
2025-03-12 18:27:46 +01:00
secondary.multi = FALSE ,
2025-04-22 10:02:12 +02:00
tertiary.type = c ( " dichotomous" , " categorical" ) ,
2025-03-12 18:27:46 +01:00
secondary.extra = NULL
) ,
2025-03-19 13:10:56 +01:00
plot_box = list (
fun = " plot_box" ,
descr = " Box plot" ,
note = " A classic way to plot data distribution by groups" ,
2025-04-22 10:02:12 +02:00
primary.type = c ( " datatime" , " continuous" , " dichotomous" , " categorical" ) ,
secondary.type = c ( " dichotomous" , " categorical" ) ,
2025-03-19 13:10:56 +01:00
secondary.multi = FALSE ,
2025-04-22 10:02:12 +02:00
tertiary.type = c ( " dichotomous" , " categorical" ) ,
2025-03-19 13:10:56 +01:00
secondary.extra = " none"
) ,
2025-03-12 18:27:46 +01:00
plot_euler = list (
fun = " plot_euler" ,
descr = " Euler diagram" ,
note = " Generate area-proportional Euler diagrams to display set relationships" ,
2025-05-05 14:45:07 +02:00
primary.type = c ( " dichotomous" , " categorical" ) ,
secondary.type = c ( " dichotomous" , " categorical" ) ,
2025-03-12 18:27:46 +01:00
secondary.multi = TRUE ,
secondary.max = 4 ,
2025-04-22 10:02:12 +02:00
tertiary.type = c ( " dichotomous" , " categorical" ) ,
2025-02-25 09:51:42 +01:00
secondary.extra = NULL
)
)
}
#' Get possible regression models
#'
#' @param data data
#'
#' @returns character vector
#' @export
#'
#' @examples
#' mtcars |>
#' default_parsing() |>
#' dplyr::pull("cyl") |>
#' possible_plots()
#'
#' mtcars |>
#' default_parsing() |>
#' dplyr::select("mpg") |>
#' possible_plots()
possible_plots <- function ( data ) {
# browser()
2025-03-20 13:13:14 +01:00
# data <- if (is.reactive(data)) data() else data
2025-02-25 09:51:42 +01:00
if ( is.data.frame ( data ) ) {
data <- data [ [1 ] ]
}
2025-03-20 11:46:02 +01:00
type <- data_type ( data )
2025-02-25 09:51:42 +01:00
if ( type == " unknown" ) {
out <- type
} else {
out <- supported_plots ( ) | >
lapply ( \ ( .x ) {
if ( type %in% .x $ primary.type ) {
.x $ descr
}
} ) | >
unlist ( )
}
unname ( out )
}
#' Get the function options based on the selected function description
#'
#' @param data vector
#'
#' @returns list
#' @export
#'
#' @examples
#' ls <- mtcars |>
#' default_parsing() |>
#' dplyr::pull(mpg) |>
#' possible_plots() |>
#' (\(.x){
#' .x[[1]]
#' })() |>
#' get_plot_options()
get_plot_options <- function ( data ) {
descrs <- supported_plots ( ) | >
lapply ( \ ( .x ) {
.x $ descr
} ) | >
unlist ( )
supported_plots ( ) | >
( \ ( .x ) {
.x [match ( data , descrs ) ]
} ) ( )
}
#' Wrapper to create plot based on provided type
#'
2025-03-11 13:42:57 +01:00
#' @param data data.frame
2025-04-15 16:14:03 +02:00
#' @param pri primary variable
#' @param sec secondary variable
#' @param ter tertiary variable
2025-02-25 09:51:42 +01:00
#' @param type plot type (derived from possible_plots() and matches custom function)
#' @param ... ignored for now
#'
2025-03-05 21:13:06 +01:00
#' @name data-plots
#'
#' @returns ggplot2 object
2025-02-25 09:51:42 +01:00
#' @export
#'
#' @examples
2025-04-15 16:14:03 +02:00
#' create_plot(mtcars, "plot_violin", "mpg", "cyl") |> attributes()
create_plot <- function ( data , type , pri , sec , ter = NULL , ... ) {
if ( ! is.null ( sec ) ) {
if ( ! any ( sec %in% names ( data ) ) ) {
sec <- NULL
}
2025-02-25 09:51:42 +01:00
}
2025-04-15 16:14:03 +02:00
if ( ! is.null ( ter ) ) {
if ( ! ter %in% names ( data ) ) {
ter <- NULL
}
2025-02-25 09:51:42 +01:00
}
2025-04-15 16:14:03 +02:00
parameters <- list (
pri = pri ,
sec = sec ,
ter = ter ,
...
)
out <- do.call (
2025-02-25 09:51:42 +01:00
type ,
2025-04-15 16:14:03 +02:00
modifyList ( parameters , list ( data = data ) )
2025-02-25 09:51:42 +01:00
)
2025-04-15 16:14:03 +02:00
code <- rlang :: call2 ( type , ! ! ! parameters , .ns = " FreesearchR" )
attr ( out , " code" ) <- code
out
2025-02-25 09:51:42 +01:00
}
#' Print label, and if missing print variable name
#'
#' @param data vector or data frame
2025-03-11 13:42:57 +01:00
#' @param var variable name. Optional.
2025-02-25 09:51:42 +01:00
#'
#' @returns character string
#' @export
#'
#' @examples
#' mtcars |> get_label(var = "mpg")
2025-03-05 21:13:06 +01:00
#' mtcars |> get_label()
2025-02-25 09:51:42 +01:00
#' mtcars$mpg |> get_label()
#' gtsummary::trial |> get_label(var = "trt")
#' 1:10 |> get_label()
get_label <- function ( data , var = NULL ) {
2025-03-20 13:13:14 +01:00
# data <- if (is.reactive(data)) data() else data
2025-03-11 13:42:57 +01:00
if ( ! is.null ( var ) & is.data.frame ( data ) ) {
2025-02-25 09:51:42 +01:00
data <- data [ [var ] ]
}
out <- REDCapCAST :: get_attr ( data = data , attr = " label" )
if ( is.na ( out ) ) {
if ( is.null ( var ) ) {
out <- deparse ( substitute ( data ) )
} else {
2025-03-05 21:13:06 +01:00
if ( is.symbol ( var ) ) {
out <- gsub ( ' \"' , " " , deparse ( substitute ( var ) ) )
} else {
out <- var
}
2025-02-25 09:51:42 +01:00
}
}
out
}
2025-03-05 21:13:06 +01:00
#' Line breaking at given number of characters for nicely plotting labels
#'
2025-03-11 13:42:57 +01:00
#' @param data string
#' @param lineLength maximum line length
#' @param fixed flag to force split at exactly the value given in lineLength.
#' Default is FALSE, only splitting at spaces.
2025-03-05 21:13:06 +01:00
#'
2025-03-11 13:42:57 +01:00
#' @returns character string
2025-03-05 21:13:06 +01:00
#' @export
#'
#' @examples
2025-03-11 13:42:57 +01:00
#' "Lorem ipsum... you know the routine" |> line_break()
2025-04-15 16:14:03 +02:00
#' paste(sample(letters[1:10], 100, TRUE), collapse = "") |> line_break(force = TRUE)
line_break <- function ( data , lineLength = 20 , force = FALSE ) {
2025-03-11 13:42:57 +01:00
if ( isTRUE ( force ) ) {
gsub ( paste0 ( " (.{1," , lineLength , " })(\\s|[[:alnum:]])" ) , " \\1\n" , data )
} else {
paste ( strwrap ( data , lineLength ) , collapse = " \n" )
}
2025-03-05 21:13:06 +01:00
## https://stackoverflow.com/a/29847221
}
2025-03-19 13:10:56 +01:00
#' Wrapping
#'
#' @param data list of ggplot2 objects
#' @param tag_levels passed to patchwork::plot_annotation if given. Default is NULL
#'
#' @returns list of ggplot2 objects
#' @export
#'
wrap_plot_list <- function ( data , tag_levels = NULL ) {
2025-04-23 14:25:38 +02:00
if ( ggplot2 :: is_ggplot ( data [ [1 ] ] ) ) {
2025-03-19 13:10:56 +01:00
if ( length ( data ) > 1 ) {
out <- data | >
( \ ( .x ) {
if ( rlang :: is_named ( .x ) ) {
purrr :: imap ( .x , \ ( .y , .i ) {
.y + ggplot2 :: ggtitle ( .i )
} )
} else {
.x
}
} ) ( ) | >
2025-04-15 16:14:03 +02:00
align_axes ( ) | >
2025-03-19 13:10:56 +01:00
patchwork :: wrap_plots ( guides = " collect" , axes = " collect" , axis_titles = " collect" )
if ( ! is.null ( tag_levels ) ) {
out <- out + patchwork :: plot_annotation ( tag_levels = tag_levels )
}
} else {
out <- data
}
2025-03-13 12:41:50 +01:00
} else {
2025-03-19 13:10:56 +01:00
cli :: cli_abort ( " Can only wrap lists of {.cls ggplot} objects" )
2025-03-13 12:41:50 +01:00
}
out
}
2025-04-15 16:14:03 +02:00
#' Aligns axes between plots
2025-03-19 13:10:56 +01:00
#'
#' @param ... ggplot2 objects or list of ggplot2 objects
#'
#' @returns list of ggplot2 objects
#' @export
#'
2025-04-15 16:14:03 +02:00
align_axes <- function ( ... ) {
2025-03-13 12:41:50 +01:00
# https://stackoverflow.com/questions/62818776/get-axis-limits-from-ggplot-object
# https://github.com/thomasp85/patchwork/blob/main/R/plot_multipage.R#L150
2025-04-23 14:25:38 +02:00
if ( ggplot2 :: is_ggplot ( ..1 ) ) {
2025-04-15 16:14:03 +02:00
## Assumes list of ggplots
2025-03-13 12:41:50 +01:00
p <- list ( ... )
} else if ( is.list ( ..1 ) ) {
2025-04-15 16:14:03 +02:00
## Assumes list with list of ggplots
2025-03-13 12:41:50 +01:00
p <- ..1
} else {
cli :: cli_abort ( " Can only align {.cls ggplot} objects or a list of them" )
}
2025-03-19 13:10:56 +01:00
yr <- clean_common_axis ( p , " y" )
2025-03-13 12:41:50 +01:00
2025-03-19 13:10:56 +01:00
xr <- clean_common_axis ( p , " x" )
2025-03-13 12:41:50 +01:00
2025-03-20 11:46:02 +01:00
suppressWarnings ( {
p | > purrr :: map ( ~ .x + ggplot2 :: xlim ( xr ) + ggplot2 :: ylim ( yr ) )
2025-04-15 16:14:03 +02:00
} )
2025-03-13 12:41:50 +01:00
}
2025-03-19 13:10:56 +01:00
#' Extract and clean axis ranges
#'
#' @param p plot
#' @param axis axis. x or y.
#'
#' @returns vector
#' @export
#'
clean_common_axis <- function ( p , axis ) {
purrr :: map ( p , ~ ggplot2 :: layer_scales ( .x ) [ [axis ] ] $ get_limits ( ) ) | >
unlist ( ) | >
( \ ( .x ) {
if ( is.numeric ( .x ) ) {
range ( .x )
} else {
2025-03-20 11:46:02 +01:00
as.character ( .x )
2025-03-19 13:10:56 +01:00
}
} ) ( ) | >
unique ( )
}
2025-03-13 12:41:50 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//data-import.R
2025-03-13 12:41:50 +01:00
########
2025-03-17 15:00:13 +01:00
data_import_ui <- function ( id ) {
ns <- shiny :: NS ( id )
shiny :: fluidRow (
shiny :: column ( width = 2 ) ,
shiny :: column (
width = 8 ,
shiny :: h4 ( " Choose your data source" ) ,
shiny :: br ( ) ,
shinyWidgets :: radioGroupButtons (
inputId = " source" ,
selected = " env" ,
choices = c (
" File upload" = " file" ,
" REDCap server export" = " redcap" ,
" Local or sample data" = " env"
) ,
width = " 100%"
) ,
shiny :: helpText ( " Upload a file from your device, get data directly from REDCap or select a sample data set for testing from the app." ) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: conditionalPanel (
condition = " input.source=='file'" ,
import_file_ui (
id = ns ( " file_import" ) ,
layout_params = " dropdown" ,
title = " Choose a datafile to upload" ,
file_extensions = c ( " .csv" , " .tsv" , " .txt" , " .xls" , " .xlsx" , " .rds" , " .sas7bdat" , " .ods" , " .dta" )
)
) ,
shiny :: conditionalPanel (
condition = " input.source=='redcap'" ,
m_redcap_readUI ( id = ns ( " redcap_import" ) )
) ,
shiny :: conditionalPanel (
condition = " input.source=='env'" ,
import_globalenv_ui ( id = ns ( " env" ) , title = NULL )
) ,
shiny :: conditionalPanel (
condition = " input.source=='redcap'" ,
DT :: DTOutput ( outputId = ns ( " redcap_prev" ) )
)
)
)
}
data_import_server <- function ( id ) {
module <- function ( input , output , session ) {
ns <- session $ ns
rv <- shiny :: reactiveValues (
data_temp = NULL ,
code = list ( )
)
data_file <- import_file_server (
id = ns ( " file_import" ) ,
show_data_in = " popup" ,
trigger_return = " change" ,
2025-03-19 13:10:56 +01:00
return_class = " data.frame"
2025-03-17 15:00:13 +01:00
)
shiny :: observeEvent ( data_file $ data ( ) , {
shiny :: req ( data_file $ data ( ) )
2025-03-17 20:26:30 +01:00
2025-03-17 15:00:13 +01:00
rv $ data_temp <- data_file $ data ( )
rv $ code <- append_list ( data = data_file $ code ( ) , list = rv $ code , index = " import" )
} )
data_redcap <- m_redcap_readServer (
id = " redcap_import"
)
shiny :: observeEvent ( data_redcap ( ) , {
# rv$data_original <- purrr::pluck(data_redcap(), "data")()
rv $ data_temp <- data_redcap ( )
} )
from_env <- datamods :: import_globalenv_server (
id = " env" ,
trigger_return = " change" ,
btn_show_data = FALSE ,
reset = reactive ( input $ hidden )
)
shiny :: observeEvent ( from_env $ data ( ) , {
shiny :: req ( from_env $ data ( ) )
rv $ data_temp <- from_env $ data ( )
# rv$code <- append_list(data = from_env$code(),list = rv$code,index = "import")
} )
return ( list (
# status = reactive(temporary_rv$status),
# name = reactive(temporary_rv$name),
# code = reactive(temporary_rv$code),
data = shiny :: reactive ( rv $ data_temp )
) )
}
shiny :: moduleServer (
id = id ,
module = module
)
}
#' Test app for the data-import module
2025-03-13 12:41:50 +01:00
#'
2025-03-17 15:00:13 +01:00
#' @rdname data-import
2025-03-13 12:41:50 +01:00
#'
2025-03-17 15:00:13 +01:00
#' @examples
#' \dontrun{
#' data_import_demo_app()
2025-03-13 12:41:50 +01:00
#' }
2025-03-17 15:00:13 +01:00
data_import_demo_app <- function ( ) {
ui <- shiny :: fluidPage (
data_import_ui ( " data_import" ) ,
toastui :: datagridOutput2 ( outputId = " table" ) ,
DT :: DTOutput ( " data_summary" )
)
server <- function ( input , output , session ) {
imported <- shiny :: reactive ( data_import_server ( id = " data_import" ) )
# output$data_summary <- DT::renderDataTable(
# {
# shiny::req(data_val$data)
# data_val$data
# },
# options = list(
# scrollX = TRUE,
# pageLength = 5
# )
# )
output $ table <- toastui :: renderDatagrid2 ( {
req ( imported $ data )
toastui :: datagrid (
data = head ( imported $ data , 5 ) ,
theme = " striped" ,
colwidths = " guess" ,
minBodyHeight = 250
)
} )
}
shiny :: shinyApp ( ui , server )
}
2024-12-18 10:37:37 +01:00
2025-02-25 09:51:42 +01:00
2024-12-19 11:34:25 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//data-summary.R
2024-12-19 11:34:25 +01:00
########
2025-01-16 11:24:26 +01:00
#' Data summary module
#'
#' @param id Module id. (Use 'ns("id")')
#'
#' @name data-summary
#' @returns Shiny ui module
#' @export
2025-01-15 16:21:38 +01:00
data_summary_ui <- function ( id ) {
ns <- NS ( id )
2024-12-19 11:34:25 +01:00
2025-01-16 11:24:26 +01:00
toastui :: datagridOutput ( outputId = ns ( " tbl_summary" ) )
2025-01-15 16:21:38 +01:00
}
2024-12-19 11:34:25 +01:00
2025-01-20 13:18:36 +01:00
#'
2025-01-16 11:24:26 +01:00
#' @param data data
#' @param color.main main color
#' @param color.sec secondary color
2025-04-09 12:31:08 +02:00
#' @param ... arguments passed to create_overview_datagrid
2025-01-16 11:24:26 +01:00
#'
#' @name data-summary
#' @returns shiny server module
#' @export
2025-01-15 16:21:38 +01:00
data_summary_server <- function ( id ,
2025-01-16 11:24:26 +01:00
data ,
color.main ,
2025-01-20 13:18:36 +01:00
color.sec ,
... ) {
2025-01-15 16:21:38 +01:00
shiny :: moduleServer (
id = id ,
module = function ( input , output , session ) {
ns <- session $ ns
2024-12-19 11:34:25 +01:00
2025-01-16 11:24:26 +01:00
output $ tbl_summary <-
2025-01-15 16:21:38 +01:00
toastui :: renderDatagrid (
2025-01-17 15:59:24 +01:00
{
shiny :: req ( data ( ) )
data ( ) | >
2025-01-16 11:24:26 +01:00
overview_vars ( ) | >
2025-04-09 12:31:08 +02:00
create_overview_datagrid ( ... ) | >
2025-01-16 11:24:26 +01:00
add_sparkline (
column = " vals" ,
color.main = color.main ,
color.sec = color.sec
)
2025-01-17 15:59:24 +01:00
}
)
2025-01-16 11:24:26 +01:00
2025-01-15 16:21:38 +01:00
}
)
}
2024-12-19 11:34:25 +01:00
2025-01-16 11:24:26 +01:00
#' Add sparkline to datagrid
#'
#' @param grid grid
#' @param column clumn to transform
#'
#' @returns datagrid
#' @export
#'
#' @examples
#' grid <- mtcars |>
#' default_parsing() |>
#' overview_vars() |>
#' toastui::datagrid() |>
#' add_sparkline()
#' grid
2025-01-15 16:21:38 +01:00
add_sparkline <- function ( grid , column = " vals" , color.main = " #2a8484" , color.sec = " #84EF84" ) {
out <- toastui :: grid_sparkline (
grid = grid ,
column = column ,
renderer = function ( data ) {
data_cl <- class ( data )
2025-04-08 13:45:07 +02:00
if ( all ( sapply ( data , is.na ) ) ) {
type <- " line"
ds <- data.frame ( x = NA , y = NA )
horizontal <- FALSE
} else if ( identical ( data_cl , " factor" ) ) {
2025-01-15 16:21:38 +01:00
type <- " column"
s <- summary ( data )
ds <- data.frame ( x = names ( s ) , y = s )
horizontal <- FALSE
2025-03-11 13:42:57 +01:00
} else if ( identical ( data_cl , " logical" ) ) {
type <- " column"
s <- table ( data )
ds <- data.frame ( x = names ( s ) , y = as.vector ( s ) )
horizontal <- FALSE
2025-01-15 16:21:38 +01:00
} else if ( any ( c ( " numeric" , " integer" ) %in% data_cl ) ) {
2025-01-16 11:24:26 +01:00
if ( is_consecutive ( data ) ) {
2025-01-15 16:21:38 +01:00
type <- " line"
ds <- data.frame ( x = NA , y = NA )
horizontal <- FALSE
} else {
type <- " box"
ds <- data.frame ( x = 1 , y = data )
horizontal <- TRUE
}
} else if ( any ( c ( " Date" , " POSIXct" , " POSIXt" , " hms" , " difftime" ) %in% data_cl ) ) {
type <- " line"
ds <- data.frame ( x = seq_along ( data ) , y = data )
horizontal <- FALSE
} else {
type <- " line"
ds <- data.frame ( x = NA , y = NA )
horizontal <- FALSE
}
apexcharter :: apex (
ds ,
apexcharter :: aes ( x , y ) ,
type = type ,
auto_update = TRUE
) | >
apexcharter :: ax_chart ( sparkline = list ( enabled = TRUE ) ) | >
apexcharter :: ax_plotOptions (
boxPlot = apexcharter :: boxplot_opts ( color.upper = color.sec , color.lower = color.main ) ,
bar = apexcharter :: bar_opts ( horizontal = horizontal )
) | >
apexcharter :: ax_colors (
c ( color.main , color.sec )
)
}
)
2024-12-19 11:34:25 +01:00
2025-01-15 16:21:38 +01:00
toastui :: grid_columns (
grid = out ,
columns = column ,
minWidth = 200
)
}
2024-12-19 11:34:25 +01:00
2025-01-16 11:24:26 +01:00
#' Checks if elements in vector are equally spaced as indication of ID
#'
#' @param data vector
#'
2025-01-16 12:23:39 +01:00
#' @returns logical
2025-01-16 11:24:26 +01:00
#' @export
#'
#' @examples
#' 1:10 |> is_consecutive()
#' sample(1:100,40) |> is_consecutive()
is_consecutive <- function ( data ) {
suppressWarnings ( length ( unique ( diff ( as.numeric ( data ) ) ) ) == 1 )
}
2024-12-19 15:26:23 +01:00
2025-01-16 11:24:26 +01:00
#' Create a data overview data.frame ready for sparklines
#'
#' @param data data
#'
#' @returns data.frame
#' @export
#'
#' @examples
#' mtcars |> overview_vars()
2025-01-15 16:21:38 +01:00
overview_vars <- function ( data ) {
data <- as.data.frame ( data )
2024-12-19 15:26:23 +01:00
2025-01-15 16:21:38 +01:00
dplyr :: tibble (
2025-04-22 10:02:12 +02:00
icon = get_classes ( data ) ,
class = icon ,
2025-01-15 16:21:38 +01:00
name = names ( data ) ,
n_missing = unname ( colSums ( is.na ( data ) ) ) ,
p_complete = 1 - n_missing / nrow ( data ) ,
n_unique = get_n_unique ( data ) ,
vals = as.list ( data )
)
}
2024-12-19 15:26:23 +01:00
2025-01-16 11:24:26 +01:00
#' Create a data overview datagrid
#'
#' @param data data
#'
#' @returns datagrid
#' @export
#'
#' @examples
#' mtcars |>
#' overview_vars() |>
#' create_overview_datagrid()
2025-04-09 12:31:08 +02:00
create_overview_datagrid <- function ( data , ... ) {
2025-01-15 16:21:38 +01:00
# browser()
gridTheme <- getOption ( " datagrid.theme" )
if ( length ( gridTheme ) < 1 ) {
datamods ::: apply_grid_theme ( )
}
on.exit ( toastui :: reset_grid_theme ( ) )
2024-12-19 15:26:23 +01:00
2025-01-15 16:21:38 +01:00
col.names <- names ( data )
2024-12-19 15:26:23 +01:00
2025-01-15 16:21:38 +01:00
std_names <- c (
" Name" = " name" ,
2025-04-15 16:14:03 +02:00
" Icon" = " icon" ,
2025-04-22 10:02:12 +02:00
" Class" = " class" ,
2025-02-26 12:18:46 +01:00
" Type" = " type" ,
" Missings" = " n_missing" ,
2025-01-15 16:21:38 +01:00
" Complete" = " p_complete" ,
" Unique" = " n_unique" ,
2025-02-26 12:18:46 +01:00
" Distribution" = " vals"
2025-01-15 16:21:38 +01:00
)
2024-12-19 15:26:23 +01:00
2025-01-15 16:21:38 +01:00
headers <- lapply ( col.names , \ ( .x ) {
if ( .x %in% std_names ) {
names ( std_names ) [match ( .x , std_names ) ]
} else {
.x
}
} ) | > unlist ( )
2024-12-19 15:26:23 +01:00
2025-01-15 16:21:38 +01:00
grid <- toastui :: datagrid (
data = data ,
theme = " default" ,
2025-04-09 12:31:08 +02:00
colwidths = " fit" ,
...
2025-01-15 16:21:38 +01:00
)
2024-12-19 15:26:23 +01:00
2025-01-15 16:21:38 +01:00
grid <- toastui :: grid_columns (
grid = grid ,
columns = col.names ,
header = headers ,
2025-02-26 12:18:46 +01:00
resizable = TRUE
)
grid <- toastui :: grid_columns (
grid = grid ,
columns = " vals" ,
width = 120
)
grid <- toastui :: grid_columns (
grid = grid ,
2025-04-15 16:14:03 +02:00
columns = " icon" ,
2025-02-26 12:18:46 +01:00
header = " " ,
align = " center" , sortable = FALSE ,
width = 40
2025-01-15 16:21:38 +01:00
)
2024-12-19 15:26:23 +01:00
2025-01-15 16:21:38 +01:00
grid <- add_class_icon (
grid = grid ,
2025-04-15 16:14:03 +02:00
column = " icon" ,
2025-04-22 10:02:12 +02:00
fun = class_icons
2025-01-15 16:21:38 +01:00
)
2024-12-19 15:26:23 +01:00
2025-01-16 11:24:26 +01:00
grid <- toastui :: grid_format (
grid = grid ,
" p_complete" ,
formatter = toastui :: JS ( " function(obj) {return (obj.value*100).toFixed(0) + '%';}" )
)
2024-12-19 15:26:23 +01:00
2025-01-20 13:18:36 +01:00
## This could obviously be extended, which will added even more complexity.
2025-01-20 11:48:31 +01:00
grid <- toastui :: grid_filters (
grid = grid ,
2025-01-20 13:18:36 +01:00
column = " name" ,
# columns = unname(std_names[std_names!="vals"]),
showApplyBtn = FALSE ,
2025-01-20 11:48:31 +01:00
showClearBtn = TRUE ,
type = " text"
)
2025-01-15 16:21:38 +01:00
return ( grid )
}
2024-12-19 15:26:23 +01:00
2025-01-16 11:24:26 +01:00
#' Convert class grid column to icon
#'
#' @param grid grid
#' @param column column
#'
#' @returns datagrid
#' @export
#'
#' @examples
2025-01-16 12:23:39 +01:00
#' mtcars |>
#' overview_vars() |>
#' toastui::datagrid() |>
#' add_class_icon()
2025-04-15 16:14:03 +02:00
add_class_icon <- function ( grid , column = " class" , fun = class_icons ) {
2025-01-15 16:21:38 +01:00
out <- toastui :: grid_format (
grid = grid ,
column = column ,
formatter = function ( value ) {
lapply (
X = value ,
2025-04-15 16:14:03 +02:00
FUN = fun
2025-01-15 16:21:38 +01:00
)
}
)
2024-12-19 15:26:23 +01:00
2025-01-15 16:21:38 +01:00
toastui :: grid_columns (
grid = out ,
header = NULL ,
columns = column ,
width = 60
)
}
2024-12-19 15:26:23 +01:00
2025-04-15 16:14:03 +02:00
#' Get data class icons
#'
#' @param x character vector of data classes
#'
2025-04-22 10:02:12 +02:00
#' @returns list
2025-04-15 16:14:03 +02:00
#' @export
#'
#' @examples
2025-04-22 10:02:12 +02:00
#' "numeric" |> class_icons()|> str()
#' mtcars |> sapply(class) |> class_icons() |> str()
2025-04-15 16:14:03 +02:00
class_icons <- function ( x ) {
if ( length ( x ) > 1 ) {
2025-04-22 10:02:12 +02:00
lapply ( x , class_icons )
2025-04-15 16:14:03 +02:00
} else {
if ( identical ( x , " numeric" ) ) {
shiny :: icon ( " calculator" )
} else if ( identical ( x , " factor" ) ) {
shiny :: icon ( " chart-simple" )
} else if ( identical ( x , " integer" ) ) {
shiny :: icon ( " arrow-down-1-9" )
} else if ( identical ( x , " character" ) ) {
shiny :: icon ( " arrow-down-a-z" )
} else if ( identical ( x , " logical" ) ) {
shiny :: icon ( " toggle-off" )
2025-04-24 12:53:47 +02:00
} else if ( any ( c ( " Date" , " POSIXt" ) %in% x ) ) {
2025-04-15 16:14:03 +02:00
shiny :: icon ( " calendar-days" )
2025-04-24 12:53:47 +02:00
} else if ( any ( " POSIXct" , " hms" ) %in% x ) {
2025-04-15 16:14:03 +02:00
shiny :: icon ( " clock" )
} else {
shiny :: icon ( " table" )
} }
}
#' Get data type icons
#'
#' @param x character vector of data classes
#'
2025-04-22 10:02:12 +02:00
#' @returns list
2025-04-15 16:14:03 +02:00
#' @export
#'
#' @examples
#' "ordinal" |> type_icons()
#' default_parsing(mtcars) |> sapply(data_type) |> type_icons()
type_icons <- function ( x ) {
if ( length ( x ) > 1 ) {
2025-04-22 10:02:12 +02:00
lapply ( x , class_icons )
2025-04-15 16:14:03 +02:00
} else {
if ( identical ( x , " continuous" ) ) {
shiny :: icon ( " calculator" )
} else if ( identical ( x , " categorical" ) ) {
shiny :: icon ( " chart-simple" )
} else if ( identical ( x , " ordinal" ) ) {
shiny :: icon ( " arrow-down-1-9" )
} else if ( identical ( x , " text" ) ) {
shiny :: icon ( " arrow-down-a-z" )
} else if ( identical ( x , " dichotomous" ) ) {
shiny :: icon ( " toggle-off" )
} else if ( identical ( x , " datetime" ) ) {
shiny :: icon ( " calendar-days" )
2025-04-15 16:27:43 +02:00
} else if ( identical ( x , " id" ) ) {
shiny :: icon ( " id-card" )
2025-04-15 16:14:03 +02:00
} else {
shiny :: icon ( " table" )
2025-04-15 16:27:43 +02:00
}
}
2025-04-15 16:14:03 +02:00
}
2025-04-24 12:53:47 +02:00
#' Easily get variable icon based on data type or class
#'
#' @param data variable or data frame
#' @param class.type "type" or "class". Default is "class"
#'
#' @returns svg icon
#' @export
#'
#' @examples
#' mtcars[1] |> get_var_icon("class")
#' default_parsing(mtcars) |> get_var_icon()
get_var_icon <- function ( data , class.type = c ( " class" , " type" ) ) {
if ( is.data.frame ( data ) ) {
lapply ( data , get_var_icon )
} else {
class.type <- match.arg ( class.type )
switch ( class.type ,
type = {
type_icons ( data_type ( data ) )
} ,
class = {
class ( data ) [1 ] | > class_icons ( )
}
)
}
}
########
#### Current file: /Users/au301842/FreesearchR/R//datagrid-infos-mod.R
########
#' Display a table in a window
#'
#' @param data a data object (either a `matrix` or a `data.frame`).
#' @param title Title to be displayed in window.
#' @param show_classes Show variables classes under variables names in table header.
#' @param type Display table in a pop-up with [shinyWidgets::show_alert()],
#' in modal window with [shiny::showModal()] or in a WinBox window with [shinyWidgets::WinBox()].
#' @param options Arguments passed to [toastui::datagrid()].
#' @param width Width of the window, only used if `type = "popup"` or `type = "winbox"`.
#' @param ... Additional options, such as `wbOptions = wbOptions()` or `wbControls = wbControls()`.
#'
#' @note
#' If you use `type = "winbox"`, you'll need to use `shinyWidgets::html_dependency_winbox()` somewhere in your UI.
#'
#' @return No value.
#' @export
#'
show_data <- function ( data ,
title = NULL ,
options = NULL ,
show_classes = TRUE ,
type = c ( " popup" , " modal" , " winbox" ) ,
width = " 65%" ,
... ) { # nocov start
type <- match.arg ( type )
data <- as.data.frame ( data )
args <- list ( ... )
gridTheme <- getOption ( " datagrid.theme" )
if ( length ( gridTheme ) < 1 ) {
datamods ::: apply_grid_theme ( )
}
on.exit ( toastui :: reset_grid_theme ( ) )
if ( is.null ( options ) )
options <- list ( )
2025-05-10 11:31:26 +02:00
options $ height <- 500
2025-04-24 12:53:47 +02:00
options $ minBodyHeight <- 400
options $ data <- data
options $ theme <- " default"
options $ colwidths <- " guess"
options $ guess_colwidths_opts <- list ( min_width = 90 , max_width = 400 , mul = 1 , add = 10 )
if ( isTRUE ( show_classes ) )
options $ summary <- construct_col_summary ( data )
datatable <- rlang :: exec ( toastui :: datagrid , ! ! ! options )
datatable <- toastui :: grid_columns ( datatable , className = " font-monospace" )
if ( identical ( type , " winbox" ) ) {
stopifnot (
" You need shinyWidgets >= 0.8.4" = packageVersion ( " shinyWidgets" ) >= " 0.8.4"
)
wb_options <- if ( is.null ( args $ wbOptions ) ) {
shinyWidgets :: wbOptions (
height = " 600px" ,
width = width ,
modal = TRUE
)
} else {
modifyList (
shinyWidgets :: wbOptions (
height = " 600px" ,
width = width ,
modal = TRUE
) ,
args $ wbOptions
)
}
wb_controls <- if ( is.null ( args $ wbControls ) ) {
shinyWidgets :: wbControls ( )
} else {
args $ wbControls
}
shinyWidgets :: WinBox (
title = title ,
ui = datatable ,
options = wb_options ,
controls = wb_controls ,
padding = " 0 5px"
)
} else if ( identical ( type , " popup" ) ) {
shinyWidgets :: show_alert (
title = NULL ,
text = tags $ div (
if ( ! is.null ( title ) ) {
tagList (
tags $ h3 ( title ) ,
tags $ hr ( )
)
} ,
style = " color: #000 !important;" ,
datatable
) ,
closeOnClickOutside = TRUE ,
showCloseButton = TRUE ,
btn_labels = NA ,
html = TRUE ,
width = width
)
} else {
showModal ( modalDialog (
title = tagList (
datamods ::: button_close_modal ( ) ,
title
) ,
tags $ div (
style = css ( minHeight = validateCssUnit ( options $ height ) ) ,
toastui :: renderDatagrid2 ( datatable )
) ,
size = " xl" ,
footer = NULL ,
easyClose = TRUE
) )
}
} # nocov end
#' @importFrom htmltools tagList tags css
describe_col_char <- function ( x , with_summary = TRUE ) {
tags $ div (
style = css ( padding = " 3px 0" , fontSize = " x-small" ) ,
tags $ div (
style = css ( fontStyle = " italic" ) ,
get_var_icon ( x ) ,
# phosphoricons::ph("text-aa"),
" character"
) ,
if ( with_summary ) {
tagList (
tags $ hr ( style = css ( margin = " 3px 0" ) ) ,
tags $ div (
i18n ( " Unique:" ) , length ( unique ( x ) )
) ,
tags $ div (
i18n ( " Missing:" ) , sum ( is.na ( x ) )
) ,
tags $ div (
style = css ( whiteSpace = " normal" , wordBreak = " break-all" ) ,
i18n ( " Most Common:" ) , gsub (
pattern = " '" ,
replacement = " \u07F4" ,
x = names ( sort ( table ( x ) , decreasing = TRUE ) ) [1 ]
)
) ,
tags $ div (
" \u00A0"
)
)
}
)
}
fmt_p <- function ( val , tot ) {
paste0 ( round ( val / tot * 100 , 1 ) , " %" )
}
describe_col_factor <- function ( x , with_summary = TRUE ) {
count <- sort ( table ( x , useNA = " always" ) , decreasing = TRUE )
total <- sum ( count )
one <- count [ ! is.na ( names ( count ) ) ] [1 ]
two <- count [ ! is.na ( names ( count ) ) ] [2 ]
missing <- count [is.na ( names ( count ) ) ]
tags $ div (
style = css ( padding = " 3px 0" , fontSize = " x-small" ) ,
tags $ div (
style = css ( fontStyle = " italic" ) ,
get_var_icon ( x ) ,
# phosphoricons::ph("list-bullets"),
" factor"
) ,
if ( with_summary ) {
tagList (
tags $ hr ( style = css ( margin = " 3px 0" ) ) ,
tags $ div (
names ( one ) , " :" , fmt_p ( one , total )
) ,
tags $ div (
names ( two ) , " :" , fmt_p ( two , total )
) ,
tags $ div (
" Missing" , " :" , fmt_p ( missing , total )
) ,
tags $ div (
" \u00A0"
)
)
}
)
}
describe_col_num <- function ( x , with_summary = TRUE ) {
tags $ div (
style = css ( padding = " 3px 0" , fontSize = " x-small" ) ,
tags $ div (
style = css ( fontStyle = " italic" ) ,
get_var_icon ( x ) ,
# phosphoricons::ph("hash"),
" numeric"
) ,
if ( with_summary ) {
tagList (
tags $ hr ( style = css ( margin = " 3px 0" ) ) ,
tags $ div (
i18n ( " Min:" ) , round ( min ( x , na.rm = TRUE ) , 2 )
) ,
tags $ div (
i18n ( " Mean:" ) , round ( mean ( x , na.rm = TRUE ) , 2 )
) ,
tags $ div (
i18n ( " Max:" ) , round ( max ( x , na.rm = TRUE ) , 2 )
) ,
tags $ div (
i18n ( " Missing:" ) , sum ( is.na ( x ) )
)
)
}
)
}
describe_col_date <- function ( x , with_summary = TRUE ) {
tags $ div (
style = css ( padding = " 3px 0" , fontSize = " x-small" ) ,
tags $ div (
style = css ( fontStyle = " italic" ) ,
get_var_icon ( x ) ,
# phosphoricons::ph("calendar"),
" date"
) ,
if ( with_summary ) {
tagList (
tags $ hr ( style = css ( margin = " 3px 0" ) ) ,
tags $ div (
i18n ( " Min:" ) , min ( x , na.rm = TRUE )
) ,
tags $ div (
i18n ( " Max:" ) , max ( x , na.rm = TRUE )
) ,
tags $ div (
i18n ( " Missing:" ) , sum ( is.na ( x ) )
) ,
tags $ div (
" \u00A0"
)
)
}
)
}
describe_col_datetime <- function ( x , with_summary = TRUE ) {
tags $ div (
style = css ( padding = " 3px 0" , fontSize = " x-small" ) ,
tags $ div (
style = css ( fontStyle = " italic" ) ,
get_var_icon ( x ) ,
# phosphoricons::ph("clock"),
" datetime"
) ,
if ( with_summary ) {
tagList (
tags $ hr ( style = css ( margin = " 3px 0" ) ) ,
tags $ div (
i18n ( " Min:" ) , min ( x , na.rm = TRUE )
) ,
tags $ div (
i18n ( " Max:" ) , max ( x , na.rm = TRUE )
) ,
tags $ div (
i18n ( " Missing:" ) , sum ( is.na ( x ) )
) ,
tags $ div (
" \u00A0"
)
)
}
)
}
describe_col_other <- function ( x , with_summary = TRUE ) {
tags $ div (
style = css ( padding = " 3px 0" , fontSize = " x-small" ) ,
tags $ div (
style = css ( fontStyle = " italic" ) ,
get_var_icon ( x ) ,
# phosphoricons::ph("clock"),
paste ( class ( x ) , collapse = " , " )
) ,
if ( with_summary ) {
tagList (
tags $ hr ( style = css ( margin = " 3px 0" ) ) ,
tags $ div (
i18n ( " Unique:" ) , length ( unique ( x ) )
) ,
tags $ div (
i18n ( " Missing:" ) , sum ( is.na ( x ) )
) ,
tags $ div (
" \u00A0"
) ,
tags $ div (
" \u00A0"
)
)
}
)
}
construct_col_summary <- function ( data ) {
list (
position = " top" ,
height = 90 ,
columnContent = lapply (
X = setNames ( names ( data ) , names ( data ) ) ,
FUN = function ( col ) {
values <- data [ [col ] ]
content <- if ( inherits ( values , " character" ) ) {
describe_col_char ( values )
} else if ( inherits ( values , " factor" ) ) {
describe_col_factor ( values )
} else if ( inherits ( values , c ( " numeric" , " integer" ) ) ) {
describe_col_num ( values )
} else if ( inherits ( values , c ( " Date" ) ) ) {
describe_col_date ( values )
} else if ( inherits ( values , c ( " POSIXt" ) ) ) {
describe_col_datetime ( values )
} else {
describe_col_other ( values )
}
list (
template = toastui :: JS (
" function(value) {" ,
sprintf (
" return '%s';" ,
gsub ( replacement = " " , pattern = " \n" , x = htmltools :: doRenderTags ( content ) )
) ,
" }"
)
)
}
)
)
}
2025-04-15 16:14:03 +02:00
2025-01-15 16:21:38 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//helpers.R
2024-12-18 10:37:37 +01:00
########
2025-01-16 11:24:26 +01:00
#' Wrapper function to get function from character vector referring to function from namespace. Passed to 'do.call()'
#'
#' @description
#' This function follows the idea from this comment: https://stackoverflow.com/questions/38983179/do-call-a-function-in-r-without-loading-the-package
#' @param x function or function name
#'
#' @return function or character vector
#' @export
#'
#' @examples
#' getfun("stats::lm")
2024-12-18 10:37:37 +01:00
getfun <- function ( x ) {
if ( " character" %in% class ( x ) ) {
if ( length ( grep ( " ::" , x ) ) > 0 ) {
parts <- strsplit ( x , " ::" ) [ [1 ] ]
requireNamespace ( parts [1 ] )
getExportedValue ( parts [1 ] , parts [2 ] )
}
} else {
x
}
}
2025-01-16 11:24:26 +01:00
#' Wrapper to save data in RDS, load into specified qmd and render
#'
#' @param data list to pass to qmd
#' @param ... Passed to `quarto::quarto_render()`
#'
#' @return output file name
#' @export
#'
2025-03-24 14:40:30 +01:00
write_quarto <- function ( data , ... ) {
2024-12-18 10:37:37 +01:00
# Exports data to temporary location
#
# I assume this is more secure than putting it in the www folder and deleting
# on session end
2025-01-23 08:44:38 +01:00
# temp <- base::tempfile(fileext = ".rds")
# readr::write_rds(data, file = here)
readr :: write_rds ( data , file = " www/web_data.rds" )
2024-12-18 10:37:37 +01:00
## Specifying a output path will make the rendering fail
## Ref: https://github.com/quarto-dev/quarto-cli/discussions/4041
## Outputs to the same as the .qmd file
quarto :: quarto_render (
2025-01-23 08:44:38 +01:00
execute_params = list ( data.file = " web_data.rds" ) ,
# execute_params = list(data.file = temp),
2024-12-18 10:37:37 +01:00
...
)
}
2025-03-24 14:40:30 +01:00
write_rmd <- function ( data , ... ) {
2025-01-23 13:21:41 +01:00
# Exports data to temporary location
#
# I assume this is more secure than putting it in the www folder and deleting
# on session end
# temp <- base::tempfile(fileext = ".rds")
# readr::write_rds(data, file = here)
readr :: write_rds ( data , file = " www/web_data.rds" )
## Specifying a output path will make the rendering fail
## Ref: https://github.com/quarto-dev/quarto-cli/discussions/4041
## Outputs to the same as the .qmd file
rmarkdown :: render (
2025-05-05 14:45:07 +02:00
params = list ( data.file = " web_data.rds" , version = app_version ( ) ) ,
2025-01-23 13:21:41 +01:00
# execute_params = list(data.file = temp),
...
)
}
2025-01-16 11:24:26 +01:00
#' Flexible file import based on extension
#'
#' @param file file name
#' @param consider.na character vector of strings to consider as NAs
#'
#' @return tibble
#' @export
#'
#' @examples
#' read_input("https://raw.githubusercontent.com/agdamsbo/cognitive.index.lookup/main/data/sample.csv")
2024-12-18 10:37:37 +01:00
read_input <- function ( file , consider.na = c ( " NA" , ' ""' , " " ) ) {
ext <- tools :: file_ext ( file )
if ( ext == " csv" ) {
df <- readr :: read_csv ( file = file , na = consider.na )
} else if ( ext %in% c ( " xls" , " xlsx" ) ) {
2025-04-23 14:25:38 +02:00
df <- readxl :: read_excel ( file = file , na.strings = consider.na )
2024-12-18 10:37:37 +01:00
} else if ( ext == " dta" ) {
df <- haven :: read_dta ( file = file )
} else if ( ext == " ods" ) {
df <- readODS :: read_ods ( path = file )
} else if ( ext == " rds" ) {
df <- readr :: read_rds ( file = file )
} else {
stop ( " I n p u t f i l e f o r m a t h a s t o b e o n o f :
' .csv' , ' .xls' , ' .xlsx' , ' .dta' , ' .ods' or ' .rds' " )
}
df
}
2025-01-16 11:24:26 +01:00
#' Convert string of arguments to list of arguments
#'
#' @description
#' Idea from the answer: https://stackoverflow.com/a/62979238
#'
#' @param string string to convert to list to use with do.call
#'
#' @return list
#' @export
#'
2025-04-15 08:55:35 +02:00
#' @examples
#' argsstring2list("A=1:5,b=2:4")
#'
2024-12-18 10:37:37 +01:00
argsstring2list <- function ( string ) {
eval ( parse ( text = paste0 ( " list(" , string , " )" ) ) )
}
2025-01-16 11:24:26 +01:00
#' Factorize variables in data.frame
#'
#' @param data data.frame
#' @param vars variables to force factorize
#'
#' @return data.frame
#' @export
2025-04-15 08:55:35 +02:00
#'
#' @examples
2025-04-22 10:02:12 +02:00
#' factorize(mtcars, names(mtcars))
2024-12-18 10:37:37 +01:00
factorize <- function ( data , vars ) {
if ( ! is.null ( vars ) ) {
data | >
dplyr :: mutate (
dplyr :: across (
dplyr :: all_of ( vars ) ,
2024-12-19 21:21:29 +01:00
REDCapCAST :: as_factor
2024-12-18 10:37:37 +01:00
)
)
} else {
data
}
}
dummy_Imports <- function ( ) {
list (
MASS :: as.fractions ( ) ,
broom :: augment ( ) ,
broom.helpers :: all_categorical ( ) ,
here :: here ( ) ,
cardx :: all_of ( ) ,
parameters :: ci ( ) ,
DT :: addRow ( ) ,
bslib :: accordion ( )
)
# https://github.com/hadley/r-pkgs/issues/828
}
2025-01-16 11:24:26 +01:00
#' Title
#'
#' @param data data
#' @param output.format output
#' @param filename filename
#' @param ... passed on
#'
#' @returns data
#' @export
#'
2024-12-18 10:37:37 +01:00
file_export <- function ( data , output.format = c ( " df" , " teal" , " list" ) , filename , ... ) {
output.format <- match.arg ( output.format )
filename <- gsub ( " -" , " _" , filename )
if ( output.format == " teal" ) {
out <- within (
teal_data ( ) ,
{
assign ( name , value | >
2025-01-15 16:21:38 +01:00
dplyr :: bind_cols ( .name_repair = " unique_quiet" ) | >
2024-12-19 21:21:29 +01:00
default_parsing ( ) )
2024-12-18 10:37:37 +01:00
} ,
value = data ,
name = filename
)
datanames ( out ) <- filename
} else if ( output.format == " df" ) {
2024-12-19 21:21:29 +01:00
out <- data | >
default_parsing ( )
2024-12-18 10:37:37 +01:00
} else if ( output.format == " list" ) {
out <- list (
data = data ,
name = filename
)
2024-12-19 21:21:29 +01:00
out <- c ( out , ... )
2024-12-18 10:37:37 +01:00
}
out
}
2025-01-16 11:24:26 +01:00
#' Default data parsing
#'
#' @param data data
#'
#' @returns data.frame or tibble
#' @export
#'
#' @examples
#' mtcars |> str()
#' mtcars |>
#' default_parsing() |>
#' str()
2025-04-11 13:23:18 +02:00
#' head(starwars, 5) |> str()
2025-04-10 15:46:42 +02:00
#' starwars |>
#' default_parsing() |>
#' head(5) |>
#' str()
2024-12-19 21:21:29 +01:00
default_parsing <- function ( data ) {
2025-03-24 14:40:30 +01:00
name_labels <- lapply ( data , \ ( .x ) REDCapCAST :: get_attr ( .x , attr = " label" ) )
2025-04-11 13:23:18 +02:00
# browser()
2025-01-15 16:21:38 +01:00
out <- data | >
2025-04-10 15:46:42 +02:00
setNames ( make.names ( names ( data ) , unique = TRUE ) ) | >
## Temporary step to avoid nested list and crashing
remove_nested_list ( ) | >
2024-12-19 21:21:29 +01:00
REDCapCAST :: parse_data ( ) | >
REDCapCAST :: as_factor ( ) | >
2025-03-24 14:40:30 +01:00
REDCapCAST :: numchar2fct ( numeric.threshold = 8 , character.throshold = 10 ) | >
2025-03-19 13:10:56 +01:00
REDCapCAST :: as_logical ( ) | >
REDCapCAST :: fct_drop ( )
2025-01-15 16:21:38 +01:00
2025-04-11 13:23:18 +02:00
set_column_label ( out , setNames ( name_labels , names ( out ) ) , overwrite = FALSE )
# purrr::map2(
# out,
# name_labels[names(name_labels) %in% names(out)],
# \(.x, .l){
# if (!(is.na(.l) | .l == "")) {
# REDCapCAST::set_attr(.x, .l, attr = "label")
# } else {
# attr(x = .x, which = "label") <- NULL
# .x
# }
# # REDCapCAST::set_attr(data = .x, label = .l,attr = "label", overwrite = FALSE)
# }
# ) |> dplyr::bind_cols()
2025-01-15 16:21:38 +01:00
}
2025-04-15 08:55:35 +02:00
#' Remove empty/NA attributes
2025-01-16 11:24:26 +01:00
#'
#' @param data data
#'
2025-04-15 08:55:35 +02:00
#' @returns data of same class as input
2025-01-16 11:24:26 +01:00
#' @export
#'
#' @examples
2025-04-22 10:02:12 +02:00
#' ds <- mtcars |>
#' lapply(\(.x) REDCapCAST::set_attr(.x, label = NA, attr = "label")) |>
#' dplyr::bind_cols()
2025-03-24 14:40:30 +01:00
#' ds |>
2025-04-15 08:55:35 +02:00
#' remove_empty_attr() |>
2025-03-24 14:40:30 +01:00
#' str()
2025-04-22 10:02:12 +02:00
#' mtcars |>
#' lapply(\(.x) REDCapCAST::set_attr(.x, label = NA, attr = "label")) |>
#' remove_empty_attr() |>
2025-04-15 08:55:35 +02:00
#' str()
#'
remove_empty_attr <- function ( data ) {
2025-04-22 10:02:12 +02:00
if ( is.data.frame ( data ) ) {
data | >
lapply ( remove_empty_attr ) | >
dplyr :: bind_cols ( )
} else if ( is.list ( data ) ) {
2025-04-15 08:55:35 +02:00
data | > lapply ( remove_empty_attr )
2025-04-22 10:02:12 +02:00
} else {
attributes ( data ) [is.na ( attributes ( data ) ) ] <- NULL
data
2025-04-15 08:55:35 +02:00
}
2024-12-18 10:37:37 +01:00
}
2025-02-07 16:24:09 +01:00
#' Removes columns with completenes below cutoff
#'
#' @param data data frame
#' @param cutoff numeric
#'
#' @returns data frame
#' @export
#'
#' @examples
2025-03-24 14:40:30 +01:00
#' data.frame(a = 1:10, b = NA, c = c(2, NA)) |> remove_empty_cols(cutoff = .5)
remove_empty_cols <- function ( data , cutoff = .7 ) {
filter <- apply ( X = data , MARGIN = 2 , FUN = \ ( .x ) {
sum ( as.numeric ( ! is.na ( .x ) ) ) / length ( .x )
2025-02-07 16:24:09 +01:00
} ) >= cutoff
data [filter ]
}
2024-12-18 10:37:37 +01:00
2025-02-25 09:51:42 +01:00
#' Append list with named index
#'
#' @param data data to add to list
#' @param list list
#' @param index index name
#'
#' @returns list
2025-03-19 13:10:56 +01:00
#' @export
2025-02-25 09:51:42 +01:00
#'
#' @examples
2025-03-24 14:40:30 +01:00
#' ls_d <- list(test = c(1:20))
2025-02-25 09:51:42 +01:00
#' ls_d <- list()
2025-03-24 14:40:30 +01:00
#' data.frame(letters[1:20], 1:20) |> append_list(ls_d, "letters")
#' letters[1:20] |> append_list(ls_d, "letters")
append_list <- function ( data , list , index ) {
2025-02-25 09:51:42 +01:00
## This will overwrite and not warn
## Not very safe, but convenient to append code to list
2025-03-24 14:40:30 +01:00
if ( index %in% names ( list ) ) {
2025-02-25 09:51:42 +01:00
list [ [index ] ] <- data
out <- list
} else {
2025-03-24 14:40:30 +01:00
out <- setNames ( c ( list , list ( data ) ) , c ( names ( list ) , index ) )
2025-02-25 09:51:42 +01:00
}
out
}
2025-03-12 18:27:46 +01:00
#' Get missingsness fraction
#'
#' @param data data
#'
#' @returns numeric vector
#' @export
#'
#' @examples
2025-03-24 14:40:30 +01:00
#' c(NA, 1:10, rep(NA, 3)) |> missing_fraction()
missing_fraction <- function ( data ) {
NROW ( data [is.na ( data ) ] ) / NROW ( data )
}
#' Ultra short data dascription
#'
#' @param data
#'
#' @returns character vector
#' @export
#'
#' @examples
#' data.frame(
#' sample(1:8, 20, TRUE),
#' sample(c(1:8, NA), 20, TRUE)
#' ) |> data_description()
2025-04-14 10:10:33 +02:00
data_description <- function ( data , data_text = " Data" ) {
2025-03-24 14:40:30 +01:00
data <- if ( shiny :: is.reactive ( data ) ) data ( ) else data
2025-03-26 12:07:28 +01:00
n <- nrow ( data )
n_var <- ncol ( data )
n_complete <- sum ( complete.cases ( data ) )
2025-04-10 15:46:42 +02:00
p_complete <- n_complete / n
2025-03-26 12:07:28 +01:00
2025-03-24 14:40:30 +01:00
sprintf (
2025-04-15 16:14:03 +02:00
" %s has %s observations and %s variables, with %s (%s%%) complete cases." ,
2025-04-14 10:10:33 +02:00
data_text ,
2025-03-26 12:07:28 +01:00
n ,
n_var ,
n_complete ,
signif ( 100 * p_complete , 3 )
2025-03-24 14:40:30 +01:00
)
2025-03-12 18:27:46 +01:00
}
2025-04-14 10:10:33 +02:00
#' Filter function to filter data set by variable type
#'
#' @param data data frame
#' @param type vector of data types (recognised: data_types)
#'
#' @returns data.frame
#' @export
#'
#' @examples
2025-04-15 08:55:35 +02:00
#' default_parsing(mtcars) |>
#' data_type_filter(type = c("categorical", "continuous")) |>
#' attributes()
#' default_parsing(mtcars) |>
#' data_type_filter(type = NULL) |>
#' attributes()
2025-04-14 10:10:33 +02:00
#' \dontrun{
2025-04-15 08:55:35 +02:00
#' default_parsing(mtcars) |> data_type_filter(type = c("test", "categorical", "continuous"))
2025-04-14 10:10:33 +02:00
#' }
2025-04-15 08:55:35 +02:00
data_type_filter <- function ( data , type ) {
2025-04-14 10:10:33 +02:00
## Please ensure to only provide recognised data types
2025-04-22 10:02:12 +02:00
assertthat :: assert_that ( all ( type %in% names ( data_types ( ) ) ) )
2025-04-14 10:10:33 +02:00
2025-04-15 08:55:35 +02:00
if ( ! is.null ( type ) ) {
out <- data [data_type ( data ) %in% type ]
code <- rlang :: call2 ( " data_type_filter" , ! ! ! list ( type = type ) , .ns = " FreesearchR" )
attr ( out , " code" ) <- code
} else {
out <- data
}
2025-04-14 10:10:33 +02:00
out
}
2025-04-02 11:31:04 +02:00
#' Drop-in replacement for the base::sort_by with option to remove NAs
#'
#' @param x x
#' @param y y
#' @param na.rm remove NAs
#' @param ... passed to base_sort_by
#'
#' @returns vector
#' @export
#'
#' @examples
2025-04-10 15:46:42 +02:00
#' sort_by(c("Multivariable", "Univariable"), c("Univariable", "Minimal", "Multivariable"))
sort_by <- function ( x , y , na.rm = FALSE , ... ) {
out <- base :: sort_by ( x , y , ... )
if ( na.rm == TRUE ) {
2025-04-02 11:31:04 +02:00
out [ ! is.na ( out ) ]
} else {
out
}
}
2025-04-10 15:46:42 +02:00
get_ggplot_label <- function ( data , label ) {
2025-04-23 14:25:38 +02:00
assertthat :: assert_that ( ggplot2 :: is_ggplot ( data ) )
2025-04-02 11:31:04 +02:00
data $ labels [ [label ] ]
}
2025-03-12 18:27:46 +01:00
2025-04-03 13:11:02 +02:00
#' Return if available
#'
#' @param data vector
#' @param default assigned value for missings
#'
#' @returns vector
#' @export
#'
#' @examples
#' NULL |> if_not_missing("new")
2025-04-10 15:46:42 +02:00
#' c(2, "a", NA) |> if_not_missing()
2025-04-03 13:11:02 +02:00
#' "See" |> if_not_missing()
2025-04-10 15:46:42 +02:00
if_not_missing <- function ( data , default = NULL ) {
if ( length ( data ) > 1 ) {
Reduce ( c , lapply ( data , if_not_missing ) )
} else if ( is.na ( data ) || is.null ( data ) ) {
2025-04-03 13:11:02 +02:00
return ( default )
} else {
return ( data )
}
}
2025-04-09 12:31:08 +02:00
#' Merge list of expressions
#'
#' @param data list
#'
#' @returns expression
#' @export
#'
#' @examples
#' list(
2025-04-10 15:46:42 +02:00
#' rlang::call2(.fn = "select", !!!list(c("cyl", "disp")), .ns = "dplyr"),
#' rlang::call2(.fn = "default_parsing", .ns = "FreesearchR")
2025-04-09 12:31:08 +02:00
#' ) |> merge_expression()
2025-04-10 15:46:42 +02:00
merge_expression <- function ( data ) {
2025-04-09 12:31:08 +02:00
Reduce (
f = function ( x , y ) rlang :: expr ( ! ! x %>% ! ! y ) ,
x = data
)
}
2025-04-11 13:23:18 +02:00
#' Reduce character vector with the native pipe operator or character string
#'
#' @param data list
#'
#' @returns character string
#' @export
#'
#' @examples
#' list(
#' "mtcars",
#' rlang::call2(.fn = "select", !!!list(c("cyl", "disp")), .ns = "dplyr"),
#' rlang::call2(.fn = "default_parsing", .ns = "FreesearchR")
#' ) |>
#' lapply(expression_string) |>
#' pipe_string() |>
#' expression_string("data<-")
pipe_string <- function ( data , collapse = " |>\n" ) {
if ( is.list ( data ) ) {
Reduce (
f = function ( x , y ) glue :: glue ( " {x}{collapse}{y}" ) ,
x = data
)
} else {
data
}
}
2025-04-09 12:31:08 +02:00
#' Deparses expression as string, substitutes native pipe and adds assign
#'
#' @param data expression
#'
#' @returns string
#' @export
#'
#' @examples
#' list(
2025-04-15 08:55:35 +02:00
#' as.symbol(paste0("mtcars$", "mpg")),
2025-04-10 15:46:42 +02:00
#' rlang::call2(.fn = "select", !!!list(c("cyl", "disp")), .ns = "dplyr"),
#' rlang::call2(.fn = "default_parsing", .ns = "FreesearchR")
#' ) |>
#' merge_expression() |>
#' expression_string()
2025-04-11 13:23:18 +02:00
expression_string <- function ( data , assign.str = " " ) {
exp.str <- if ( is.call ( data ) ) deparse ( data ) else data
# browser()
out <- paste0 ( assign.str , gsub ( " %>%" , " |>\n" , paste ( gsub ( ' "' , " '" , paste ( exp.str , collapse = " " ) ) , collapse = " " ) ) )
gsub ( " |`" , " " , out )
2025-04-09 12:31:08 +02:00
}
2025-04-15 08:55:35 +02:00
#' Very simple function to remove nested lists, like when uploading .rds
2025-04-10 15:46:42 +02:00
#'
#' @param data data
#'
#' @returns data.frame
#' @export
#'
#' @examples
#' dplyr::tibble(a = 1:10, b = rep(list("a"), 10)) |> remove_nested_list()
#' dplyr::tibble(a = 1:10, b = rep(list(c("a", "b")), 10)) |> as.data.frame()
remove_nested_list <- function ( data ) {
data [ ! sapply ( data , is.list ) ]
}
2025-04-09 12:31:08 +02:00
2025-04-11 13:23:18 +02:00
#' (Re)label columns in data.frame
#'
#' @param data data.frame to be labelled
#' @param label named list or vector
#'
#' @returns data.frame
#' @export
#'
#' @examples
#' ls <- list("mpg" = "", "cyl" = "Cylinders", "disp" = "", "hp" = "", "drat" = "", "wt" = "", "qsec" = "", "vs" = "", "am" = "", "gear" = "", "carb" = "")
#' ls2 <- c("mpg" = "", "cyl" = "Cylinders", "disp" = "", "hp" = "Horses", "drat" = "", "wt" = "", "qsec" = "", "vs" = "", "am" = "", "gear" = "", "carb" = "")
#' ls3 <- c("mpg" = "", "cyl" = "", "disp" = "", "hp" = "Horses", "drat" = "", "wt" = "", "qsec" = "", "vs" = "", "am" = "", "gear" = "", "carb" = "")
#' mtcars |>
#' set_column_label(ls) |>
#' set_column_label(ls2) |>
#' set_column_label(ls3)
#' rlang::expr(FreesearchR::set_column_label(label = !!ls3)) |> expression_string()
set_column_label <- function ( data , label , overwrite = TRUE ) {
purrr :: imap ( data , function ( .data , .name ) {
ls <- if ( is.list ( label ) ) unlist ( label ) else label
ls [ls == " " ] <- NA
if ( .name %in% names ( ls ) ) {
out <- REDCapCAST :: set_attr ( .data , unname ( ls [.name ] ) , attr = " label" , overwrite = overwrite )
remove_empty_attr ( out )
} else {
.data
}
} ) | > dplyr :: bind_cols ( .name_repair = " unique_quiet" )
}
#' Append a column to a data.frame
#'
#' @param data data
#' @param column new column (vector) or data.frame with 1 column
#' @param name new name (pre-fix)
#' @param index desired location. May be "left", "right" or numeric index.
#'
#' @returns data.frame
#' @export
#'
#' @examples
#' mtcars |>
#' dplyr::mutate(mpg_cut = mpg) |>
#' append_column(mtcars$mpg, "mpg_cutter")
append_column <- function ( data , column , name , index = " right" ) {
assertthat :: assert_that ( NCOL ( column ) == 1 )
assertthat :: assert_that ( length ( index ) == 1 )
if ( index == " right" ) {
index <- ncol ( data ) + 1
} else if ( index == " left" ) {
index <- 1
} else if ( is.numeric ( index ) ) {
if ( index > ncol ( data ) ) {
index <- ncol ( data ) + 1
}
} else {
index <- ncol ( data ) + 1
}
## Identifying potential naming conflicts
nm_conflicts <- names ( data ) [startsWith ( names ( data ) , name ) ]
## Simple attemt to create new unique name
if ( length ( nm_conflicts ) > 0 ) {
name <- glue :: glue ( " {name}_{length(nm_conflicts)+1}" )
}
## If the above not achieves a unique name, the generic approach is used
if ( name %in% names ( data ) ) {
name <- make.names ( c ( name , names ( data ) ) , unique = TRUE ) [1 ]
}
new_df <- setNames ( data.frame ( column ) , name )
list (
data [seq_len ( index - 1 ) ] ,
new_df ,
if ( ! index > ncol ( data ) ) data [index : ncol ( data ) ]
) | >
dplyr :: bind_cols ( )
}
2025-04-22 10:02:12 +02:00
#' Test if element is identical to the previous
#'
#' @param data data. vector, data.frame or list
#' @param no.name logical to remove names attribute before testing
#'
#' @returns logical vector
#' @export
#'
#' @examples
#' c(1, 1, 2, 3, 3, 2, 4, 4) |> is_identical_to_previous()
#' mtcars[c(1, 1, 2, 3, 3, 2, 4, 4)] |> is_identical_to_previous()
#' list(1, 1, list(2), "A", "a", "a") |> is_identical_to_previous()
is_identical_to_previous <- function ( data , no.name = TRUE ) {
if ( is.data.frame ( data ) ) {
lagged <- data.frame ( FALSE , data [seq_len ( length ( data ) - 1 ) ] )
} else {
lagged <- c ( FALSE , data [seq_len ( length ( data ) - 1 ) ] )
}
vapply ( seq_len ( length ( data ) ) , \ ( .x ) {
if ( isTRUE ( no.name ) ) {
identical ( unname ( lagged [.x ] ) , unname ( data [.x ] ) )
} else {
identical ( lagged [.x ] , data [.x ] )
}
} , FUN.VALUE = logical ( 1 ) )
}
2025-05-10 11:31:26 +02:00
#' Simplified version of the snakecase packages to_snake_case
#'
#' @param data character string vector
#'
#' @returns vector
#' @export
#'
#' @examples
#' c("foo bar", "fooBar21", "!!Foo'B'a-r", "foo_bar", "F OO bar") |> simple_snake()
simple_snake <- function ( data ) {
gsub ( " [\\s+]" , " _" , gsub ( " [^\\w\\s:-]" , " " , tolower ( data ) , perl = TRUE ) , perl = TRUE )
}
2025-05-05 14:45:07 +02:00
########
#### Current file: /Users/au301842/FreesearchR/R//hosted_version.R
########
2025-05-10 11:31:26 +02:00
hosted_version <- function ( ) ' v25.5.2-250510'
2025-05-05 14:45:07 +02:00
2025-04-24 12:53:47 +02:00
########
#### Current file: /Users/au301842/FreesearchR/R//html_dependency_freesearchr.R
########
html_dependency_FreesearchR <- function ( ) {
htmltools :: htmlDependency (
name = " FreesearchR" ,
version = packageVersion ( " FreesearchR" ) ,
src = list ( href = " FreesearchR" , file = " assets" ) ,
package = " FreesearchR" ,
stylesheet = " css/FreesearchR.css"
)
}
2025-03-11 13:42:57 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//import-file-ext.R
2025-03-11 13:42:57 +01:00
########
#' @title Import data from a file
#'
#' @description Let user upload a file and import data
#'
#' @param preview_data Show or not a preview of the data under the file input.
#' @param file_extensions File extensions accepted by [shiny::fileInput()], can also be MIME type.
#' @param layout_params How to display import parameters : in a dropdown button or inline below file input.
#'
#' @export
#'
#' @name import-file
#'
#'
import_file_ui <- function ( id ,
2025-03-13 12:41:50 +01:00
title = " " ,
2025-03-11 13:42:57 +01:00
preview_data = TRUE ,
file_extensions = c ( " .csv" , " .txt" , " .xls" , " .xlsx" , " .rds" , " .fst" , " .sas7bdat" , " .sav" ) ,
layout_params = c ( " dropdown" , " inline" ) ) {
2025-03-13 12:41:50 +01:00
ns <- shiny :: NS ( id )
2025-03-11 13:42:57 +01:00
if ( ! is.null ( layout_params ) ) {
layout_params <- match.arg ( layout_params )
}
if ( isTRUE ( title ) ) {
2025-03-13 12:41:50 +01:00
title <- shiny :: tags $ h4 (
2025-03-11 13:42:57 +01:00
datamods ::: i18n ( " Import a file" ) ,
class = " datamods-title"
)
}
params_ui <- shiny :: fluidRow (
shiny :: column (
width = 6 ,
shinyWidgets :: numericInputIcon (
inputId = ns ( " skip_rows" ) ,
label = datamods ::: i18n ( " Rows to skip before reading data:" ) ,
value = 0 ,
min = 0 ,
icon = list ( " n =" ) ,
size = " sm" ,
width = " 100%"
) ,
shiny :: tagAppendChild (
shinyWidgets :: textInputIcon (
inputId = ns ( " na_label" ) ,
label = datamods ::: i18n ( " Missing values character(s):" ) ,
value = " NA,,'',na" ,
icon = list ( " NA" ) ,
size = " sm" ,
width = " 100%"
) ,
2025-03-13 12:41:50 +01:00
shiny :: helpText ( phosphoricons :: ph ( " info" ) , datamods ::: i18n ( " if several use a comma (',') to separate them" ) )
2025-03-11 13:42:57 +01:00
)
) ,
shiny :: column (
width = 6 ,
shinyWidgets :: textInputIcon (
inputId = ns ( " dec" ) ,
label = datamods ::: i18n ( " Decimal separator:" ) ,
value = " ." ,
icon = list ( " 0.00" ) ,
size = " sm" ,
width = " 100%"
) ,
selectInputIcon (
inputId = ns ( " encoding" ) ,
label = datamods ::: i18n ( " Encoding:" ) ,
2025-03-13 12:41:50 +01:00
choices = c (
" UTF-8" = " UTF-8" ,
" Latin1" = " latin1"
) ,
2025-03-11 13:42:57 +01:00
icon = phosphoricons :: ph ( " text-aa" ) ,
size = " sm" ,
width = " 100%"
)
)
)
file_ui <- shiny :: tagAppendAttributes (
shiny :: fileInput (
inputId = ns ( " file" ) ,
label = datamods ::: i18n ( " Upload a file:" ) ,
buttonLabel = datamods ::: i18n ( " Browse..." ) ,
placeholder = datamods ::: i18n ( " No file selected" ) ,
accept = file_extensions ,
2025-03-17 15:00:13 +01:00
width = " 100%" ,
## A solution to allow multiple file upload is being considered
multiple = FALSE
2025-03-11 13:42:57 +01:00
) ,
class = " mb-0"
)
if ( identical ( layout_params , " dropdown" ) ) {
file_ui <- shiny :: tags $ div (
style = htmltools :: css (
display = " grid" ,
gridTemplateColumns = " 1fr 50px" ,
gridColumnGap = " 10px"
) ,
file_ui ,
shiny :: tags $ div (
class = " shiny-input-container" ,
shiny :: tags $ label (
class = " control-label" ,
`for` = ns ( " dropdown_params" ) ,
" ..." ,
style = htmltools :: css ( visibility = " hidden" )
) ,
shinyWidgets :: dropMenu (
shiny :: actionButton (
inputId = ns ( " dropdown_params" ) ,
2025-03-13 12:41:50 +01:00
label = phosphoricons :: ph ( " gear" , title = " Parameters" ) ,
2025-03-11 13:42:57 +01:00
width = " 50px" ,
class = " px-1"
) ,
params_ui
)
)
)
}
2025-03-13 12:41:50 +01:00
shiny :: tags $ div (
2025-03-11 13:42:57 +01:00
class = " datamods-import" ,
datamods ::: html_dependency_datamods ( ) ,
title ,
file_ui ,
if ( identical ( layout_params , " inline" ) ) params_ui ,
2025-03-13 12:41:50 +01:00
shiny :: tags $ div (
2025-03-11 13:42:57 +01:00
class = " hidden" ,
id = ns ( " sheet-container" ) ,
shinyWidgets :: pickerInput (
inputId = ns ( " sheet" ) ,
label = datamods ::: i18n ( " Select sheet to import:" ) ,
choices = NULL ,
2025-03-13 12:41:50 +01:00
width = " 100%" ,
multiple = TRUE
2025-03-11 13:42:57 +01:00
)
) ,
2025-03-13 12:41:50 +01:00
shiny :: tags $ div (
2025-03-11 13:42:57 +01:00
id = ns ( " import-placeholder" ) ,
shinyWidgets :: alert (
id = ns ( " import-result" ) ,
status = " info" ,
shiny :: tags $ b ( datamods ::: i18n ( " No file selected:" ) ) ,
sprintf ( datamods ::: i18n ( " You can import %s files" ) , paste ( file_extensions , collapse = " , " ) ) ,
dismissible = TRUE
)
) ,
if ( isTRUE ( preview_data ) ) {
2025-03-17 15:00:13 +01:00
toastui :: datagridOutput2 ( outputId = ns ( " table" ) )
} ,
2025-03-13 12:41:50 +01:00
shiny :: uiOutput (
2025-03-11 13:42:57 +01:00
outputId = ns ( " container_confirm_btn" ) ,
style = " margin-top: 20px;"
2025-03-17 15:00:13 +01:00
) ,
2025-03-11 13:42:57 +01:00
tags $ div (
style = htmltools :: css ( display = " none" ) ,
2025-03-17 15:00:13 +01:00
shiny :: checkboxInput (
inputId = ns ( " preview_data" ) ,
label = NULL ,
value = isTRUE ( preview_data )
)
2025-03-11 13:42:57 +01:00
)
)
}
#'
#' @export
#'
#'
#' @rdname import-file
import_file_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" ) ,
2025-03-17 15:00:13 +01:00
reset = reactive ( NULL ) ) {
read_fns <- list (
ods = " import_ods" ,
dta = " import_dta" ,
csv = " import_delim" ,
tsv = " import_delim" ,
txt = " import_delim" ,
xls = " import_xls" ,
xlsx = " import_xls" ,
rds = " import_rds"
)
2025-03-11 13:42:57 +01:00
trigger_return <- match.arg ( trigger_return )
return_class <- match.arg ( return_class )
module <- function ( input , output , session ) {
ns <- session $ ns
2025-03-13 12:41:50 +01:00
imported_rv <- shiny :: reactiveValues ( data = NULL , name = NULL )
2025-04-11 13:23:18 +02:00
temporary_rv <- shiny :: reactiveValues ( data = NULL , name = NULL , status = NULL , sheets = 1 )
2025-03-11 13:42:57 +01:00
2025-03-13 12:41:50 +01:00
shiny :: observeEvent ( reset ( ) , {
2025-03-11 13:42:57 +01:00
temporary_rv $ data <- NULL
temporary_rv $ name <- NULL
temporary_rv $ status <- NULL
} )
2025-03-13 12:41:50 +01:00
output $ container_confirm_btn <- shiny :: renderUI ( {
2025-03-11 13:42:57 +01:00
if ( identical ( trigger_return , " button" ) ) {
datamods ::: button_import ( )
}
} )
2025-03-13 12:41:50 +01:00
shiny :: observeEvent ( input $ file , {
2025-04-11 13:23:18 +02:00
## Several steps are taken to ensure no errors on changed input file
temporary_rv $ sheets <- 1
2025-03-13 12:41:50 +01:00
if ( isTRUE ( is_workbook ( input $ file $ datapath ) ) ) {
if ( isTRUE ( is_excel ( input $ file $ datapath ) ) ) {
2025-04-11 13:23:18 +02:00
temporary_rv $ sheets <- readxl :: excel_sheets ( input $ file $ datapath )
2025-03-13 12:41:50 +01:00
} else if ( isTRUE ( is_ods ( input $ file $ datapath ) ) ) {
2025-04-11 13:23:18 +02:00
temporary_rv $ sheets <- readODS :: ods_sheets ( input $ file $ datapath )
2025-03-13 12:41:50 +01:00
}
2025-04-11 13:23:18 +02:00
selected <- temporary_rv $ sheets [1 ]
2025-03-13 12:41:50 +01:00
2025-03-11 13:42:57 +01:00
shinyWidgets :: updatePickerInput (
session = session ,
inputId = " sheet" ,
2025-04-08 13:45:07 +02:00
selected = selected ,
2025-04-11 13:23:18 +02:00
choices = temporary_rv $ sheets
2025-03-11 13:42:57 +01:00
)
datamods ::: showUI ( paste0 ( " #" , ns ( " sheet-container" ) ) )
} else {
datamods ::: hideUI ( paste0 ( " #" , ns ( " sheet-container" ) ) )
}
} )
2025-03-13 12:41:50 +01:00
observeEvent (
list (
input $ file ,
input $ sheet ,
input $ skip_rows ,
input $ dec ,
input $ encoding ,
input $ na_label
) ,
{
req ( input $ file )
2025-04-11 13:23:18 +02:00
if ( ! all ( input $ sheet %in% temporary_rv $ sheets ) ) {
2025-04-23 14:25:38 +02:00
sheets <- 1
2025-04-11 13:23:18 +02:00
} else {
sheets <- input $ sheet
}
2025-03-13 12:41:50 +01:00
extension <- tools :: file_ext ( input $ file $ datapath )
2025-03-11 13:42:57 +01:00
parameters <- list (
file = input $ file $ datapath ,
2025-04-11 13:23:18 +02:00
sheet = sheets ,
2025-03-11 13:42:57 +01:00
skip = input $ skip_rows ,
dec = input $ dec ,
encoding = input $ encoding ,
na.strings = datamods ::: split_char ( input $ na_label )
)
2025-03-17 20:26:30 +01:00
2025-03-17 15:00:13 +01:00
parameters <- parameters [which ( names ( parameters ) %in% rlang :: fn_fmls_names ( get ( read_fns [ [extension ] ] ) ) ) ]
# parameters <- parameters[which(names(parameters) %in% rlang::fn_fmls_names(read_fns[[extension]]))]
2025-03-11 13:42:57 +01:00
imported <- try ( rlang :: exec ( read_fns [ [extension ] ] , ! ! ! parameters ) , silent = TRUE )
2025-03-19 13:10:56 +01:00
code <- rlang :: call2 ( read_fns [ [extension ] ] , ! ! ! modifyList ( parameters , list ( file = input $ file $ name ) ) , .ns = " FreesearchR" )
2025-03-11 13:42:57 +01:00
2025-03-13 12:41:50 +01:00
if ( inherits ( imported , " try-error" ) ) {
imported <- try ( rlang :: exec ( rio :: import , ! ! ! parameters [1 ] ) , silent = TRUE )
code <- rlang :: call2 ( " import" , ! ! ! list ( file = input $ file $ name ) , .ns = " rio" )
}
2025-03-11 13:42:57 +01:00
2025-03-13 12:41:50 +01:00
if ( inherits ( imported , " try-error" ) || NROW ( imported ) < 1 ) {
datamods ::: toggle_widget ( inputId = " confirm" , enable = FALSE )
datamods ::: insert_error ( mssg = datamods ::: i18n ( attr ( imported , " condition" ) $ message ) )
temporary_rv $ status <- " error"
temporary_rv $ data <- NULL
temporary_rv $ name <- NULL
temporary_rv $ code <- 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 ,
extra = if ( isTRUE ( input $ preview_data ) ) datamods ::: i18n ( " First five rows are shown below:" )
)
2025-03-11 13:42:57 +01:00
)
2025-03-13 12:41:50 +01:00
temporary_rv $ status <- " success"
temporary_rv $ data <- imported
temporary_rv $ name <- input $ file $ name
temporary_rv $ code <- code
}
} ,
ignoreInit = TRUE
)
2025-03-11 13:42:57 +01:00
observeEvent ( input $ see_data , {
2025-04-23 14:25:38 +02:00
tryCatch (
{
datamods ::: show_data ( default_parsing ( temporary_rv $ data ) , title = datamods ::: i18n ( " Imported data" ) , type = show_data_in )
} ,
# warning = function(warn) {
# showNotification(warn, type = "warning")
# },
error = function ( err ) {
showNotification ( err , type = " err" )
}
2025-04-08 13:45:07 +02:00
)
2025-03-11 13:42:57 +01:00
} )
output $ table <- toastui :: renderDatagrid2 ( {
req ( temporary_rv $ data )
2025-04-23 14:25:38 +02:00
tryCatch (
{
toastui :: datagrid (
data = setNames ( head ( temporary_rv $ data , 5 ) , make.names ( names ( temporary_rv $ data ) , unique = TRUE ) ) ,
theme = " striped" ,
colwidths = " guess" ,
minBodyHeight = 250
)
} ,
error = function ( err ) {
showNotification ( err , type = " err" )
}
2025-04-08 13:45:07 +02:00
)
2025-03-11 13:42:57 +01:00
} )
observeEvent ( input $ confirm , {
imported_rv $ data <- temporary_rv $ data
imported_rv $ name <- temporary_rv $ name
imported_rv $ code <- temporary_rv $ code
} )
if ( identical ( trigger_return , " button" ) ) {
return ( list (
status = reactive ( temporary_rv $ status ) ,
name = reactive ( imported_rv $ name ) ,
code = reactive ( imported_rv $ code ) ,
data = reactive ( datamods ::: as_out ( imported_rv $ data , return_class ) )
) )
} else {
return ( list (
status = reactive ( temporary_rv $ status ) ,
name = reactive ( temporary_rv $ name ) ,
code = reactive ( temporary_rv $ code ) ,
data = reactive ( datamods ::: as_out ( temporary_rv $ data , return_class ) )
) )
}
}
moduleServer (
id = id ,
module = module
)
}
# utils -------------------------------------------------------------------
is_excel <- function ( path ) {
isTRUE ( tools :: file_ext ( path ) %in% c ( " xls" , " xlsx" ) )
}
2025-03-12 18:27:46 +01:00
is_ods <- function ( path ) {
isTRUE ( tools :: file_ext ( path ) %in% c ( " ods" ) )
}
is_sas <- function ( path ) {
isTRUE ( tools :: file_ext ( path ) %in% c ( " sas7bdat" ) )
}
2025-03-13 12:41:50 +01:00
is_workbook <- function ( path ) {
is_excel ( path ) || is_ods ( path )
}
2025-03-17 15:00:13 +01:00
# File import functions ---------------------------------------------------
#' Wrapper to ease data file import
#'
#' @param file path to the file
#' @param sheet for Excel files, sheet to read
#' @param skip number of row to skip
#' @param encoding file encoding
#' @param na.strings character(s) to interpret as missing values.
#'
2025-03-12 18:27:46 +01:00
#'
2025-03-17 15:00:13 +01:00
#' @name import-file-type
2025-03-12 18:27:46 +01:00
#'
#' @returns data.frame
#' @export
#'
import_delim <- function ( file , skip , encoding , na.strings ) {
data.table :: fread (
file = file ,
na.strings = na.strings ,
skip = skip ,
2025-03-13 12:41:50 +01:00
check.names = TRUE ,
encoding = encoding ,
data.table = FALSE ,
logical01 = TRUE ,
logicalYN = TRUE ,
keepLeadingZeros = TRUE
)
}
2025-03-17 15:00:13 +01:00
#' @name import-file-type
#'
#' @returns data.frame
#' @export
#'
2025-03-13 12:41:50 +01:00
import_xls <- function ( file , sheet , skip , na.strings ) {
tryCatch (
{
2025-04-11 13:23:18 +02:00
## If sheet is null, this allows purrr::map to run
if ( is.null ( sheet ) ) sheet <- 1
2025-03-13 12:41:50 +01:00
sheet | >
purrr :: map ( \ ( .x ) {
2025-04-23 14:25:38 +02:00
readxl :: read_excel (
path = file ,
2025-03-13 12:41:50 +01:00
sheet = .x ,
2025-04-23 14:25:38 +02:00
na = na.strings ,
skip = skip ,
.name_repair = " unique_quiet" ,
trim_ws = TRUE
2025-03-13 12:41:50 +01:00
)
2025-04-23 14:25:38 +02:00
# openxlsx2::read_xlsx(
# file = file,
# sheet = .x,
# skip_empty_rows = TRUE,
# start_row = skip - 1,
# na.strings = na.strings
# )
2025-03-13 12:41:50 +01:00
} ) | >
purrr :: reduce ( dplyr :: full_join )
} ,
2025-04-08 13:45:07 +02:00
# warning = function(warn) {
# showNotification(paste0(warn), type = "warning")
# },
2025-03-13 12:41:50 +01:00
error = function ( err ) {
showNotification ( paste0 ( err ) , type = " err" )
}
)
}
2025-03-17 15:00:13 +01:00
#' @name import-file-type
#'
#' @returns data.frame
#' @export
#'
2025-03-13 12:41:50 +01:00
import_ods <- function ( file , sheet , skip , na.strings ) {
2025-03-13 14:13:18 +01:00
tryCatch (
{
2025-04-11 13:23:18 +02:00
if ( is.null ( sheet ) ) sheet <- 1
2025-03-13 14:13:18 +01:00
sheet | >
purrr :: map ( \ ( .x ) {
readODS :: read_ods (
path = file ,
sheet = .x ,
skip = skip ,
na = na.strings
)
} ) | >
purrr :: reduce ( dplyr :: full_join )
} ,
2025-04-08 13:45:07 +02:00
# warning = function(warn) {
# showNotification(paste0(warn), type = "warning")
# },
2025-03-13 14:13:18 +01:00
error = function ( err ) {
showNotification ( paste0 ( err ) , type = " err" )
}
2025-03-12 18:27:46 +01:00
)
}
2025-03-17 15:00:13 +01:00
#' @name import-file-type
#'
#' @returns data.frame
#' @export
#'
import_dta <- function ( file ) {
haven :: read_dta (
file = file ,
.name_repair = " unique_quiet"
)
}
#' @name import-file-type
#'
#' @returns data.frame
#' @export
#'
import_rds <- function ( file ) {
readr :: read_rds (
2025-04-02 11:31:04 +02:00
file = file
2025-03-17 15:00:13 +01:00
)
}
2025-03-12 18:27:46 +01:00
#' @title Create a select input control with icon(s)
#'
#' @description Extend form controls by adding text or icons before,
#' after, or on both sides of a classic `selectInput`.
#'
#' @inheritParams shiny::selectInput
#'
#' @return A numeric input control that can be added to a UI definition.
#' @export
#'
#' @importFrom shiny restoreInput
#' @importFrom htmltools tags validateCssUnit css
#'
selectInputIcon <- function ( inputId ,
label ,
choices ,
selected = NULL ,
multiple = FALSE ,
selectize = TRUE ,
size = NULL ,
width = NULL ,
icon = NULL ) {
selected <- shiny :: restoreInput ( id = inputId , default = selected )
2025-03-13 12:41:50 +01:00
shiny :: tags $ div (
2025-03-12 18:27:46 +01:00
class = " form-group shiny-input-container" ,
shinyWidgets ::: label_input ( inputId , label ) ,
style = htmltools ::: css ( width = htmltools ::: validateCssUnit ( width ) ) ,
2025-03-13 12:41:50 +01:00
shiny :: tags $ div (
2025-03-12 18:27:46 +01:00
class = " input-group" ,
class = shinyWidgets ::: validate_size ( size ) ,
shinyWidgets ::: markup_input_group ( icon , " left" , theme_func = shiny :: getCurrentTheme ) ,
shiny :: tags $ select (
id = inputId ,
class = " form-control select-input-icon" ,
shiny ::: selectOptions ( choices , selected , inputId , selectize )
) ,
shinyWidgets ::: markup_input_group ( icon , " right" , theme_func = shiny :: getCurrentTheme )
) ,
shinyWidgets ::: html_dependency_input_icons ( )
)
}
2025-03-13 14:13:18 +01:00
#' Test app for the import_file module
#'
#' @rdname import-file_module
#'
#' @examples
#' \dontrun{
#' import_file_demo_app()
#' }
import_file_demo_app <- function ( ) {
ui <- shiny :: fluidPage (
# theme = bslib::bs_theme(version = 5L),
# theme = bslib::bs_theme(version = 5L, preset = "bootstrap"),
shiny :: tags $ h3 ( " Import data from a file" ) ,
shiny :: fluidRow (
shiny :: column (
width = 4 ,
import_file_ui (
id = " myid" ,
file_extensions = c ( " .csv" , " .tsv" , " .txt" , " .xls" , " .xlsx" , " .rds" , " .sas7bdat" , " .ods" , " .dta" ) ,
layout_params = " dropdown" # "inline" # or "dropdown"
)
) ,
shiny :: column (
width = 8 ,
shiny :: tags $ b ( " Import status:" ) ,
shiny :: verbatimTextOutput ( outputId = " status" ) ,
shiny :: tags $ b ( " Name:" ) ,
shiny :: verbatimTextOutput ( outputId = " name" ) ,
shiny :: tags $ b ( " Code:" ) ,
shiny :: verbatimTextOutput ( outputId = " code" ) ,
shiny :: tags $ b ( " Data:" ) ,
shiny :: verbatimTextOutput ( outputId = " data" )
2025-03-12 18:27:46 +01:00
)
)
)
2025-03-13 14:13:18 +01:00
server <- function ( input , output , session ) {
imported <- import_file_server (
id = " myid" ,
show_data_in = " popup" ,
trigger_return = " change" ,
2025-03-17 15:00:13 +01:00
return_class = " data.frame"
2025-03-13 12:41:50 +01:00
)
2025-03-12 18:27:46 +01:00
2025-03-13 14:13:18 +01:00
output $ status <- shiny :: renderPrint ( {
imported $ status ( )
} )
output $ name <- shiny :: renderPrint ( {
imported $ name ( )
} )
output $ code <- shiny :: renderPrint ( {
imported $ code ( )
} )
output $ data <- shiny :: renderPrint ( {
imported $ data ( )
} )
}
2025-03-13 12:41:50 +01:00
shiny :: shinyApp ( ui , server )
}
2025-03-12 18:27:46 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//launch_FreesearchR.R
2025-03-19 13:10:56 +01:00
########
#' Easily launch the FreesearchR app
#'
#' @description
#' All data.frames in the global environment will be accessible through the app.
#'
#' @param ... passed on to `shiny::runApp()`
#'
#' @returns shiny app
#' @export
#'
#' @examples
#' \dontrun{
#' data(mtcars)
2025-04-29 12:11:38 +02:00
#' launch_FreesearchR(launch.browser = TRUE)
2025-03-19 13:10:56 +01:00
#' }
launch_FreesearchR <- function ( ... ) {
appDir <- system.file ( " apps" , " FreesearchR" , package = " FreesearchR" )
if ( appDir == " " ) {
stop ( " Could not find the app directory. Try re-installing `FreesearchR`." , call. = FALSE )
}
a <- shiny :: runApp ( appDir = paste0 ( appDir , " /app.R" ) , ... )
return ( invisible ( a ) )
}
2025-04-29 12:11:38 +02:00
2025-03-19 13:10:56 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//plot_box.R
2025-03-19 13:10:56 +01:00
########
#' Beautiful box plot(s)
#'
#' @returns ggplot2 object
#' @export
#'
#' @name data-plots
#'
#' @examples
2025-04-15 16:14:03 +02:00
#' mtcars |> plot_box(pri = "mpg", sec = "cyl", ter = "gear")
2025-03-19 13:10:56 +01:00
#' mtcars |>
#' default_parsing() |>
2025-04-15 16:14:03 +02:00
#' plot_box(pri = "mpg", sec = "cyl", ter = "gear")
plot_box <- function ( data , pri , sec , ter = NULL ) {
if ( ! is.null ( ter ) ) {
ds <- split ( data , data [ter ] )
2025-03-19 13:10:56 +01:00
} else {
ds <- list ( data )
}
out <- lapply ( ds , \ ( .ds ) {
plot_box_single (
data = .ds ,
2025-04-15 16:14:03 +02:00
pri = pri ,
sec = sec
2025-03-19 13:10:56 +01:00
)
} )
wrap_plot_list ( out )
}
#' Create nice box-plots
#'
#' @name data-plots
#'
2025-03-26 12:07:28 +01:00
#' @returns ggplot object
2025-03-19 13:10:56 +01:00
#' @export
#'
#' @examples
2025-04-23 14:25:38 +02:00
#' mtcars |> plot_box_single("mpg")
2025-03-19 13:10:56 +01:00
#' mtcars |> plot_box_single("mpg","cyl")
2025-04-15 16:14:03 +02:00
plot_box_single <- function ( data , pri , sec = NULL , seed = 2103 ) {
2025-03-19 13:10:56 +01:00
set.seed ( seed )
2025-04-15 16:14:03 +02:00
if ( is.null ( sec ) ) {
sec <- " All"
2025-04-23 14:25:38 +02:00
data [ [sec ] ] <- sec
2025-03-19 13:10:56 +01:00
}
2025-04-15 16:14:03 +02:00
discrete <- ! data_type ( data [ [sec ] ] ) %in% " continuous"
2025-03-19 13:10:56 +01:00
data | >
2025-04-15 16:14:03 +02:00
ggplot2 :: ggplot ( ggplot2 :: aes ( x = ! ! dplyr :: sym ( pri ) , y = ! ! dplyr :: sym ( sec ) , fill = ! ! dplyr :: sym ( sec ) , group = ! ! dplyr :: sym ( sec ) ) ) +
2025-03-19 13:10:56 +01:00
ggplot2 :: geom_boxplot ( linewidth = 1.8 , outliers = FALSE ) +
## THis could be optional in future
2025-04-28 08:31:23 +02:00
ggplot2 :: geom_jitter ( color = " black" , size = 2 , alpha = 0.9 , width = 0.1 , height = .2 ) +
2025-03-19 13:10:56 +01:00
ggplot2 :: coord_flip ( ) +
2025-03-19 13:28:34 +01:00
viridis :: scale_fill_viridis ( discrete = discrete , option = " D" ) +
2025-03-19 13:10:56 +01:00
# ggplot2::theme_void() +
2025-03-19 13:28:34 +01:00
ggplot2 :: theme_bw ( base_size = 24 ) +
2025-03-19 13:10:56 +01:00
ggplot2 :: theme (
legend.position = " none" ,
# panel.grid.major = element_blank(),
# panel.grid.minor = element_blank(),
# axis.text.y = element_blank(),
# axis.title.y = element_blank(),
2025-03-19 13:28:34 +01:00
# text = ggplot2::element_text(size = 20),
2025-03-19 13:10:56 +01:00
# axis.text = ggplot2::element_blank(),
# plot.title = element_blank(),
panel.background = ggplot2 :: element_rect ( fill = " white" ) ,
plot.background = ggplot2 :: element_rect ( fill = " white" ) ,
2025-03-19 13:28:34 +01:00
panel.border = ggplot2 :: element_blank ( ) ,
panel.grid.major = ggplot2 :: element_blank ( ) ,
panel.grid.minor = ggplot2 :: element_blank ( ) ,
axis.line = ggplot2 :: element_line ( colour = " black" ) ,
axis.ticks = ggplot2 :: element_line ( colour = " black" )
2025-03-19 13:10:56 +01:00
)
}
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//plot_euler.R
2025-03-12 18:27:46 +01:00
########
#' Area proportional venn diagrams
#'
#' @description
#' 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
#'
#' @param combinations set relationships as a named numeric vector, matrix, or
#' data.frame(See `eulerr::euler`)
#' @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
ggeulerr <- function (
combinations ,
show_quantities = TRUE ,
show_labels = TRUE ,
... ) {
# browser()
data <-
eulerr :: euler ( combinations = combinations , ... ) | >
plot ( quantities = show_quantities ) | >
purrr :: pluck ( " data" )
tibble :: as_tibble ( data $ ellipses , rownames = " Variables" ) | >
ggplot2 :: ggplot ( ) +
ggforce :: geom_ellipse (
mapping = ggplot2 :: aes (
x0 = h , y0 = k , a = a , b = b , angle = 0 , fill = Variables
) ,
alpha = 0.5 ,
linewidth = 1.5
) +
ggplot2 :: geom_text (
data = {
data $ centers | >
dplyr :: mutate (
label = labels | > purrr :: map2 ( quantities , ~ {
if ( ! is.na ( .x ) && ! is.na ( .y ) && show_labels ) {
paste0 ( .x , " \n" , sprintf ( .y , fmt = " %.2g" ) )
} else if ( ! is.na ( .x ) && show_labels ) {
.x
} else if ( ! is.na ( .y ) ) {
.y
} else {
" "
}
} )
)
} ,
mapping = ggplot2 :: aes ( x = x , y = y , label = label ) ,
size = 8
) +
ggplot2 :: theme ( panel.grid = ggplot2 :: element_blank ( ) ) +
ggplot2 :: coord_fixed ( ) +
ggplot2 :: scale_fill_hue ( )
}
#' Easily plot euler diagrams
#'
#' @param data data
#' @param x name of main variable
#' @param y name of secondary variables
#' @param z grouping variable
#' @param seed seed
#'
#' @returns patchwork object
#' @export
#'
#' @examples
#' data.frame(
#' A = sample(c(TRUE, TRUE, FALSE), 50, TRUE),
#' B = sample(c("A", "C"), 50, TRUE),
#' C = sample(c(TRUE, FALSE, FALSE, FALSE), 50, TRUE),
#' D = sample(c(TRUE, FALSE, FALSE, FALSE), 50, TRUE)
#' ) |> plot_euler("A", c("B", "C"), "D", seed = 4)
#' mtcars |> plot_euler("vs", "am", seed = 1)
2025-04-15 16:14:03 +02:00
plot_euler <- function ( data , pri , sec , ter = NULL , seed = 2103 ) {
2025-03-12 18:27:46 +01:00
set.seed ( seed = seed )
2025-04-15 16:14:03 +02:00
if ( ! is.null ( ter ) ) {
ds <- split ( data , data [ter ] )
2025-03-12 18:27:46 +01:00
} else {
ds <- list ( data )
}
out <- lapply ( ds , \ ( .x ) {
2025-04-15 16:14:03 +02:00
.x [c ( pri , sec ) ] | >
2025-03-12 18:27:46 +01:00
as.data.frame ( ) | >
plot_euler_single ( )
} )
2025-03-19 13:10:56 +01:00
# names(out)
2025-03-13 12:41:50 +01:00
wrap_plot_list ( out )
# patchwork::wrap_plots(out, guides = "collect")
2025-03-12 18:27:46 +01:00
}
#' Easily plot single euler diagrams
#'
#' @returns ggplot2 object
#' @export
#'
#' @examples
#' data.frame(
#' A = sample(c(TRUE, TRUE, FALSE), 50, TRUE),
#' B = sample(c("A", "C"), 50, TRUE),
#' C = sample(c(TRUE, FALSE, FALSE, FALSE), 50, TRUE),
#' D = sample(c(TRUE, FALSE, FALSE, FALSE), 50, TRUE)
#' ) |> plot_euler_single()
#' mtcars[c("vs", "am")] |> plot_euler_single()
plot_euler_single <- function ( data ) {
2025-05-05 14:45:07 +02:00
# if (any("categorical" %in% data_type(data))){
# shape <- "ellipse"
# } else {
# shape <- "circle"
# }
2025-03-12 18:27:46 +01:00
data | >
ggeulerr ( shape = " circle" ) +
ggplot2 :: theme_void ( ) +
ggplot2 :: theme (
2025-03-19 13:10:56 +01:00
legend.position = " none" ,
2025-03-12 18:27:46 +01:00
# panel.grid.major = element_blank(),
# panel.grid.minor = element_blank(),
# axis.text.y = element_blank(),
# axis.title.y = element_blank(),
text = ggplot2 :: element_text ( size = 20 ) ,
axis.text = ggplot2 :: element_blank ( ) ,
# plot.title = element_blank(),
# panel.background = ggplot2::element_rect(fill = "white"),
plot.background = ggplot2 :: element_rect ( fill = " white" ) ,
panel.border = ggplot2 :: element_blank ( )
)
}
2025-03-13 12:41:50 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//plot_hbar.R
2025-03-13 12:41:50 +01:00
########
#' Nice horizontal stacked bars (Grotta bars)
#'
#' @returns ggplot2 object
#' @export
#'
#' @name data-plots
#'
#' @examples
2025-04-15 16:14:03 +02:00
#' mtcars |> plot_hbars(pri = "carb", sec = "cyl")
#' mtcars |> plot_hbars(pri = "carb", sec = NULL)
plot_hbars <- function ( data , pri , sec , ter = NULL ) {
out <- vertical_stacked_bars ( data = data , score = pri , group = sec , strata = ter )
2025-03-13 12:41:50 +01:00
out
}
#' Vertical stacked bar plot wrapper
#'
#' @param data data.frame
#' @param score outcome variable
#' @param group grouping variable
#' @param strata stratifying variable
#' @param t.size text size
#'
#' @return ggplot2 object
#' @export
#'
vertical_stacked_bars <- function ( data ,
score = " full_score" ,
group = " pase_0_q" ,
strata = NULL ,
t.size = 10 ,
l.color = " black" ,
l.size = .5 ,
draw.lines = TRUE ) {
if ( is.null ( group ) ) {
df.table <- data [c ( score , group , strata ) ] | >
dplyr :: mutate ( " All" = 1 ) | >
table ( )
group <- " All"
draw.lines <- FALSE
} else {
df.table <- data [c ( score , group , strata ) ] | >
table ( )
}
p <- df.table | >
rankinPlot :: grottaBar (
scoreName = score ,
groupName = group ,
textColor = c ( " black" , " white" ) ,
strataName = strata ,
textCut = 6 ,
textSize = 20 ,
printNumbers = " none" ,
lineSize = l.size ,
returnData = TRUE
)
colors <- viridisLite :: viridis ( nrow ( df.table ) )
contrast_cut <-
sum ( contrast_text ( colors , threshold = .3 ) == " white" )
score_label <- ifelse ( is.na ( REDCapCAST :: get_attr ( data $ score , " label" ) ) , score , REDCapCAST :: get_attr ( data $ score , " label" ) )
group_label <- ifelse ( is.na ( REDCapCAST :: get_attr ( data $ group , " label" ) ) , group , REDCapCAST :: get_attr ( data $ group , " label" ) )
p | >
( \ ( .x ) {
.x $ plot +
ggplot2 :: geom_text (
data = .x $ rectData [which ( .x $ rectData $ n >
0 ) , ] ,
size = t.size ,
fontface = " plain" ,
ggplot2 :: aes (
x = group ,
y = p_prev + 0.49 * p ,
color = as.numeric ( score ) > contrast_cut ,
# label = paste0(sprintf("%2.0f", 100 * p),"%"),
label = sprintf ( " %2.0f" , 100 * p )
)
) +
ggplot2 :: labs ( fill = score_label ) +
ggplot2 :: scale_fill_manual ( values = rev ( colors ) ) +
ggplot2 :: theme (
legend.position = " bottom" ,
axis.title = ggplot2 :: element_text ( ) ,
) +
ggplot2 :: xlab ( group_label ) +
ggplot2 :: ylab ( NULL )
# viridis::scale_fill_viridis(discrete = TRUE, direction = -1, option = "D")
} ) ( )
}
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//plot_ridge.R
2025-03-13 12:41:50 +01:00
########
#' Plot nice ridge plot
#'
#' @returns ggplot2 object
#' @export
#'
#' @name data-plots
#'
#' @examples
#' mtcars |>
#' default_parsing() |>
#' plot_ridge(x = "mpg", y = "cyl")
#' mtcars |> plot_ridge(x = "mpg", y = "cyl", z = "gear")
plot_ridge <- function ( data , x , y , z = NULL , ... ) {
if ( ! is.null ( z ) ) {
ds <- split ( data , data [z ] )
} else {
ds <- list ( data )
}
out <- lapply ( ds , \ ( .ds ) {
ggplot2 :: ggplot ( .ds , ggplot2 :: aes ( x = ! ! dplyr :: sym ( x ) , y = ! ! dplyr :: sym ( y ) , fill = ! ! dplyr :: sym ( y ) ) ) +
ggridges :: geom_density_ridges ( ) +
ggridges :: theme_ridges ( ) +
ggplot2 :: theme ( legend.position = " none" ) | > rempsyc ::: theme_apa ( )
} )
patchwork :: wrap_plots ( out )
}
2025-03-12 18:27:46 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//plot_sankey.R
2025-03-12 18:27:46 +01:00
########
#' Readying data for sankey plot
#'
#' @name data-plots
#'
#' @returns data.frame
#' @export
#'
#' @examples
#' ds <- data.frame(g = sample(LETTERS[1:2], 100, TRUE), first = REDCapCAST::as_factor(sample(letters[1:4], 100, TRUE)), last = sample(c(letters[1:4], NA), 100, TRUE, prob = c(rep(.23, 4), .08)))
#' ds |> sankey_ready("first", "last")
#' ds |> sankey_ready("first", "last", numbers = "percentage")
#' data.frame(
#' g = sample(LETTERS[1:2], 100, TRUE),
#' first = REDCapCAST::as_factor(sample(letters[1:4], 100, TRUE)),
#' last = sample(c(TRUE, FALSE, FALSE), 100, TRUE)
#' ) |>
#' sankey_ready("first", "last")
2025-04-15 16:14:03 +02:00
sankey_ready <- function ( data , pri , sec , numbers = " count" , ... ) {
2025-03-12 18:27:46 +01:00
## TODO: Ensure ordering x and y
## Ensure all are factors
2025-04-15 16:14:03 +02:00
data [c ( pri , sec ) ] <- data [c ( pri , sec ) ] | >
2025-03-12 18:27:46 +01:00
dplyr :: mutate ( dplyr :: across ( ! dplyr :: where ( is.factor ) , forcats :: as_factor ) )
2025-04-23 14:25:38 +02:00
out <- dplyr :: count ( data , ! ! dplyr :: sym ( pri ) , ! ! dplyr :: sym ( sec ) , .drop = FALSE )
2025-03-12 18:27:46 +01:00
out <- out | >
2025-04-15 16:14:03 +02:00
dplyr :: group_by ( ! ! dplyr :: sym ( pri ) ) | >
2025-03-12 18:27:46 +01:00
dplyr :: mutate ( gx.sum = sum ( n ) ) | >
dplyr :: ungroup ( ) | >
2025-04-15 16:14:03 +02:00
dplyr :: group_by ( ! ! dplyr :: sym ( sec ) ) | >
2025-03-12 18:27:46 +01:00
dplyr :: mutate ( gy.sum = sum ( n ) ) | >
dplyr :: ungroup ( )
if ( numbers == " count" ) {
out <- out | > dplyr :: mutate (
2025-04-15 16:14:03 +02:00
lx = factor ( paste0 ( ! ! dplyr :: sym ( pri ) , " \n(n=" , gx.sum , " )" ) ) ,
ly = factor ( paste0 ( ! ! dplyr :: sym ( sec ) , " \n(n=" , gy.sum , " )" ) )
2025-03-12 18:27:46 +01:00
)
} else if ( numbers == " percentage" ) {
out <- out | > dplyr :: mutate (
2025-04-15 16:14:03 +02:00
lx = factor ( paste0 ( ! ! dplyr :: sym ( pri ) , " \n(" , round ( ( gx.sum / sum ( n ) ) * 100 , 1 ) , " %)" ) ) ,
ly = factor ( paste0 ( ! ! dplyr :: sym ( sec ) , " \n(" , round ( ( gy.sum / sum ( n ) ) * 100 , 1 ) , " %)" ) )
2025-03-12 18:27:46 +01:00
)
}
2025-04-15 16:14:03 +02:00
if ( is.factor ( data [ [pri ] ] ) ) {
index <- match ( levels ( data [ [pri ] ] ) , str_remove_last ( levels ( out $ lx ) , " \n" ) )
2025-03-12 18:27:46 +01:00
out $ lx <- factor ( out $ lx , levels = levels ( out $ lx ) [index ] )
}
2025-04-15 16:14:03 +02:00
if ( is.factor ( data [ [sec ] ] ) ) {
index <- match ( levels ( data [ [sec ] ] ) , str_remove_last ( levels ( out $ ly ) , " \n" ) )
2025-03-12 18:27:46 +01:00
out $ ly <- factor ( out $ ly , levels = levels ( out $ ly ) [index ] )
}
out
}
str_remove_last <- function ( data , pattern = " \n" ) {
strsplit ( data , split = pattern ) | >
lapply ( \ ( .x ) paste ( unlist ( .x [ [ - length ( .x ) ] ] ) , collapse = pattern ) ) | >
unlist ( )
}
#' Beautiful sankey plot with option to split by a tertiary group
#'
#' @returns ggplot2 object
#' @export
#'
#' @name data-plots
#'
#' @examples
#' ds <- data.frame(g = sample(LETTERS[1:2], 100, TRUE), first = REDCapCAST::as_factor(sample(letters[1:4], 100, TRUE)), last = REDCapCAST::as_factor(sample(letters[1:4], 100, TRUE)))
#' ds |> plot_sankey("first", "last")
2025-04-23 14:25:38 +02:00
#' ds |> plot_sankey("first", "last", color.group = "sec")
#' ds |> plot_sankey("first", "last", ter = "g", color.group = "sec")
#' mtcars |>
#' default_parsing() |>
#' plot_sankey("cyl", "gear", "am", color.group = "pri")
#' ## In this case, the last plot as the secondary variable in wrong order
#' ## Dont know why...
#' mtcars |>
#' default_parsing() |>
#' plot_sankey("cyl", "gear", "vs", color.group = "pri")
plot_sankey <- function ( data , pri , sec , ter = NULL , color.group = " pri" , colors = NULL ) {
2025-04-15 16:14:03 +02:00
if ( ! is.null ( ter ) ) {
ds <- split ( data , data [ter ] )
2025-03-12 18:27:46 +01:00
} else {
ds <- list ( data )
}
out <- lapply ( ds , \ ( .ds ) {
2025-04-23 14:25:38 +02:00
plot_sankey_single ( .ds , pri = pri , sec = sec , color.group = color.group , colors = colors )
2025-03-12 18:27:46 +01:00
} )
patchwork :: wrap_plots ( out )
2025-03-11 13:42:57 +01:00
}
2025-03-12 18:27:46 +01:00
#' Beautiful sankey plot
2025-03-11 13:42:57 +01:00
#'
2025-03-12 18:27:46 +01:00
#' @param color.group set group to colour by. "x" or "y".
#' @param colors optinally specify colors. Give NA color, color for each level
#' in primary group and color for each level in secondary group.
#' @param ... passed to sankey_ready()
2025-03-11 13:42:57 +01:00
#'
2025-03-12 18:27:46 +01:00
#' @returns ggplot2 object
2025-03-11 13:42:57 +01:00
#' @export
#'
2025-03-12 18:27:46 +01:00
#' @examples
#' ds <- data.frame(g = sample(LETTERS[1:2], 100, TRUE), first = REDCapCAST::as_factor(sample(letters[1:4], 100, TRUE)), last = REDCapCAST::as_factor(sample(letters[1:4], 100, TRUE)))
#' ds |> plot_sankey_single("first", "last")
2025-04-23 14:25:38 +02:00
#' ds |> plot_sankey_single("first", "last", color.group = "sec")
2025-03-12 18:27:46 +01:00
#' data.frame(
#' g = sample(LETTERS[1:2], 100, TRUE),
#' first = REDCapCAST::as_factor(sample(letters[1:4], 100, TRUE)),
#' last = sample(c(TRUE, FALSE, FALSE), 100, TRUE)
#' ) |>
2025-04-15 16:14:03 +02:00
#' plot_sankey_single("first", "last", color.group = "pri")
2025-04-23 14:25:38 +02:00
#' mtcars |>
#' default_parsing() |>
#' str()
#' plot_sankey_single("cyl", "vs", color.group = "pri")
2025-04-15 16:14:03 +02:00
plot_sankey_single <- function ( data , pri , sec , color.group = c ( " pri" , " sec" ) , colors = NULL , ... ) {
2025-03-12 18:27:46 +01:00
color.group <- match.arg ( color.group )
2025-04-23 14:25:38 +02:00
data_orig <- data
data [c ( pri , sec ) ] <- data [c ( pri , sec ) ] | >
dplyr :: mutate ( dplyr :: across ( dplyr :: where ( is.factor ) , forcats :: fct_drop ) )
# browser()
2025-04-15 16:14:03 +02:00
data <- data | > sankey_ready ( pri = pri , sec = sec , ... )
2025-03-11 13:42:57 +01:00
2025-03-12 18:27:46 +01:00
library ( ggalluvial )
2025-03-11 13:42:57 +01:00
2025-03-12 18:27:46 +01:00
na.color <- " #2986cc"
box.color <- " #1E4B66"
2025-03-11 13:42:57 +01:00
2025-03-12 18:27:46 +01:00
if ( is.null ( colors ) ) {
2025-04-15 16:14:03 +02:00
if ( color.group == " sec" ) {
2025-04-23 14:25:38 +02:00
main.colors <- viridisLite :: viridis ( n = length ( levels ( data_orig [ [sec ] ] ) ) )
## Only keep colors for included levels
main.colors <- main.colors [match ( levels ( data [ [sec ] ] ) , levels ( data_orig [ [sec ] ] ) ) ]
2025-04-15 16:14:03 +02:00
secondary.colors <- rep ( na.color , length ( levels ( data [ [pri ] ] ) ) )
2025-03-12 18:27:46 +01:00
label.colors <- Reduce ( c , lapply ( list ( secondary.colors , rev ( main.colors ) ) , contrast_text ) )
} else {
2025-04-23 14:25:38 +02:00
main.colors <- viridisLite :: viridis ( n = length ( levels ( data_orig [ [pri ] ] ) ) )
## Only keep colors for included levels
main.colors <- main.colors [match ( levels ( data [ [pri ] ] ) , levels ( data_orig [ [pri ] ] ) ) ]
2025-04-15 16:14:03 +02:00
secondary.colors <- rep ( na.color , length ( levels ( data [ [sec ] ] ) ) )
2025-03-12 18:27:46 +01:00
label.colors <- Reduce ( c , lapply ( list ( rev ( main.colors ) , secondary.colors ) , contrast_text ) )
}
colors <- c ( na.color , main.colors , secondary.colors )
} else {
label.colors <- contrast_text ( colors )
}
2025-03-11 13:42:57 +01:00
2025-04-15 16:14:03 +02:00
group_labels <- c ( get_label ( data , pri ) , get_label ( data , sec ) ) | >
2025-03-12 18:27:46 +01:00
sapply ( line_break ) | >
unname ( )
2025-03-11 13:42:57 +01:00
2025-03-12 18:27:46 +01:00
p <- ggplot2 :: ggplot ( data , ggplot2 :: aes ( y = n , axis1 = lx , axis2 = ly ) )
2025-03-11 13:42:57 +01:00
2025-04-15 16:14:03 +02:00
if ( color.group == " sec" ) {
2025-03-12 18:27:46 +01:00
p <- p +
ggalluvial :: geom_alluvium (
2025-04-23 14:25:38 +02:00
ggplot2 :: aes (
fill = ! ! dplyr :: sym ( sec ) # ,
## Including will print strings when levels are empty
# color = !!dplyr::sym(sec)
) ,
2025-03-12 18:27:46 +01:00
width = 1 / 16 ,
alpha = .8 ,
knot.pos = 0.4 ,
curve_type = " sigmoid"
2025-04-15 16:14:03 +02:00
) + ggalluvial :: geom_stratum ( ggplot2 :: aes ( fill = ! ! dplyr :: sym ( sec ) ) ,
2025-03-12 18:27:46 +01:00
size = 2 ,
width = 1 / 3.4
2025-03-11 13:42:57 +01:00
)
2025-03-12 18:27:46 +01:00
} else {
p <- p +
ggalluvial :: geom_alluvium (
2025-04-23 14:25:38 +02:00
ggplot2 :: aes (
fill = ! ! dplyr :: sym ( pri ) # ,
# color = !!dplyr::sym(pri)
) ,
2025-03-12 18:27:46 +01:00
width = 1 / 16 ,
alpha = .8 ,
knot.pos = 0.4 ,
curve_type = " sigmoid"
2025-04-15 16:14:03 +02:00
) + ggalluvial :: geom_stratum ( ggplot2 :: aes ( fill = ! ! dplyr :: sym ( pri ) ) ,
2025-03-12 18:27:46 +01:00
size = 2 ,
width = 1 / 3.4
)
}
2025-03-11 13:42:57 +01:00
2025-03-12 18:27:46 +01:00
p +
ggplot2 :: geom_text (
stat = " stratum" ,
ggplot2 :: aes ( label = after_stat ( stratum ) ) ,
colour = label.colors ,
size = 8 ,
lineheight = 1
) +
ggplot2 :: scale_x_continuous (
breaks = 1 : 2 ,
labels = group_labels
) +
ggplot2 :: scale_fill_manual ( values = colors [ -1 ] , na.value = colors [1 ] ) +
2025-04-23 14:25:38 +02:00
# ggplot2::scale_color_manual(values = main.colors) +
2025-03-12 18:27:46 +01:00
ggplot2 :: theme_void ( ) +
ggplot2 :: theme (
legend.position = " none" ,
# panel.grid.major = element_blank(),
# panel.grid.minor = element_blank(),
# axis.text.y = element_blank(),
# axis.title.y = element_blank(),
axis.text.x = ggplot2 :: element_text ( size = 20 ) ,
# text = element_text(size = 5),
# plot.title = element_blank(),
# panel.background = ggplot2::element_rect(fill = "white"),
plot.background = ggplot2 :: element_rect ( fill = " white" ) ,
panel.border = ggplot2 :: element_blank ( )
)
2025-03-11 13:42:57 +01:00
}
2025-03-13 12:41:50 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//plot_scatter.R
2025-03-13 12:41:50 +01:00
########
#' Beautiful violin plot
#'
#' @returns ggplot2 object
#' @export
#'
#' @name data-plots
#'
#' @examples
2025-04-15 16:14:03 +02:00
#' mtcars |> plot_scatter(pri = "mpg", sec = "wt")
plot_scatter <- function ( data , pri , sec , ter = NULL ) {
if ( is.null ( ter ) ) {
2025-03-13 12:41:50 +01:00
rempsyc :: nice_scatter (
data = data ,
2025-04-15 16:14:03 +02:00
predictor = sec ,
response = pri ,
xtitle = get_label ( data , var = sec ) ,
ytitle = get_label ( data , var = pri )
2025-03-13 12:41:50 +01:00
)
} else {
rempsyc :: nice_scatter (
data = data ,
2025-04-15 16:14:03 +02:00
predictor = sec ,
response = pri ,
group = ter ,
xtitle = get_label ( data , var = sec ) ,
ytitle = get_label ( data , var = pri )
2025-03-13 12:41:50 +01:00
)
}
}
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//plot_violin.R
2025-03-13 12:41:50 +01:00
########
#' Beatiful violin plot
#'
#' @returns ggplot2 object
#' @export
#'
#' @name data-plots
#'
#' @examples
2025-04-15 16:14:03 +02:00
#' mtcars |> plot_violin(pri = "mpg", sec = "cyl", ter = "gear")
plot_violin <- function ( data , pri , sec , ter = NULL ) {
if ( ! is.null ( ter ) ) {
ds <- split ( data , data [ter ] )
2025-03-13 12:41:50 +01:00
} else {
ds <- list ( data )
}
out <- lapply ( ds , \ ( .ds ) {
rempsyc :: nice_violin (
data = .ds ,
2025-04-15 16:14:03 +02:00
group = sec ,
response = pri ,
xtitle = get_label ( data , var = sec ) ,
ytitle = get_label ( data , var = pri )
2025-03-13 12:41:50 +01:00
)
} )
wrap_plot_list ( out )
# patchwork::wrap_plots(out,guides = "collect")
}
2024-12-19 11:34:25 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//plot-download-module.R
2025-04-02 11:31:04 +02:00
########
plot_download_ui <- regression_ui <- function ( id , ... ) {
ns <- shiny :: NS ( id )
shiny :: tagList (
shinyWidgets :: noUiSliderInput (
inputId = ns ( " plot_height" ) ,
label = " Plot height (mm)" ,
min = 50 ,
max = 300 ,
value = 100 ,
step = 1 ,
format = shinyWidgets :: wNumbFormat ( decimals = 0 ) ,
color = datamods ::: get_primary_color ( )
) ,
shinyWidgets :: noUiSliderInput (
inputId = ns ( " plot_width" ) ,
label = " Plot width (mm)" ,
min = 50 ,
max = 300 ,
value = 100 ,
step = 1 ,
format = shinyWidgets :: wNumbFormat ( decimals = 0 ) ,
color = datamods ::: get_primary_color ( )
) ,
shiny :: selectInput (
inputId = ns ( " plot_type" ) ,
label = " File format" ,
choices = list (
" png" ,
" tiff" ,
" eps" ,
" pdf" ,
" jpeg" ,
" svg"
)
) ,
shiny :: br ( ) ,
# Button
shiny :: downloadButton (
outputId = ns ( " download_plot" ) ,
label = " Download plot" ,
icon = shiny :: icon ( " download" )
)
)
}
plot_download_server <- function ( id ,
data ,
file_name = " reg_plot" ,
... ) {
shiny :: moduleServer (
id = id ,
module = function ( input , output , session ) {
# ns <- session$ns
output $ download_plot <- shiny :: downloadHandler (
filename = paste0 ( file_name , " ." , input $ plot_type ) ,
content = function ( file ) {
shiny :: withProgress ( message = " Saving the plot. Hold on for a moment.." , {
ggplot2 :: ggsave (
filename = file ,
plot = data ,
width = input $ plot_width ,
height = input $ plot_height ,
dpi = 300 ,
units = " mm" , scale = 2
)
} )
}
)
}
)
}
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//redcap_read_shiny_module.R
2024-12-19 11:34:25 +01:00
########
2025-01-16 11:24:26 +01:00
#' Shiny module to browser and export REDCap data
#'
#' @param id Namespace id
#' @param include_title logical to include title
#'
#' @rdname redcap_read_shiny_module
#'
#' @return shiny ui element
#' @export
2025-04-03 13:11:02 +02:00
m_redcap_readUI <- function ( id , title = TRUE , url = NULL ) {
2024-12-18 10:37:37 +01:00
ns <- shiny :: NS ( id )
2025-03-13 12:41:50 +01:00
if ( isTRUE ( title ) ) {
title <- shiny :: tags $ h4 (
" Import data from REDCap" ,
class = " redcap-module-title"
)
}
2025-01-15 16:21:38 +01:00
server_ui <- shiny :: tagList (
# width = 6,
2025-02-27 13:34:45 +01:00
shiny :: tags $ h4 ( " REDCap server" ) ,
2024-12-18 10:37:37 +01:00
shiny :: textInput (
inputId = ns ( " uri" ) ,
2025-02-27 13:34:45 +01:00
label = " Web address" ,
2025-04-03 13:11:02 +02:00
value = if_not_missing ( url , " https://redcap.your.institution/" )
2024-12-18 10:37:37 +01:00
) ,
2025-02-27 13:34:45 +01:00
shiny :: helpText ( " Format should be either 'https://redcap.your.institution/' or 'https://your.institution/redcap/'" ) ,
2024-12-18 10:37:37 +01:00
shiny :: textInput (
inputId = ns ( " api" ) ,
label = " API token" ,
value = " "
2025-02-26 21:09:08 +01:00
) ,
2025-02-27 13:34:45 +01:00
shiny :: helpText ( " The token is a string of 32 numbers and letters." ) ,
2025-04-03 13:11:02 +02:00
shiny :: br ( ) ,
shiny :: br ( ) ,
2025-02-27 13:34:45 +01:00
shiny :: actionButton (
inputId = ns ( " data_connect" ) ,
label = " Connect" ,
icon = shiny :: icon ( " link" , lib = " glyphicon" ) ,
2025-04-03 13:11:02 +02:00
width = " 100%" ,
2025-02-27 13:34:45 +01:00
disabled = TRUE
) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
2025-02-26 21:09:08 +01:00
tags $ div (
id = ns ( " connect-placeholder" ) ,
shinyWidgets :: alert (
id = ns ( " connect-result" ) ,
status = " info" ,
2025-02-27 13:34:45 +01:00
tags $ p ( phosphoricons :: ph ( " info" , weight = " bold" ) , " Please fill in server address (URI) and API token, then press 'Connect'." )
2025-02-26 21:09:08 +01:00
) ,
dismissible = TRUE
2025-02-27 13:34:45 +01:00
) ,
shiny :: br ( )
2024-12-18 10:37:37 +01:00
)
2025-04-03 13:11:02 +02:00
filter_ui <-
shiny :: tagList (
# width = 6,
shiny :: uiOutput ( outputId = ns ( " arms" ) ) ,
shiny :: textInput (
inputId = ns ( " filter" ) ,
label = " Optional filter logic (e.g., [gender] = 'female')"
)
)
2024-12-18 10:37:37 +01:00
params_ui <-
2025-01-15 16:21:38 +01:00
shiny :: tagList (
# width = 6,
2024-12-18 10:37:37 +01:00
shiny :: tags $ h4 ( " Data import parameters" ) ,
shiny :: helpText ( " Options here will show, when API and uri are typed" ) ,
2025-04-09 12:31:08 +02:00
shiny :: tags $ br ( ) ,
2024-12-18 10:37:37 +01:00
shiny :: uiOutput ( outputId = ns ( " fields" ) ) ,
2025-04-03 13:11:02 +02:00
shiny :: tags $ div (
class = " shiny-input-container" ,
shiny :: tags $ label (
class = " control-label" ,
`for` = ns ( " dropdown_params" ) ,
" ..." ,
style = htmltools :: css ( visibility = " hidden" )
) ,
shinyWidgets :: dropMenu (
shiny :: actionButton (
inputId = ns ( " dropdown_params" ) ,
label = " Add data filters" ,
icon = shiny :: icon ( " filter" ) ,
width = " 100%" ,
class = " px-1"
) ,
filter_ui
) ,
shiny :: helpText ( " Optionally filter project arms if logitudinal or apply server side data filters" )
) ,
2025-04-09 12:31:08 +02:00
shiny :: tags $ br ( ) ,
2025-03-11 13:42:57 +01:00
shiny :: uiOutput ( outputId = ns ( " data_type" ) ) ,
shiny :: uiOutput ( outputId = ns ( " fill" ) ) ,
2025-02-27 13:34:45 +01:00
shiny :: actionButton (
inputId = ns ( " data_import" ) ,
2024-12-18 10:37:37 +01:00
label = " Import" ,
icon = shiny :: icon ( " download" , lib = " glyphicon" ) ,
2025-02-27 13:34:45 +01:00
width = " 100%" ,
disabled = TRUE
2024-12-18 10:37:37 +01:00
) ,
2025-04-03 13:11:02 +02:00
shiny :: tags $ br ( ) ,
shiny :: tags $ br ( ) ,
tags $ div (
id = ns ( " retrieved-placeholder" ) ,
shinyWidgets :: alert (
id = ns ( " retrieved-result" ) ,
status = " info" ,
tags $ p ( phosphoricons :: ph ( " info" , weight = " bold" ) , " Please specify data to download, then press 'Import'." )
) ,
dismissible = TRUE
2025-05-05 14:45:07 +02:00
) # ,
2025-04-03 13:11:02 +02:00
## TODO: Use busy indicator like on download to have button activate/deactivate
2025-02-27 13:34:45 +01:00
# bslib::input_task_button(
# id = ns("data_import"),
# label = "Import",
# icon = shiny::icon("download", lib = "glyphicon"),
# label_busy = "Just a minute...",
# icon_busy = fontawesome::fa_i("arrows-rotate",
# class = "fa-spin",
# "aria-hidden" = "true"
# ),
# type = "primary",
# auto_reset = TRUE#,state="busy"
# ),
2025-04-03 13:11:02 +02:00
# shiny::br(),
# shiny::helpText("Press 'Import' to get data from the REDCap server. Check the preview below before proceeding.")
2024-12-18 10:37:37 +01:00
)
2025-04-03 13:11:02 +02:00
shiny :: fluidPage (
title = title ,
server_ui ,
shiny :: conditionalPanel (
condition = " output.connect_success == true" ,
params_ui ,
ns = ns
) ,
shiny :: br ( )
2024-12-18 10:37:37 +01:00
)
}
2025-02-27 13:34:45 +01:00
2025-01-16 11:24:26 +01:00
#' @rdname redcap_read_shiny_module
#'
#' @return shiny server module
#' @export
#'
2025-02-26 21:09:08 +01:00
m_redcap_readServer <- function ( id ) {
2024-12-18 10:37:37 +01:00
module <- function ( input , output , session ) {
ns <- session $ ns
2025-02-26 21:09:08 +01:00
data_rv <- shiny :: reactiveValues (
dd_status = NULL ,
data_status = NULL ,
2025-02-27 13:34:45 +01:00
uri = NULL ,
project_name = NULL ,
2025-02-26 21:09:08 +01:00
info = NULL ,
arms = NULL ,
dd_list = NULL ,
2025-03-11 13:42:57 +01:00
data = NULL ,
rep_fields = NULL ,
2025-03-17 15:00:13 +01:00
code = NULL
2025-02-26 21:09:08 +01:00
)
2024-12-18 10:37:37 +01:00
2025-02-27 13:34:45 +01:00
shiny :: observeEvent ( list ( input $ api , input $ uri ) , {
2025-03-13 12:41:50 +01:00
shiny :: req ( input $ api )
shiny :: req ( input $ uri )
2025-04-03 13:11:02 +02:00
if ( ! is.null ( input $ uri ) ) {
uri <- paste0 ( ifelse ( endsWith ( input $ uri , " /" ) , input $ uri , paste0 ( input $ uri , " /" ) ) , " api/" )
2025-03-13 12:41:50 +01:00
} else {
uri <- input $ uri
}
2025-02-27 13:34:45 +01:00
if ( is_valid_redcap_url ( uri ) & is_valid_token ( input $ api ) ) {
data_rv $ uri <- uri
shiny :: updateActionButton ( inputId = " data_connect" , disabled = FALSE )
} else {
shiny :: updateActionButton ( inputId = " data_connect" , disabled = TRUE )
}
} )
tryCatch (
{
2025-02-26 21:09:08 +01:00
shiny :: observeEvent (
list (
2025-02-27 13:34:45 +01:00
input $ data_connect
2025-02-26 21:09:08 +01:00
) ,
{
shiny :: req ( input $ api )
2025-02-27 13:34:45 +01:00
shiny :: req ( data_rv $ uri )
2024-12-18 10:37:37 +01:00
2025-02-26 21:09:08 +01:00
parameters <- list (
2025-02-27 13:34:45 +01:00
redcap_uri = data_rv $ uri ,
2025-02-26 21:09:08 +01:00
token = input $ api
)
2024-12-18 10:37:37 +01:00
2025-02-26 21:09:08 +01:00
# browser()
2025-05-10 11:31:26 +02:00
shiny :: withProgress (
{
imported <- try ( rlang :: exec ( REDCapR :: redcap_metadata_read , ! ! ! parameters ) , silent = TRUE )
} ,
message = paste ( " Connecting to" , data_rv $ uri )
)
2024-12-18 10:37:37 +01:00
2025-02-26 21:09:08 +01:00
## TODO: Simplify error messages
if ( inherits ( imported , " try-error" ) || NROW ( imported ) < 1 || ifelse ( is.list ( imported ) , ! isTRUE ( imported $ success ) , FALSE ) ) {
if ( ifelse ( is.list ( imported ) , ! isTRUE ( imported $ success ) , FALSE ) ) {
mssg <- imported $ raw_text
} else {
mssg <- attr ( imported , " condition" ) $ message
}
2025-02-27 13:34:45 +01:00
datamods ::: insert_error ( mssg = mssg , selector = " connect" )
2025-02-26 21:09:08 +01:00
data_rv $ dd_status <- " error"
data_rv $ dd_list <- NULL
} else if ( isTRUE ( imported $ success ) ) {
2025-02-27 13:34:45 +01:00
data_rv $ dd_status <- " success"
2025-03-11 13:42:57 +01:00
data_rv $ info <- REDCapR :: redcap_project_info_read (
2025-02-27 13:34:45 +01:00
redcap_uri = data_rv $ uri ,
token = input $ api
2025-03-11 13:42:57 +01:00
) $ data
2025-02-26 21:09:08 +01:00
datamods ::: insert_alert (
selector = ns ( " connect" ) ,
status = " success" ,
2025-04-03 13:11:02 +02:00
include_data_alert (
see_data_text = " Click to see data dictionary" ,
2025-05-10 11:31:26 +02:00
dataIdName = " see_dd" ,
2025-05-05 14:45:07 +02:00
extra = tags $ p (
tags $ b ( phosphoricons :: ph ( " check" , weight = " bold" ) , " Connected to server!" ) ,
glue :: glue ( " The {data_rv$info$project_title} project is loaded." )
) ,
2025-02-26 21:09:08 +01:00
btn_show_data = TRUE
)
)
data_rv $ dd_list <- imported
}
} ,
ignoreInit = TRUE
)
2025-02-27 13:34:45 +01:00
} ,
warning = function ( warn ) {
showNotification ( paste0 ( warn ) , type = " warning" )
} ,
error = function ( err ) {
showNotification ( paste0 ( err ) , type = " err" )
}
)
2025-02-26 21:09:08 +01:00
2025-04-03 13:11:02 +02:00
output $ connect_success <- shiny :: reactive ( identical ( data_rv $ dd_status , " success" ) )
shiny :: outputOptions ( output , " connect_success" , suspendWhenHidden = FALSE )
2025-05-10 11:31:26 +02:00
shiny :: observeEvent ( input $ see_dd , {
show_data (
2025-02-26 21:09:08 +01:00
purrr :: pluck ( data_rv $ dd_list , " data" ) ,
title = " Data dictionary" ,
type = " modal" ,
show_classes = FALSE ,
tags $ b ( " Preview:" )
2025-02-27 13:34:45 +01:00
)
2025-02-26 21:09:08 +01:00
} )
2024-12-18 10:37:37 +01:00
2025-05-10 11:31:26 +02:00
shiny :: observeEvent ( input $ see_data , {
show_data (
# purrr::pluck(data_rv$dd_list, "data"),
data_rv $ data ,
title = " Imported data set" ,
type = " modal" ,
show_classes = FALSE ,
tags $ b ( " Preview:" )
)
} )
2024-12-18 10:37:37 +01:00
arms <- shiny :: reactive ( {
shiny :: req ( input $ api )
2025-02-27 13:34:45 +01:00
shiny :: req ( data_rv $ uri )
2024-12-18 10:37:37 +01:00
REDCapR :: redcap_event_read (
2025-02-27 13:34:45 +01:00
redcap_uri = data_rv $ uri ,
2024-12-18 10:37:37 +01:00
token = input $ api
) $ data
} )
output $ fields <- shiny :: renderUI ( {
2025-02-26 21:09:08 +01:00
shiny :: req ( data_rv $ dd_list )
2024-12-18 10:37:37 +01:00
shinyWidgets :: virtualSelectInput (
inputId = ns ( " fields" ) ,
2025-02-27 13:34:45 +01:00
label = " Select variables to import:" ,
2025-02-26 21:09:08 +01:00
choices = purrr :: pluck ( data_rv $ dd_list , " data" ) | >
2024-12-18 10:37:37 +01:00
dplyr :: select ( field_name , form_name ) | >
( \ ( .x ) {
2025-03-11 13:42:57 +01:00
split ( .x $ field_name , REDCapCAST :: as_factor ( .x $ form_name ) )
2025-02-27 13:34:45 +01:00
} ) ( ) ,
updateOn = " change" ,
2024-12-18 10:37:37 +01:00
multiple = TRUE ,
search = TRUE ,
showValueAsTags = TRUE
)
} )
2025-03-11 13:42:57 +01:00
output $ data_type <- shiny :: renderUI ( {
shiny :: req ( data_rv $ info )
if ( isTRUE ( data_rv $ info $ has_repeating_instruments_or_events ) ) {
vectorSelectInput (
inputId = ns ( " data_type" ) ,
label = " Select the data format to import" ,
choices = c (
" Wide data (One row for each subject)" = " wide" ,
" Long data for project with repeating instruments (default REDCap)" = " long"
) ,
selected = " wide" ,
multiple = FALSE
)
}
} )
output $ fill <- shiny :: renderUI ( {
shiny :: req ( data_rv $ info )
shiny :: req ( input $ data_type )
## Get repeated field
data_rv $ rep_fields <- data_rv $ dd_list $ data $ field_name [
data_rv $ dd_list $ data $ form_name %in% repeated_instruments (
uri = data_rv $ uri ,
token = input $ api
)
]
if ( input $ data_type == " long" && isTRUE ( any ( input $ fields %in% data_rv $ rep_fields ) ) ) {
vectorSelectInput (
inputId = ns ( " fill" ) ,
label = " Fill missing values?" ,
choices = c (
" Yes, fill missing, non-repeated values" = " yes" ,
" No, leave the data as is" = " no"
) ,
2025-04-08 13:45:07 +02:00
selected = " no" ,
2025-03-11 13:42:57 +01:00
multiple = FALSE
)
}
} )
2025-02-27 13:34:45 +01:00
shiny :: observeEvent ( input $ fields , {
if ( is.null ( input $ fields ) | length ( input $ fields ) == 0 ) {
shiny :: updateActionButton ( inputId = " data_import" , disabled = TRUE )
} else {
shiny :: updateActionButton ( inputId = " data_import" , disabled = FALSE )
}
} )
2025-02-26 21:09:08 +01:00
2024-12-18 10:37:37 +01:00
output $ arms <- shiny :: renderUI ( {
2025-04-03 13:11:02 +02:00
if ( NROW ( arms ( ) ) > 0 ) {
vectorSelectInput (
inputId = ns ( " arms" ) ,
selected = NULL ,
label = " Filter by events/arms" ,
choices = stats :: setNames ( arms ( ) [ [3 ] ] , arms ( ) [ [1 ] ] ) ,
multiple = TRUE
)
}
2024-12-18 10:37:37 +01:00
} )
2025-02-26 21:09:08 +01:00
shiny :: observeEvent ( input $ data_import , {
2024-12-18 10:37:37 +01:00
shiny :: req ( input $ fields )
2025-04-03 13:11:02 +02:00
# browser()
2025-02-26 21:09:08 +01:00
record_id <- purrr :: pluck ( data_rv $ dd_list , " data" ) [ [1 ] ] [1 ]
2024-12-18 10:37:37 +01:00
2025-03-11 13:42:57 +01:00
2025-02-26 21:09:08 +01:00
parameters <- list (
2025-02-27 13:34:45 +01:00
uri = data_rv $ uri ,
2024-12-18 10:37:37 +01:00
token = input $ api ,
fields = unique ( c ( record_id , input $ fields ) ) ,
events = input $ arms ,
raw_or_label = " both" ,
2025-03-11 13:42:57 +01:00
filter_logic = input $ filter ,
2025-04-03 13:11:02 +02:00
split_forms = ifelse (
input $ data_type == " long" && ! is.null ( input $ data_type ) ,
" none" ,
" all"
)
2024-12-18 10:37:37 +01:00
)
2025-02-27 13:34:45 +01:00
shiny :: withProgress ( message = " Downloading REDCap data. Hold on for a moment.." , {
imported <- try ( rlang :: exec ( REDCapCAST :: read_redcap_tables , ! ! ! parameters ) , silent = TRUE )
} )
2025-03-17 15:00:13 +01:00
2025-05-10 11:31:26 +02:00
parameters_code <- parameters [c ( " uri" , " fields" , " events" , " raw_or_label" , " filter_logic" ) ]
code <- rlang :: call2 (
" easy_redcap" ,
! ! ! utils :: modifyList (
parameters_code ,
list (
data_format = ifelse (
input $ data_type == " long" && ! is.null ( input $ data_type ) ,
" long" ,
" wide"
) ,
project.name = simple_snake ( data_rv $ info $ project_title )
)
) ,
2025-04-03 13:11:02 +02:00
.ns = " REDCapCAST"
)
2025-02-26 21:09:08 +01:00
if ( inherits ( imported , " try-error" ) || NROW ( imported ) < 1 ) {
data_rv $ data_status <- " error"
data_rv $ data_list <- NULL
2025-04-03 13:11:02 +02:00
data_rv $ data_message <- imported $ raw_text
2024-12-18 10:37:37 +01:00
} else {
2025-02-26 21:09:08 +01:00
data_rv $ data_status <- " success"
2025-04-03 13:11:02 +02:00
data_rv $ data_message <- " Requested data was retrieved!"
2025-03-11 13:42:57 +01:00
## The data management below should be separated to allow for changing
## "wide"/"long" without re-importing data
2025-04-03 13:11:02 +02:00
if ( parameters $ split_form == " all" ) {
2025-03-11 13:42:57 +01:00
# browser()
out <- imported | >
# redcap_wider()
REDCapCAST :: redcap_wider ( )
} else {
if ( input $ fill == " yes" ) {
## Repeated fields
## Non-repeated fields in current dataset
inc_non_rep <- names ( imported ) [ ! names ( imported ) %in% data_rv $ rep_fields ]
out <- imported | >
drop_empty_event ( ) | >
dplyr :: group_by ( ! ! dplyr :: sym ( names ( imported ) [1 ] ) ) | >
tidyr :: fill ( inc_non_rep ) | >
dplyr :: ungroup ( )
} else {
out <- imported | >
drop_empty_event ( )
}
}
2025-04-03 13:11:02 +02:00
# browser()
in_data_check <- parameters $ fields %in% names ( out ) |
sapply ( names ( out ) , \ ( .x ) any ( sapply ( parameters $ fields , \ ( .y ) startsWith ( .x , .y ) ) ) )
if ( ! any ( in_data_check [ -1 ] ) ) {
data_rv $ data_status <- " warning"
data_rv $ data_message <- " Data retrieved, but it looks like only the ID was retrieved from the server. Please check with your REDCap administrator that you have required permissions for data access."
}
if ( ! all ( in_data_check ) ) {
data_rv $ data_status <- " warning"
data_rv $ data_message <- " 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."
}
2025-03-17 15:00:13 +01:00
data_rv $ code <- code
2025-03-11 13:42:57 +01:00
data_rv $ data <- out | >
2025-02-26 21:09:08 +01:00
dplyr :: select ( - dplyr :: ends_with ( " _complete" ) ) | >
2025-03-11 13:42:57 +01:00
# dplyr::select(-dplyr::any_of(record_id)) |>
2025-02-26 21:09:08 +01:00
REDCapCAST :: suffix2label ( )
2024-12-18 10:37:37 +01:00
}
} )
2025-02-26 21:09:08 +01:00
2025-04-03 13:11:02 +02:00
shiny :: observeEvent (
data_rv $ data_status ,
{
# browser()
if ( identical ( data_rv $ data_status , " error" ) ) {
datamods ::: insert_error ( mssg = data_rv $ data_message , selector = ns ( " retrieved" ) )
} else if ( identical ( data_rv $ data_status , " success" ) ) {
datamods ::: insert_alert (
selector = ns ( " retrieved" ) ,
status = data_rv $ data_status ,
2025-05-10 11:31:26 +02:00
# tags$p(
# tags$b(phosphoricons::ph("check", weight = "bold"), "Success!"),
# data_rv$data_message
# ),
include_data_alert (
see_data_text = " Click to see the imported data" ,
dataIdName = " see_data" ,
extra = tags $ p (
tags $ b ( phosphoricons :: ph ( " check" , weight = " bold" ) , data_rv $ data_message )
) ,
btn_show_data = TRUE
2025-04-03 13:11:02 +02:00
)
)
} else {
datamods ::: insert_alert (
selector = ns ( " retrieved" ) ,
status = data_rv $ data_status ,
tags $ p (
tags $ b ( phosphoricons :: ph ( " warning" , weight = " bold" ) , " Warning!" ) ,
data_rv $ data_message
)
)
}
}
)
2025-03-11 13:42:57 +01:00
2025-03-17 15:00:13 +01:00
return ( list (
status = shiny :: reactive ( data_rv $ data_status ) ,
name = shiny :: reactive ( data_rv $ info $ project_title ) ,
info = shiny :: reactive ( data_rv $ info ) ,
code = shiny :: reactive ( data_rv $ code ) ,
data = shiny :: reactive ( data_rv $ data )
) )
2024-12-18 10:37:37 +01:00
}
shiny :: moduleServer (
id = id ,
module = module
)
}
2025-02-26 21:09:08 +01:00
#' @importFrom htmltools tagList tags
#' @importFrom shiny icon getDefaultReactiveDomain
2025-03-03 08:44:46 +01:00
include_data_alert <- function ( dataIdName = " see_data" ,
2025-02-26 21:09:08 +01:00
btn_show_data ,
2025-02-27 13:34:45 +01:00
see_data_text = " Click to see data" ,
2025-02-26 21:09:08 +01:00
extra = NULL ,
session = shiny :: getDefaultReactiveDomain ( ) ) {
if ( isTRUE ( btn_show_data ) ) {
success_message <- tagList (
extra ,
tags $ br ( ) ,
shiny :: actionLink (
inputId = session $ ns ( dataIdName ) ,
2025-03-11 13:42:57 +01:00
label = tagList ( phosphoricons :: ph ( " book-open-text" ) , see_data_text )
2025-02-26 21:09:08 +01:00
)
)
}
return ( success_message )
}
2025-02-25 09:51:42 +01:00
# #' REDCap import teal data module
# #'
# #' @rdname redcap_read_shiny_module
# tdm_redcap_read <- teal::teal_data_module(
# ui <- function(id) {
# shiny::fluidPage(
# m_redcap_readUI(id)
# )
# },
# server = function(id) {
# m_redcap_readServer(id, output.format = "teal")
# }
# )
2024-12-18 10:37:37 +01:00
2024-12-19 11:34:25 +01:00
2025-03-11 13:42:57 +01:00
#' Test if url is valid format for REDCap API
2025-02-27 13:34:45 +01:00
#'
2025-03-11 13:42:57 +01:00
#' @param url url
2025-02-27 13:34:45 +01:00
#'
2025-03-11 13:42:57 +01:00
#' @returns logical
2025-02-27 13:34:45 +01:00
#' @export
#'
#' @examples
#' url <- c(
#' "www.example.com",
2025-03-11 13:42:57 +01:00
#' "redcap.your.inst/api/",
#' "https://redcap.your.inst/api/",
#' "https://your.inst/redcap/api/",
#' "https://www.your.inst/redcap/api/"
2025-02-27 13:34:45 +01:00
#' )
#' is_valid_redcap_url(url)
is_valid_redcap_url <- function ( url ) {
pattern <- " https://[^ /$.?#].[^\\s]*/api/$"
stringr :: str_detect ( url , pattern )
}
#' Validate REDCap token
#'
#' @param token token
#' @param pattern_env pattern
#'
2025-03-11 13:42:57 +01:00
#' @returns logical
2025-02-27 13:34:45 +01:00
#' @export
#'
#' @examples
#' token <- paste(sample(c(1:9, LETTERS[1:6]), 32, TRUE), collapse = "")
#' is_valid_token(token)
is_valid_token <- function ( token , pattern_env = NULL , nchar = 32 ) {
checkmate :: assert_character ( token , any.missing = TRUE , len = 1 )
if ( ! is.null ( pattern_env ) ) {
checkmate :: assert_character ( pattern_env ,
any.missing = FALSE ,
len = 1
)
pattern <- pattern_env
} else {
pattern <- glue :: glue ( " ^([0-9A-Fa-f]{<nchar>})(?:\\n)?$" ,
.open = " <" ,
.close = " >"
)
}
if ( is.na ( token ) ) {
out <- FALSE
} else if ( is.null ( token ) ) {
out <- FALSE
} else if ( nchar ( token ) == 0L ) {
out <- FALSE
} else if ( ! grepl ( pattern , token , perl = TRUE ) ) {
out <- FALSE
} else {
out <- TRUE
}
out
}
2025-03-11 13:42:57 +01:00
#' Get names of repeated instruments
#'
#' @param uri REDCap database uri
#' @param token database token
#'
#' @returns vector
#' @export
#'
repeated_instruments <- function ( uri , token ) {
instruments <- REDCapR :: redcap_event_instruments ( redcap_uri = uri , token = token )
unique ( instruments $ data $ form [duplicated ( instruments $ data $ form ) ] )
}
#' Drop empty events from REDCap export
#'
#' @param data data
#' @param event "redcap_event_name", "redcap_repeat_instrument" or
#' "redcap_repeat_instance"
#'
#' @returns data.frame
#' @export
#'
drop_empty_event <- function ( data , event = " redcap_event_name" ) {
generics <- c ( names ( data ) [1 ] , " redcap_event_name" , " redcap_repeat_instrument" , " redcap_repeat_instance" )
filt <- split ( data , data [ [event ] ] ) | >
lapply ( \ ( .x ) {
dplyr :: select ( .x , - tidyselect :: all_of ( generics ) ) | >
REDCapCAST :: all_na ( )
} ) | >
unlist ( )
data [data [ [event ] ] %in% names ( filt ) [ ! filt ] , ]
}
2025-02-27 13:34:45 +01:00
2025-01-16 11:24:26 +01:00
#' Test app for the redcap_read_shiny_module
#'
#' @rdname redcap_read_shiny_module
#'
#' @examples
#' \dontrun{
2025-02-26 21:09:08 +01:00
#' redcap_demo_app()
2025-01-16 11:24:26 +01:00
#' }
2025-02-26 21:09:08 +01:00
redcap_demo_app <- function ( ) {
2024-12-18 10:37:37 +01:00
ui <- shiny :: fluidPage (
2025-04-03 13:11:02 +02:00
m_redcap_readUI ( " data" , url = NULL ) ,
2025-03-17 15:00:13 +01:00
DT :: DTOutput ( " data" ) ,
shiny :: tags $ b ( " Code:" ) ,
shiny :: verbatimTextOutput ( outputId = " code" )
2024-12-18 10:37:37 +01:00
)
server <- function ( input , output , session ) {
2025-03-17 15:00:13 +01:00
data_val <- m_redcap_readServer ( id = " data" )
2024-12-18 10:37:37 +01:00
2025-03-17 15:00:13 +01:00
output $ data <- DT :: renderDataTable (
2024-12-18 10:37:37 +01:00
{
2025-02-26 21:09:08 +01:00
shiny :: req ( data_val $ data )
data_val $ data ( )
2024-12-18 10:37:37 +01:00
} ,
options = list (
scrollX = TRUE ,
pageLength = 5
2025-02-26 21:09:08 +01:00
) ,
2024-12-18 10:37:37 +01:00
)
2025-03-17 15:00:13 +01:00
output $ code <- shiny :: renderPrint ( {
shiny :: req ( data_val $ code )
data_val $ code ( )
} )
2024-12-18 10:37:37 +01:00
}
shiny :: shinyApp ( ui , server )
}
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//regression_model.R
2024-12-18 10:37:37 +01:00
########
2025-01-16 11:24:26 +01:00
#' Create a regression model programatically
#'
#' @param data data set
#' @param fun Name of function as character vector or function to use for model creation.
#' @param vars character vector of variables to include
#' @param outcome.str Name of outcome variable. Character vector.
#' @param auto.mode Make assumptions on function dependent on outcome data format. Overwrites other arguments.
#' @param formula.str Formula as string. Passed through 'glue::glue'. If given, 'outcome.str' and 'vars' are ignored. Optional.
#' @param args.list List of arguments passed to 'fun' with 'do.call'.
#' @param ... ignored for now
#'
#' @importFrom stats as.formula
#'
#' @return object of standard class for fun
#' @export
2025-02-25 09:51:42 +01:00
#' @rdname regression_model
2025-01-16 11:24:26 +01:00
#'
#' @examples
#' gtsummary::trial |>
#' regression_model(outcome.str = "age")
#' gtsummary::trial |>
#' regression_model(
#' outcome.str = "age",
#' auto.mode = FALSE,
#' fun = "stats::lm",
#' formula.str = "{outcome.str}~.",
#' args.list = NULL
#' )
2025-01-30 14:32:11 +01:00
#' gtsummary::trial |>
#' default_parsing() |>
#' regression_model(
#' outcome.str = "trt",
#' auto.mode = FALSE,
#' fun = "stats::glm",
#' args.list = list(family = binomial(link = "logit"))
#' )
#' m <- mtcars |>
2025-01-17 15:59:24 +01:00
#' default_parsing() |>
#' regression_model(
#' outcome.str = "mpg",
#' auto.mode = FALSE,
#' fun = "stats::lm",
#' formula.str = "{outcome.str}~{paste(vars,collapse='+')}",
#' args.list = NULL,
#' vars = c("mpg", "cyl")
2025-01-30 14:32:11 +01:00
#' )
2025-03-20 11:46:02 +01:00
#' broom::tidy(m)
2024-12-18 10:37:37 +01:00
regression_model <- function ( data ,
2025-04-11 13:23:18 +02:00
outcome.str = NULL ,
2025-01-17 15:59:24 +01:00
auto.mode = FALSE ,
2024-12-18 10:37:37 +01:00
formula.str = NULL ,
args.list = NULL ,
fun = NULL ,
vars = NULL ,
... ) {
if ( ! is.null ( formula.str ) ) {
if ( formula.str == " " ) {
formula.str <- NULL
}
}
2025-01-30 14:32:11 +01:00
## This will handle if outcome is not in data for nicer shiny behavior
2025-04-11 13:23:18 +02:00
if ( isTRUE ( ! outcome.str %in% names ( data ) ) ) {
2025-01-30 14:32:11 +01:00
outcome.str <- names ( data ) [1 ]
2025-04-11 13:23:18 +02:00
print ( " Outcome variable is not in data, first column is used" )
2025-01-17 15:59:24 +01:00
}
2024-12-18 10:37:37 +01:00
if ( ! is.null ( formula.str ) ) {
2025-01-17 15:59:24 +01:00
formula.glue <- glue :: glue ( formula.str )
2025-04-11 13:23:18 +02:00
outcome.str <- NULL
2024-12-18 10:37:37 +01:00
} else {
assertthat :: assert_that ( outcome.str %in% names ( data ) ,
msg = " Outcome variable is not present in the provided dataset"
)
2025-01-17 15:59:24 +01:00
formula.glue <- glue :: glue ( " {outcome.str}~{paste(vars,collapse='+')}" )
2024-12-18 10:37:37 +01:00
}
2025-04-11 13:23:18 +02:00
if ( is.null ( vars ) ) {
vars <- names ( data ) [ ! names ( data ) %in% outcome.str ]
} else if ( ! is.null ( outcome.str ) ) {
if ( outcome.str %in% vars ) {
vars <- vars [ ! vars %in% outcome.str ]
}
data <- data | > dplyr :: select ( dplyr :: all_of ( c ( vars , outcome.str ) ) )
}
2024-12-18 10:37:37 +01:00
# Formatting character variables as factor
# Improvement should add a missing vector to format as NA
data <- data | >
purrr :: map ( \ ( .x ) {
if ( is.character ( .x ) ) {
2024-12-19 21:21:29 +01:00
suppressWarnings ( REDCapCAST :: as_factor ( .x ) )
2024-12-18 10:37:37 +01:00
} else {
.x
}
} ) | >
2025-01-15 16:21:38 +01:00
dplyr :: bind_cols ( .name_repair = " unique_quiet" )
2024-12-18 10:37:37 +01:00
if ( is.null ( fun ) ) auto.mode <- TRUE
2025-04-09 12:31:08 +02:00
if ( isTRUE ( auto.mode ) ) {
2024-12-18 10:37:37 +01:00
if ( is.numeric ( data [ [outcome.str ] ] ) ) {
fun <- " stats::lm"
} else if ( is.factor ( data [ [outcome.str ] ] ) ) {
if ( length ( levels ( data [ [outcome.str ] ] ) ) == 2 ) {
fun <- " stats::glm"
args.list <- list ( family = stats :: binomial ( link = " logit" ) )
} else if ( length ( levels ( data [ [outcome.str ] ] ) ) > 2 ) {
fun <- " MASS::polr"
args.list <- list (
Hess = TRUE ,
method = " logistic"
)
} else {
stop ( " The provided output variable only has one level" )
}
} else {
stop ( " Output variable should be either numeric or factor for auto.mode" )
}
}
assertthat :: assert_that ( " character" %in% class ( fun ) ,
msg = " Please provide the function as a character vector."
)
out <- do.call (
getfun ( fun ) ,
c (
2025-01-30 14:32:11 +01:00
list (
data = data ,
formula = as.formula ( formula.glue )
) ,
2024-12-18 10:37:37 +01:00
args.list
)
)
2025-04-02 11:31:04 +02:00
# out <- REDCapCAST::set_attr(out,label = fun,attr = "fun.call")
2024-12-18 10:37:37 +01:00
# Recreating the call
# out$call <- match.call(definition=eval(parse(text=fun)), call(fun, data = 'data',formula = as.formula(formula.str),args.list))
return ( out )
}
2025-01-16 11:24:26 +01:00
#' Create a regression model programatically
#'
#' @param data data set
#' @param fun Name of function as character vector or function to use for model creation.
#' @param vars character vector of variables to include
#' @param outcome.str Name of outcome variable. Character vector.
#' @param args.list List of arguments passed to 'fun' with 'do.call'.
#' @param ... ignored for now
#'
#' @importFrom stats as.formula
2025-02-25 09:51:42 +01:00
#' @rdname regression_model
2025-01-16 11:24:26 +01:00
#'
#' @return object of standard class for fun
#' @export
#'
#' @examples
#' \dontrun{
#' gtsummary::trial |>
#' regression_model_uv(outcome.str = "age")
#' gtsummary::trial |>
#' regression_model_uv(
#' outcome.str = "age",
#' fun = "stats::lm",
#' args.list = NULL
#' )
2025-01-30 14:32:11 +01:00
#' m <- gtsummary::trial |> regression_model_uv(
2025-01-16 11:24:26 +01:00
#' outcome.str = "trt",
#' fun = "stats::glm",
#' args.list = list(family = stats::binomial(link = "logit"))
#' )
2025-03-20 11:46:02 +01:00
#' lapply(m, broom::tidy) |> dplyr::bind_rows()
2025-01-16 11:24:26 +01:00
#' }
2024-12-18 10:37:37 +01:00
regression_model_uv <- function ( data ,
outcome.str ,
args.list = NULL ,
fun = NULL ,
vars = NULL ,
... ) {
2025-01-30 14:32:11 +01:00
## This will handle if outcome is not in data for nicer shiny behavior
2025-03-20 11:46:02 +01:00
if ( ! outcome.str %in% names ( data ) ) {
2025-01-30 14:32:11 +01:00
outcome.str <- names ( data ) [1 ]
print ( " outcome is not in data, first column is used" )
}
2024-12-18 10:37:37 +01:00
if ( ! is.null ( vars ) ) {
data <- data | >
dplyr :: select ( dplyr :: all_of (
unique ( c ( outcome.str , vars ) )
) )
}
if ( is.null ( args.list ) ) {
args.list <- list ( )
}
if ( is.null ( fun ) ) {
if ( is.numeric ( data [ [outcome.str ] ] ) ) {
fun <- " stats::lm"
} else if ( is.factor ( data [ [outcome.str ] ] ) ) {
if ( length ( levels ( data [ [outcome.str ] ] ) ) == 2 ) {
fun <- " stats::glm"
args.list <- list ( family = stats :: binomial ( link = " logit" ) )
} else if ( length ( levels ( data [ [outcome.str ] ] ) ) > 2 ) {
fun <- " MASS::polr"
args.list <- list (
Hess = TRUE ,
method = " logistic"
)
} else {
stop ( " The provided output variable only has one level" )
}
} else {
stop ( " Output variable should be either numeric or factor for auto.mode" )
}
}
assertthat :: assert_that ( " character" %in% class ( fun ) ,
msg = " Please provide the function as a character vector."
)
out <- names ( data ) [ ! names ( data ) %in% outcome.str ] | >
purrr :: map ( \ ( .var ) {
do.call (
regression_model ,
c (
2025-01-30 14:32:11 +01:00
list (
data = data [match ( c ( outcome.str , .var ) , names ( data ) ) ] ,
outcome.str = outcome.str
) ,
args.list
2024-12-18 10:37:37 +01:00
)
)
} )
return ( out )
}
2025-01-17 15:59:24 +01:00
### HELPERS
2025-04-14 10:10:33 +02:00
#' Data type assessment.
2025-01-17 15:59:24 +01:00
#'
2025-04-14 10:10:33 +02:00
#' @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.
2025-01-17 15:59:24 +01:00
#'
#' @returns outcome type
#' @export
#'
#' @examples
#' mtcars |>
#' default_parsing() |>
2025-03-20 11:46:02 +01:00
#' lapply(data_type)
2025-04-14 10:10:33 +02:00
#' mtcars |>
#' default_parsing() |>
#' data_type()
2025-03-20 11:46:02 +01:00
#' 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()
2025-04-14 10:10:33 +02:00
#' as.Date(1:20) |> data_type()
2025-03-20 11:46:02 +01:00
data_type <- function ( data ) {
2025-04-14 10:10:33 +02:00
if ( is.data.frame ( data ) ) {
sapply ( data , data_type )
} else {
cl_d <- class ( data )
if ( all ( is.na ( data ) ) ) {
out <- " empty"
} else if ( length ( unique ( data ) ) < 2 ) {
out <- " monotone"
} else if ( any ( c ( " factor" , " logical" ) %in% cl_d ) | length ( unique ( data ) ) == 2 ) {
if ( identical ( " logical" , cl_d ) | length ( unique ( data ) ) == 2 ) {
out <- " dichotomous"
2025-03-20 11:46:02 +01:00
} else {
2025-04-22 10:02:12 +02:00
# if (is.ordered(data)) {
# out <- "ordinal"
# } else {
2025-04-14 10:10:33 +02:00
out <- " categorical"
2025-04-22 10:02:12 +02:00
# }
2025-03-20 11:46:02 +01:00
}
2025-04-14 10:10:33 +02:00
} else if ( identical ( cl_d , " character" ) ) {
out <- " text"
} else if ( any ( c ( " hms" , " Date" , " POSIXct" , " POSIXt" ) %in% cl_d ) ) {
out <- " datetime"
} else if ( ! length ( unique ( data ) ) == 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"
2025-01-17 15:59:24 +01:00
}
2025-04-14 10:10:33 +02:00
out
2025-01-17 15:59:24 +01:00
}
2025-04-14 10:10:33 +02:00
}
2025-01-17 15:59:24 +01:00
2025-04-14 10:10:33 +02:00
#' Recognised data types from data_type
#'
#' @returns vector
#' @export
#'
#' @examples
#' data_types()
data_types <- function ( ) {
2025-04-22 10:02:12 +02:00
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" )
)
2025-01-17 15:59:24 +01:00
}
#' Implemented functions
#'
#' @description
#' Library of supported functions. The list name and "descr" element should be
#' unique for each element on list.
#'
#'
#' @returns list
#' @export
#'
#' @examples
#' supported_functions()
supported_functions <- function ( ) {
list (
lm = list (
descr = " Linear regression model" ,
design = " cross-sectional" ,
out.type = " continuous" ,
fun = " stats::lm" ,
args.list = NULL ,
2025-01-23 08:44:38 +01:00
formula.str = " {outcome.str}~{paste(vars,collapse='+')}" ,
2025-01-30 14:32:11 +01:00
table.fun = " gtsummary::tbl_regression" ,
table.args.list = list ( exponentiate = FALSE )
2025-01-17 15:59:24 +01:00
) ,
glm = list (
descr = " Logistic regression model" ,
design = " cross-sectional" ,
out.type = " dichotomous" ,
fun = " stats::glm" ,
2025-04-09 12:31:08 +02:00
args.list = list ( family = " binomial" ) ,
2025-01-23 08:44:38 +01:00
formula.str = " {outcome.str}~{paste(vars,collapse='+')}" ,
2025-01-30 14:32:11 +01:00
table.fun = " gtsummary::tbl_regression" ,
table.args.list = list ( )
2025-01-17 15:59:24 +01:00
) ,
polr = list (
descr = " Ordinal logistic regression model" ,
design = " cross-sectional" ,
2025-04-22 10:02:12 +02:00
out.type = c ( " categorical" ) ,
2025-01-17 15:59:24 +01:00
fun = " MASS::polr" ,
args.list = list (
Hess = TRUE ,
method = " logistic"
) ,
2025-01-23 08:44:38 +01:00
formula.str = " {outcome.str}~{paste(vars,collapse='+')}" ,
2025-01-30 14:32:11 +01:00
table.fun = " gtsummary::tbl_regression" ,
table.args.list = list ( )
2025-01-17 15:59:24 +01:00
)
)
}
#' Get possible regression models
#'
#' @param data data
#'
2025-02-25 09:51:42 +01:00
#' @returns character vector
2025-01-17 15:59:24 +01:00
#' @export
#'
#' @examples
#' mtcars |>
#' default_parsing() |>
#' dplyr::pull("cyl") |>
#' possible_functions(design = "cross-sectional")
#'
#' mtcars |>
#' default_parsing() |>
#' dplyr::select("cyl") |>
#' possible_functions(design = "cross-sectional")
possible_functions <- function ( data , design = c ( " cross-sectional" ) ) {
2025-04-11 13:23:18 +02:00
#
2025-03-20 13:13:14 +01:00
# data <- if (is.reactive(data)) data() else data
2025-01-17 15:59:24 +01:00
if ( is.data.frame ( data ) ) {
data <- data [ [1 ] ]
}
design <- match.arg ( design )
2025-03-20 11:46:02 +01:00
type <- data_type ( data )
2025-01-17 15:59:24 +01:00
design_ls <- supported_functions ( ) | >
lapply ( \ ( .x ) {
if ( design %in% .x $ design ) {
.x
}
} )
if ( type == " unknown" ) {
out <- type
} else {
out <- design_ls | >
lapply ( \ ( .x ) {
if ( type %in% .x $ out.type ) {
.x $ descr
}
} ) | >
unlist ( )
}
unname ( out )
}
#' Get the function options based on the selected function description
#'
#' @param data vector
#'
#' @returns list
#' @export
#'
#' @examples
#' mtcars |>
#' default_parsing() |>
#' dplyr::pull(mpg) |>
#' possible_functions(design = "cross-sectional") |>
#' (\(.x){
#' .x[[1]]
#' })() |>
#' get_fun_options()
get_fun_options <- function ( data ) {
descrs <- supported_functions ( ) | >
lapply ( \ ( .x ) {
.x $ descr
} ) | >
unlist ( )
supported_functions ( ) | >
( \ ( .x ) {
.x [match ( data , descrs ) ]
} ) ( )
}
#' Wrapper to create regression model based on supported models
#'
#' @description
#' Output is a concatenated list of model information and model
#'
#'
#' @param data data
#' @param outcome.str name of outcome variable
#' @param fun.descr Description of chosen function matching description in
#' "supported_functions()"
#' @param fun name of custom function. Default is NULL.
#' @param formula.str custom formula glue string. Default is NULL.
#' @param args.list custom character string to be converted using
#' argsstring2list() or list of arguments. Default is NULL.
#' @param ... ignored
#'
2025-02-25 09:51:42 +01:00
#' @returns list
2025-01-17 15:59:24 +01:00
#' @export
2025-02-25 09:51:42 +01:00
#' @rdname regression_model
2025-01-17 15:59:24 +01:00
#'
#' @examples
2025-01-30 14:32:11 +01:00
#' \dontrun{
2025-01-17 15:59:24 +01:00
#' gtsummary::trial |>
#' regression_model(
#' outcome.str = "age",
#' fun = "stats::lm",
#' formula.str = "{outcome.str}~.",
#' args.list = NULL
#' )
#' ls <- regression_model_list(data = default_parsing(mtcars), outcome.str = "cyl", fun.descr = "Ordinal logistic regression model")
#' summary(ls$model)
2025-04-09 12:31:08 +02:00
#' ls <- regression_model_list(data = default_parsing(mtcars), outcome.str = "mpg", fun.descr = "Linear regression model")
2025-01-30 14:32:11 +01:00
#'
#' ls <- regression_model_list(data = default_parsing(gtsummary::trial), outcome.str = "trt", fun.descr = "Logistic regression model")
#' tbl <- gtsummary::tbl_regression(ls$model, exponentiate = TRUE)
#' m <- gtsummary::trial |>
#' default_parsing() |>
#' regression_model(
#' outcome.str = "trt",
#' fun = "stats::glm",
#' formula.str = "{outcome.str}~.",
2025-04-09 12:31:08 +02:00
#' args.list = list(family = "binomial")
2025-01-30 14:32:11 +01:00
#' )
#' tbl2 <- gtsummary::tbl_regression(m, exponentiate = TRUE)
#' broom::tidy(ls$model)
#' broom::tidy(m)
#' }
2025-01-17 15:59:24 +01:00
regression_model_list <- function ( data ,
outcome.str ,
fun.descr ,
fun = NULL ,
formula.str = NULL ,
args.list = NULL ,
vars = NULL ,
... ) {
options <- get_fun_options ( fun.descr ) | >
( \ ( .x ) {
.x [ [1 ] ]
} ) ( )
## Custom, specific fun, args and formula options
if ( is.null ( formula.str ) ) {
formula.str.c <- options $ formula.str
} else {
formula.str.c <- formula.str
}
if ( is.null ( fun ) ) {
fun.c <- options $ fun
} else {
fun.c <- fun
}
if ( is.null ( args.list ) ) {
args.list.c <- options $ args.list
} else {
args.list.c <- args.list
}
if ( is.character ( args.list.c ) ) args.list.c <- argsstring2list ( args.list.c )
## Handling vars to print code
if ( is.null ( vars ) ) {
vars <- names ( data ) [ ! names ( data ) %in% outcome.str ]
} else {
if ( outcome.str %in% vars ) {
vars <- vars [ ! vars %in% outcome.str ]
}
}
2025-04-09 12:31:08 +02:00
parameters <- list (
2025-04-11 13:23:18 +02:00
data = data ,
2025-04-09 12:31:08 +02:00
fun = fun.c ,
2025-04-11 13:23:18 +02:00
formula.str = glue :: glue ( formula.str.c ) ,
2025-04-09 12:31:08 +02:00
args.list = args.list.c
)
2025-01-17 15:59:24 +01:00
model <- do.call (
regression_model ,
2025-04-11 13:23:18 +02:00
parameters
2025-01-17 15:59:24 +01:00
)
2025-04-11 13:23:18 +02:00
parameters_code <- Filter (
length ,
modifyList ( parameters , list (
2025-04-14 10:10:33 +02:00
data = as.symbol ( " df" ) ,
2025-04-11 13:23:18 +02:00
formula.str = as.character ( glue :: glue ( formula.str.c ) ) ,
outcome.str = NULL
# args.list = NULL,
2025-04-14 10:10:33 +02:00
) )
)
2025-04-09 12:31:08 +02:00
2025-04-11 13:23:18 +02:00
## The easiest solution was to simple paste as a string
## The rlang::call2 or rlang::expr functions would probably work as well
# code <- glue::glue("FreesearchR::regression_model({parameters_print}, args.list=list({list2str(args.list.c)}))", .null = "NULL")
2025-04-14 10:10:33 +02:00
code <- rlang :: call2 ( " regression_model" , ! ! ! parameters_code , .ns = " FreesearchR" )
2025-01-17 15:59:24 +01:00
list (
options = options ,
model = model ,
2025-04-11 13:23:18 +02:00
code = expression_string ( code )
2025-01-17 15:59:24 +01:00
)
}
list2str <- function ( data ) {
out <- purrr :: imap ( data , \ ( .x , .i ) {
if ( is.logical ( .x ) ) {
arg <- .x
} else {
arg <- glue :: glue ( " '{.x}'" )
}
glue :: glue ( " {.i} = {arg}" )
} ) | >
unlist ( ) | >
paste ( collapse = ( " , " ) )
2025-01-30 14:32:11 +01:00
if ( out == " " ) {
2025-01-17 15:59:24 +01:00
return ( NULL )
} else {
out
}
}
2025-01-30 14:32:11 +01:00
#' @returns list
2025-01-17 15:59:24 +01:00
#' @export
2025-02-25 09:51:42 +01:00
#' @rdname regression_model
2025-01-17 15:59:24 +01:00
#'
#' @examples
2025-01-30 14:32:11 +01:00
#' \dontrun{
2025-03-20 11:46:02 +01:00
#' gtsummary::trial |>
#' regression_model_uv(
#' outcome.str = "trt",
#' fun = "stats::glm",
#' args.list = list(family = stats::binomial(link = "logit"))
#' ) |>
#' lapply(broom::tidy) |>
#' dplyr::bind_rows()
2025-01-17 15:59:24 +01:00
#' ms <- regression_model_uv_list(data = default_parsing(mtcars), outcome.str = "mpg", fun.descr = "Linear regression model")
2025-04-09 12:31:08 +02:00
#' ms$code
2025-04-11 13:23:18 +02:00
#' ls <- regression_model_uv_list(data = default_parsing(mtcars), outcome.str = "am", fun.descr = "Logistic regression model")
#' ls$code
2025-03-20 11:46:02 +01:00
#' lapply(ms$model, broom::tidy) |> dplyr::bind_rows()
2025-01-30 14:32:11 +01:00
#' }
2025-01-17 15:59:24 +01:00
regression_model_uv_list <- function ( data ,
outcome.str ,
fun.descr ,
fun = NULL ,
formula.str = NULL ,
args.list = NULL ,
vars = NULL ,
... ) {
options <- get_fun_options ( fun.descr ) | >
( \ ( .x ) {
.x [ [1 ] ]
} ) ( )
## Custom, specific fun, args and formula options
if ( is.null ( formula.str ) ) {
formula.str.c <- options $ formula.str
} else {
formula.str.c <- formula.str
}
if ( is.null ( fun ) ) {
fun.c <- options $ fun
} else {
fun.c <- fun
}
if ( is.null ( args.list ) ) {
args.list.c <- options $ args.list
} else {
args.list.c <- args.list
}
if ( is.character ( args.list.c ) ) args.list.c <- argsstring2list ( args.list.c )
## Handling vars to print code
if ( is.null ( vars ) ) {
vars <- names ( data ) [ ! names ( data ) %in% outcome.str ]
} else {
if ( outcome.str %in% vars ) {
vars <- vars [ ! vars %in% outcome.str ]
}
}
# assertthat::assert_that("character" %in% class(fun),
# msg = "Please provide the function as a character vector."
# )
# model <- do.call(
# regression_model,
# c(
# list(data = data),
# list(outcome.str = outcome.str),
# list(fun = fun.c),
# list(formula.str = formula.str.c),
# args.list.c
# )
# )
model <- vars | >
lapply ( \ ( .var ) {
2025-04-11 13:23:18 +02:00
parameters <-
list (
fun = fun.c ,
data = data [c ( outcome.str , .var ) ] ,
formula.str = as.character ( glue :: glue ( gsub ( " vars" , " .var" , formula.str.c ) ) ) ,
args.list = args.list.c
)
2025-04-09 12:31:08 +02:00
out <- do.call (
2025-01-17 15:59:24 +01:00
regression_model ,
2025-04-11 13:23:18 +02:00
parameters
2025-01-17 15:59:24 +01:00
)
2025-04-09 12:31:08 +02:00
## This is the very long version
## Handles deeply nested glue string
2025-04-11 13:23:18 +02:00
# code <- glue::glue("FreesearchR::regression_model(data=df,{list2str(modifyList(parameters,list(data=NULL,args.list=list2str(args.list.c))))})")
2025-04-14 10:10:33 +02:00
code <- rlang :: call2 ( " regression_model" , ! ! ! modifyList ( parameters , list ( data = as.symbol ( " df" ) , args.list = args.list.c ) ) , .ns = " FreesearchR" )
2025-04-09 12:31:08 +02:00
REDCapCAST :: set_attr ( out , code , " code" )
} )
2025-01-17 15:59:24 +01:00
2025-04-09 12:31:08 +02:00
code <- model | >
lapply ( \ ( .x ) REDCapCAST :: get_attr ( .x , " code" ) ) | >
2025-04-11 13:23:18 +02:00
lapply ( expression_string ) | >
pipe_string ( collapse = " ,\n" ) | >
2025-04-09 12:31:08 +02:00
( \ ( .x ) {
2025-04-11 13:23:18 +02:00
paste0 ( " list(\n" , .x , " )" )
2025-04-09 12:31:08 +02:00
} ) ( )
2025-01-17 15:59:24 +01:00
list (
options = options ,
model = model ,
code = code
)
}
2024-12-18 10:37:37 +01:00
2025-04-11 13:23:18 +02:00
# regression_model(mtcars, fun = "stats::lm", formula.str = "mpg~cyl")
2025-01-30 14:32:11 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//regression_plot.R
2025-01-30 14:32:11 +01:00
########
#' Regression coef plot from gtsummary. Slightly modified to pass on arguments
#'
#' @param x (`tbl_regression`, `tbl_uvregression`)\cr
#' A 'tbl_regression' or 'tbl_uvregression' object
2025-04-03 13:11:02 +02:00
#' @param plot_ref (scalar `logical`)\cr
#' plot reference values
#' @param remove_header_rows (scalar `logical`)\cr
#' logical indicating whether to remove header rows
#' for categorical variables. Default is `TRUE`
#' @param remove_reference_rows (scalar `logical`)\cr
#' logical indicating whether to remove reference rows
#' for categorical variables. Default is `FALSE`.
2025-01-30 14:32:11 +01:00
#' @param ... arguments passed to `ggstats::ggcoef_plot(...)`
#'
#' @returns ggplot object
#' @export
#'
#' @examples
#' \dontrun{
2025-03-19 13:10:56 +01:00
#' mod <- lm(mpg ~ ., default_parsing(mtcars))
2025-01-30 14:32:11 +01:00
#' p <- mod |>
#' gtsummary::tbl_regression() |>
#' plot(colour = "variable")
#' }
#'
plot.tbl_regression <- function ( x ,
2025-03-19 13:10:56 +01:00
plot_ref = TRUE ,
remove_header_rows = TRUE ,
remove_reference_rows = FALSE ,
2025-01-30 14:32:11 +01:00
... ) {
# check_dots_empty()
gtsummary ::: check_pkg_installed ( " ggstats" )
gtsummary ::: check_not_missing ( x )
# gtsummary:::check_scalar_logical(remove_header_rows)
# gtsummary:::check_scalar_logical(remove_reference_rows)
df_coefs <- x $ table_body
2025-03-19 13:10:56 +01:00
if ( isTRUE ( remove_header_rows ) ) {
df_coefs <- df_coefs | > dplyr :: filter ( ! header_row %in% TRUE )
}
if ( isTRUE ( remove_reference_rows ) ) {
df_coefs <- df_coefs | > dplyr :: filter ( ! reference_row %in% TRUE )
}
2025-01-30 14:32:11 +01:00
2025-03-19 13:10:56 +01:00
# Removes redundant label
2025-01-30 14:32:11 +01:00
df_coefs $ label [df_coefs $ row_type == " label" ] <- " "
2025-03-24 14:40:30 +01:00
# browser()
2025-03-19 13:10:56 +01:00
# Add estimate value to reference level
2025-03-24 14:40:30 +01:00
if ( plot_ref == TRUE ) {
df_coefs [df_coefs $ var_type %in% c ( " categorical" , " dichotomous" ) & df_coefs $ reference_row & ! is.na ( df_coefs $ reference_row ) , " estimate" ] <- if ( x $ inputs $ exponentiate ) 1 else 0
}
2025-01-30 14:32:11 +01:00
2025-03-19 13:10:56 +01:00
p <- df_coefs | >
ggstats :: ggcoef_plot ( exponentiate = x $ inputs $ exponentiate , ... )
2025-01-30 14:32:11 +01:00
2025-03-24 14:40:30 +01:00
if ( x $ inputs $ exponentiate ) {
2025-03-19 13:10:56 +01:00
p <- symmetrical_scale_x_log10 ( p )
}
p
}
2025-01-30 14:32:11 +01:00
#' Wrapper to pivot gtsummary table data to long for plotting
#'
#' @param list a custom regression models list
#' @param model.names names of models to include
#'
#' @returns list
#' @export
#'
merge_long <- function ( list , model.names ) {
l_subset <- list $ tables [model.names ]
l_merged <- l_subset | > tbl_merge ( )
df_body <- l_merged $ table_body
sel_list <- lapply ( seq_along ( l_subset ) , \ ( .i ) {
endsWith ( names ( df_body ) , paste0 ( " _" , .i ) )
} ) | >
setNames ( names ( l_subset ) )
common <- ! Reduce ( `|` , sel_list )
df_body_long <- sel_list | >
purrr :: imap ( \ ( .l , .i ) {
d <- dplyr :: bind_cols (
df_body [common ] ,
df_body [.l ] ,
model = .i
)
setNames ( d , gsub ( " _[0-9]{,}$" , " " , names ( d ) ) )
} ) | >
2025-03-24 14:40:30 +01:00
dplyr :: bind_rows ( ) | >
2025-03-31 14:37:28 +02:00
dplyr :: mutate ( model = REDCapCAST :: as_factor ( model ) )
2025-01-30 14:32:11 +01:00
l_merged $ table_body <- df_body_long
l_merged $ inputs $ exponentiate <- ! identical ( class ( list $ models $ Multivariable $ model ) , " lm" )
l_merged
}
2025-03-19 13:10:56 +01:00
#' Easily round log scale limits for nice plots
#'
#' @param data data
#' @param fun rounding function (floor/ceiling)
#' @param ... ignored
#'
#' @returns numeric vector
#' @export
#'
#' @examples
2025-03-24 14:40:30 +01:00
#' limit_log(-.1, floor)
#' limit_log(.1, ceiling)
#' limit_log(-2.1, ceiling)
#' limit_log(2.1, ceiling)
limit_log <- function ( data , fun , ... ) {
fun ( 10 ^ - floor ( data ) * 10 ^data ) / 10 ^ - floor ( data )
}
#' Create summetric log ticks
#'
#' @param data numeric vector
#'
2025-03-26 12:07:28 +01:00
#' @returns numeric vector
2025-03-24 14:40:30 +01:00
#' @export
#'
#' @examples
#' c(sample(seq(.1, 1, .1), 3), sample(1:10, 3)) |> create_log_tics()
create_log_tics <- function ( data ) {
sort ( round ( unique ( c ( 1 / data , data , 1 ) ) , 2 ) )
2025-03-19 13:10:56 +01:00
}
#' Ensure symmetrical plot around 1 on a logarithmic x scale for ratio plots
#'
#' @param plot ggplot2 plot
#' @param breaks breaks used and mirrored
#' @param ... ignored
#'
#' @returns ggplot2 object
#' @export
#'
2025-03-24 14:40:30 +01:00
symmetrical_scale_x_log10 <- function ( plot , breaks = c ( 1 , 2 , 3 , 5 , 10 ) , ... ) {
2025-03-19 13:10:56 +01:00
rx <- ggplot2 :: layer_scales ( plot ) $ x $ get_limits ( )
2025-03-24 14:40:30 +01:00
x_min <- floor ( 10 * rx [1 ] ) / 10
x_max <- ceiling ( 10 * rx [2 ] ) / 10
2025-03-19 13:10:56 +01:00
2025-03-24 14:40:30 +01:00
rx_min <- limit_log ( rx [1 ] , floor )
rx_max <- limit_log ( rx [2 ] , ceiling )
2025-03-19 13:10:56 +01:00
2025-03-24 14:40:30 +01:00
max_abs_x <- max ( abs ( c ( x_min , x_max ) ) )
2025-03-19 13:10:56 +01:00
2025-03-24 14:40:30 +01:00
ticks <- log10 ( breaks ) + ( ceiling ( max_abs_x ) - 1 )
2025-03-19 13:28:34 +01:00
2025-03-24 14:40:30 +01:00
plot + ggplot2 :: scale_x_log10 ( limits = c ( rx_min , rx_max ) , breaks = create_log_tics ( 10 ^ticks [ticks <= max_abs_x ] ) )
2025-03-19 13:10:56 +01:00
}
2024-12-18 10:37:37 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//regression_table.R
2024-12-18 10:37:37 +01:00
########
2025-01-16 11:24:26 +01:00
#' Create table of regression model
#'
#' @param x regression model
#' @param args.list list of arguments passed to 'fun'.
#' @param fun function to use for table creation. Default is "gtsummary::tbl_regression".
#' @param ... passed to methods
#'
#' @return object of standard class for fun
#' @export
#' @name regression_table
#'
#' @examples
#' \dontrun{
2025-01-23 08:44:38 +01:00
#' tbl <- gtsummary::trial |>
2025-01-16 11:24:26 +01:00
#' regression_model(
#' outcome.str = "stage",
#' fun = "MASS::polr"
#' ) |>
#' regression_table(args.list = list("exponentiate" = TRUE))
#' gtsummary::trial |>
#' regression_model(
#' outcome.str = "age",
#' fun = "stats::lm",
#' formula.str = "{outcome.str}~.",
#' args.list = NULL
#' ) |>
2025-04-02 11:31:04 +02:00
#' regression_table() |>
#' plot()
2025-01-16 11:24:26 +01:00
#' gtsummary::trial |>
#' regression_model(
#' outcome.str = "trt",
#' fun = "stats::glm",
#' args.list = list(family = binomial(link = "logit"))
#' ) |>
#' regression_table()
#' gtsummary::trial |>
#' regression_model_uv(
#' outcome.str = "trt",
#' fun = "stats::glm",
#' args.list = list(family = stats::binomial(link = "logit"))
#' ) |>
#' regression_table()
#' gtsummary::trial |>
#' regression_model_uv(
#' outcome.str = "stage",
#' args.list = list(family = stats::binomial(link = "logit"))
#' ) |>
#' regression_table()
#'
#' list(
#' "Univariable" = regression_model_uv,
#' "Multivariable" = regression_model
#' ) |>
#' lapply(\(.fun){
#' do.call(
#' .fun,
#' c(
#' list(data = gtsummary::trial),
#' list(outcome.str = "stage")
#' )
#' )
#' }) |>
#' purrr::map(regression_table) |>
#' tbl_merge()
2025-04-02 11:31:04 +02:00
#' }
2025-01-16 11:24:26 +01:00
#' 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
2025-03-19 13:10:56 +01:00
#' class(x) <- class(x)[class(x) != "freesearchr_model"]
2025-01-16 11:24:26 +01:00
#'
#' 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()
#' }
2024-12-18 10:37:37 +01:00
regression_table <- function ( x , ... ) {
2025-04-02 11:31:04 +02:00
if ( " list" %in% class ( x ) ) {
2024-12-18 10:37:37 +01:00
x | >
purrr :: map ( \ ( .m ) {
regression_table_create ( x = .m , ... ) | >
gtsummary :: add_n ( )
} ) | >
gtsummary :: tbl_stack ( )
} else {
2025-04-02 11:31:04 +02:00
regression_table_create ( x , ... )
2024-12-18 10:37:37 +01:00
}
}
2025-04-02 11:31:04 +02:00
regression_table_create <- function ( x , ... , args.list = NULL , fun = " gtsummary::tbl_regression" , theme = c ( " jama" , " lancet" , " nejm" , " qjecon" ) ) {
2024-12-18 10:37:37 +01:00
# Stripping custom class
2025-03-19 13:10:56 +01:00
class ( x ) <- class ( x ) [class ( x ) != " freesearchr_model" ]
2024-12-18 10:37:37 +01:00
2025-04-02 11:31:04 +02:00
theme <- match.arg ( theme )
2024-12-18 10:37:37 +01:00
if ( any ( c ( length ( class ( x ) ) != 1 , class ( x ) != " lm" ) ) ) {
if ( ! " exponentiate" %in% names ( args.list ) ) {
2025-01-24 11:26:14 +01:00
args.list <- c ( args.list , list ( exponentiate = TRUE , p.values = TRUE ) )
2024-12-18 10:37:37 +01:00
}
}
2025-04-02 11:31:04 +02:00
gtsummary :: theme_gtsummary_journal ( journal = theme )
if ( inherits ( x , " polr" ) ) {
# browser()
out <- do.call ( getfun ( fun ) , c ( list ( x = x ) , args.list ) )
# out <- do.call(getfun(fun), c(list(x = x, tidy_fun = list(residual_type = "normal")), args.list))
# out <- do.call(what = getfun(fun),
# args = c(
# list(
# x = x,
# tidy_fun = list(
# conf.int = TRUE,
# conf.level = 0.95,
# residual_type = "normal")),
# args.list)
# )
} else {
out <- do.call ( getfun ( fun ) , c ( list ( x = x ) , args.list ) )
}
out
2024-12-18 10:37:37 +01:00
}
2025-01-16 11:24:26 +01:00
#' A substitue to gtsummary::tbl_merge, that will use list names for the tab
#' spanner names.
#'
#' @param data gtsummary list object
#'
#' @return gt summary list object
#' @export
#'
2024-12-18 10:37:37 +01:00
tbl_merge <- function ( data ) {
if ( is.null ( names ( data ) ) ) {
data | > gtsummary :: tbl_merge ( )
} else {
data | > gtsummary :: tbl_merge ( tab_spanner = names ( data ) )
}
}
2025-01-23 08:44:38 +01:00
# 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"))
2024-12-18 10:37:37 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//regression-module.R
2024-12-18 10:37:37 +01:00
########
2025-04-11 13:23:18 +02:00
### On rewriting this module
###
### This module (and the plotting module) should be rewritten to allow for
### dynamically defining variable-selection for model evaluation.
### The principle of having a library of supported functions is fine, but should
### be expanded.
###
###
# list(
# lm = list(
# descr = "Linear regression model",
# design = "cross-sectional",
# parameters=list(
# fun = "stats::lm",
# args.list = NULL
# ),
# variables = list(
# outcome.str = list(
# fun = "columnSelectInput",
# multiple = FALSE,
# label = "Select the dependent/outcome variable."
# )
# ),
# out.type = "continuous",
# formula.str = "{outcome.str}~{paste(vars,collapse='+')}",
# table.fun = "gtsummary::tbl_regression",
# table.args.list = list(exponentiate = FALSE)
# ))
#
# Regarding the regression model, it really should be the design selection,
# that holds the input selection information, as this is what is deciding
# the number and type of primary inputs.
#
# Cross-sectional: outcome
# MMRM: outcome, random effect (id, time)
# Survival: time, status, strata(?)
#
#
2025-04-02 11:31:04 +02:00
regression_ui <- function ( id , ... ) {
ns <- shiny :: NS ( id )
shiny :: tagList (
title = " " ,
sidebar = bslib :: sidebar (
shiny :: uiOutput ( outputId = ns ( " data_info" ) , inline = TRUE ) ,
bslib :: accordion (
open = " acc_reg" ,
multiple = FALSE ,
bslib :: accordion_panel (
value = " acc_reg" ,
title = " Regression" ,
icon = bsicons :: bs_icon ( " calculator" ) ,
shiny :: uiOutput ( outputId = ns ( " outcome_var" ) ) ,
# shiny::selectInput(
# inputId = "design",
# label = "Study design",
# selected = "no",
# inline = TRUE,
# choices = list(
# "Cross-sectional" = "cross-sectional"
# )
# ),
shiny :: uiOutput ( outputId = ns ( " regression_type" ) ) ,
shiny :: radioButtons (
inputId = ns ( " add_regression_p" ) ,
label = " Add p-value" ,
inline = TRUE ,
selected = " yes" ,
choices = list (
" Yes" = " yes" ,
" No" = " no"
)
) ,
shiny :: radioButtons (
inputId = ns ( " all" ) ,
label = " Specify covariables" ,
inline = TRUE , selected = 2 ,
choiceNames = c (
" Yes" ,
" No"
) ,
choiceValues = c ( 1 , 2 )
) ,
shiny :: conditionalPanel (
condition = " input.all==1" ,
shiny :: uiOutput ( outputId = ns ( " regression_vars" ) ) ,
shiny :: helpText ( " If none are selected, all are included." ) ,
shiny :: tags $ br ( ) ,
ns = ns
) ,
bslib :: input_task_button (
id = ns ( " load" ) ,
label = " Analyse" ,
icon = bsicons :: bs_icon ( " pencil" ) ,
label_busy = " Working..." ,
icon_busy = fontawesome :: fa_i ( " arrows-rotate" ,
class = " fa-spin" ,
" aria-hidden" = " true"
) ,
type = " secondary" ,
auto_reset = TRUE
) ,
2025-04-11 13:23:18 +02:00
shiny :: helpText ( " Press 'Analyse' to create the regression model and after changing parameters." ) ,
2025-04-02 11:31:04 +02:00
shiny :: tags $ br ( )
) ,
do.call (
bslib :: accordion_panel ,
c (
list (
value = " acc_plot" ,
title = " Coefficient plot" ,
icon = bsicons :: bs_icon ( " bar-chart-steps" ) ,
shiny :: tags $ br ( ) ,
shiny :: uiOutput ( outputId = ns ( " plot_model" ) )
) ,
# plot_download_ui(ns("reg_plot_download"))
shiny :: tagList (
shinyWidgets :: noUiSliderInput (
inputId = ns ( " plot_height" ) ,
label = " Plot height (mm)" ,
min = 50 ,
max = 300 ,
value = 100 ,
step = 1 ,
format = shinyWidgets :: wNumbFormat ( decimals = 0 ) ,
color = datamods ::: get_primary_color ( )
) ,
shinyWidgets :: noUiSliderInput (
inputId = ns ( " plot_width" ) ,
label = " Plot width (mm)" ,
min = 50 ,
max = 300 ,
value = 100 ,
step = 1 ,
format = shinyWidgets :: wNumbFormat ( decimals = 0 ) ,
color = datamods ::: get_primary_color ( )
) ,
shiny :: selectInput (
inputId = ns ( " plot_type" ) ,
label = " File format" ,
choices = list (
" png" ,
" tiff" ,
" eps" ,
" pdf" ,
" jpeg" ,
" svg"
)
) ,
shiny :: br ( ) ,
# Button
shiny :: downloadButton (
outputId = ns ( " download_plot" ) ,
label = " Download plot" ,
icon = shiny :: icon ( " download" )
)
)
)
) ,
bslib :: accordion_panel (
value = " acc_checks" ,
title = " Checks" ,
icon = bsicons :: bs_icon ( " clipboard-check" ) ,
shiny :: uiOutput ( outputId = ns ( " plot_checks" ) )
)
)
) ,
bslib :: nav_panel (
title = " Regression table" ,
gt :: gt_output ( outputId = ns ( " table2" ) )
) ,
bslib :: nav_panel (
title = " Coefficient plot" ,
shiny :: plotOutput ( outputId = ns ( " regression_plot" ) , height = " 80vh" )
) ,
bslib :: nav_panel (
title = " Model checks" ,
shiny :: plotOutput ( outputId = ns ( " check" ) , height = " 90vh" )
)
2024-12-18 10:37:37 +01:00
)
}
2025-04-02 11:31:04 +02:00
regression_server <- function ( id ,
data ,
... ) {
shiny :: moduleServer (
id = id ,
module = function ( input , output , session ) {
ns <- session $ ns
2024-12-18 10:37:37 +01:00
2025-04-02 11:31:04 +02:00
rv <- shiny :: reactiveValues (
data = NULL ,
plot = NULL ,
check = NULL ,
list = list ( )
)
2024-12-18 10:37:37 +01:00
2025-04-02 11:31:04 +02:00
data_r <- shiny :: reactive ( {
if ( shiny :: is.reactive ( data ) ) {
data ( )
} else {
data
}
} )
2024-12-18 10:37:37 +01:00
2025-04-02 11:31:04 +02:00
output $ data_info <- shiny :: renderUI ( {
shiny :: req ( regression_vars ( ) )
shiny :: req ( data_r ( ) )
data_description ( data_r ( ) [regression_vars ( ) ] )
} )
2024-12-18 10:37:37 +01:00
2025-04-02 11:31:04 +02:00
##############################################################################
#########
######### Input fields
#########
##############################################################################
2024-12-18 10:37:37 +01:00
2025-04-02 11:31:04 +02:00
## Keep these "old" selection options as a simple alternative to the modification pane
2025-02-07 16:24:09 +01:00
2024-12-18 10:37:37 +01:00
2025-04-02 11:31:04 +02:00
output $ regression_vars <- shiny :: renderUI ( {
columnSelectInput (
inputId = ns ( " regression_vars" ) ,
selected = NULL ,
label = " Covariables to include" ,
data = data_r ( ) ,
multiple = TRUE
)
} )
output $ outcome_var <- shiny :: renderUI ( {
columnSelectInput (
inputId = ns ( " outcome_var" ) ,
selected = NULL ,
label = " Select outcome variable" ,
data = data_r ( ) ,
multiple = FALSE
)
} )
output $ regression_type <- shiny :: renderUI ( {
shiny :: req ( input $ outcome_var )
shiny :: selectizeInput (
inputId = ns ( " regression_type" ) ,
label = " Choose regression analysis" ,
## The below ifelse statement handles the case of loading a new dataset
choices = possible_functions (
data = dplyr :: select (
data_r ( ) ,
ifelse ( input $ outcome_var %in% names ( data_r ( ) ) ,
input $ outcome_var ,
names ( data_r ( ) ) [1 ]
)
) , design = " cross-sectional"
) ,
multiple = FALSE
)
} )
output $ factor_vars <- shiny :: renderUI ( {
shiny :: selectizeInput (
inputId = ns ( " factor_vars" ) ,
selected = colnames ( data_r ( ) ) [sapply ( data_r ( ) , is.factor ) ] ,
label = " Covariables to format as categorical" ,
choices = colnames ( data_r ( ) ) ,
multiple = TRUE
)
} )
## Collected regression variables
regression_vars <- shiny :: reactive ( {
if ( is.null ( input $ regression_vars ) ) {
out <- colnames ( data_r ( ) )
} else {
out <- unique ( c ( input $ regression_vars , input $ outcome_var ) )
}
return ( out )
} )
output $ strat_var <- shiny :: renderUI ( {
columnSelectInput (
inputId = ns ( " strat_var" ) ,
selected = " none" ,
label = " Select variable to stratify baseline" ,
data = data_r ( ) ,
col_subset = c (
" none" ,
names ( data_r ( ) ) [unlist ( lapply ( data_r ( ) , data_type ) ) %in% c ( " dichotomous" , " categorical" , " ordinal" ) ]
)
)
} )
output $ plot_model <- shiny :: renderUI ( {
shiny :: req ( rv $ list $ regression $ tables )
shiny :: selectInput (
inputId = ns ( " plot_model" ) ,
selected = 1 ,
label = " Select models to plot" ,
choices = names ( rv $ list $ regression $ tables ) ,
multiple = TRUE
)
} )
##############################################################################
#########
######### Regression analysis
#########
##############################################################################
shiny :: observeEvent (
input $ load ,
{
shiny :: req ( input $ outcome_var )
rv $ list $ regression $ models <- NULL
tryCatch (
{
## Which models to create should be decided by input
## Could also include
## imputed or
## minimally adjusted
model_lists <- list (
2025-04-09 12:31:08 +02:00
" Univariable" = " regression_model_uv_list" ,
" Multivariable" = " regression_model_list"
2025-04-02 11:31:04 +02:00
) | >
lapply ( \ ( .fun ) {
2025-04-30 10:02:29 +02:00
parameters <- list (
2025-04-09 12:31:08 +02:00
data = data_r ( ) [regression_vars ( ) ] ,
2025-04-30 10:02:29 +02:00
outcome.str = input $ outcome_var ,
fun.descr = input $ regression_type
2025-04-09 12:31:08 +02:00
)
do.call (
2025-04-02 11:31:04 +02:00
.fun ,
2025-04-09 12:31:08 +02:00
parameters
2025-04-02 11:31:04 +02:00
)
} )
rv $ list $ regression $ params <- get_fun_options ( input $ regression_type ) | >
( \ ( .x ) {
.x [ [1 ] ]
} ) ( )
rv $ list $ regression $ models <- model_lists
} ,
error = function ( err ) {
showNotification ( paste0 ( " Creating regression models failed with the following error: " , err ) , type = " err" )
}
)
}
)
##############################################################################
#########
######### Model checks
#########
##############################################################################
shiny :: observeEvent (
list (
rv $ list $ regression $ models
) ,
{
shiny :: req ( rv $ list $ regression $ models )
tryCatch (
{
rv $ check <- lapply ( rv $ list $ regression $ models , \ ( .x ) {
.x $ model
} ) | >
purrr :: pluck ( " Multivariable" ) | >
performance :: check_model ( )
} ,
# warning = function(warn) {
# showNotification(paste0(warn), type = "warning")
# },
error = function ( err ) {
showNotification ( paste0 ( " Running model assumptions checks failed with the following error: " , err ) , type = " err" )
}
)
}
)
rv $ check_plot <- shiny :: reactive ( plot ( rv $ check ) )
output $ plot_checks <- shiny :: renderUI ( {
shiny :: req ( rv $ list $ regression $ models )
shiny :: req ( rv $ check_plot )
## Implement correct plotting
names <- sapply ( rv $ check_plot ( ) , \ ( .i ) {
# .i$labels$title
get_ggplot_label ( .i , " title" )
} )
vectorSelectInput (
inputId = ns ( " plot_checks" ) ,
selected = 1 ,
label = " Select checks to plot" ,
choices = names ,
multiple = TRUE
)
} )
output $ check <- shiny :: renderPlot (
{
shiny :: req ( rv $ check_plot )
shiny :: req ( input $ plot_checks )
2025-04-30 10:02:29 +02:00
## Print checks if a regression table is present
if ( ! is.null ( rv $ list $ regression $ tables ) ) {
p <- rv $ check_plot ( ) +
# patchwork::wrap_plots() +
patchwork :: plot_annotation ( title = " Multivariable regression model checks" )
2025-04-02 11:31:04 +02:00
2025-04-30 10:02:29 +02:00
layout <- sapply ( seq_len ( length ( p ) ) , \ ( .x ) {
patchwork :: area ( .x , 1 )
} )
2025-04-02 11:31:04 +02:00
2025-04-30 10:02:29 +02:00
p_list <- p + patchwork :: plot_layout ( design = Reduce ( c , layout ) )
2025-04-02 11:31:04 +02:00
2025-04-30 10:02:29 +02:00
index <- match (
input $ plot_checks ,
sapply ( rv $ check_plot ( ) , \ ( .i ) {
get_ggplot_label ( .i , " title" )
} )
)
2025-04-02 11:31:04 +02:00
2025-04-30 10:02:29 +02:00
ls <- list ( )
2025-04-02 11:31:04 +02:00
2025-04-30 10:02:29 +02:00
for ( i in index ) {
p <- p_list [ [i ] ] +
ggplot2 :: theme (
axis.text = ggplot2 :: element_text ( size = 10 ) ,
axis.title = ggplot2 :: element_text ( size = 12 ) ,
legend.text = ggplot2 :: element_text ( size = 12 ) ,
plot.subtitle = ggplot2 :: element_text ( size = 12 ) ,
plot.title = ggplot2 :: element_text ( size = 18 )
)
ls <- c ( ls , list ( p ) )
2025-04-02 11:31:04 +02:00
}
2025-04-30 10:02:29 +02:00
# browser()
tryCatch (
{
out <- patchwork :: wrap_plots ( ls , ncol = if ( length ( ls ) == 1 ) 1 else 2 )
} ,
error = function ( err ) {
showNotification ( err , type = " err" )
}
)
out
} else {
return ( NULL )
}
2025-04-02 11:31:04 +02:00
} ,
alt = " Assumptions testing of the multivariable regression model"
)
2025-04-30 10:02:29 +02:00
shiny :: observeEvent (
list (
2025-05-05 14:45:07 +02:00
data_r ( ) ,
regression_vars ( )
2025-04-30 10:02:29 +02:00
) ,
{
rv $ list $ regression $ tables <- NULL
}
)
### Creating the regression table
2025-04-02 11:31:04 +02:00
shiny :: observeEvent (
input $ load ,
{
shiny :: req ( rv $ list $ regression $ models )
## To avoid plotting old models on fail/error
rv $ list $ regression $ tables <- NULL
2025-04-30 13:02:26 +02:00
# browser()
2025-04-02 11:31:04 +02:00
tryCatch (
{
2025-04-09 12:31:08 +02:00
parameters <- list (
add_p = input $ add_regression_p == " no"
)
2025-04-02 11:31:04 +02:00
out <- lapply ( rv $ list $ regression $ models , \ ( .x ) {
.x $ model
} ) | >
2025-04-09 12:31:08 +02:00
purrr :: map ( \ ( .x ) {
do.call (
regression_table ,
2025-04-30 10:02:29 +02:00
append_list ( .x , parameters , " x" )
2025-04-09 12:31:08 +02:00
)
2025-04-30 10:02:29 +02:00
} )
2025-04-09 12:31:08 +02:00
# if (input$add_regression_p == "no") {
# out <- out |>
# lapply(\(.x){
# .x |>
# gtsummary::modify_column_hide(
# column = "p.value"
# )
# })
# }
rv $ list $ regression $ models | >
2025-04-30 10:02:29 +02:00
purrr :: imap ( \ ( .x , .i ) {
2025-04-09 12:31:08 +02:00
rv $ list $ regression $ models [ [.i ] ] [ [ " code_table" ] ] <- paste (
2025-04-30 10:02:29 +02:00
.x $ code ,
expression_string ( rlang :: call2 ( .fn = " regression_table" , ! ! ! parameters , .ns = " FreesearchR" ) , assign.str = NULL ) ,
sep = " |>\n"
)
2025-04-09 12:31:08 +02:00
} )
2025-04-02 11:31:04 +02:00
rv $ list $ regression $ tables <- out
rv $ list $ input <- input
2025-04-30 13:02:26 +02:00
2025-04-02 11:31:04 +02:00
} ,
warning = function ( warn ) {
showNotification ( paste0 ( warn ) , type = " warning" )
} ,
error = function ( err ) {
showNotification ( paste0 ( " Creating a regression table failed with the following error: " , err ) , type = " err" )
}
)
}
)
output $ table2 <- gt :: render_gt ( {
2025-04-30 10:02:29 +02:00
## Print checks if a regression table is present
if ( ! is.null ( rv $ list $ regression $ tables ) ) {
rv $ list $ regression $ tables | >
tbl_merge ( ) | >
gtsummary :: as_gt ( ) | >
gt :: tab_header ( gt :: md ( glue :: glue ( " **Table 2: {rv$list$regression$params$descr}**" ) ) )
} else {
return ( NULL )
}
2025-04-02 11:31:04 +02:00
} )
##############################################################################
#########
######### Coefficients plot
#########
##############################################################################
shiny :: observeEvent ( list (
input $ plot_model ,
rv $ list $ regression
) , {
shiny :: req ( input $ plot_model )
tryCatch (
{
p <- merge_long (
rv $ list $ regression ,
sort_by (
input $ plot_model ,
c ( " Univariable" , " Minimal" , " Multivariable" ) ,
na.rm = TRUE
)
) | >
( \ ( .x ) {
if ( length ( input $ plot_model ) > 1 ) {
plot.tbl_regression (
x = .x ,
colour = " model" ,
dodged = TRUE
) +
ggplot2 :: theme ( legend.position = " bottom" ) +
ggplot2 :: guides ( color = ggplot2 :: guide_legend ( reverse = TRUE ) )
} else {
plot.tbl_regression (
x = .x ,
colour = " variable"
) +
ggplot2 :: theme ( legend.position = " none" )
}
} ) ( )
rv $ plot <- p +
ggplot2 :: scale_y_discrete ( labels = scales :: label_wrap ( 15 ) ) +
gg_theme_shiny ( )
} ,
error = function ( err ) {
showNotification ( paste0 ( err ) , type = " err" )
}
)
} )
output $ regression_plot <- shiny :: renderPlot (
{
shiny :: req ( input $ plot_model )
rv $ plot
} ,
alt = " Regression coefficient plot"
)
# plot_download_server(
# id = ns("reg_plot_download"),
# data = shiny::reactive(rv$plot)
# )
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.." , {
ggplot2 :: ggsave (
filename = file ,
plot = rv $ plot ,
width = input $ plot_width ,
height = input $ plot_height ,
dpi = 300 ,
units = " mm" , scale = 2
)
} )
}
)
##############################################################################
#########
######### Output
#########
##############################################################################
return ( shiny :: reactive ( {
2025-04-30 13:02:26 +02:00
rv $ list
2025-04-02 11:31:04 +02:00
} ) )
}
)
}
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//report.R
2025-04-02 11:31:04 +02:00
########
#' Split vector by an index and embed addition
#'
#' @param data vector
#' @param index split index
#' @param add addition
#'
#' @return vector
#' @export
#'
index_embed <- function ( data , index , add = NULL ) {
start <- seq_len ( index )
end <- seq_along ( data ) [ - start ]
c (
data [start ] ,
add ,
data [end ]
)
}
#' Specify format arguments to include in qmd header/frontmatter
#'
#' @param data vector
#' @param fileformat format to include
#'
#' @return vector
#' @export
#'
specify_qmd_format <- function ( data , fileformat = c ( " docx" , " odt" , " pdf" , " all" ) ) {
fileformat <- match.arg ( fileformat )
args_list <- default_format_arguments ( ) | > purrr :: imap ( format_writer )
if ( fileformat == " all" ) {
out <- data | > index_embed ( index = 4 , add = Reduce ( c , args_list ) )
} else {
out <- data | > index_embed ( index = 4 , add = args_list [ [fileformat ] ] )
}
out
}
#' Merges list of named arguments for qmd header generation
#'
#' @param data vector
#' @param name name
#'
#' @return vector
#' @export
#'
format_writer <- function ( data , name ) {
if ( data == " default" ) {
glue :: glue ( " {name}: {data}" )
} else {
warning ( " Not implemented" )
}
}
#' Defaults qmd formats
#'
#' @return list
#' @export
#'
default_format_arguments <- function ( ) {
list (
docx = list ( " default" ) ,
odt = list ( " default" ) ,
pdf = list ( " default" )
)
}
#' Wrapper to modify quarto file to render specific formats
#'
#' @param file filename
#' @param format desired output
#'
#' @return none
#' @export
#'
modify_qmd <- function ( file , format ) {
readLines ( file ) | >
specify_qmd_format ( fileformat = " all" ) | >
writeLines ( paste0 ( tools :: file_path_sans_ext ( file ) , " _format." , tools :: file_ext ( file ) ) )
}
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//syntax_highlight.R
########
## Inpiration:
##
## https://stackoverflow.com/questions/47445260/how-to-enable-syntax-highlighting-in-r-shiny-app-with-htmloutput
prismCodeBlock <- function ( code ) {
tagList (
HTML ( html_code_wrap ( code ) ) ,
tags $ script ( " Prism.highlightAll()" )
)
}
prismDependencies <- tags $ head (
tags $ script ( src = " https://cdnjs.cloudflare.com/ajax/libs/prism/1.8.4/prism.min.js" ) ,
tags $ link ( rel = " stylesheet" , type = " text/css" ,
href = " https://cdnjs.cloudflare.com/ajax/libs/prism/1.8.4/themes/prism.min.css" )
)
prismRDependency <- tags $ head (
tags $ script ( src = " https://cdnjs.cloudflare.com/ajax/libs/prism/1.8.4/components/prism-r.min.js" )
)
html_code_wrap <- function ( string , lang = " r" ) {
glue :: glue ( " < p r e > < c o d e c l a s s = ' l a n g u a g e - { l a n g } ' > { s t r i n g }
< / code > < / pre > " )
}
########
#### Current file: /Users/au301842/FreesearchR/R//theme.R
2025-04-02 11:31:04 +02:00
########
#' Custom theme based on unity
#'
#' @param ... everything passed on to bslib::bs_theme()
#'
#' @returns theme list
#' @export
custom_theme <- function ( ... ,
version = 5 ,
2025-04-24 13:16:33 +02:00
primary = FreesearchR_colors ( " primary" ) ,
secondary = FreesearchR_colors ( " secondary" ) ,
2025-04-02 11:31:04 +02:00
bootswatch = " united" ,
base_font = bslib :: font_google ( " Montserrat" ) ,
2025-04-24 12:53:47 +02:00
heading_font = bslib :: font_google ( " Public Sans" , wght = " 700" ) ,
2025-04-24 13:16:33 +02:00
code_font = bslib :: font_google ( " Open Sans" ) ,
success = FreesearchR_colors ( " success" ) ,
info = FreesearchR_colors ( " info" ) ,
warning = FreesearchR_colors ( " warning" ) ,
danger = FreesearchR_colors ( " danger" )
2025-04-02 11:31:04 +02:00
# fg = "#000",
# bg="#fff",
# base_font = bslib::font_google("Alice"),
# heading_font = bslib::font_google("Jost", wght = "800"),
# heading_font = bslib::font_google("Noto Serif"),
# heading_font = bslib::font_google("Alice"),
2025-04-24 12:53:47 +02:00
) {
2025-04-02 11:31:04 +02:00
bslib :: bs_theme (
... ,
2025-02-07 16:24:09 +01:00
" navbar-bg" = primary ,
2024-12-19 11:34:25 +01:00
version = version ,
primary = primary ,
secondary = secondary ,
bootswatch = bootswatch ,
base_font = base_font ,
heading_font = heading_font ,
2025-04-24 13:16:33 +02:00
code_font = code_font ,
success = success ,
info = info ,
warning = warning ,
danger = danger
2024-12-18 10:37:37 +01:00
)
}
2025-04-24 13:16:33 +02:00
FreesearchR_colors <- function ( choose = NULL ) {
out <- c (
primary = " #1E4A8F" ,
secondary = " #FF6F61" ,
success = " #00C896" ,
warning = " #FFB100" ,
danger = " #FF3A2F" ,
extra = " #8A4FFF" ,
info = " #11A0EC" ,
bg = " #FFFFFF" ,
dark = " #2D2D42" ,
fg = " #000000"
2025-04-24 12:53:47 +02:00
)
2025-04-24 13:16:33 +02:00
if ( ! is.null ( choose ) ) {
2025-04-30 13:02:26 +02:00
unname ( out [choose ] )
2025-04-24 13:16:33 +02:00
} else {
out
}
2025-04-24 12:53:47 +02:00
}
2024-12-18 10:37:37 +01:00
2025-04-24 13:16:33 +02:00
2025-01-30 14:32:11 +01:00
#' GGplot default theme for plotting in Shiny
#'
#' @param data ggplot object
#'
#' @returns ggplot object
#' @export
#'
2025-04-24 12:53:47 +02:00
gg_theme_shiny <- function ( ) {
ggplot2 :: theme (
axis.title = ggplot2 :: element_text ( size = 18 ) ,
axis.text = ggplot2 :: element_text ( size = 14 ) ,
strip.text = ggplot2 :: element_text ( size = 14 ) ,
legend.title = ggplot2 :: element_text ( size = 18 ) ,
legend.text = ggplot2 :: element_text ( size = 14 ) ,
plot.title = ggplot2 :: element_text ( size = 24 ) ,
plot.subtitle = ggplot2 :: element_text ( size = 18 )
)
2025-01-30 14:32:11 +01:00
}
#' GGplot default theme for plotting export objects
#'
#' @param data ggplot object
#'
#' @returns ggplot object
#' @export
#'
2025-04-24 12:53:47 +02:00
gg_theme_export <- function ( ) {
ggplot2 :: theme (
axis.title = ggplot2 :: element_text ( size = 18 ) ,
axis.text.x = ggplot2 :: element_text ( size = 14 ) ,
legend.title = ggplot2 :: element_text ( size = 18 ) ,
legend.text = ggplot2 :: element_text ( size = 14 ) ,
plot.title = ggplot2 :: element_text ( size = 24 )
)
2025-02-25 09:51:42 +01:00
}
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//update-factor-ext.R
2025-02-25 09:51:42 +01:00
########
## Works, but not implemented
##
## These edits mainly allows for
#' @title Module to Reorder the Levels of a Factor Variable
#'
#' @description
#' This module contain an interface to reorder the levels of a factor variable.
#'
#'
#' @param id Module ID.
#'
#' @return A [shiny::reactive()] function returning the data.
#' @export
#'
#' @importFrom shiny NS fluidRow tagList column actionButton
#' @importFrom shinyWidgets virtualSelectInput prettyCheckbox
#' @importFrom toastui datagridOutput
#' @importFrom htmltools tags
#'
#' @name update-factor
#'
update_factor_ui <- function ( id ) {
ns <- NS ( id )
tagList (
tags $ style (
" .tui-grid-row-header-draggable span {width: 3px !important; height: 3px !important;}"
) ,
fluidRow (
column (
width = 6 ,
virtualSelectInput (
inputId = ns ( " variable" ) ,
label = i18n ( " Factor variable to reorder:" ) ,
choices = NULL ,
width = " 100%" ,
zIndex = 50
)
) ,
column (
width = 3 ,
class = " d-flex align-items-end" ,
actionButton (
inputId = ns ( " sort_levels" ) ,
label = tagList (
ph ( " sort-ascending" ) ,
i18n ( " Sort by levels" )
) ,
class = " btn-outline-primary mb-3" ,
width = " 100%"
)
) ,
column (
width = 3 ,
class = " d-flex align-items-end" ,
actionButton (
inputId = ns ( " sort_occurrences" ) ,
label = tagList (
ph ( " sort-ascending" ) ,
i18n ( " Sort by count" )
) ,
class = " btn-outline-primary mb-3" ,
width = " 100%"
)
)
) ,
datagridOutput ( ns ( " grid" ) ) ,
tags $ div (
class = " float-end" ,
prettyCheckbox (
inputId = ns ( " new_var" ) ,
label = i18n ( " Create a new variable (otherwise replaces the one selected)" ) ,
value = FALSE ,
status = " primary" ,
outline = TRUE ,
inline = TRUE
) ,
actionButton (
inputId = ns ( " create" ) ,
label = tagList ( ph ( " arrow-clockwise" ) , i18n ( " Update factor variable" ) ) ,
class = " btn-outline-primary"
)
) ,
tags $ div ( class = " clearfix" )
)
}
#' @param data_r A [shiny::reactive()] function returning a `data.frame`.
#'
#' @export
#'
#' @importFrom shiny moduleServer observeEvent reactive reactiveValues req bindEvent isTruthy updateActionButton
#' @importFrom shinyWidgets updateVirtualSelect
#' @importFrom toastui renderDatagrid datagrid grid_columns grid_colorbar
#'
#' @rdname update-factor
update_factor_server <- function ( id , data_r = reactive ( NULL ) ) {
moduleServer (
id ,
function ( input , output , session ) {
rv <- reactiveValues ( data = NULL , data_grid = NULL )
bindEvent ( observe ( {
data <- data_r ( )
rv $ data <- data
vars_factor <- vapply ( data , is.factor , logical ( 1 ) )
vars_factor <- names ( vars_factor ) [vars_factor ]
updateVirtualSelect (
inputId = " variable" ,
choices = vars_factor ,
selected = if ( isTruthy ( input $ variable ) ) input $ variable else vars_factor [1 ]
)
} ) , data_r ( ) , input $ hidden )
observeEvent ( input $ variable , {
data <- req ( data_r ( ) )
variable <- req ( input $ variable )
grid <- as.data.frame ( table ( data [ [variable ] ] ) )
rv $ data_grid <- grid
} )
observeEvent ( input $ sort_levels , {
if ( input $ sort_levels %% 2 == 1 ) {
decreasing <- FALSE
label <- tagList (
ph ( " sort-descending" ) ,
" Sort Levels"
)
} else {
decreasing <- TRUE
label <- tagList (
ph ( " sort-ascending" ) ,
" Sort Levels"
)
}
updateActionButton ( inputId = " sort_levels" , label = as.character ( label ) )
rv $ data_grid <- rv $ data_grid [order ( rv $ data_grid [ [1 ] ] , decreasing = decreasing ) , ]
} )
observeEvent ( input $ sort_occurrences , {
if ( input $ sort_occurrences %% 2 == 1 ) {
decreasing <- FALSE
label <- tagList (
ph ( " sort-descending" ) ,
i18n ( " Sort count" )
)
} else {
decreasing <- TRUE
label <- tagList (
ph ( " sort-ascending" ) ,
i18n ( " Sort count" )
)
}
updateActionButton ( inputId = " sort_occurrences" , label = as.character ( label ) )
rv $ data_grid <- rv $ data_grid [order ( rv $ data_grid [ [2 ] ] , decreasing = decreasing ) , ]
} )
output $ grid <- renderDatagrid ( {
req ( rv $ data_grid )
gridTheme <- getOption ( " datagrid.theme" )
if ( length ( gridTheme ) < 1 ) {
datamods ::: apply_grid_theme ( )
}
on.exit ( toastui :: reset_grid_theme ( ) )
data <- rv $ data_grid
data <- add_var_toset ( data , " Var1" , " New label" )
grid <- datagrid (
data = data ,
draggable = TRUE ,
sortable = FALSE ,
data_as_input = TRUE
)
grid <- grid_columns (
grid ,
columns = c ( " Var1" , " Var1_toset" , " Freq" ) ,
header = c ( i18n ( " Levels" ) , " New label" , i18n ( " Count" ) )
)
grid <- grid_colorbar (
grid ,
column = " Freq" ,
label_outside = TRUE ,
label_width = " 30px" ,
background = " #D8DEE9" ,
bar_bg = datamods ::: get_primary_color ( ) ,
from = c ( 0 , max ( rv $ data_grid $ Freq ) + 1 )
)
grid <- toastui :: grid_style_column (
grid = grid ,
column = " Var1_toset" ,
fontStyle = " italic"
)
grid <- toastui :: grid_editor (
grid = grid ,
column = " Var1_toset" ,
type = " text"
)
grid
} )
data_updated_r <- reactive ( {
data <- req ( data_r ( ) )
variable <- req ( input $ variable )
grid <- req ( input $ grid_data )
name_var <- if ( isTRUE ( input $ new_var ) ) {
paste0 ( variable , " _updated" )
} else {
variable
}
data [ [name_var ] ] <- factor (
as.character ( data [ [variable ] ] ) ,
levels = grid [ [ " Var1" ] ]
)
data [ [name_var ] ] <- factor (
data [ [variable ] ] ,
labels = ifelse ( grid [ [ " Var1_toset" ] ] == " New label" , grid [ [ " Var1" ] ] , grid [ [ " Var1_toset" ] ] )
)
data
} )
data_returned_r <- observeEvent ( input $ create , {
rv $ data <- data_updated_r ( )
} )
return ( reactive ( rv $ data ) )
}
)
}
#' @inheritParams shiny::modalDialog
#' @export
#'
#' @importFrom shiny showModal modalDialog textInput
#' @importFrom htmltools tagList
#'
#' @rdname update-factor
modal_update_factor <- function ( id ,
title = i18n ( " Update levels of a factor" ) ,
easyClose = TRUE ,
size = " l" ,
footer = NULL ) {
ns <- NS ( id )
showModal ( modalDialog (
title = tagList ( title , datamods ::: button_close_modal ( ) ) ,
update_factor_ui ( id ) ,
tags $ div (
style = " display: none;" ,
textInput ( inputId = ns ( " hidden" ) , label = NULL , value = datamods ::: genId ( ) )
) ,
easyClose = easyClose ,
size = size ,
footer = footer
) )
}
#' @inheritParams shinyWidgets::WinBox
#' @export
#'
#' @importFrom shinyWidgets WinBox wbOptions wbControls
#' @importFrom htmltools tagList
2025-03-19 13:10:56 +01:00
#' @rdname update-factor
2025-02-25 09:51:42 +01:00
winbox_update_factor <- function ( id ,
title = i18n ( " Update levels of a factor" ) ,
options = shinyWidgets :: wbOptions ( ) ,
controls = shinyWidgets :: wbControls ( ) ) {
ns <- NS ( id )
WinBox (
title = title ,
ui = tagList (
update_factor_ui ( id ) ,
tags $ div (
style = " display: none;" ,
textInput ( inputId = ns ( " hidden" ) , label = NULL , value = genId ( ) )
)
) ,
options = modifyList (
shinyWidgets :: wbOptions ( height = " 615px" , modal = TRUE ) ,
options
) ,
controls = controls ,
auto_height = FALSE
)
2025-01-30 14:32:11 +01:00
}
2025-02-25 09:51:42 +01:00
2024-12-18 10:37:37 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//update-variables-ext.R
2024-12-18 10:37:37 +01:00
########
2025-01-15 16:21:38 +01:00
library ( data.table )
library ( rlang )
2025-01-16 11:24:26 +01:00
#' Select, rename and convert variables
#'
#' @param id Module id. See [shiny::moduleServer()].
#' @param title Module's title, if `TRUE` use the default title,
#' use \code{NULL} for no title or a `shiny.tag` for a custom one.
#'
#' @return A [shiny::reactive()] function returning the updated data.
#' @export
#'
#' @name update-variables
#'
2025-03-13 15:04:29 +01:00
update_variables_ui <- function ( id , title = " " ) {
2025-01-15 16:21:38 +01:00
ns <- NS ( id )
if ( isTRUE ( title ) ) {
title <- htmltools :: tags $ h4 (
i18n ( " Update & select variables" ) ,
class = " datamods-title"
)
}
htmltools :: tags $ div (
class = " datamods-update" ,
shinyWidgets :: html_dependency_pretty ( ) ,
title ,
htmltools :: tags $ div (
style = " min-height: 25px;" ,
htmltools :: tags $ div (
shiny :: uiOutput ( outputId = ns ( " data_info" ) , inline = TRUE ) ,
shiny :: tagAppendAttributes (
shinyWidgets :: dropMenu (
placement = " bottom-end" ,
shiny :: actionButton (
inputId = ns ( " settings" ) ,
label = phosphoricons :: ph ( " gear" ) ,
class = " pull-right float-right"
) ,
shinyWidgets :: textInputIcon (
inputId = ns ( " format" ) ,
label = i18n ( " Date format:" ) ,
value = " %Y-%m-%d" ,
icon = list ( phosphoricons :: ph ( " clock" ) )
) ,
shinyWidgets :: textInputIcon (
inputId = ns ( " origin" ) ,
label = i18n ( " Date to use as origin to convert date/datetime:" ) ,
value = " 1970-01-01" ,
icon = list ( phosphoricons :: ph ( " calendar" ) )
) ,
shinyWidgets :: textInputIcon (
inputId = ns ( " dec" ) ,
label = i18n ( " Decimal separator:" ) ,
value = " ." ,
icon = list ( " 0.00" )
)
) ,
style = " display: inline;"
)
2024-12-18 10:37:37 +01:00
) ,
2025-01-15 16:21:38 +01:00
htmltools :: tags $ br ( ) ,
toastui :: datagridOutput ( outputId = ns ( " table" ) )
2024-12-18 10:37:37 +01:00
) ,
2025-01-15 16:21:38 +01:00
htmltools :: tags $ br ( ) ,
htmltools :: tags $ div (
id = ns ( " update-placeholder" ) ,
shinyWidgets :: alert (
id = ns ( " update-result" ) ,
status = " info" ,
phosphoricons :: ph ( " info" ) ,
2025-02-26 12:18:46 +01:00
paste (
" Select variables to keep (if none selected, all are kept), rename" ,
" variables and labels, and convert variable type/class in the table" ,
" above. Apply changes by clicking the button below."
)
2025-01-15 16:21:38 +01:00
)
2024-12-18 10:37:37 +01:00
) ,
2024-12-19 11:34:25 +01:00
shiny :: actionButton (
2025-01-15 16:21:38 +01:00
inputId = ns ( " validate" ) ,
label = htmltools :: tagList (
2025-04-02 11:31:04 +02:00
phosphoricons :: ph ( " arrow-circle-right" , title = datamods :: i18n ( " Apply changes" ) ) ,
2025-01-15 16:21:38 +01:00
datamods :: i18n ( " Apply changes" )
) ,
width = " 100%"
)
)
}
2025-01-16 11:24:26 +01:00
#' @export
#'
#' @param id Module's ID
#' @param data a \code{data.frame} or a \code{reactive} function returning a \code{data.frame}.
#' @param height Height for the table.
#' @param return_data_on_init Return initial data when module is called.
#' @param try_silent logical: should the report of error messages be suppressed?
#'
#' @rdname update-variables
#'
2025-01-15 16:21:38 +01:00
update_variables_server <- function ( id ,
data ,
height = NULL ,
return_data_on_init = FALSE ,
try_silent = FALSE ) {
shiny :: moduleServer (
id = id ,
module = function ( input , output , session ) {
ns <- session $ ns
updated_data <- shiny :: reactiveValues ( x = NULL )
data_r <- shiny :: reactive ( {
if ( shiny :: is.reactive ( data ) ) {
data ( )
} else {
data
}
} )
output $ data_info <- shiny :: renderUI ( {
shiny :: req ( data_r ( ) )
2025-04-03 06:31:05 +02:00
data_description ( data_r ( ) )
# sprintf(i18n("Data has %s observations and %s variables."), nrow(data), ncol(data))
2025-01-15 16:21:38 +01:00
} )
variables_r <- shiny :: reactive ( {
shiny :: validate (
shiny :: need ( data ( ) , i18n ( " No data to display." ) )
)
data <- data_r ( )
if ( isTRUE ( return_data_on_init ) ) {
updated_data $ x <- data
} else {
updated_data $ x <- NULL
}
summary_vars ( data )
} )
output $ table <- toastui :: renderDatagrid ( {
shiny :: req ( variables_r ( ) )
2025-04-02 11:31:04 +02:00
variables <- variables_r ( )
2025-01-15 16:21:38 +01:00
update_variables_datagrid (
variables ,
height = height ,
selectionId = ns ( " row_selected" ) ,
buttonId = " validate"
)
} )
shiny :: observeEvent ( input $ validate ,
{
updated_data $ list_rename <- NULL
updated_data $ list_select <- NULL
updated_data $ list_mutate <- NULL
updated_data $ list_relabel <- NULL
2025-04-10 15:46:42 +02:00
# shiny::req(updated_data$x)
2025-01-15 16:21:38 +01:00
data <- data_r ( )
new_selections <- input $ row_selected
if ( length ( new_selections ) < 1 ) {
new_selections <- seq_along ( data )
}
2025-04-02 11:31:04 +02:00
2025-01-15 16:21:38 +01:00
data_inputs <- data.table :: as.data.table ( input $ table_data )
data.table :: setorderv ( data_inputs , " rowKey" )
old_names <- data_inputs $ name
new_names <- data_inputs $ name_toset
new_names [new_names == " New name" ] <- NA
new_names [is.na ( new_names ) ] <- old_names [is.na ( new_names ) ]
new_names [new_names == " " ] <- old_names [new_names == " " ]
2025-04-10 15:46:42 +02:00
# browser()
2025-01-15 16:21:38 +01:00
old_label <- data_inputs $ label
new_label <- data_inputs $ label_toset
2025-04-11 13:23:18 +02:00
new_label [new_label == " New label" ] <- old_label [new_label == " New label" ]
## Later, "" will be interpreted as NA/empty and removed
new_label [is.na ( new_label ) | new_label %in% c ( ' ""' , " ''" , " " ) ] <- " "
# new_label[is.na(new_label)] <- old_label[is.na(new_label)]
new_label <- setNames ( new_label , new_names )
2025-01-15 16:21:38 +01:00
new_classes <- data_inputs $ class_toset
new_classes [new_classes == " Select" ] <- NA
data_sv <- variables_r ( )
vars_to_change <- get_vars_to_convert ( data_sv , setNames ( as.list ( new_classes ) , old_names ) )
res_update <- try (
{
# convert
if ( nrow ( vars_to_change ) > 0 ) {
data <- convert_to (
data = data ,
variable = vars_to_change $ name ,
new_class = vars_to_change $ class_to_set ,
origin = input $ origin ,
format = input $ format ,
dec = input $ dec
)
}
list_mutate <- attr ( data , " code_03_convert" )
# rename
list_rename <- setNames (
as.list ( old_names ) ,
unlist ( new_names , use.names = FALSE )
)
list_rename <- list_rename [names ( list_rename ) != unlist ( list_rename , use.names = FALSE ) ]
names ( data ) <- unlist ( new_names , use.names = FALSE )
# relabel
list_relabel <- as.list ( new_label )
2025-04-11 13:23:18 +02:00
data <- set_column_label ( data , list_relabel )
2025-01-15 16:21:38 +01:00
# select
list_select <- setdiff ( names ( data ) , names ( data ) [new_selections ] )
data <- data [ , new_selections , drop = FALSE ]
} ,
silent = try_silent
)
if ( inherits ( res_update , " try-error" ) ) {
datamods ::: insert_error ( selector = " update" )
} else {
datamods ::: insert_alert (
selector = ns ( " update" ) ,
status = " success" ,
tags $ b ( phosphoricons :: ph ( " check" ) , datamods :: i18n ( " Data successfully updated!" ) )
)
updated_data $ x <- data
updated_data $ list_rename <- list_rename
updated_data $ list_select <- list_select
updated_data $ list_mutate <- list_mutate
updated_data $ list_relabel <- list_relabel
}
} ,
ignoreNULL = TRUE ,
ignoreInit = TRUE
)
2025-04-02 11:31:04 +02:00
# shiny::observeEvent(input$close,
# {
2025-01-15 16:21:38 +01:00
return ( shiny :: reactive ( {
2025-04-10 15:46:42 +02:00
shiny :: req ( updated_data $ x )
# browser()
2025-01-15 16:21:38 +01:00
data <- updated_data $ x
code <- list ( )
if ( ! is.null ( data ) && shiny :: isTruthy ( updated_data $ list_mutate ) && length ( updated_data $ list_mutate ) > 0 ) {
2025-04-11 13:23:18 +02:00
code <- c ( code , list ( rlang :: call2 ( " mutate" , ! ! ! updated_data $ list_mutate , .ns = " dplyr" ) ) )
2025-01-15 16:21:38 +01:00
}
if ( ! is.null ( data ) && shiny :: isTruthy ( updated_data $ list_rename ) && length ( updated_data $ list_rename ) > 0 ) {
2025-04-11 13:23:18 +02:00
code <- c ( code , list ( rlang :: call2 ( " rename" , ! ! ! updated_data $ list_rename , .ns = " dplyr" ) ) )
2025-01-15 16:21:38 +01:00
}
if ( ! is.null ( data ) && shiny :: isTruthy ( updated_data $ list_select ) && length ( updated_data $ list_select ) > 0 ) {
2025-04-11 13:23:18 +02:00
code <- c ( code , list ( rlang :: expr ( dplyr :: select ( - dplyr :: any_of ( c ( ! ! ! updated_data $ list_select ) ) ) ) ) )
2025-01-15 16:21:38 +01:00
}
if ( ! is.null ( data ) && shiny :: isTruthy ( updated_data $ list_relabel ) && length ( updated_data $ list_relabel ) > 0 ) {
2025-04-11 13:23:18 +02:00
code <- c ( code , list ( rlang :: call2 ( " set_column_label" , label = updated_data $ list_relabel , .ns = " FreesearchR" ) ) )
2025-01-15 16:21:38 +01:00
}
if ( length ( code ) > 0 ) {
attr ( data , " code" ) <- Reduce (
f = function ( x , y ) rlang :: expr ( ! ! x %>% ! ! y ) ,
x = code
)
}
return ( data )
} ) )
2025-04-10 15:46:42 +02:00
# })
2025-04-02 11:31:04 +02:00
# shiny::reactive({
# data <- updated_data$x
# code <- list()
# if (!is.null(data) && shiny::isTruthy(updated_data$list_mutate) && length(updated_data$list_mutate) > 0) {
# code <- c(code, list(rlang::call2("mutate", !!!updated_data$list_mutate)))
# }
# if (!is.null(data) && shiny::isTruthy(updated_data$list_rename) && length(updated_data$list_rename) > 0) {
# code <- c(code, list(rlang::call2("rename", !!!updated_data$list_rename)))
# }
# if (!is.null(data) && shiny::isTruthy(updated_data$list_select) && length(updated_data$list_select) > 0) {
# code <- c(code, list(rlang::expr(select(-any_of(c(!!!updated_data$list_select))))))
# }
# if (!is.null(data) && shiny::isTruthy(updated_data$list_relabel) && length(updated_data$list_relabel) > 0) {
# code <- c(code, list(rlang::call2("purrr::map2(list_relabel,
# function(.data,.label){
# REDCapCAST::set_attr(.data,.label,attr = 'label')
# }) |> dplyr::bind_cols(.name_repair = 'unique_quiet')")))
# }
# if (length(code) > 0) {
# attr(data, "code") <- Reduce(
# f = function(x, y) rlang::expr(!!x %>% !!y),
# x = code
# )
# }
# updated_data$return_data <- data
# })
# shiny::observeEvent(input$close,
# {
# shiny::req(input$close)
# return(shiny::reactive({
# data <- updated_data$return_data
# return(data)
# }))
# })
2025-01-15 16:21:38 +01:00
}
)
}
2025-02-26 12:18:46 +01:00
modal_update_variables <- function ( id ,
2025-04-02 11:31:04 +02:00
title = " Select, rename and reclass variables" ,
easyClose = TRUE ,
size = " xl" ,
footer = NULL ) {
2025-02-26 12:18:46 +01:00
ns <- NS ( id )
showModal ( modalDialog (
title = tagList ( title , datamods ::: button_close_modal ( ) ) ,
update_variables_ui ( id ) ,
2025-04-02 11:31:04 +02:00
# tags$div(
# style = "display: none;",
# textInput(inputId = ns("hidden"), label = NULL, value = datamods:::genId())
# ),
2025-02-26 12:18:46 +01:00
easyClose = easyClose ,
size = size ,
footer = footer
) )
}
2025-01-15 16:21:38 +01:00
# utils -------------------------------------------------------------------
2025-01-16 11:24:26 +01:00
#' Get variables classes from a \code{data.frame}
#'
#' @param data a \code{data.frame}
#'
#' @return a \code{character} vector as same length as number of variables
#' @noRd
#'
#' @examples
#'
#' get_classes(mtcars)
2025-01-15 16:21:38 +01:00
get_classes <- function ( data ) {
classes <- lapply (
X = data ,
FUN = function ( x ) {
paste ( class ( x ) , collapse = " , " )
}
)
unlist ( classes , use.names = FALSE )
}
2025-01-16 11:24:26 +01:00
#' Get count of unique values in variables of \code{data.frame}
#'
#' @param data a \code{data.frame}
#'
#' @return a \code{numeric} vector as same length as number of variables
#' @noRd
#'
#'
#' @examples
#' get_n_unique(mtcars)
2025-01-15 16:21:38 +01:00
get_n_unique <- function ( data ) {
u <- lapply ( data , FUN = function ( x ) {
if ( is.atomic ( x ) ) {
data.table :: uniqueN ( x )
} else {
NA_integer_
}
} )
unlist ( u , use.names = FALSE )
}
2025-01-16 11:24:26 +01:00
#' Add padding 0 to a vector
#'
#' @param x a \code{vector}
#'
#' @return a \code{character} vector
#' @noRd
#'
#' @examples
#'
#' pad0(1:10)
#' pad0(c(1, 15, 150, NA))
2025-01-15 16:21:38 +01:00
pad0 <- function ( x ) {
NAs <- which ( is.na ( x ) )
x <- formatC ( x , width = max ( nchar ( as.character ( x ) ) , na.rm = TRUE ) , flag = " 0" )
x [NAs ] <- NA
x
}
2025-01-16 11:24:26 +01:00
#' Variables summary
#'
#' @param data a \code{data.frame}
#'
#' @return a \code{data.frame}
#' @noRd
#'
#' @examples
#'
#' summary_vars(iris)
#' summary_vars(mtcars)
2025-01-15 16:21:38 +01:00
summary_vars <- function ( data ) {
data <- as.data.frame ( data )
datsum <- dplyr :: tibble (
name = names ( data ) ,
label = lapply ( data , \ ( .x ) REDCapCAST :: get_attr ( .x , " label" ) ) | > unlist ( ) ,
class = get_classes ( data ) ,
2025-02-25 09:51:42 +01:00
n_missing = unname ( colSums ( is.na ( data ) ) ) ,
p_complete = 1 - n_missing / nrow ( data ) ,
2025-01-15 16:21:38 +01:00
n_unique = get_n_unique ( data )
)
datsum
}
add_var_toset <- function ( data , var_name , default = " " ) {
datanames <- names ( data )
datanames <- append (
x = datanames ,
values = paste0 ( var_name , " _toset" ) ,
after = which ( datanames == var_name )
)
data [ [paste0 ( var_name , " _toset" ) ] ] <- default
data [ , datanames ]
}
2025-01-16 12:23:39 +01:00
#' Modified from the datamods pacakge
#'
#' @param data data
#'
#' @param height height
#' @param selectionId selectionId
#' @param buttonId buttonId
#'
2025-01-16 11:24:26 +01:00
#' @examples
#' mtcars |>
#' summary_vars() |>
#' update_variables_datagrid()
#'
2025-01-15 16:21:38 +01:00
update_variables_datagrid <- function ( data , height = NULL , selectionId = NULL , buttonId = NULL ) {
# browser()
data <- add_var_toset ( data , " name" , " New name" )
data <- add_var_toset ( data , " class" , " Select" )
data <- add_var_toset ( data , " label" , " New label" )
gridTheme <- getOption ( " datagrid.theme" )
if ( length ( gridTheme ) < 1 ) {
datamods ::: apply_grid_theme ( )
}
on.exit ( toastui :: reset_grid_theme ( ) )
col.names <- names ( data )
std_names <- c (
" name" , " name_toset" , " label" , " label_toset" , " class" , " class_toset" , " n_missing" , " p_complete" , " n_unique"
) | >
setNames ( c (
" Name" , " New name" , " Label" , " New label" , " Class" , " New class" , " Missing" , " Complete" , " Unique"
) )
headers <- lapply ( col.names , \ ( .x ) {
if ( .x %in% std_names ) {
names ( std_names ) [match ( .x , std_names ) ]
} else {
.x
}
} ) | > unlist ( )
grid <- toastui :: datagrid (
data = data ,
theme = " default" ,
colwidths = NULL
)
grid <- toastui :: grid_columns (
grid = grid ,
columns = col.names ,
header = headers ,
minWidth = 100
)
2025-02-25 09:51:42 +01:00
grid <- toastui :: grid_format (
grid = grid ,
" p_complete" ,
formatter = toastui :: JS ( " function(obj) {return (obj.value*100).toFixed(0) + '%';}" )
)
2025-01-15 16:21:38 +01:00
grid <- toastui :: grid_style_column (
grid = grid ,
column = " name_toset" ,
fontStyle = " italic"
)
grid <- toastui :: grid_style_column (
grid = grid ,
column = " label_toset" ,
fontStyle = " italic"
)
grid <- toastui :: grid_style_column (
grid = grid ,
column = " class_toset" ,
fontStyle = " italic"
)
2025-02-07 16:24:09 +01:00
grid <- toastui :: grid_filters (
grid = grid ,
column = " name" ,
# columns = unname(std_names[std_names!="vals"]),
showApplyBtn = FALSE ,
showClearBtn = TRUE ,
type = " text"
)
2025-01-15 16:21:38 +01:00
# grid <- toastui::grid_columns(
# grid = grid,
# columns = "name_toset",
# editor = list(type = "text"),
# validation = toastui::validateOpts()
# )
#
# grid <- toastui::grid_columns(
# grid = grid,
# columns = "label_toset",
# editor = list(type = "text"),
# validation = toastui::validateOpts()
# )
#
# grid <- toastui::grid_columns(
# grid = grid,
# columns = "class_toset",
# editor = list(
# type = "radio",
# options = list(
# instantApply = TRUE,
# listItems = lapply(
# X = c("Select", "character", "factor", "numeric", "integer", "date", "datetime", "hms"),
# FUN = function(x) {
# list(text = x, value = x)
# }
# )
# )
# ),
# validation = toastui::validateOpts()
# )
grid <- toastui :: grid_editor (
grid = grid ,
column = " name_toset" ,
type = " text"
)
grid <- toastui :: grid_editor (
grid = grid ,
column = " label_toset" ,
type = " text"
)
grid <- toastui :: grid_editor (
grid = grid ,
column = " class_toset" ,
type = " select" ,
2025-02-26 12:18:46 +01:00
choices = c ( " Select" , " character" , " factor" , " numeric" , " integer" , " date" , " datetime" , " hms" )
2025-01-15 16:21:38 +01:00
)
grid <- toastui :: grid_editor_opts (
grid = grid ,
editingEvent = " click" ,
actionButtonId = NULL ,
session = NULL
)
grid <- toastui :: grid_selection_row (
grid = grid ,
inputId = selectionId ,
type = " checkbox" ,
return = " index"
)
return ( grid )
}
2025-01-16 11:24:26 +01:00
#' Convert a variable to specific new class
#'
#' @param data A \code{data.frame}
#' @param variable Name of the variable to convert
#' @param new_class Class to set
#' @param ... Other arguments passed on to methods.
#'
#' @return A \code{data.frame}
#' @noRd
#'
#' @importFrom utils type.convert
#' @importFrom rlang sym expr
#'
#' @examples
#' dat <- data.frame(
#' v1 = month.name,
#' v2 = month.abb,
#' v3 = 1:12,
#' v4 = as.numeric(Sys.Date() + 0:11),
#' v5 = as.character(Sys.Date() + 0:11),
#' v6 = as.factor(c("a", "a", "b", "a", "b", "a", "a", "b", "a", "b", "b", "a")),
#' v7 = as.character(11:22),
#' stringsAsFactors = FALSE
#' )
#'
#' str(dat)
#'
#' str(convert_to(dat, "v3", "character"))
#' str(convert_to(dat, "v6", "character"))
#' str(convert_to(dat, "v7", "numeric"))
#' str(convert_to(dat, "v4", "date", origin = "1970-01-01"))
#' str(convert_to(dat, "v5", "date"))
#'
#' str(convert_to(dat, c("v1", "v3"), c("factor", "character")))
#'
#' str(convert_to(dat, c("v1", "v3", "v4"), c("factor", "character", "date"), origin = "1970-01-01"))
#'
2025-01-15 16:21:38 +01:00
convert_to <- function ( data ,
variable ,
new_class = c ( " character" , " factor" , " numeric" , " integer" , " date" , " datetime" , " hms" ) ,
... ) {
new_class <- match.arg ( new_class , several.ok = TRUE )
stopifnot ( length ( new_class ) == length ( variable ) )
args <- list ( ... )
2025-02-07 16:24:09 +01:00
args $ format <- clean_sep ( args $ format )
2025-01-15 16:21:38 +01:00
if ( length ( variable ) > 1 ) {
for ( i in seq_along ( variable ) ) {
data <- convert_to ( data , variable [i ] , new_class [i ] , ... )
}
return ( data )
}
if ( identical ( new_class , " character" ) ) {
data [ [variable ] ] <- as.character ( x = data [ [variable ] ] , ... )
attr ( data , " code_03_convert" ) <- c (
attr ( data , " code_03_convert" ) ,
setNames ( list ( expr ( as.character ( ! ! sym ( variable ) ) ) ) , variable )
)
} else if ( identical ( new_class , " factor" ) ) {
2025-04-03 13:11:02 +02:00
data [ [variable ] ] <- REDCapCAST :: as_factor ( x = data [ [variable ] ] )
2025-01-15 16:21:38 +01:00
attr ( data , " code_03_convert" ) <- c (
attr ( data , " code_03_convert" ) ,
2025-04-03 13:11:02 +02:00
setNames ( list ( expr ( REDCapCAST :: as_factor ( ! ! sym ( variable ) ) ) ) , variable )
2025-01-15 16:21:38 +01:00
)
} else if ( identical ( new_class , " numeric" ) ) {
2025-04-02 11:31:04 +02:00
data [ [variable ] ] <- as.numeric ( data [ [variable ] ] , ... )
# This is the original, that would convert to character and then to numeric
# resulting in all NAs, setting as.is = FALSE would result in a numeric
# vector in order of appearance. Now it is acting like integer conversion
# data[[variable]] <- as.numeric(type.convert(data[[variable]], as.is = TRUE, ...))
2025-01-15 16:21:38 +01:00
attr ( data , " code_03_convert" ) <- c (
attr ( data , " code_03_convert" ) ,
setNames ( list ( expr ( as.numeric ( ! ! sym ( variable ) ) ) ) , variable )
)
} else if ( identical ( new_class , " integer" ) ) {
data [ [variable ] ] <- as.integer ( x = data [ [variable ] ] , ... )
attr ( data , " code_03_convert" ) <- c (
attr ( data , " code_03_convert" ) ,
setNames ( list ( expr ( as.integer ( ! ! sym ( variable ) ) ) ) , variable )
)
} else if ( identical ( new_class , " date" ) ) {
2025-02-07 16:24:09 +01:00
data [ [variable ] ] <- as.Date ( x = clean_date ( data [ [variable ] ] ) , ... )
2025-01-15 16:21:38 +01:00
attr ( data , " code_03_convert" ) <- c (
attr ( data , " code_03_convert" ) ,
2025-04-02 11:31:04 +02:00
setNames ( list ( expr ( as.Date ( clean_date ( ! ! sym ( variable ) ) , origin = ! ! args $ origin , format = clean_sep ( ! ! args $ format ) ) ) ) , variable )
2025-01-15 16:21:38 +01:00
)
} else if ( identical ( new_class , " datetime" ) ) {
data [ [variable ] ] <- as.POSIXct ( x = data [ [variable ] ] , ... )
attr ( data , " code_03_convert" ) <- c (
attr ( data , " code_03_convert" ) ,
setNames ( list ( expr ( as.POSIXct ( ! ! sym ( variable ) ) ) ) , variable )
)
} else if ( identical ( new_class , " hms" ) ) {
data [ [variable ] ] <- hms :: as_hms ( x = data [ [variable ] ] )
attr ( data , " code_03_convert" ) <- c (
attr ( data , " code_03_convert" ) ,
setNames ( list ( expr ( hms :: as_hms ( ! ! sym ( variable ) ) ) ) , variable )
)
}
return ( data )
}
2025-01-16 11:24:26 +01:00
#' Get variable(s) to convert
#'
#' @param vars Output of [summary_vars()]
#' @param classes_input List of inputs containing new classes
#'
#' @return a `data.table`.
#' @noRd
#'
#' @importFrom data.table data.table as.data.table
#'
#' @examples
#' # 2 variables to convert
#' new_classes <- list(
#' "Sepal.Length" = "numeric",
#' "Sepal.Width" = "numeric",
#' "Petal.Length" = "character",
#' "Petal.Width" = "numeric",
#' "Species" = "character"
#' )
#' get_vars_to_convert(summary_vars(iris), new_classes)
#'
#'
#' # No changes
#' new_classes <- list(
#' "Sepal.Length" = "numeric",
#' "Sepal.Width" = "numeric",
#' "Petal.Length" = "numeric",
#' "Petal.Width" = "numeric",
#' "Species" = "factor"
#' )
#' get_vars_to_convert(summary_vars(iris), new_classes)
#'
#' # Not set = NA or ""
#' new_classes <- list(
#' "Sepal.Length" = NA,
#' "Sepal.Width" = NA,
#' "Petal.Length" = NA,
#' "Petal.Width" = NA,
#' "Species" = NA
#' )
#' get_vars_to_convert(summary_vars(iris), new_classes)
#'
#' # Set for one var
#' new_classes <- list(
#' "Sepal.Length" = "",
#' "Sepal.Width" = "",
#' "Petal.Length" = "",
#' "Petal.Width" = "",
#' "Species" = "character"
#' )
#' get_vars_to_convert(summary_vars(iris), new_classes)
#'
#' new_classes <- list(
#' "mpg" = "character",
#' "cyl" = "numeric",
#' "disp" = "character",
#' "hp" = "numeric",
#' "drat" = "character",
#' "wt" = "character",
#' "qsec" = "numeric",
#' "vs" = "character",
#' "am" = "numeric",
#' "gear" = "character",
#' "carb" = "integer"
#' )
#' get_vars_to_convert(summary_vars(mtcars), new_classes)
2025-01-15 16:21:38 +01:00
get_vars_to_convert <- function ( vars , classes_input ) {
vars <- data.table :: as.data.table ( vars )
classes_input <- data.table :: data.table (
name = names ( classes_input ) ,
class_to_set = unlist ( classes_input , use.names = FALSE ) ,
stringsAsFactors = FALSE
)
classes_input <- classes_input [ ! is.na ( class_to_set ) & class_to_set != " " ]
classes_df <- merge ( x = vars , y = classes_input , by = " name" )
classes_df <- classes_df [ ! is.na ( class_to_set ) ]
classes_df [class != class_to_set ]
}
2025-02-07 16:24:09 +01:00
#' gsub wrapper for piping with default values for separator substituting
#'
#' @param data character vector
#' @param old.sep old separator
#' @param new.sep new separator
#'
#' @returns character vector
#' @export
#'
2025-04-02 11:31:04 +02:00
clean_sep <- function ( data , old.sep = " [-.,/]" , new.sep = " -" ) {
gsub ( old.sep , new.sep , data )
2025-02-07 16:24:09 +01:00
}
#' Attempts at applying uniform date format
#'
#' @param data character string vector of possible dates
#'
#' @returns character string
#' @export
#'
2025-04-02 11:31:04 +02:00
clean_date <- function ( data ) {
2025-02-07 16:24:09 +01:00
data | >
clean_sep ( ) | >
sapply ( \ ( .x ) {
2025-04-02 11:31:04 +02:00
if ( is.na ( .x ) ) {
2025-02-07 16:24:09 +01:00
.x
} else {
2025-04-02 11:31:04 +02:00
strsplit ( .x , " -" ) | >
unlist ( ) | >
2025-02-07 16:24:09 +01:00
lapply ( \ ( .y ) {
2025-04-02 11:31:04 +02:00
if ( nchar ( .y ) == 1 ) paste0 ( " 0" , .y ) else .y
} ) | >
paste ( collapse = " -" )
2025-02-07 16:24:09 +01:00
}
} ) | >
unname ( )
}
2025-01-15 16:21:38 +01:00
2025-03-11 13:42:57 +01:00
########
2025-04-11 13:23:18 +02:00
#### Current file: /Users/au301842/FreesearchR/R//wide2long.R
2025-03-11 13:42:57 +01:00
########
#' Alternative pivoting method for easily pivoting based on name pattern
#'
#' @description
#' This function requires and assumes a systematic naming of variables.
#' For now only supports one level pivoting. Adding more levels would require
#' an added "ignore" string pattern or similarly. Example 2.
#'
#'
#' @param data data
#' @param pattern pattern(s) to match. Character vector of length 1 or more.
#' @param type type of match. can be one of "prefix","infix" or "suffix".
#' @param id.col ID column. Will fill ID for all. Column name or numeric index.
#' Default is "1", first column.
#' @param instance.name
#'
#' @returns data.frame
#' @export
#'
#' @examples
#' data.frame(
#' 1:20, sample(70:80, 20, TRUE),
#' sample(70:100, 20, TRUE),
#' sample(70:100, 20, TRUE),
#' sample(170:200, 20, TRUE)
#' ) |>
#' setNames(c("id", "age", "weight_0", "weight_1", "height_1")) |>
#' wide2long(pattern = c("_0", "_1"), type = "suffix")
#' data.frame(
#' 1:20, sample(70:80, 20, TRUE),
#' sample(70:100, 20, TRUE),
#' sample(70:100, 20, TRUE),
#' sample(170:200, 20, TRUE)
#' ) |>
#' setNames(c("id", "age", "weight_0", "weight_a_1", "height_b_1")) |>
#' wide2long(pattern = c("_0", "_1"), type = "suffix")
#' # Optional filling of missing values by last observation carried forward
#' # Needed for mmrm analyses
#' long_missings |>
#' # Fills record ID assuming none are missing
#' tidyr::fill(record_id) |>
#' # Grouping by ID for the last step
#' dplyr::group_by(record_id) |>
#' # Filling missing data by ID
#' tidyr::fill(names(long_missings)[!names(long_missings) %in% new_names]) |>
#' # Remove grouping
#' dplyr::ungroup()
wide2long <- function (
data ,
pattern ,
type = c ( " prefix" , " infix" , " suffix" ) ,
id.col = 1 ,
instance.name = " instance" ) {
type <- match.arg ( type )
## Give the unique suffix names to use for identifying repeated measures
# suffixes <- c("_0", "_1")
## If no ID column is present, one is added
if ( id.col == " none" | is.null ( id.col ) ) {
data <- stats :: setNames (
data.frame ( seq_len ( nrow ( data ) ) , data ) ,
make.names ( c ( " id" , names ( data ) ) , unique = TRUE )
)
id.col <- 1
}
# browser()
## Relevant columns are determined based on suffixes
cols <- names ( data ) [grepl_fix ( names ( data ) , pattern = pattern , type = type ) ]
## New colnames are created by removing suffixes
new_names <- unique ( gsub ( paste ( pattern , collapse = " |" ) , " " , cols ) )
out <- split ( data , seq_len ( nrow ( data ) ) ) | > # Splits dataset by row
# Starts data modifications for each subject
lapply ( \ ( .x ) {
## Pivots data with repeated measures as determined by the defined suffixes
long_ls <- split.default (
# Subset only repeated data
.x [cols ] ,
# ... and split by meassure
gsub ( paste ( new_names , collapse = " |" ) , " " , cols )
) | >
# Sort data by order of given suffixes to ensure chronology
sort_by ( pattern ) | >
# New colnames are applied
lapply ( \ ( .y ) {
setNames (
.y ,
gsub ( paste ( pattern , collapse = " |" ) , " " , names ( .y ) )
)
} )
# Subsets non-pivotted data (this is assumed to belong to same )
single <- .x [ - match ( cols , names ( .x ) ) ]
# Extends with empty rows to get same dimensions as long data
single [ ( nrow ( single ) + 1 ) : length ( long_ls ) , ] <- NA
# Fills ID col
single [id.col ] <- single [1 , id.col ]
# Everything is merged together
merged <- dplyr :: bind_cols (
single ,
# Instance names are defined as suffixes without leading non-characters
REDCapCAST :: as_factor ( data.frame ( gsub (
" ^[^[:alnum:]]+" , " " ,
names ( long_ls )
) ) ) ,
dplyr :: bind_rows ( long_ls )
)
# Ensure unique new names based on supplied
colnames ( merged ) <- make.names (
c (
names ( single ) ,
instance.name ,
names ( merged ) [ ( NCOL ( single ) + 2 ) : NCOL ( merged ) ]
) ,
unique = TRUE
)
merged
} ) | > dplyr :: bind_rows ( )
rownames ( out ) <- NULL
out
}
#' Matches pattern to vector based on match type
#'
#' @param data vector
#' @param pattern pattern(s) to match. Character vector of length 1 or more.
#' @param type type of match. can be one of "prefix","infix" or "suffix".
#'
#' @returns logical vector
#' @export
#'
#' @examples
#' c("id", "age", "weight_0", "weight_1") |> grepl_fix(pattern = c("_0", "_1"), type = "suffix")
grepl_fix <- function ( data , pattern , type = c ( " prefix" , " infix" , " suffix" ) ) {
type <- match.arg ( type )
if ( type == " prefix" ) {
grepl ( paste0 ( " ^(" , paste ( pattern , collapse = " |" ) , " )*" ) , data )
} else if ( type == " suffix" ) {
grepl ( paste0 ( " *(" , paste ( pattern , collapse = " |" ) , " )$" ) , data )
} else if ( type == " infix" ) {
grepl ( paste0 ( " *(" , paste ( pattern , collapse = " |" ) , " )*" ) , data )
}
}
2025-01-15 16:21:38 +01:00
########
2025-04-30 13:02:26 +02:00
#### Current file: /Users/au301842/FreesearchR/dev/header_include.R
########
header_include <- function ( ) {
shiny :: tags $ head (
tags $ link ( rel = " stylesheet" , type = " text/css" , href = " style.css" ) )
}
########
#### Current file: /Users/au301842/FreesearchR/app/ui.R
2025-01-15 16:21:38 +01:00
########
# ns <- NS(id)
2025-04-22 10:02:12 +02:00
2025-01-15 16:21:38 +01:00
ui_elements <- list (
##############################################################################
#########
######### Home panel
#########
##############################################################################
" home" = bslib :: nav_panel (
2025-03-19 13:10:56 +01:00
title = " FreesearchR" ,
2025-02-25 09:51:42 +01:00
shiny :: fluidRow (
shiny :: column ( width = 2 ) ,
shiny :: column (
width = 8 ,
shiny :: markdown ( readLines ( " www/intro.md" ) ) ,
shiny :: column ( width = 2 )
)
) ,
2025-01-15 16:21:38 +01:00
icon = shiny :: icon ( " home" )
) ,
##############################################################################
#########
######### Import panel
#########
##############################################################################
" import" = bslib :: nav_panel (
title = " Import" ,
2025-02-25 09:51:42 +01:00
shiny :: fluidRow (
shiny :: column ( width = 2 ) ,
shiny :: column (
width = 8 ,
2025-02-26 12:18:46 +01:00
shiny :: h4 ( " Choose your data source" ) ,
shiny :: br ( ) ,
shinyWidgets :: radioGroupButtons (
inputId = " source" ,
2025-04-29 15:09:12 +02:00
selected = " file" ,
2025-02-26 12:18:46 +01:00
choices = c (
" File upload" = " file" ,
2025-02-26 21:09:08 +01:00
" REDCap server export" = " redcap" ,
" Local or sample data" = " env"
2025-02-26 12:18:46 +01:00
) ,
2025-04-29 15:09:12 +02:00
size = " lg"
2025-02-26 12:18:46 +01:00
) ,
2025-04-29 15:09:12 +02:00
shiny :: tags $ script ( ' document.querySelector("#source div").style.width = "100%"' ) ,
2025-02-26 12:18:46 +01:00
shiny :: helpText ( " Upload a file from your device, get data directly from REDCap or select a sample data set for testing from the app." ) ,
2025-02-26 21:09:08 +01:00
shiny :: br ( ) ,
shiny :: br ( ) ,
2025-02-26 12:18:46 +01:00
shiny :: conditionalPanel (
condition = " input.source=='file'" ,
2025-03-11 13:42:57 +01:00
import_file_ui (
id = " file_import" ,
layout_params = " dropdown" ,
2025-03-13 12:41:50 +01:00
# title = "Choose a datafile to upload",
2025-03-17 15:00:13 +01:00
file_extensions = c ( " .csv" , " .tsv" , " .txt" , " .xls" , " .xlsx" , " .rds" , " .ods" , " .dta" )
2025-02-26 12:18:46 +01:00
)
) ,
shiny :: conditionalPanel (
condition = " input.source=='redcap'" ,
2025-03-13 12:41:50 +01:00
m_redcap_readUI (
id = " redcap_import" ,
title = " "
)
2025-02-26 12:18:46 +01:00
) ,
shiny :: conditionalPanel (
condition = " input.source=='env'" ,
import_globalenv_ui ( id = " env" , title = NULL )
) ,
2025-05-10 11:31:26 +02:00
# shiny::conditionalPanel(
# condition = "input.source=='redcap'",
# DT::DTOutput(outputId = "redcap_prev")
# ),
2025-04-03 13:11:02 +02:00
shiny :: conditionalPanel (
condition = " output.data_loaded == true" ,
2025-04-11 13:23:18 +02:00
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: h5 ( " Specify variables to include" ) ,
shiny :: fluidRow (
shiny :: column (
width = 6 ,
2025-05-10 11:31:26 +02:00
shiny :: p ( " Filter by completeness threshold:" ) ,
2025-04-11 13:23:18 +02:00
shiny :: br ( ) ,
shinyWidgets :: noUiSliderInput (
inputId = " complete_cutoff" ,
label = NULL ,
update_on = " end" ,
min = 0 ,
max = 100 ,
step = 5 ,
value = 70 ,
format = shinyWidgets :: wNumbFormat ( decimals = 0 ) ,
color = datamods ::: get_primary_color ( )
) ,
shiny :: helpText ( " Exclude variables with completeness below the specified percentage." ) ,
2025-05-10 11:31:26 +02:00
shiny :: br ( )
) ,
shiny :: column (
width = 6 ,
shiny :: p ( " Specify manually:" ) ,
2025-04-11 13:23:18 +02:00
shiny :: br ( ) ,
shiny :: uiOutput ( outputId = " import_var" ) ,
2025-05-10 11:31:26 +02:00
shiny :: br ( )
2025-04-11 13:23:18 +02:00
)
2025-05-10 11:31:26 +02:00
) ,
shiny :: uiOutput ( outputId = " data_info_import" , inline = TRUE )
2025-02-26 12:18:46 +01:00
) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: actionButton (
inputId = " act_start" ,
label = " Start" ,
width = " 100%" ,
2025-02-27 13:34:45 +01:00
icon = shiny :: icon ( " play" ) ,
disabled = TRUE
2025-02-26 12:18:46 +01:00
) ,
shiny :: helpText ( ' After importing, hit "Start" or navigate to the desired tab.' ) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: column ( width = 2 )
2025-02-25 09:51:42 +01:00
)
)
2025-01-15 16:21:38 +01:00
) ,
##############################################################################
#########
######### Data overview panel
#########
##############################################################################
" overview" =
# bslib::nav_panel_hidden(
bslib :: nav_panel (
# value = "overview",
title = " Data" ,
bslib :: navset_bar (
fillable = TRUE ,
bslib :: nav_panel (
2025-02-26 12:18:46 +01:00
title = " Overview" ,
tags $ h3 ( " Overview and filtering" ) ,
2025-02-25 09:51:42 +01:00
fluidRow (
shiny :: column (
width = 9 ,
2025-03-24 14:40:30 +01:00
shiny :: uiOutput ( outputId = " data_info" , inline = TRUE ) ,
2025-02-25 09:51:42 +01:00
shiny :: tags $ p (
2025-04-11 13:23:18 +02:00
" Below is a short summary table, on the right you can click to browse data and create data filters."
2025-02-25 09:51:42 +01:00
)
)
) ,
fluidRow (
shiny :: column (
width = 9 ,
2025-05-08 10:12:49 +02:00
data_summary_ui ( id = " data_summary" ) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: br ( )
2025-02-25 09:51:42 +01:00
) ,
shiny :: column (
width = 3 ,
2025-04-02 11:31:04 +02:00
shiny :: actionButton (
inputId = " modal_browse" ,
label = " Browse data" ,
2025-04-11 13:23:18 +02:00
width = " 100%" ,
disabled = TRUE
2025-04-02 11:31:04 +02:00
) ,
shiny :: tags $ br ( ) ,
shiny :: tags $ br ( ) ,
2025-04-14 10:10:33 +02:00
shiny :: uiOutput ( outputId = " column_filter" ) ,
2025-04-22 10:02:12 +02:00
shiny :: helpText ( " Variable " , tags $ a (
" data type" ,
2025-04-23 14:25:38 +02:00
href = " https://agdamsbo.github.io/FreesearchR/articles/data-types.html" ,
2025-04-22 10:02:12 +02:00
target = " _blank" ,
rel = " noopener noreferrer"
) , " filtering." ) ,
2025-04-14 11:18:24 +02:00
shiny :: tags $ br ( ) ,
2025-04-14 10:10:33 +02:00
shiny :: tags $ br ( ) ,
2025-02-25 09:51:42 +01:00
IDEAFilter :: IDEAFilter_ui ( " data_filter" ) ,
2025-04-14 11:18:24 +02:00
shiny :: helpText ( " Observations level filtering." ) ,
shiny :: tags $ br ( ) ,
2025-02-25 09:51:42 +01:00
shiny :: tags $ br ( )
)
2025-04-10 15:46:42 +02:00
) ,
shiny :: tags $ br ( ) ,
shiny :: tags $ br ( ) ,
shiny :: tags $ br ( ) ,
shiny :: tags $ br ( ) ,
shiny :: tags $ br ( )
2025-02-25 09:51:42 +01:00
) ,
2025-02-26 12:18:46 +01:00
bslib :: nav_panel (
title = " Modify" ,
tags $ h3 ( " Subset, rename and convert variables" ) ,
2025-02-25 09:51:42 +01:00
fluidRow (
shiny :: column (
width = 9 ,
2025-04-11 13:23:18 +02:00
shiny :: tags $ p (
shiny :: markdown ( " Below, are several options for simple data manipulation like update variables by renaming, creating new labels (for nicer tables in the report) and changing variable classes (numeric, factor/categorical etc.)." ) ,
2025-05-05 20:16:38 +02:00
shiny :: markdown ( " There are more advanced options to modify factor/categorical variables as well as create new factor from a continous variable or new variables with *R* code. At the bottom you can restore the original data." ) ,
shiny :: markdown ( " Please note that data modifications are applied before any filtering." )
2025-04-11 13:23:18 +02:00
)
2025-02-26 12:18:46 +01:00
)
) ,
2025-04-03 06:31:05 +02:00
# shiny::tags$br(),
update_variables_ui ( " modal_variables" ) ,
2025-03-11 13:42:57 +01:00
shiny :: tags $ br ( ) ,
shiny :: tags $ br ( ) ,
2025-04-24 13:05:54 +02:00
shiny :: tags $ h4 ( " Advanced data manipulation" ) ,
shiny :: tags $ p ( " Below options allow more advanced varaible manipulations." ) ,
2025-04-02 11:31:04 +02:00
shiny :: tags $ br ( ) ,
shiny :: tags $ br ( ) ,
2025-04-03 06:31:05 +02:00
shiny :: fluidRow (
2025-02-26 12:18:46 +01:00
shiny :: column (
2025-04-03 06:31:05 +02:00
width = 4 ,
shiny :: actionButton (
inputId = " modal_update" ,
label = " Reorder factor levels" ,
width = " 100%"
) ,
shiny :: tags $ br ( ) ,
shiny :: helpText ( " Reorder the levels of factor/categorical variables." ) ,
2025-04-24 13:05:54 +02:00
shiny :: tags $ br ( ) ,
2025-04-14 11:18:24 +02:00
shiny :: tags $ br ( )
2025-02-25 09:51:42 +01:00
) ,
2025-02-26 12:18:46 +01:00
shiny :: column (
2025-04-03 06:31:05 +02:00
width = 4 ,
shiny :: actionButton (
inputId = " modal_cut" ,
label = " New factor" ,
width = " 100%"
) ,
2025-04-02 11:31:04 +02:00
shiny :: tags $ br ( ) ,
2025-04-14 11:18:24 +02:00
shiny :: helpText ( " Create factor/categorical variable from a continous variable (number/date/time)." ) ,
2025-04-24 13:05:54 +02:00
shiny :: tags $ br ( ) ,
2025-04-14 11:18:24 +02:00
shiny :: tags $ br ( )
2025-02-25 09:51:42 +01:00
) ,
2025-02-26 12:18:46 +01:00
shiny :: column (
2025-04-03 06:31:05 +02:00
width = 4 ,
shiny :: actionButton (
inputId = " modal_column" ,
label = " New variable" ,
width = " 100%"
) ,
shiny :: tags $ br ( ) ,
2025-04-14 11:18:24 +02:00
shiny :: helpText ( shiny :: markdown ( " Create a new variable/column based on an *R*-expression." ) ) ,
2025-04-24 13:05:54 +02:00
shiny :: tags $ br ( ) ,
2025-04-14 11:18:24 +02:00
shiny :: tags $ br ( )
2025-02-26 12:18:46 +01:00
)
) ,
2025-04-02 11:31:04 +02:00
tags $ h4 ( " Compare modified data to original" ) ,
2025-02-26 12:18:46 +01:00
shiny :: tags $ br ( ) ,
shiny :: tags $ p (
2025-04-03 06:31:05 +02:00
" Raw print of the original vs the modified data."
2025-02-25 09:51:42 +01:00
) ,
2025-02-26 12:18:46 +01:00
shiny :: tags $ br ( ) ,
2025-04-03 06:31:05 +02:00
shiny :: fluidRow (
shiny :: column (
2025-02-25 09:51:42 +01:00
width = 6 ,
2025-04-03 06:31:05 +02:00
shiny :: tags $ b ( " Original data:" ) ,
2025-02-25 09:51:42 +01:00
# verbatimTextOutput("original"),
2025-04-03 06:31:05 +02:00
shiny :: verbatimTextOutput ( " original_str" )
2025-02-25 09:51:42 +01:00
) ,
2025-04-03 06:31:05 +02:00
shiny :: column (
2025-02-25 09:51:42 +01:00
width = 6 ,
2025-04-03 06:31:05 +02:00
shiny :: tags $ b ( " Modified data:" ) ,
2025-02-25 09:51:42 +01:00
# verbatimTextOutput("modified"),
2025-04-03 06:31:05 +02:00
shiny :: verbatimTextOutput ( " modified_str" )
2025-02-25 09:51:42 +01:00
)
2025-04-03 06:31:05 +02:00
) ,
shiny :: tags $ br ( ) ,
shiny :: actionButton (
inputId = " data_reset" ,
label = " Restore original data" ,
width = " 100%"
) ,
shiny :: tags $ br ( ) ,
shiny :: helpText ( " Reset to original imported dataset. Careful! There is no un-doing." ) ,
shiny :: tags $ br ( )
2024-12-18 10:37:37 +01:00
)
)
) ,
##############################################################################
#########
2025-02-07 16:24:09 +01:00
######### Descriptive analyses panel
2024-12-18 10:37:37 +01:00
#########
##############################################################################
2025-02-07 16:24:09 +01:00
" describe" =
2024-12-18 10:37:37 +01:00
bslib :: nav_panel (
2025-02-07 16:24:09 +01:00
title = " Evaluate" ,
id = " navdescribe" ,
2024-12-18 10:37:37 +01:00
bslib :: navset_bar (
title = " " ,
sidebar = bslib :: sidebar (
2025-04-22 10:02:12 +02:00
shiny :: uiOutput ( outputId = " data_info_nochar" , inline = TRUE ) ,
2025-01-17 15:59:24 +01:00
bslib :: accordion (
open = " acc_chars" ,
multiple = FALSE ,
2025-01-30 14:32:11 +01:00
bslib :: accordion_panel (
value = " acc_chars" ,
title = " Characteristics" ,
icon = bsicons :: bs_icon ( " table" ) ,
shiny :: uiOutput ( " strat_var" ) ,
shiny :: helpText ( " Only factor/categorical variables are available for stratification. Go back to the 'Data' tab to reclass a variable if it's not on the list." ) ,
shiny :: conditionalPanel (
condition = " input.strat_var!='none'" ,
shiny :: radioButtons (
inputId = " add_p" ,
label = " Compare strata?" ,
selected = " no" ,
inline = TRUE ,
choices = list (
" No" = " no" ,
" Yes" = " yes"
)
) ,
shiny :: helpText ( " Option to perform statistical comparisons between strata in baseline table." )
2025-03-20 13:13:14 +01:00
) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: actionButton (
inputId = " act_eval" ,
label = " Evaluate" ,
width = " 100%" ,
icon = shiny :: icon ( " calculator" ) ,
2025-04-11 13:23:18 +02:00
disabled = TRUE
2025-01-30 14:32:11 +01:00
)
) ,
2025-02-07 16:24:09 +01:00
bslib :: accordion_panel (
2025-02-07 17:09:52 +01:00
vlaue = " acc_cor" ,
2025-02-07 16:24:09 +01:00
title = " Correlations" ,
2025-03-13 14:13:18 +01:00
icon = bsicons :: bs_icon ( " bounding-box" ) ,
2025-02-07 17:09:52 +01:00
shiny :: uiOutput ( " outcome_var_cor" ) ,
2025-03-13 14:13:18 +01:00
shiny :: helpText ( " To avoid evaluating the correlation of the outcome variable, this can be excluded from the plot or select 'none'." ) ,
2025-02-07 17:09:52 +01:00
shiny :: br ( ) ,
2025-02-25 09:51:42 +01:00
shinyWidgets :: noUiSliderInput (
2025-02-07 16:24:09 +01:00
inputId = " cor_cutoff" ,
label = " Correlation cut-off" ,
min = 0 ,
max = 1 ,
2025-02-25 09:51:42 +01:00
step = .01 ,
2025-02-07 17:09:52 +01:00
value = .8 ,
2025-02-25 09:51:42 +01:00
format = shinyWidgets :: wNumbFormat ( decimals = 2 ) ,
color = datamods ::: get_primary_color ( )
2025-03-13 14:13:18 +01:00
) ,
shiny :: helpText ( " Set the cut-off for considered 'highly correlated'." )
2025-02-07 16:24:09 +01:00
)
)
) ,
bslib :: nav_panel (
2025-04-09 12:31:08 +02:00
title = " Characteristics" ,
2025-02-07 16:24:09 +01:00
gt :: gt_output ( outputId = " table1" )
) ,
bslib :: nav_panel (
2025-04-09 12:31:08 +02:00
title = " Correlations" ,
2025-02-07 16:24:09 +01:00
data_correlations_ui ( id = " correlations" , height = 600 )
)
)
) ,
##############################################################################
#########
2025-02-25 09:51:42 +01:00
######### Download panel
#########
##############################################################################
" visuals" = bslib :: nav_panel (
title = " Visuals" ,
id = " navvisuals" ,
do.call (
bslib :: navset_bar ,
c (
data_visuals_ui ( " visuals" ) ,
shiny :: tagList (
bslib :: nav_spacer ( ) ,
2025-04-23 14:25:38 +02:00
bslib :: nav_item (
# shiny::img(shiny::icon("book")),
shiny :: tags $ a (
href = " https://agdamsbo.github.io/FreesearchR/articles/visuals.html" ,
" Notes (external)" ,
target = " _blank" ,
rel = " noopener noreferrer"
2025-02-25 09:51:42 +01:00
)
)
)
)
)
) ,
##############################################################################
#########
2025-02-07 16:24:09 +01:00
######### Regression analyses panel
#########
##############################################################################
" analyze" =
bslib :: nav_panel (
title = " Regression" ,
id = " navanalyses" ,
2025-04-02 11:31:04 +02:00
do.call (
bslib :: navset_bar ,
regression_ui ( " regression" )
2024-12-18 10:37:37 +01:00
)
) ,
##############################################################################
#########
2025-02-07 16:24:09 +01:00
######### Download panel
#########
##############################################################################
" download" =
bslib :: nav_panel (
title = " Download" ,
id = " navdownload" ,
2025-02-25 09:51:42 +01:00
shiny :: fluidRow (
shiny :: column ( width = 2 ) ,
shiny :: column (
width = 8 ,
2025-02-26 12:18:46 +01:00
shiny :: fluidRow (
shiny :: column (
width = 6 ,
shiny :: h4 ( " Report" ) ,
shiny :: helpText ( " Choose your favourite output file format for further work, and download, when the analyses are done." ) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: selectInput (
inputId = " output_type" ,
label = " Output format" ,
selected = NULL ,
choices = list (
" MS Word" = " docx" ,
" LibreOffice" = " odt"
# ,
# "PDF" = "pdf",
# "All the above" = "all"
)
) ,
shiny :: br ( ) ,
# Button
shiny :: downloadButton (
outputId = " report" ,
label = " Download report" ,
icon = shiny :: icon ( " download" )
)
# shiny::helpText("If choosing to output to MS Word, please note, that when opening the document, two errors will pop-up. Choose to repair and choose not to update references. The issue is being worked on. You can always choose LibreOffice instead."),
) ,
shiny :: column (
width = 6 ,
shiny :: h4 ( " Data" ) ,
shiny :: helpText ( " Choose your favourite output data format to download the modified data." ) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
shiny :: selectInput (
inputId = " data_type" ,
label = " Data format" ,
selected = NULL ,
choices = list (
" R" = " rds" ,
" stata" = " dta" ,
" CSV" = " csv"
)
) ,
shiny :: helpText ( " No metadata is saved when exporting to csv." ) ,
shiny :: br ( ) ,
shiny :: br ( ) ,
# Button
shiny :: downloadButton (
outputId = " data_modified" ,
label = " Download data" ,
icon = shiny :: icon ( " download" )
)
2025-02-07 16:24:09 +01:00
)
) ,
shiny :: br ( ) ,
2025-02-25 09:51:42 +01:00
shiny :: br ( ) ,
2025-03-19 09:14:36 +01:00
shiny :: h4 ( " Code snippets" ) ,
2025-04-11 13:23:18 +02:00
shiny :: tags $ p ( " Below are the code bits used to create the final data set and the main analyses." ) ,
shiny :: tags $ p ( " This can be used as a starting point for learning to code and for reproducibility." ) ,
shiny :: tagList (
lapply (
paste0 ( " code_" , c (
2025-04-24 11:00:56 +02:00
" import" , " format" , " data" , " variables" , " filter" , " table1" , " univariable" , " multivariable"
2025-04-11 13:23:18 +02:00
) ) ,
\ ( .x ) shiny :: htmlOutput ( outputId = .x )
)
2025-04-09 12:31:08 +02:00
) ,
2025-02-26 12:18:46 +01:00
shiny :: tags $ br ( ) ,
2025-04-09 12:31:08 +02:00
shiny :: br ( )
) ,
shiny :: column ( width = 2 )
2025-02-25 09:51:42 +01:00
)
2025-02-07 16:24:09 +01:00
) ,
##############################################################################
#########
2025-05-05 14:45:07 +02:00
######### Feedback link
#########
##############################################################################
" feedback" = bslib :: nav_item (
# shiny::img(shiny::icon("book")),
shiny :: tags $ a (
href = " https://redcap.au.dk/surveys/?s=JPCLPTXYDKFA8DA8" ,
2025-05-08 11:38:02 +02:00
" Feedback" , shiny :: icon ( " arrow-up-right-from-square" ) ,
2025-05-05 14:45:07 +02:00
target = " _blank" ,
rel = " noopener noreferrer"
)
) ,
##############################################################################
#########
######### Documentation link
2024-12-18 10:37:37 +01:00
#########
##############################################################################
2025-01-16 12:23:39 +01:00
" docs" = bslib :: nav_item (
# shiny::img(shiny::icon("book")),
shiny :: tags $ a (
2025-03-17 21:13:49 +01:00
href = " https://agdamsbo.github.io/FreesearchR/" ,
2025-05-08 11:38:02 +02:00
" Docs" , shiny :: icon ( " arrow-up-right-from-square" ) ,
2025-01-16 14:24:38 +01:00
target = " _blank" ,
rel = " noopener noreferrer"
)
2025-01-16 12:23:39 +01:00
)
# bslib::nav_panel(
# title = "Documentation",
# # shiny::tags$iframe("www/docs.html", height=600, width=535),
# shiny::htmlOutput("docs_file"),
# shiny::br()
# )
2024-12-18 10:37:37 +01:00
)
# Initial attempt at creating light and dark versions
light <- custom_theme ( )
2024-12-18 11:26:00 +01:00
dark <- custom_theme (
bg = " #000" ,
fg = " #fff"
)
2024-12-18 10:37:37 +01:00
# Fonts to consider:
# https://webdesignerdepot.com/17-open-source-fonts-youll-actually-love/
2025-01-15 16:21:38 +01:00
ui <- bslib :: page_fixed (
2025-04-11 13:23:18 +02:00
prismDependencies ,
prismRDependency ,
2025-04-30 13:02:26 +02:00
header_include ( ) ,
2025-04-30 10:02:29 +02:00
## This adds the actual favicon
## png and ico versions are kept for compatibility
2025-05-08 11:38:02 +02:00
shiny :: tags $ head ( tags $ link ( rel = " shortcut icon" , href = " favicon.svg" ) ) ,
2025-03-19 13:10:56 +01:00
title = " FreesearchR" ,
2024-12-18 10:37:37 +01:00
theme = light ,
shiny :: useBusyIndicators ( ) ,
2025-05-08 11:38:02 +02:00
shinyjs :: useShinyjs ( ) ,
shiny :: div (
id = " loading_page" ,
# shiny::h1("Loading the FreesearchR app..."),
shinybusy :: add_busy_spinner ( position = " full-page" )
) ,
shinyjs :: hidden (
shiny :: div (
id = " main_content" ,
bslib :: page_navbar (
id = " main_panel" ,
ui_elements $ home ,
ui_elements $ import ,
ui_elements $ overview ,
ui_elements $ describe ,
ui_elements $ visuals ,
ui_elements $ analyze ,
ui_elements $ download ,
bslib :: nav_spacer ( ) ,
ui_elements $ feedback ,
ui_elements $ docs ,
fillable = FALSE ,
footer = shiny :: tags $ footer (
style = " background-color: #14131326; padding: 4px; text-align: center; bottom: 0; width: 100%;" ,
shiny :: p (
style = " margin: 1" ,
" Data is only stored for analyses and deleted when the app is closed." , shiny :: markdown ( " Consider [running ***FreesearchR*** locally](https://agdamsbo.github.io/FreesearchR/#run-locally-on-your-own-machine) if working with sensitive data." )
) ,
shiny :: p (
style = " margin: 1; color: #888;" ,
shiny :: tags $ a ( " Docs" , href = " https://agdamsbo.github.io/FreesearchR/" , target = " _blank" , rel = " noopener noreferrer" ) , " | " , hosted_version ( ) , " | " , shiny :: tags $ a ( " License: AGPLv3" , href = " https://github.com/agdamsbo/FreesearchR/blob/main/LICENSE.md" , target = " _blank" , rel = " noopener noreferrer" ) , " | " , shiny :: tags $ a ( " Source" , href = " https://github.com/agdamsbo/FreesearchR/" , target = " _blank" , rel = " noopener noreferrer" ) , " | " , shiny :: tags $ a ( " Share feedback" , href = " https://redcap.au.dk/surveys/?s=JPCLPTXYDKFA8DA8" , target = " _blank" , rel = " noopener noreferrer" )
) ,
)
)
2024-12-19 11:34:25 +01:00
)
2024-12-18 10:37:37 +01:00
)
)
########
2025-04-30 13:02:26 +02:00
#### Current file: /Users/au301842/FreesearchR/app/server.R
2024-12-18 10:37:37 +01:00
########
2025-05-10 11:31:26 +02:00
library ( shiny )
# library(shinyjs)
# library(methods)
2024-12-18 10:37:37 +01:00
library ( readr )
library ( MASS )
library ( stats )
library ( gt )
2025-04-24 12:53:47 +02:00
# library(openxlsx2)
2024-12-18 10:37:37 +01:00
library ( haven )
library ( readODS )
library ( bslib )
library ( assertthat )
library ( dplyr )
library ( quarto )
library ( here )
library ( broom )
library ( broom.helpers )
library ( easystats )
library ( patchwork )
library ( DHARMa )
2025-01-15 16:21:38 +01:00
library ( apexcharter )
2024-12-18 10:37:37 +01:00
library ( toastui )
2025-01-15 16:21:38 +01:00
library ( datamods )
2024-12-18 10:37:37 +01:00
library ( IDEAFilter )
library ( shinyWidgets )
library ( DT )
2025-04-24 12:53:47 +02:00
library ( data.table )
2025-01-23 08:44:38 +01:00
library ( gtsummary )
2025-05-10 11:31:26 +02:00
library ( bsicons )
2024-12-18 10:37:37 +01:00
2025-04-10 15:46:42 +02:00
data ( starwars )
2025-01-23 13:21:41 +01:00
data ( mtcars )
2025-04-10 15:46:42 +02:00
data ( trial )
2025-05-08 11:38:02 +02:00
load_data <- function ( ) {
Sys.sleep ( 1 )
2025-05-10 11:31:26 +02:00
shinyjs :: hide ( " loading_page" )
shinyjs :: show ( " main_content" )
2025-05-08 11:38:02 +02:00
}
2024-12-18 10:37:37 +01:00
server <- function ( input , output , session ) {
## Listing files in www in session start to keep when ending and removing
## everything else.
files.to.keep <- list.files ( " www/" )
2025-05-08 11:38:02 +02:00
load_data ( )
2024-12-19 11:34:25 +01:00
##############################################################################
#########
######### Night mode (just very popular, not really needed)
#########
##############################################################################
2024-12-18 10:37:37 +01:00
# observeEvent(input$dark_mode,{
# session$setCurrentTheme(
# if (isTRUE(input$dark_mode)) dark else light
# )})
2024-12-19 11:34:25 +01:00
# observe({
# if(input$dark_mode==TRUE)
# session$setCurrentTheme(bs_theme_update(theme = custom_theme(version = 5)))
# if(input$dark_mode==FALSE)
# session$setCurrentTheme(bs_theme_update(theme = custom_theme(version = 5, bg = "#000",fg="#fff")))
# })
##############################################################################
#########
######### Setting reactive values
#########
##############################################################################
2024-12-18 10:37:37 +01:00
rv <- shiny :: reactiveValues (
2024-12-18 15:46:02 +01:00
list = list ( ) ,
2025-04-30 13:02:26 +02:00
regression = NULL ,
2024-12-18 10:37:37 +01:00
ds = NULL ,
local_temp = NULL ,
2024-12-18 11:26:00 +01:00
ready = NULL ,
2024-12-18 10:37:37 +01:00
test = " no" ,
data_original = NULL ,
2025-03-12 18:27:46 +01:00
data_temp = NULL ,
2024-12-18 10:37:37 +01:00
data = NULL ,
2025-04-14 10:10:33 +02:00
data_variables = NULL ,
2025-01-17 15:59:24 +01:00
data_filtered = NULL ,
2025-02-25 09:51:42 +01:00
models = NULL ,
code = list ( )
2024-12-18 10:37:37 +01:00
)
##############################################################################
#########
######### Data import section
#########
##############################################################################
2025-03-11 13:42:57 +01:00
data_file <- import_file_server (
2024-12-18 10:37:37 +01:00
id = " file_import" ,
show_data_in = " popup" ,
trigger_return = " change" ,
2025-03-17 15:00:13 +01:00
return_class = " data.frame"
2024-12-18 10:37:37 +01:00
)
shiny :: observeEvent ( data_file $ data ( ) , {
shiny :: req ( data_file $ data ( ) )
2025-03-12 18:27:46 +01:00
rv $ data_temp <- data_file $ data ( )
2025-04-11 13:23:18 +02:00
rv $ code <- modifyList ( x = rv $ code , list ( import = data_file $ code ( ) ) )
2024-12-18 10:37:37 +01:00
} )
2025-03-17 15:00:13 +01:00
from_redcap <- m_redcap_readServer (
2025-03-13 12:41:50 +01:00
id = " redcap_import"
2024-12-18 10:37:37 +01:00
)
2025-03-17 15:00:13 +01:00
shiny :: observeEvent ( from_redcap $ data ( ) , {
rv $ data_temp <- from_redcap $ data ( )
2025-04-11 13:23:18 +02:00
rv $ code <- modifyList ( x = rv $ code , list ( import = from_redcap $ code ( ) ) )
2024-12-18 10:37:37 +01:00
} )
2025-04-11 13:23:18 +02:00
## This is used to ensure the reactive data is retrieved
2025-05-10 11:31:26 +02:00
# output$redcap_prev <- DT::renderDT(
# {
# DT::datatable(head(from_redcap$data(), 5),
# caption = "First 5 observations"
# )
# },
# server = TRUE
# )
2024-12-18 10:37:37 +01:00
2025-02-25 09:51:42 +01:00
from_env <- datamods :: import_globalenv_server (
2024-12-18 10:37:37 +01:00
id = " env" ,
trigger_return = " change" ,
btn_show_data = FALSE ,
reset = reactive ( input $ hidden )
)
shiny :: observeEvent ( from_env $ data ( ) , {
shiny :: req ( from_env $ data ( ) )
2025-04-11 13:23:18 +02:00
2025-03-12 18:27:46 +01:00
rv $ data_temp <- from_env $ data ( )
2025-04-11 13:23:18 +02:00
rv $ code <- modifyList ( x = rv $ code , list ( import = from_env $ name ( ) ) )
2024-12-18 10:37:37 +01:00
} )
2025-03-12 18:27:46 +01:00
output $ import_var <- shiny :: renderUI ( {
shiny :: req ( rv $ data_temp )
preselect <- names ( rv $ data_temp ) [sapply ( rv $ data_temp , missing_fraction ) <= input $ complete_cutoff / 100 ]
shinyWidgets :: virtualSelectInput (
inputId = " import_var" ,
label = " Select variables to include" ,
selected = preselect ,
choices = names ( rv $ data_temp ) ,
2025-03-13 15:04:29 +01:00
updateOn = " change" ,
2025-03-12 18:27:46 +01:00
multiple = TRUE ,
search = TRUE ,
showValueAsTags = TRUE
)
} )
2025-04-03 13:11:02 +02:00
output $ data_loaded <- shiny :: reactive ( {
! is.null ( rv $ data_temp )
2025-04-10 15:46:42 +02:00
} )
2025-04-03 13:11:02 +02:00
2025-04-10 15:46:42 +02:00
shiny :: observeEvent ( input $ source , {
2025-04-03 13:11:02 +02:00
rv $ data_temp <- NULL
2025-04-10 15:46:42 +02:00
} )
2025-04-03 13:11:02 +02:00
shiny :: outputOptions ( output , " data_loaded" , suspendWhenHidden = FALSE )
2025-03-12 18:27:46 +01:00
shiny :: observeEvent (
eventExpr = list (
2025-03-24 14:40:30 +01:00
input $ import_var ,
2025-04-03 13:11:02 +02:00
input $ complete_cutoff ,
rv $ data_temp
2025-03-12 18:27:46 +01:00
) ,
handlerExpr = {
shiny :: req ( rv $ data_temp )
2025-04-09 12:31:08 +02:00
shiny :: req ( input $ import_var )
2025-04-02 11:31:04 +02:00
# browser()
2025-04-03 13:11:02 +02:00
temp_data <- rv $ data_temp
2025-04-10 15:46:42 +02:00
if ( all ( input $ import_var %in% names ( temp_data ) ) ) {
2025-04-03 13:11:02 +02:00
temp_data <- temp_data | > dplyr :: select ( input $ import_var )
}
rv $ data_original <- temp_data | >
2025-03-12 18:27:46 +01:00
default_parsing ( )
2025-03-17 15:00:13 +01:00
2025-04-24 11:00:56 +02:00
rv $ code $ import <- rv $ code $ import | >
expression_string ( assign.str = " df <-" )
rv $ code $ format <- list (
" df" ,
2025-04-11 13:23:18 +02:00
rlang :: expr ( dplyr :: select ( dplyr :: all_of ( ! ! input $ import_var ) ) ) ,
2025-04-10 15:46:42 +02:00
rlang :: call2 ( .fn = " default_parsing" , .ns = " FreesearchR" )
2025-04-09 12:31:08 +02:00
) | >
2025-04-11 13:23:18 +02:00
lapply ( expression_string ) | >
pipe_string ( ) | >
expression_string ( assign.str = " df <-" )
2025-04-03 13:11:02 +02:00
2025-03-17 15:00:13 +01:00
rv $ code $ filter <- NULL
rv $ code $ modify <- NULL
2025-04-10 15:46:42 +02:00
} , ignoreNULL = FALSE
2025-03-12 18:27:46 +01:00
)
2025-03-24 14:40:30 +01:00
output $ data_info_import <- shiny :: renderUI ( {
shiny :: req ( rv $ data_original )
data_description ( rv $ data_original )
} )
2025-04-11 13:23:18 +02:00
## Activating action buttons on data imported
2025-02-27 13:34:45 +01:00
shiny :: observeEvent ( rv $ data_original , {
if ( is.null ( rv $ data_original ) | NROW ( rv $ data_original ) == 0 ) {
shiny :: updateActionButton ( inputId = " act_start" , disabled = TRUE )
2025-04-11 13:23:18 +02:00
shiny :: updateActionButton ( inputId = " modal_browse" , disabled = TRUE )
shiny :: updateActionButton ( inputId = " act_eval" , disabled = TRUE )
2025-02-27 13:34:45 +01:00
} else {
shiny :: updateActionButton ( inputId = " act_start" , disabled = FALSE )
2025-04-11 13:23:18 +02:00
shiny :: updateActionButton ( inputId = " modal_browse" , disabled = FALSE )
shiny :: updateActionButton ( inputId = " act_eval" , disabled = FALSE )
2025-02-27 13:34:45 +01:00
}
} )
2025-01-15 16:21:38 +01:00
2024-12-18 10:37:37 +01:00
##############################################################################
#########
######### Data modification section
#########
##############################################################################
2025-02-07 16:24:09 +01:00
shiny :: observeEvent (
eventExpr = list (
2025-04-03 13:11:02 +02:00
rv $ data_original
2025-02-07 16:24:09 +01:00
) ,
handlerExpr = {
shiny :: req ( rv $ data_original )
2025-02-26 12:18:46 +01:00
2025-03-12 18:27:46 +01:00
rv $ data <- rv $ data_original
2025-02-07 16:24:09 +01:00
}
)
2025-01-16 11:24:26 +01:00
2025-02-26 12:18:46 +01:00
## For now this solution work, but I would prefer to solve this with the above
2025-03-12 18:27:46 +01:00
shiny :: observeEvent ( input $ reset_confirm ,
{
if ( isTRUE ( input $ reset_confirm ) ) {
shiny :: req ( rv $ data_original )
rv $ data <- rv $ data_original
2025-03-17 15:00:13 +01:00
rv $ code $ filter <- NULL
2025-04-14 10:10:33 +02:00
rv $ code $ variables <- NULL
2025-03-17 15:00:13 +01:00
rv $ code $ modify <- NULL
2025-03-12 18:27:46 +01:00
}
} ,
ignoreNULL = TRUE
)
2025-02-26 12:18:46 +01:00
2025-01-16 11:24:26 +01:00
shiny :: observeEvent ( input $ data_reset , {
shinyWidgets :: ask_confirmation (
2025-02-26 12:18:46 +01:00
cancelOnDismiss = TRUE ,
2025-01-16 11:24:26 +01:00
inputId = " reset_confirm" ,
2025-02-26 12:18:46 +01:00
title = " Please confirm data reset?" ,
type = " warning"
2025-01-16 11:24:26 +01:00
)
} )
2025-01-15 16:21:38 +01:00
2025-01-16 11:24:26 +01:00
#########
2025-01-15 16:21:38 +01:00
######### Modifications
2025-01-16 11:24:26 +01:00
#########
2025-01-15 16:21:38 +01:00
2024-12-18 10:37:37 +01:00
## Using modified version of the datamods::cut_variable_server function
## Further modifications are needed to have cut/bin options based on class of variable
## Could be defined server-side
2025-01-16 11:24:26 +01:00
2025-03-24 14:40:30 +01:00
output $ data_info <- shiny :: renderUI ( {
shiny :: req ( data_filter ( ) )
2025-04-14 11:18:24 +02:00
data_description ( data_filter ( ) , " The filtered data" )
2025-03-24 14:40:30 +01:00
} )
2025-01-16 11:24:26 +01:00
######### Create factor
shiny :: observeEvent (
input $ modal_cut ,
2025-03-13 15:04:29 +01:00
modal_cut_variable ( " modal_cut" , title = " Create new factor" )
2025-01-16 11:24:26 +01:00
)
2025-02-25 09:51:42 +01:00
2024-12-18 10:37:37 +01:00
data_modal_cut <- cut_variable_server (
id = " modal_cut" ,
data_r = shiny :: reactive ( rv $ data )
)
2025-02-25 09:51:42 +01:00
2025-03-17 15:00:13 +01:00
shiny :: observeEvent ( data_modal_cut ( ) , {
rv $ data <- data_modal_cut ( )
2025-03-18 08:27:05 +01:00
rv $ code $ modify [ [length ( rv $ code $ modify ) + 1 ] ] <- attr ( rv $ data , " code" )
} )
2024-12-18 10:37:37 +01:00
2025-01-16 11:24:26 +01:00
######### Modify factor
2024-12-18 10:37:37 +01:00
2025-01-16 11:24:26 +01:00
shiny :: observeEvent (
input $ modal_update ,
2025-03-13 15:04:29 +01:00
datamods :: modal_update_factor ( id = " modal_update" , title = " Reorder factor levels" )
2025-01-16 11:24:26 +01:00
)
2025-02-25 09:51:42 +01:00
2024-12-18 10:37:37 +01:00
data_modal_update <- datamods :: update_factor_server (
id = " modal_update" ,
data_r = reactive ( rv $ data )
)
2025-02-25 09:51:42 +01:00
2024-12-18 10:37:37 +01:00
shiny :: observeEvent ( data_modal_update ( ) , {
shiny :: removeModal ( )
rv $ data <- data_modal_update ( )
2025-03-18 08:27:05 +01:00
rv $ code $ modify [ [length ( rv $ code $ modify ) + 1 ] ] <- attr ( rv $ data , " code" )
2024-12-18 10:37:37 +01:00
} )
2025-01-16 11:24:26 +01:00
######### Create column
shiny :: observeEvent (
input $ modal_column ,
2025-04-24 12:53:47 +02:00
modal_create_column (
2025-03-13 15:04:29 +01:00
id = " modal_column" ,
2025-04-24 13:05:54 +02:00
footer = shiny :: markdown ( " This window is aimed at advanced users and require some *R*-experience!" ) ,
2025-03-13 15:04:29 +01:00
title = " Create new variables"
)
2025-01-16 11:24:26 +01:00
)
2025-04-24 12:53:47 +02:00
data_modal_r <- create_column_server (
2025-01-16 11:24:26 +01:00
id = " modal_column" ,
data_r = reactive ( rv $ data )
)
2025-01-20 11:27:54 +01:00
shiny :: observeEvent (
data_modal_r ( ) ,
{
rv $ data <- data_modal_r ( )
2025-03-18 08:27:05 +01:00
rv $ code $ modify [ [length ( rv $ code $ modify ) + 1 ] ] <- attr ( rv $ data , " code" )
2025-01-20 11:27:54 +01:00
}
)
2024-12-18 10:37:37 +01:00
2025-03-18 08:27:05 +01:00
######### Subset, rename, reclass
2024-12-18 10:37:37 +01:00
2025-01-16 11:24:26 +01:00
updated_data <- update_variables_server (
2025-02-26 12:18:46 +01:00
id = " modal_variables" ,
2025-03-18 08:27:05 +01:00
data = shiny :: reactive ( rv $ data ) ,
2024-12-18 10:37:37 +01:00
return_data_on_init = FALSE
)
2025-01-15 16:21:38 +01:00
shiny :: observeEvent ( updated_data ( ) , {
2024-12-18 10:37:37 +01:00
rv $ data <- updated_data ( )
2025-03-18 08:27:05 +01:00
rv $ code $ modify [ [length ( rv $ code $ modify ) + 1 ] ] <- attr ( rv $ data , " code" )
2024-12-18 10:37:37 +01:00
} )
2025-04-14 10:10:33 +02:00
### Column filter
### Completely implemented, but it takes a little considering where in the
### data flow to implement, as it will act destructively on previous
### manipulations
output $ column_filter <- shiny :: renderUI ( {
shiny :: req ( rv $ data )
# c("dichotomous", "ordinal", "categorical", "datatime", "continuous")
shinyWidgets :: virtualSelectInput (
inputId = " column_filter" ,
label = " Select variable types to include" ,
selected = unique ( data_type ( rv $ data ) ) ,
choices = unique ( data_type ( rv $ data ) ) ,
updateOn = " change" ,
multiple = TRUE ,
search = FALSE ,
showValueAsTags = TRUE
)
} )
2025-04-15 08:55:35 +02:00
shiny :: observe ( {
# shiny::req(input$column_filter)
2025-04-14 11:18:24 +02:00
out <- data_type_filter ( rv $ data , input $ column_filter )
rv $ data_variables <- out
2025-04-15 08:55:35 +02:00
if ( ! is.null ( input $ column_filter ) ) {
rv $ code $ variables <- attr ( out , " code" )
}
2025-04-14 10:10:33 +02:00
# rv$code$modify[[length(rv$code$modify) + 1]] <- attr(rv$data, "code")
} )
2025-03-18 08:27:05 +01:00
######### Data filter
2024-12-18 10:37:37 +01:00
# IDEAFilter has the least cluttered UI, but might have a License issue
2025-04-29 12:11:38 +02:00
# Consider using shinyDataFilter, though not on CRAN
2025-03-18 08:27:05 +01:00
data_filter <- IDEAFilter :: IDEAFilter ( " data_filter" ,
2025-04-14 10:10:33 +02:00
data = shiny :: reactive ( rv $ data_variables ) ,
2025-03-18 08:27:05 +01:00
verbose = TRUE
)
2024-12-18 10:37:37 +01:00
2025-01-17 15:59:24 +01:00
shiny :: observeEvent (
list (
2025-04-14 10:10:33 +02:00
shiny :: reactive ( rv $ data_variables ) ,
2025-01-17 15:59:24 +01:00
shiny :: reactive ( rv $ data_original ) ,
data_filter ( ) ,
2025-04-02 11:31:04 +02:00
# regression_vars(),
2025-01-20 12:00:50 +01:00
input $ complete_cutoff
2025-01-20 11:27:54 +01:00
) ,
{
2025-03-18 08:27:05 +01:00
### Save filtered data
2025-01-20 11:27:54 +01:00
rv $ data_filtered <- data_filter ( )
2025-01-17 15:59:24 +01:00
2025-03-18 08:27:05 +01:00
### Save filtered data
2025-03-20 13:13:14 +01:00
### without empty factor levels
2025-01-20 11:27:54 +01:00
rv $ list $ data <- data_filter ( ) | >
2025-04-14 10:10:33 +02:00
REDCapCAST :: fct_drop ( ) | >
( \ ( .x ) {
.x [ ! sapply ( .x , is.character ) ]
} ) ( )
2024-12-18 10:37:37 +01:00
2025-04-11 13:23:18 +02:00
## This looks messy!! But it works as intended for now
2025-02-25 09:51:42 +01:00
out <- gsub (
" filter" , " dplyr::filter" ,
gsub (
" \\s{2,}" , " " ,
paste0 (
capture.output ( attr ( rv $ data_filtered , " code" ) ) ,
collapse = " "
)
2024-12-18 10:37:37 +01:00
)
)
2025-01-16 11:24:26 +01:00
2025-02-25 09:51:42 +01:00
out <- strsplit ( out , " %>%" ) | >
unlist ( ) | >
( \ ( .x ) {
2025-04-11 13:23:18 +02:00
paste ( c ( " df <- df" , .x [ -1 ] , " REDCapCAST::fct_drop()" ) ,
2025-03-18 08:27:05 +01:00
collapse = " |> \n "
)
2025-02-25 09:51:42 +01:00
} ) ( )
2025-01-16 11:24:26 +01:00
2025-02-25 09:51:42 +01:00
rv $ code <- append_list ( data = out , list = rv $ code , index = " filter" )
}
)
2025-03-18 08:27:05 +01:00
######### Data preview
### Overview
data_summary_server (
id = " data_summary" ,
data = shiny :: reactive ( {
rv $ data_filtered
} ) ,
color.main = " #2A004E" ,
color.sec = " #C62300" ,
2025-04-09 12:31:08 +02:00
pagination = 10
2025-03-18 08:27:05 +01:00
)
2025-04-02 11:31:04 +02:00
observeEvent ( input $ modal_browse , {
2025-04-24 12:53:47 +02:00
show_data ( REDCapCAST :: fct_drop ( rv $ data_filtered ) , title = " Uploaded data overview" , type = " modal" )
2025-04-02 11:31:04 +02:00
} )
2025-03-18 08:27:05 +01:00
output $ original_str <- renderPrint ( {
str ( rv $ data_original )
} )
output $ modified_str <- renderPrint ( {
str ( as.data.frame ( rv $ data_filtered ) | >
REDCapCAST :: set_attr (
label = NULL ,
attr = " code"
) )
} )
2025-04-30 10:02:29 +02:00
## Evaluation table/plots reset on data change
## This does not work (!?)
shiny :: observeEvent (
list (
rv $ data_filtered
) ,
{
shiny :: req ( rv $ data_filtered )
rv $ list $ table1 <- NULL
2025-04-30 13:02:26 +02:00
# rv$regression <- NULL
2025-04-30 10:02:29 +02:00
}
)
2025-03-18 08:27:05 +01:00
2025-04-09 12:31:08 +02:00
##############################################################################
#########
2025-03-18 08:27:05 +01:00
######### Code export
2025-04-09 12:31:08 +02:00
#########
##############################################################################
2025-04-11 13:23:18 +02:00
## This really should be collapsed to only one call, but I'll leave it for now
## as a working example of dynamically defining outputs and rendering.
# output$code_import <- shiny::renderPrint({
# shiny::req(rv$code$import)
# cat(c("#Data import\n", rv$code$import))
# })
output $ code_import <- shiny :: renderUI ( {
2025-05-05 20:16:38 +02:00
shiny :: req ( rv $ code $ import )
2025-04-24 11:00:56 +02:00
prismCodeBlock ( paste0 ( " #Data import\n" , rv $ code $ import ) )
} )
2025-05-05 20:16:38 +02:00
output $ code_format <- shiny :: renderUI ( {
shiny :: req ( rv $ code $ format )
2025-04-24 11:00:56 +02:00
prismCodeBlock ( paste0 ( " #Data import formatting\n" , rv $ code $ format ) )
2025-03-12 18:27:46 +01:00
} )
2025-02-25 09:51:42 +01:00
2025-04-11 13:23:18 +02:00
output $ code_data <- shiny :: renderUI ( {
2025-03-18 08:27:05 +01:00
shiny :: req ( rv $ code $ modify )
2025-04-10 15:46:42 +02:00
# browser()
2025-04-22 10:02:12 +02:00
## This will create three lines for each modification
# ls <- rv$code$modify
## This will remove all non-unique entries
# ls <- rv$code$modify |> unique()
## This will only remove all non-repeating entries
ls <- rv $ code $ modify [ ! is_identical_to_previous ( rv $ code $ modify ) ]
2025-04-10 15:46:42 +02:00
out <- ls | >
2025-04-11 13:23:18 +02:00
lapply ( expression_string ) | >
pipe_string ( ) | >
expression_string ( assign.str = " df <- df |>\n" )
prismCodeBlock ( paste0 ( " #Data modifications\n" , out ) )
2025-02-25 09:51:42 +01:00
} )
2024-12-18 10:37:37 +01:00
2025-04-14 10:10:33 +02:00
output $ code_variables <- shiny :: renderUI ( {
2025-04-14 11:18:24 +02:00
shiny :: req ( rv $ code $ variables )
out <- expression_string ( rv $ code $ variables , assign.str = " df <- df |>\n" )
prismCodeBlock ( paste0 ( " #Variables filter\n" , out ) )
2025-04-14 10:10:33 +02:00
} )
2025-04-11 13:23:18 +02:00
output $ code_filter <- shiny :: renderUI ( {
2025-04-14 11:18:24 +02:00
shiny :: req ( rv $ code $ filter )
2025-04-11 13:23:18 +02:00
prismCodeBlock ( paste0 ( " #Data filter\n" , rv $ code $ filter ) )
2025-02-25 09:51:42 +01:00
} )
2024-12-18 10:37:37 +01:00
2025-04-11 13:23:18 +02:00
output $ code_table1 <- shiny :: renderUI ( {
2025-04-09 12:31:08 +02:00
shiny :: req ( rv $ code $ table1 )
2025-04-11 13:23:18 +02:00
prismCodeBlock ( paste0 ( " #Data characteristics table\n" , rv $ code $ table1 ) )
2025-04-09 12:31:08 +02:00
} )
2025-04-10 15:46:42 +02:00
## Just a note to self
## This is a very rewarding couple of lines marking new insights to dynamically rendering code
2025-04-09 12:31:08 +02:00
shiny :: observe ( {
2025-04-30 10:02:29 +02:00
shiny :: req ( rv $ regression )
2025-04-10 15:46:42 +02:00
rv $ regression ( ) $ regression $ models | > purrr :: imap ( \ ( .x , .i ) {
2025-04-11 13:23:18 +02:00
output [ [paste0 ( " code_" , tolower ( .i ) ) ] ] <- shiny :: renderUI ( {
2025-04-14 10:10:33 +02:00
prismCodeBlock ( paste0 ( paste ( " #" , .i , " regression model\n" ) , .x $ code_table ) )
2025-04-10 15:46:42 +02:00
} )
} )
2025-04-09 12:31:08 +02:00
} )
2024-12-18 10:37:37 +01:00
##############################################################################
#########
2025-01-20 11:27:54 +01:00
######### Data analyses Inputs
2024-12-18 10:37:37 +01:00
#########
##############################################################################
output $ strat_var <- shiny :: renderUI ( {
2025-03-24 14:40:30 +01:00
columnSelectInput (
2024-12-18 10:37:37 +01:00
inputId = " strat_var" ,
selected = " none" ,
label = " Select variable to stratify baseline" ,
2025-04-15 08:55:35 +02:00
data = shiny :: reactive ( rv $ data_filtered ) ( ) ,
2025-03-20 13:13:14 +01:00
col_subset = c (
2025-01-16 11:24:26 +01:00
" none" ,
2025-03-20 13:13:14 +01:00
names ( rv $ data_filtered ) [unlist ( lapply ( rv $ data_filtered , data_type ) ) %in% c ( " dichotomous" , " categorical" , " ordinal" ) ]
)
2024-12-18 10:37:37 +01:00
)
} )
2025-01-30 14:32:11 +01:00
2025-01-20 11:27:54 +01:00
##############################################################################
#########
2025-02-07 16:24:09 +01:00
######### Descriptive evaluations
2025-01-20 11:27:54 +01:00
#########
##############################################################################
2025-04-14 10:10:33 +02:00
output $ data_info_nochar <- shiny :: renderUI ( {
shiny :: req ( rv $ list $ data )
data_description ( rv $ list $ data , data_text = " The dataset without text variables" )
} )
2024-12-18 10:37:37 +01:00
shiny :: observeEvent (
2025-01-17 15:59:24 +01:00
list (
2025-03-20 13:13:14 +01:00
input $ act_eval
2025-01-17 15:59:24 +01:00
) ,
2024-12-18 10:37:37 +01:00
{
2025-01-17 15:59:24 +01:00
shiny :: req ( input $ strat_var )
shiny :: req ( rv $ list $ data )
2025-04-09 12:31:08 +02:00
parameters <- list (
by.var = input $ strat_var ,
add.p = input $ add_p == " yes" ,
add.overall = TRUE
)
2025-03-20 13:13:14 +01:00
2025-03-24 14:40:30 +01:00
shiny :: withProgress ( message = " Creating the table. Hold on for a moment.." , {
2025-04-10 15:46:42 +02:00
rv $ list $ table1 <- rlang :: exec ( create_baseline , ! ! ! append_list ( rv $ list $ data , parameters , " data" ) )
2025-03-24 14:40:30 +01:00
} )
2025-04-09 12:31:08 +02:00
rv $ code $ table1 <- glue :: glue ( " FreesearchR::create_baseline(data,{list2str(parameters)})" )
2025-01-17 15:59:24 +01:00
}
)
2025-02-07 17:09:52 +01:00
output $ outcome_var_cor <- shiny :: renderUI ( {
2025-03-13 14:13:18 +01:00
columnSelectInput (
2025-02-07 17:09:52 +01:00
inputId = " outcome_var_cor" ,
2025-03-13 14:13:18 +01:00
selected = " none" ,
data = rv $ list $ data ,
2025-02-07 17:09:52 +01:00
label = " Select outcome variable" ,
2025-03-13 14:13:18 +01:00
col_subset = c (
" none" ,
2025-02-07 17:09:52 +01:00
colnames ( rv $ list $ data )
2025-02-25 09:51:42 +01:00
) ,
2025-02-07 17:09:52 +01:00
multiple = FALSE
)
} )
2025-01-17 15:59:24 +01:00
output $ table1 <- gt :: render_gt ( {
2025-04-30 10:02:29 +02:00
if ( ! is.null ( rv $ list $ table1 ) ) {
rv $ list $ table1 | >
gtsummary :: as_gt ( ) | >
gt :: tab_header ( gt :: md ( " **Table 1: Baseline Characteristics**" ) )
} else {
return ( NULL )
}
2025-01-17 15:59:24 +01:00
} )
2025-02-25 09:51:42 +01:00
data_correlations_server (
id = " correlations" ,
data = shiny :: reactive ( {
shiny :: req ( rv $ list $ data )
2025-03-13 14:13:18 +01:00
out <- rv $ list $ data
2025-03-13 15:04:29 +01:00
if ( ! is.null ( input $ outcome_var_cor ) && input $ outcome_var_cor != " none" ) {
2025-03-13 14:13:18 +01:00
out <- out [ ! names ( out ) %in% input $ outcome_var_cor ]
}
2025-02-25 09:51:42 +01:00
out
} ) ,
cutoff = shiny :: reactive ( input $ cor_cutoff )
)
2025-02-07 16:24:09 +01:00
2025-02-25 09:51:42 +01:00
##############################################################################
#########
######### Data visuals
#########
##############################################################################
2025-02-07 16:24:09 +01:00
2025-04-14 10:10:33 +02:00
pl <- data_visuals_server ( " visuals" , data = shiny :: reactive ( rv $ list $ data ) )
2025-02-07 17:09:52 +01:00
2025-02-07 16:24:09 +01:00
##############################################################################
#########
######### Regression model analyses
#########
##############################################################################
2025-04-14 10:10:33 +02:00
rv $ regression <- regression_server ( " regression" , data = shiny :: reactive ( rv $ list $ data ) )
2024-12-18 10:37:37 +01:00
2025-04-30 13:02:26 +02:00
# shiny::observeEvent(rv$regression, {
# browser()
# if (shiny::is.reactive(rv$regression)) {
# rv$list$regression <- rv$regression()
# } else {
# rv$list$regression <- rv$regression
# }
# # rv$list$regression <- rv$regression()
# })
# output$regression_models <- renderText({
# req(rv$list$regression)
# browser()
# names(rv$list$regression)
# })
2025-01-15 16:21:38 +01:00
##############################################################################
#########
######### Page navigation
#########
##############################################################################
shiny :: observeEvent ( input $ act_start , {
2025-01-16 14:24:38 +01:00
bslib :: nav_select ( id = " main_panel" , selected = " Data" )
2025-01-15 16:21:38 +01:00
} )
2024-12-18 10:37:37 +01:00
2025-01-15 16:21:38 +01:00
##############################################################################
#########
######### Reactivity
#########
##############################################################################
2024-12-18 10:37:37 +01:00
output $ uploaded <- shiny :: reactive ( {
if ( is.null ( rv $ ds ) ) {
" no"
} else {
" yes"
}
} )
shiny :: outputOptions ( output , " uploaded" , suspendWhenHidden = FALSE )
2024-12-18 11:26:00 +01:00
output $ ready <- shiny :: reactive ( {
if ( is.null ( rv $ ready ) ) {
" no"
} else {
" yes"
}
} )
shiny :: outputOptions ( output , " ready" , suspendWhenHidden = FALSE )
2024-12-18 10:37:37 +01:00
2025-01-15 16:21:38 +01:00
##############################################################################
#########
######### Downloads
#########
##############################################################################
2024-12-18 10:37:37 +01:00
# Could be rendered with other tables or should show progress
# Investigate quarto render problems
# On temp file handling: https://github.com/quarto-dev/quarto-cli/issues/3992
output $ report <- downloadHandler (
filename = shiny :: reactive ( {
paste0 ( " report." , input $ output_type )
} ) ,
content = function ( file , type = input $ output_type ) {
## Notification is not progressing
## Presumably due to missing
2025-01-30 14:32:11 +01:00
# Simplified for .rmd output attempt
format <- ifelse ( type == " docx" , " word_document" , " odt_document" )
2025-01-23 13:21:41 +01:00
2025-04-02 11:31:04 +02:00
rv $ list $ regression <- rv $ regression ( )
2025-01-23 13:21:41 +01:00
2025-04-02 11:31:04 +02:00
shiny :: withProgress ( message = " Generating the report. Hold on for a moment.." , {
tryCatch (
{
rv $ list | >
write_rmd (
output_format = format ,
input = file.path ( getwd ( ) , " www/report.rmd" )
)
} ,
error = function ( err ) {
showNotification ( paste0 ( " We encountered the following error creating your report: " , err ) , type = " err" )
}
)
2024-12-18 10:37:37 +01:00
} )
file.rename ( paste0 ( " www/report." , type ) , file )
}
)
2025-01-15 16:21:38 +01:00
output $ data_modified <- downloadHandler (
filename = shiny :: reactive ( {
paste0 ( " modified_data." , input $ data_type )
} ) ,
content = function ( file , type = input $ data_type ) {
2025-01-16 11:24:26 +01:00
if ( type == " rds" ) {
readr :: write_rds ( rv $ list $ data , file = file )
2025-02-07 16:24:09 +01:00
} else if ( type == " dta" ) {
2025-01-16 11:24:26 +01:00
haven :: write_dta ( as.data.frame ( rv $ list $ data ) , path = file )
2025-02-25 09:51:42 +01:00
} else if ( type == " csv" ) {
2025-02-07 16:24:09 +01:00
readr :: write_csv ( rv $ list $ data , file = file )
2025-01-15 16:21:38 +01:00
}
}
)
##############################################################################
#########
######### Clearing the session on end
#########
##############################################################################
2024-12-18 10:37:37 +01:00
session $ onSessionEnded ( function ( ) {
cat ( " Session Ended\n" )
files <- list.files ( " www/" )
lapply ( files [ ! files %in% files.to.keep ] , \ ( .x ) {
unlink ( paste0 ( " www/" , .x ) , recursive = FALSE )
print ( paste ( .x , " deleted" ) )
} )
} )
}
########
2025-04-30 13:02:26 +02:00
#### Current file: /Users/au301842/FreesearchR/app/launch.R
2024-12-18 10:37:37 +01:00
########
shinyApp ( ui , server )