2025-06-20 09:47:21 +02:00
#' Data correlations evaluation module
#'
#' @param id Module id
2025-12-11 09:34:40 +01:00
#' @param ... additional UI elements to show before the table overview
2025-06-20 09:47:21 +02:00
#'
#' @name data-missings
#' @returns Shiny ui module
#' @export
2025-12-11 09:34:40 +01:00
data_missings_ui <- function ( id , ... ) {
2025-06-20 09:47:21 +02:00
ns <- shiny :: NS ( id )
2025-12-11 09:34:40 +01:00
list (
bslib :: layout_sidebar (
sidebar = bslib :: sidebar (
bslib :: accordion (
id = ns ( " acc_mis" ) ,
open = " acc_chars" ,
multiple = FALSE ,
bslib :: accordion_panel (
value = " acc_pan_mis" ,
title = " Settings" ,
icon = bsicons :: bs_icon ( " x-circle" ) ,
shiny :: uiOutput ( ns ( " missings_method" ) ) ,
shiny :: uiOutput ( ns ( " missings_var" ) ) ,
shiny :: helpText ( i18n $ t ( " Evaluate missingness by either comparing missing values across variables (optionally grouped by af categorical or dichotomous variable) or compare variables grouped by the missing status (missing or not) of an outcome variable. If there is a significant difference i the missingness, this may cause a bias in you data and should be considered carefully interpreting the data and analyses as data may not be missing at random." ) ) ,
shiny :: br ( ) ,
shiny :: actionButton (
inputId = ns ( " act_miss" ) ,
label = i18n $ t ( " Evaluate" ) ,
width = " 100%" ,
icon = shiny :: icon ( " calculator" ) ,
disabled = FALSE
)
)
)
) ,
... ,
gt :: gt_output ( outputId = ns ( " missings_table" ) )
)
2025-06-20 09:47:21 +02:00
)
}
2025-12-11 09:34:40 +01:00
## This should really just be rebuild to only contain a function
2025-06-20 09:47:21 +02:00
#'
#' @param data data
#' @param output.format output format
#'
#' @name data-missings
#' @returns shiny server module
#' @export
data_missings_server <- function ( id ,
data ,
2025-12-11 09:34:40 +01:00
max_level = 20 ,
2025-06-20 09:47:21 +02:00
... ) {
shiny :: moduleServer (
id = id ,
module = function ( input , output , session ) {
2025-12-11 09:34:40 +01:00
ns <- session $ ns
2025-06-20 09:47:21 +02:00
2025-06-25 10:50:05 +02:00
datar <- if ( is.reactive ( data ) ) data else reactive ( data )
2025-06-20 09:47:21 +02:00
rv <- shiny :: reactiveValues (
2025-06-26 09:22:21 +02:00
data = NULL ,
table = NULL
2025-06-20 09:47:21 +02:00
)
2025-12-11 09:34:40 +01:00
## Notes
##
## Code export is still missing
## Direct table export would be nice
2025-06-25 10:50:05 +02:00
2025-12-11 09:34:40 +01:00
shiny :: observe (
output $ missings_method <- shiny :: renderUI ( {
shiny :: req ( data ( ) )
vectorSelectInput (
inputId = ns ( " missings_method" ) ,
2025-12-11 17:22:47 +01:00
label = i18n $ t ( " Analysis method for missingness overview" ) ,
2025-12-11 09:34:40 +01:00
choices = setNames (
c (
" predictors" ,
" outcome"
) ,
c (
2025-12-11 17:22:47 +01:00
i18n $ t ( " Overview of missings across variables" ) ,
i18n $ t ( " Overview of difference in variables by missing status in outcome" )
2025-12-11 09:34:40 +01:00
)
)
)
} )
)
2025-06-25 10:50:05 +02:00
2025-12-11 09:34:40 +01:00
shiny :: observe ( {
output $ missings_var <- shiny :: renderUI ( {
shiny :: req ( datar ( ) )
shiny :: req ( input $ missings_method )
# browser()
if ( input $ missings_method == " predictors" ) {
2025-12-11 17:22:47 +01:00
label <- i18n $ t ( " Select a variable for grouped overview" )
2025-12-11 09:34:40 +01:00
df <- data_type_filter ( data ( ) , type = c ( " categorical" , " dichotomous" ) )
2025-12-11 17:22:47 +01:00
col_subset <- c ( " none" , names ( df ) )
2025-12-11 09:34:40 +01:00
} else {
2025-12-11 17:22:47 +01:00
label <- i18n $ t ( " Select outcome variable for overview" )
2025-12-11 09:34:40 +01:00
df <- datar ( ) [apply ( datar ( ) , 2 , anyNA ) ]
2025-12-11 17:22:47 +01:00
col_subset <- names ( df )
2025-12-11 09:34:40 +01:00
}
columnSelectInput (
inputId = ns ( " missings_var" ) ,
2025-12-11 17:22:47 +01:00
label = label ,
2025-12-11 09:34:40 +01:00
data = df ,
2025-12-11 17:22:47 +01:00
col_subset = col_subset ,
2025-12-11 09:34:40 +01:00
none_label = i18n $ t ( " No variable" )
)
} )
2025-06-25 10:50:05 +02:00
} )
2025-12-11 09:34:40 +01:00
shiny :: observeEvent (
list ( input $ act_miss ) ,
{
shiny :: req ( datar ( ) )
shiny :: req ( input $ missings_var )
# browser()
df_tbl <- datar ( )
by_var <- input $ missings_var
parameters <- list (
by_var = by_var ,
max_level = max_level ,
type = input $ missings_method
)
tryCatch (
{
shiny :: withProgress ( message = i18n $ t ( " Calculating. Hold tight for a moment.." ) , {
out <- do.call (
compare_missings ,
modifyList ( parameters , list ( data = df_tbl ) )
)
} )
} ,
error = function ( err ) {
showNotification ( paste0 ( " Error: " , err ) , type = " err" )
}
)
if ( is.null ( input $ missings_var ) || input $ missings_var == " " || ! input $ missings_var %in% names ( datar ( ) ) || input $ missings_var == " none" ) {
# if (is.null(variabler()) || variabler() == "" || !variabler() %in% names(data()) || variabler() == "none") {
# tbl <- rv$data()
if ( anyNA ( datar ( ) ) ) {
2025-12-11 17:22:47 +01:00
if ( input $ missings_method == " predictors" ) {
title <- i18n $ t ( " Overview of missing observations" )
} else {
title <- i18n $ t ( " No outcome measure chosen" )
}
2025-12-11 09:34:40 +01:00
} else {
title <- i18n $ t ( " No missing observations" )
}
2025-06-26 09:22:21 +02:00
} else {
2025-12-11 09:34:40 +01:00
## Due to reactivity, the table updates too quickly. this mitigates that issue..
if ( input $ missings_var == " predictors" ) {
title <- glue :: glue ( i18n $ t ( " Missings across variables by the variable **'{input$missings_var}'**" ) )
} else {
title <- glue :: glue ( i18n $ t ( " Missing vs non-missing observations in the variable **'{input$missings_var}'**" ) )
}
2025-06-26 09:22:21 +02:00
}
2025-12-11 09:34:40 +01:00
attr ( out , " tbl_title" ) <- title
rv $ data <- shiny :: reactive ( out )
2025-06-25 10:50:05 +02:00
}
2025-12-11 09:34:40 +01:00
)
2025-06-20 09:47:21 +02:00
2025-12-11 09:34:40 +01:00
shiny :: observeEvent (
list (
# input$act_miss
rv $ data
) ,
{
output $ missings_table <- gt :: render_gt ( {
shiny :: req ( rv $ data )
# shiny::req(input$missings_var)
# browser()
if ( " p.value" %in% names ( rv $ data ( ) [ [ " table_body" ] ] ) ) {
tbl <- rv $ data ( ) | >
gtsummary :: bold_p ( )
} else {
tbl <- rv $ data ( )
}
2025-06-26 09:22:21 +02:00
2025-12-11 09:34:40 +01:00
out <- tbl | >
gtsummary :: as_gt ( ) | >
gt :: tab_header ( title = gt :: md ( attr ( tbl , " tbl_title" ) ) )
attr ( out , " strat_var" ) <- input $ missings_var
2025-06-26 09:22:21 +02:00
2025-12-11 09:34:40 +01:00
rv $ table <- out
out
} )
}
)
return ( shiny :: reactive ( rv $ table ) )
2025-06-20 09:47:21 +02:00
}
)
}
missing_demo_app <- function ( ) {
2025-12-11 09:34:40 +01:00
ui <- do.call (
bslib :: page ,
c (
list (
title = i18n $ t ( " Missings" ) ,
icon = bsicons :: bs_icon ( " x-circle" )
) ,
data_missings_ui ( id = " data" )
)
2025-06-20 09:47:21 +02:00
)
server <- function ( input , output , session ) {
data_demo <- mtcars
2025-06-25 10:50:05 +02:00
data_demo [sample ( 1 : 32 , 10 ) , " cyl" ] <- NA
data_demo [sample ( 1 : 32 , 8 ) , " vs" ] <- NA
2025-12-11 09:34:40 +01:00
data_missings_server ( id = " data" , data = data_demo )
2025-06-20 09:47:21 +02:00
2025-12-11 09:34:40 +01:00
# visual_summary_server(id = "visual", data = data_demo)
2025-06-26 09:22:21 +02:00
2025-12-11 09:34:40 +01:00
# observeEvent(input$modal_missings, {
# tryCatch(
# {
# modal_visual_summary(id = "visual")
# },
# error = function(err) {
# showNotification(paste0("We encountered the following error browsing your data: ", err), type = "err")
# }
# )
# })
2025-06-20 09:47:21 +02:00
}
shiny :: shinyApp ( ui , server )
}
2025-12-11 09:34:40 +01:00
# missing_demo_app()
2025-06-20 09:47:21 +02:00
2025-07-03 16:19:51 +02:00
#' Pairwise comparison of missings across covariables
#'
#' @param data data frame
#' @param by_var variable to stratify by missingness
#'
#' @returns gtsummary list object
#' @export
#'
2025-12-11 09:34:40 +01:00
compare_missings <- function (
data ,
by_var ,
max_level = 20 ,
type = c ( " predictors" , " outcome" )
) {
type <- match.arg ( type )
2025-07-03 16:19:51 +02:00
if ( ! is.null ( by_var ) && by_var != " " && by_var %in% names ( data ) ) {
2025-10-31 11:37:57 +01:00
data <- data | >
lapply ( \ ( .x ) {
2025-12-11 09:34:40 +01:00
if ( is.factor ( .x ) ) {
cut_var ( .x , breaks = 20 , type = " top" )
2025-10-31 11:37:57 +01:00
} else {
.x
}
2025-12-11 09:34:40 +01:00
} ) | >
dplyr :: bind_cols ( )
2025-10-31 11:37:57 +01:00
2025-12-11 09:34:40 +01:00
if ( type == " predictors" ) {
data <- missings_logic_across ( data , exclude = by_var )
} else {
data [ [by_var ] ] <- ifelse ( is.na ( data [ [by_var ] ] ) , " Missing" , " Non-missing" )
}
2025-07-03 16:19:51 +02:00
out <- gtsummary :: tbl_summary ( data , by = by_var ) | >
gtsummary :: add_p ( )
} else {
2025-12-11 09:34:40 +01:00
if ( type == " predictors" ) {
data <- missings_logic_across ( data )
}
2025-07-03 16:19:51 +02:00
out <- gtsummary :: tbl_summary ( data )
}
2025-12-11 09:34:40 +01:00
2025-07-03 16:19:51 +02:00
out
}
2025-12-11 09:34:40 +01:00
#' Converting all variables to logicals by missing status
#'
#' @param data data
#' @param exclude character vector of variable names to be excluded
#'
#' @returns data frame
#' @export
#'
#' @examples
#' mtcars |> missings_logic_across("cyl")
#' ## gtsummary::trial |>
#' ## missings_logic_across() |>
#' ## gtsummary::tbl_summary()
missings_logic_across <- function ( data , exclude = NULL ) {
# This function includes a approach way to preserve variable labels
names ( data ) | >
lapply ( \ ( .x ) {
# browser()
# Saving original labels
lab <- REDCapCAST :: get_attr ( data [ [.x ] ] , attr = " label" )
if ( ! .x %in% exclude ) {
out <- is.na ( data [ [.x ] ] )
} else {
out <- data [ [.x ] ]
}
if ( ! is.na ( lab ) ) {
# Restoring original labels, if not NA
REDCapCAST :: set_attr ( data = out , label = lab , attr = " label" , overwrite = TRUE )
} else {
out
}
} ) | >
dplyr :: bind_cols ( .name_repair = " unique_quiet" ) | >
setNames ( names ( data ) )
}