a bit of trial and error. not completely satisfied with readcap_read-module yet

This commit is contained in:
Andreas Gammelgaard Damsbo 2024-12-09 14:00:44 +01:00
parent a5c0a01d8a
commit 00eb49c225
No known key found for this signature in database
16 changed files with 1188 additions and 385 deletions

View file

@ -39,7 +39,9 @@ Imports:
DHARMa,
teal,
IDEAFilter,
sparkline
sparkline,
datamods,
toastui
Suggests:
styler,
devtools,

View file

@ -10,6 +10,8 @@ export(format_writer)
export(getfun)
export(index_embed)
export(m_datafileUI)
export(m_redcap_readServer)
export(m_redcap_readUI)
export(modify_qmd)
export(read_input)
export(regression_model)

View file

@ -30,7 +30,6 @@ getfun <- function(x) {
#' @export
#'
write_quarto <- function(data, ...) {
# Exports data to temporary location
#
# I assume this is more secure than putting it in the www folder and deleting
@ -41,7 +40,8 @@ write_quarto <- function(data,...){
## 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(execute_params = list(data.file=temp),
quarto::quarto_render(
execute_params = list(data.file = temp),
...
)
}
@ -128,7 +128,7 @@ dummy_Imports <- function() {
}
file_export <- function(data,output.format=c("df","teal"),filename){
file_export <- function(data, output.format = c("df", "teal", "list"), filename, ...) {
output.format <- match.arg(output.format)
filename <- gsub("-", "_", filename)
@ -137,7 +137,11 @@ file_export <- function(data,output.format=c("df","teal"),filename){
out <- within(
teal_data(),
{
assign(name, value |> dplyr::bind_cols())
assign(name, value |>
dplyr::bind_cols() |>
REDCapCAST::parse_data() |>
REDCapCAST::as_factor() |>
REDCapCAST::numchar2fct())
},
value = data,
name = filename
@ -146,6 +150,13 @@ file_export <- function(data,output.format=c("df","teal"),filename){
datanames(out) <- filename
} else if (output.format == "df") {
out <- data
} else if (output.format == "list") {
out <- list(
data = data,
name = filename
)
out <- c(out,...)
}
out

View file

@ -30,7 +30,6 @@ m_datafileUI <- function(id) {
}
m_datafileServer <- function(id, output.format = "df") {
ns <- shiny::NS(id)
shiny::moduleServer(id, function(input, output, session, ...) {
ns <- shiny::NS(id)
ds <- shiny::reactive({
@ -54,7 +53,7 @@ m_datafileServer <- function(id, output.format = "df") {
} else {
out <- input$include_vars
}
return(out)
out
})
output$data_input <-
@ -64,21 +63,34 @@ m_datafileServer <- function(id, output.format = "df") {
})
shiny::eventReactive(input$submit, {
shiny::req(input$file)
# shiny::req(input$file)
file_export(
data = ds()[base_vars()] |> REDCapCAST::numchar2fct(),
data <- shiny::isolate({
ds()[base_vars()]
})
file_export(data,
output.format = output.format,
filename = tools::file_path_sans_ext(input$file$name)
tools::file_path_sans_ext(input$file$name)
)
})
})
}
#' Shiny module to browser and export REDCap data
#'
#' @param id Namespace id
#' @rdname redcap_read_shiny_module
#'
#' @return shiny ui element
#' @export
m_redcap_readUI <- function(id) {
ns <- shiny::NS(id)
shiny::tagList(
server_ui <- fluidRow(
column(
width = 6,
shiny::textInput(
inputId = ns("uri"),
label = "URI",
@ -88,59 +100,101 @@ m_redcap_readUI <- function(id) {
inputId = ns("api"),
label = "API token",
value = ""
),
shiny::tableOutput(outputId = ns("table")),
)
)
)
params_ui <- fluidRow(
column(
width = 6,
shiny::uiOutput(outputId = ns("fields")),
shiny::uiOutput(outputId = ns("instruments")),
shinyWidgets::switchInput(
inputId = "do_filter",
label = "Apply filter?",
value = FALSE,
inline = TRUE
),
# shiny::radioButtons(
# inputId = "do_filter",
# label = "Filter export?",
# selected = "no",
# inline = TRUE,
# choices = list(
# "No" = "no",
# "Yes" = "yes"
# )
# ),
shiny::conditionalPanel(
condition = "input.do_filter",
shiny::uiOutput(outputId = ns("arms")),
shiny::actionButton(inputId = ns("submit"), "Submit")
shiny::textInput(
inputId = ns("filter"),
label = "Optional filter logic (e.g., [gender] = 'female')"
)
)
)
)
shiny::fluidPage(
server_ui,
params_ui,
shiny::actionButton(inputId = ns("import"), label = "Import"),
shiny::br(),
DT::DTOutput(outputId = ns("table"))
# toastui::datagridOutput2(outputId = ns("table")),
# toastui::datagridOutput2(outputId = ns("data")),
# shiny::actionButton(inputId = ns("submit"), label = "Submit"),
# DT::DTOutput(outputId = ns("data_prev"))
)
}
m_redcap_readServer <- function(id, output.format="df") {
ns <- shiny::NS(id)
shiny::moduleServer(
id,
function(input, output, session,...) {
ns <- shiny::NS(id)
instr <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_instruments(redcap_uri = input$uri, token = input$api)
})
#' @param output.format data.frame ("df") or teal data object ("teal")
#' @rdname redcap_read_shiny_module
#'
#' @return shiny server module
#' @export
#'
m_redcap_readServer <- function(id, output.format = c("df", "teal", "list")) {
output.format <- match.arg(output.format)
output$instruments <- shiny::renderUI({
shiny::selectizeInput(
inputId = ns("instruments"),
# inputId = "instruments",
selected = NULL,
label = "Instruments to include",
choices = instr()[["data"]][[1]],
multiple = TRUE
)
})
module <- function(input, output, session) {
# ns <- shiny::NS(id)
ns <- session$ns
dd <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_metadata_read(redcap_uri = input$uri, token = input$api)
})
output$fields <- shiny::renderUI({
shiny::selectizeInput(
# inputId = "fields",
inputId = ns("fields"),
selected = NULL,
label = "Fields/variables to include",
choices = dd()[["data"]][[1]],
multiple = TRUE
)
REDCapR::redcap_metadata_read(
redcap_uri = input$uri,
token = input$api
)$data
})
arms <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_event_read(redcap_uri = input$uri, token = input$api)
REDCapR::redcap_event_read(
redcap_uri = input$uri,
token = input$api
)$data
})
output$fields <- shiny::renderUI({
shinyWidgets::virtualSelectInput(
inputId = ns("fields"),
label = "Multiple select:",
choices = dd() |>
dplyr::select(field_name, form_name) |>
(\(.x){
split(.x$field_name, .x$form_name)
})() # |>
# stats::setNames(instr()[["data"]][[2]])
,
updateOn = "close",
multiple = TRUE
)
})
output$arms <- shiny::renderUI({
@ -148,41 +202,123 @@ m_redcap_readServer <- function(id, output.format="df") {
# inputId = "arms",
inputId = ns("arms"),
selected = NULL,
label = "Arms/events to include",
choices = arms()[["data"]][[3]],
label = "Filter by events/arms",
choices = arms()[[3]],
multiple = TRUE
)
})
output$table <- shiny::renderTable({
dd()[["data"]]
output$table <- DT::renderDT(
{
shiny::req(input$api)
shiny::req(input$uri)
# dd()[["data"]][c(1,2,4,5,6,8)]
data.df <- dd()[c(1, 2, 4, 5, 6, 8)]
DT::datatable(data.df,
caption = "Subset of data dictionary"
)
},
server = TRUE
)
name <- reactive({
shiny::req(input$api)
REDCapR::redcap_project_info_read(
redcap_uri = input$uri,
token = input$api
)$data$project_title
})
shiny::eventReactive(input$submit, {
shiny::eventReactive(input$import, {
shiny::req(input$api)
data <- REDCapCAST::read_redcap_tables(
record_id <- dd()[[1]][1]
redcap_data <- REDCapCAST::read_redcap_tables(
uri = input$uri,
token = input$api,
fields = unique(c(dd()[["data"]][[1]][1], input$fields)),
forms = input$instruments,
fields = unique(c(record_id, input$fields)),
# forms = input$instruments,
events = input$arms,
raw_or_label = "both"
)
info <- REDCapR::redcap_project_info_read(redcap_uri = input$uri,
token = input$api)
data |>
raw_or_label = "both",
filter_logic = input$filter
) |>
REDCapCAST::redcap_wider() |>
REDCapCAST::suffix2label() |>
REDCapCAST::as_factor() |>
dplyr::select(-dplyr::ends_with("_complete")) |>
dplyr::select(-dplyr::any_of(dd()[["data"]][[1]][1])) |>
file_export(
dplyr::select(-dplyr::any_of(record_id)) |>
REDCapCAST::suffix2label()
out_object <- file_export(redcap_data,
output.format = output.format,
filename = info$data$project_title
filename = name()
)
if (output.format == "list") {
out <- list(
data = shiny::reactive(redcap_data)
# meta = dd()[["dd"]],
# name = name,
# filter = input$filter
)
} else {
out <- out_object
}
return(out)
})
}
shiny::moduleServer(
id = id,
module = 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")
}
)
tdm_data_upload <- teal::teal_data_module(
ui <- function(id) {
shiny::fluidPage(
m_datafileUI(id)
)
},
server = function(id) {
m_datafileServer(id, output.format = "teal")
}
)
redcap_app <- function() {
ui <- fluidPage(
m_redcap_readUI("data"),
DT::DTOutput(outputId = "redcap_prev")
)
server <- function(input, output, session) {
ds <- m_redcap_readServer("data")
output$redcap_prev <- DT::renderDT(
{
# df <- shiny::isolate(data_redcap())
# browser()
#
DT::datatable(ds(),
caption = "Observations"
)
},
server = TRUE
)
}
shinyApp(ui, server)
}
redcap_app()

View file

@ -23,7 +23,6 @@ library(REDCapCAST)
library(easystats)
library(patchwork)
library(DHARMa)
library(IDEAFilter)
# if (!requireNamespace("webResearch")) {
# devtools::install_github("agdamsbo/webResearch", quiet = TRUE, upgrade = "never")
# }
@ -296,11 +295,15 @@ server <- function(input, output, session) {
paste0("report.", input$output_type)
}),
content = function(file, type = input$output_type) {
## Notification is not progressing
## Presumably due to missing
shiny::withProgress(message = "Generating report. Hold on for a moment..", {
v$list |>
write_quarto(
output_format = type,
input = file.path(getwd(), "www/report.qmd")
)
})
file.rename(paste0("www/report.", type), file)
}
)

View file

@ -28,7 +28,6 @@ panels <- list(
)
)
ui <- bslib::page(
theme = bslib::bs_theme(
bootswatch = "minty",

View file

@ -0,0 +1,10 @@
name: webResearch
title:
username: agdamsbo
account: agdamsbo
server: shinyapps.io
hostUrl: https://api.shinyapps.io/v1
appId: 13276335
bundleId: 9436643
url: https://agdamsbo.shinyapps.io/webResearch/
version: 1

View file

@ -0,0 +1,352 @@
# project.aid::merge_scripts(list.files("R/",full.names = TRUE),dest = here::here("app/functions.R"))
# source(here::here("app/functions.R"))
# source("https://raw.githubusercontent.com/agdamsbo/webResearch/refs/heads/main/app/functions.R")
library(readr)
library(MASS)
library(stats)
library(gtsummary)
library(gt)
library(openxlsx2)
library(haven)
library(readODS)
library(shiny)
library(bslib)
library(assertthat)
library(dplyr)
library(quarto)
library(here)
library(broom)
library(broom.helpers)
library(REDCapCAST)
library(easystats)
library(patchwork)
library(DHARMa)
# if (!requireNamespace("webResearch")) {
# devtools::install_github("agdamsbo/webResearch", quiet = TRUE, upgrade = "never")
# }
# library(webResearch)
if (file.exists(here::here("functions.R"))) {
source(here::here("functions.R"))
}
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/")
v <- shiny::reactiveValues(
list = NULL,
ds = NULL,
input = exists("webResearch_data"),
local_temp = NULL,
quarto = NULL,
test = "no"
)
data_file <- datamods::import_file_server(
id = "file_import",
show_data_in = "popup",
trigger_return = "button",
return_class = "data.frame",
read_fns = list(
ods = function(file) {
readODS::read_ods(path = file)
},
dta = function(file) {
haven::read_dta(file = file)
}
)
)
data_redcap <- m_redcap_readServer(
id = "redcap_import",
output.format = "list"
)
output$redcap_prev <- DT::renderDT(
{
DT::datatable(head(purrr::pluck(data_redcap(), 1)(), 5),
caption = "First 5 observations"
)
},
server = TRUE
)
ds <- shiny::reactive({
# input$file1 will be NULL initially. After the user selects
# and uploads a file, head of that data file by default,
# or all rows if selected, will be shown.
if (v$input) {
out <- webResearch_data
} else if (input$source == "file") {
out <- data_file$data() |>
REDCapCAST::numchar2fct()
} else if (input$source == "redcap") {
out <- purrr::pluck(data_redcap(), 1)() |>
REDCapCAST::parse_data() |>
REDCapCAST::as_factor() |>
REDCapCAST::numchar2fct()
}
v$ds <- "loaded"
# browser()
# if (input$factorize == "yes") {
# out <- out |>
# REDCapCAST::numchar2fct()
# }
out
})
output$include_vars <- shiny::renderUI({
selectizeInput(
inputId = "include_vars",
selected = NULL,
label = "Covariables to include",
choices = colnames(ds()),
multiple = TRUE
)
})
output$outcome_var <- shiny::renderUI({
selectInput(
inputId = "outcome_var",
selected = NULL,
label = "Select outcome variable",
choices = colnames(ds()),
multiple = FALSE
)
})
output$strat_var <- shiny::renderUI({
selectInput(
inputId = "strat_var",
selected = "none",
label = "Select variable to stratify baseline",
choices = c("none", colnames(ds()[base_vars()])),
multiple = FALSE
)
})
output$factor_vars <- shiny::renderUI({
selectizeInput(
inputId = "factor_vars",
selected = colnames(ds())[sapply(ds(), is.factor)],
label = "Covariables to format as categorical",
choices = colnames(ds()),
multiple = TRUE
)
})
base_vars <- shiny::reactive({
if (is.null(input$include_vars)) {
out <- colnames(ds())
} else {
out <- unique(c(input$include_vars, input$outcome_var))
}
return(out)
})
## Have a look at column filters at some point
## There should be a way to use the filtering the filter data for further analyses
## Disabled for now, as the JS is apparently not isolated
output$data_table <-
DT::renderDT(
{
DT::datatable(ds()[base_vars()])
},
server = FALSE
)
output$data.classes <- gt::render_gt({
shiny::req(input$file)
data.frame(matrix(sapply(ds(), \(.x){
class(.x)[1]
}), nrow = 1)) |>
stats::setNames(names(ds())) |>
gt::gt()
})
shiny::observeEvent(input$act_start, {
bslib::nav_select(id = "main_panel", selected = "Data analysis")
})
shiny::observeEvent(
{
input$load
},
{
shiny::req(input$outcome_var)
# Assumes all character variables can be formatted as factors
data <- ds() |>
dplyr::mutate(dplyr::across(dplyr::where(is.character), as.factor))
data <- data |> factorize(vars = input$factor_vars)
# if (is.factor(data[[input$strat_var]])) {
# by.var <- input$strat_var
# } else {
# by.var <- NULL
# }
if (input$strat_var == "none") {
by.var <- NULL
} else {
by.var <- input$strat_var
}
data <- data[base_vars()]
# model <- data |>
# regression_model(
# outcome.str = input$outcome_var,
# auto.mode = input$regression_auto == 1,
# formula.str = input$regression_formula,
# fun = input$regression_fun,
# args.list = eval(parse(text = paste0("list(", input$regression_args, ")")))
# )
models <- list(
"Univariable" = regression_model_uv,
"Multivariable" = regression_model
) |>
lapply(\(.fun){
do.call(
.fun,
c(
list(data = data),
list(outcome.str = input$outcome_var),
list(formula.str = input$regression_formula),
list(fun = input$regression_fun),
list(args.list = eval(parse(text = paste0("list(", input$regression_args, ")"))))
)
)
})
# browser()
# check <- performance::check_model(purrr::pluck(models,"Multivariable") |>
# (\(x){
# class(x) <- class(x)[class(x) != "webresearch_model"]
# return(x)
# })())
check <- purrr::pluck(models, "Multivariable") |>
performance::check_model()
v$list <- list(
data = data,
check = check,
table1 = data |>
baseline_table(
fun.args =
list(
by = by.var
)
) |>
(\(.x){
if (!is.null(by.var)) {
.x |> gtsummary::add_overall()
} else {
.x
}
})() |>
(\(.x){
if (input$add_p == "yes") {
.x |>
gtsummary::add_p() |>
gtsummary::bold_p()
} else {
.x
}
})(),
table2 = models |>
purrr::map(regression_table) |>
tbl_merge(),
input = input
)
output$table1 <- gt::render_gt(
v$list$table1 |>
gtsummary::as_gt()
)
output$table2 <- gt::render_gt(
v$list$table2 |>
gtsummary::as_gt()
)
output$check <- shiny::renderPlot({
p <- plot(check) +
patchwork::plot_annotation(title = "Multivariable regression model checks")
p
# Generate checks in one column
# layout <- sapply(seq_len(length(p)), \(.x){
# patchwork::area(.x, 1)
# })
#
# p + patchwork::plot_layout(design = Reduce(c, layout))
# patchwork::wrap_plots(ncol=1) +
# patchwork::plot_annotation(title = 'Multivariable regression model checks')
})
}
)
output$uploaded <- shiny::reactive({
if (is.null(v$ds)) {
"no"
} else {
"yes"
}
})
shiny::outputOptions(output, "uploaded", suspendWhenHidden = FALSE)
output$has_input <- shiny::reactive({
if (v$input) {
"yes"
} else {
"no"
}
})
shiny::outputOptions(output, "has_input", suspendWhenHidden = FALSE)
# 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
shiny::withProgress(message = "Generating report. Hold on for a moment..", {
v$list |>
write_quarto(
output_format = type,
input = file.path(getwd(), "www/report.qmd")
)
})
file.rename(paste0("www/report.", type), file)
}
)
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"))
})
})
}

View file

@ -0,0 +1,412 @@
library(shiny)
library(bslib)
library(datamods)
library(shinyWidgets)
library(DT)
requireNamespace("gt")
# ns <- NS(id)
ui_elements <- list(
# bslib::nav_panel(
# title = "Data overview",
# # shiny::uiOutput("data.classes"),
# # shiny::uiOutput("data.input"),
# # shiny::p("Classes of uploaded data"),
# # gt::gt_output("data.classes"),
# shiny::p("Subset data"),
# DT::DTOutput(outputId = "data.input")
# ),
# bslib::nav_panel(
# title = "Baseline characteristics",
# gt::gt_output(outputId = "table1")
# ),
# bslib::nav_panel(
# title = "Regression table",
# gt::gt_output(outputId = "table2")
# ),
# bslib::nav_panel(
# title = "Regression checks",
# shiny::plotOutput(outputId = "check")
# ),
##############################################################################
#########
######### Import panel
#########
##############################################################################
"import" = bslib::nav_panel(
title = "Data import",
shiny::h4("Upload your dataset"),
shiny::conditionalPanel(
condition = "output.has_input=='yes'",
# Input: Select a file ----
shiny::helpText("Analyses are performed on provided data")
),
shiny::conditionalPanel(
condition = "output.has_input=='no'",
# Input: Select a file ----
shiny::radioButtons(
inputId = "source",
label = "Upload file or export from REDCap?",
selected = "file",
inline = TRUE,
choices = list(
"File" = "file",
"REDCap" = "redcap"
)
),
shiny::conditionalPanel(
condition = "input.source=='file'",
datamods::import_file_ui("file_import",
title = "Choose a datafile to upload",
file_extensions = c(".csv", ".txt", ".xls", ".xlsx", ".rds", ".fst", ".sas7bdat", ".sav", ".ods", ".dta")
)
),
shiny::conditionalPanel(
condition = "input.source=='redcap'",
m_redcap_readUI("redcap_import"),
DT::DTOutput(outputId = "redcap_prev")
)
),
shiny::br(),
shiny::actionButton(inputId = "act_start",label = "Start")
),
##############################################################################
#########
######### Data analyses panel
#########
##############################################################################
"analyze" = bslib::nav_panel(
title = "Data analysis",
bslib::page_navbar(
title = "",
# bslib::layout_sidebar(
# fillable = TRUE,
sidebar = bslib::sidebar(
shiny::helpText(em("Please specify relevant settings for your data, and press 'Analyse'")),
shiny::uiOutput("outcome_var"),
shiny::uiOutput("strat_var"),
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.")
),
shiny::radioButtons(
inputId = "all",
label = "Specify covariables",
inline = TRUE, selected = 2,
choiceNames = c(
"Yes",
"No"
),
choiceValues = c(1, 2)
),
shiny::conditionalPanel(
condition = "input.all==1",
shiny::uiOutput("include_vars")
),
shiny::radioButtons(
inputId = "specify_factors",
label = "Specify categorical variables?",
selected = "no",
inline = TRUE,
choices = list(
"Yes" = "yes",
"No" = "no"
)
),
shiny::conditionalPanel(
condition = "input.specify_factors=='yes'",
shiny::uiOutput("factor_vars")
),
bslib::input_task_button(
id = "load",
label = "Analyse",
icon = shiny::icon("pencil", lib = "glyphicon"),
label_busy = "Working...",
icon_busy = fontawesome::fa_i("arrows-rotate",
class = "fa-spin",
"aria-hidden" = "true"
),
type = "primary",
auto_reset = TRUE
),
shiny::helpText("If you change the parameters, press 'Analyse' again to update the tables")
# )
),
bslib::nav_spacer(),
bslib::nav_panel(
title = "Data overview",
DT::DTOutput(outputId = "data_table")
),
bslib::nav_panel(
title = "Baseline characteristics",
gt::gt_output(outputId = "table1")
),
bslib::nav_panel(
title = "Regression table",
gt::gt_output(outputId = "table2")
),
bslib::nav_panel(
title = "Regression checks",
shiny::plotOutput(outputId = "check")
)
)
),
##############################################################################
#########
######### Documentation panel
#########
##############################################################################
"docs" = bslib::nav_panel(
title = "Intro",
shiny::markdown(readLines("www/intro.md")),
shiny::br()
)
)
# cards <- list(
# "overview"=bslib::card(
# title = "Data overview",
# # shiny::uiOutput("data.classes"),
# # shiny::uiOutput("data.input"),
# # shiny::p("Classes of uploaded data"),
# # gt::gt_output("data.classes"),
# shiny::p("Subset data"),
# DT::DTOutput(outputId = "data_table")
# ),
# "baseline"=bslib::card(
# title = "Baseline characteristics",
# gt::gt_output(outputId = "table1")
# ),
# "regression"= bslib::card(
# title = "Regression table",
# gt::gt_output(outputId = "table2")
# ),
# "checks" =bslib::card(
# title = "Regression checks",
# shiny::plotOutput(outputId = "check")
# )
# )
ui <- bslib::page(
title = "freesearcheR",
theme = bslib::bs_theme(
primary = "#1E4A8F",
secondary = "#FF6F61",
bootswatch = "minty",
base_font = bslib::font_google("Montserrat"),
code_font = bslib::font_google("Open Sans")
),
bslib::page_navbar(
id = "main_panel",
ui_elements$import,
ui_elements$analyze,
ui_elements$docs
)
)
# ui <- bslib::page(
# theme = bslib::bs_theme(
# bootswatch = "minty",
# base_font = font_google("Inter"),
# code_font = font_google("JetBrains Mono")
# ),
# title = "fresearcheR - free, web-based research analyses",
# bslib::page_navbar(
# title = "fresearcheR - free, web-based research analyses",
# header = h6("Welcome to the fresearcheR tool. This is an early alpha version to act as a proof-of-concept and in no way intended for wider public use."),
# sidebar = bslib::sidebar(
# width = 300,
# open = "open",
# shiny::h4("Upload your dataset"),
# shiny::conditionalPanel(
# condition = "output.has_input=='yes'",
# # Input: Select a file ----
# shiny::helpText("Analyses are performed on provided data")
# ),
# shiny::conditionalPanel(
# condition = "output.has_input=='no'",
# # Input: Select a file ----
# shiny::radioButtons(
# inputId = "source",
# label = "Upload file or export from REDCap?",
# selected = "file",
# inline = TRUE,
# choices = list(
# "File" = "file",
# "REDCap" = "redcap"
# )
# ),
# shiny::conditionalPanel(
# condition = "input.source=='file'",
# datamods::import_file_ui("file_import",
# file_extensions = c(".csv", ".txt", ".xls", ".xlsx", ".rds", ".fst", ".sas7bdat", ".sav",".ods",".dta"))
# )
# ,
# shiny::conditionalPanel(
# condition = "input.source=='redcap'",
# m_redcap_readUI("redcap_import")
# ),
# # Does not work??
# # shiny::actionButton(inputId = "test_data",
# # label = "Load test data", class = "btn-primary")
# ),
# shiny::conditionalPanel(
# condition = "output.uploaded=='yes'",
# shiny::h4("Parameter specifications"),
# shiny::radioButtons(
# inputId = "factorize",
# label = "Factorize variables with few levels?",
# selected = "yes",
# inline = TRUE,
# choices = list(
# "Yes" = "yes",
# "No" = "no"
# )
# ),
# shiny::radioButtons(
# inputId = "regression_auto",
# label = "Automatically choose function",
# inline = TRUE,
# choiceNames = c(
# "Yes",
# "No"
# ),
# choiceValues = c(1, 2)
# ),
# shiny::conditionalPanel(
# condition = "input.regression_auto==2",
# shiny::textInput(
# inputId = "regression_formula",
# label = "Formula string to render with 'glue::glue'",
# value = NULL
# ),
# shiny::textInput(
# inputId = "regression_fun",
# label = "Function to use for analysis (needs pasckage and name)",
# value = "stats::lm"
# ),
# shiny::textInput(
# inputId = "regression_args",
# label = "Arguments to pass to the function (provided as a string)",
# value = ""
# )
# ),
# shiny::helpText(em("Please specify relevant settings for your data, and press 'Analyse'")),
# shiny::uiOutput("outcome_var"),
# shiny::uiOutput("strat_var"),
# 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.")
# ),
# shiny::radioButtons(
# inputId = "all",
# label = "Specify covariables",
# inline = TRUE, selected = 2,
# choiceNames = c(
# "Yes",
# "No"
# ),
# choiceValues = c(1, 2)
# ),
# shiny::conditionalPanel(
# condition = "input.all==1",
# shiny::uiOutput("include_vars")
# ),
# shiny::radioButtons(
# inputId = "specify_factors",
# label = "Specify categorical variables?",
# selected = "no",
# inline = TRUE,
# choices = list(
# "Yes" = "yes",
# "No" = "no"
# )
# ),
# shiny::conditionalPanel(
# condition = "input.specify_factors=='yes'",
# shiny::uiOutput("factor_vars")
# ),
# bslib::input_task_button(
# id = "load",
# label = "Analyse",
# icon = shiny::icon("pencil", lib = "glyphicon"),
# label_busy = "Working...",
# icon_busy = fontawesome::fa_i("arrows-rotate",
# class = "fa-spin",
# "aria-hidden" = "true"
# ),
# type = "primary",
# auto_reset = TRUE
# ),
# shiny::helpText("If you change the parameters, press 'Analyse' again to update the tables"),
# # shiny::actionButton("load", "Analyse", class = "btn-primary"),
# #
# # # Horizontal line ----
# tags$hr(),
# shiny::conditionalPanel(
# condition = "input.load",
# h4("Download results"),
# shiny::helpText("Choose your favourite output file format for further work."),
# shiny::selectInput(
# inputId = "output_type",
# label = "Choose your desired output format",
# selected = NULL,
# choices = list(
# "Word" = "docx",
# "LibreOffice" = "odt"
# # ,
# # "PDF" = "pdf",
# # "All the above" = "all"
# )
# ),
#
# # Button
# downloadButton(
# outputId = "report",
# label = "Download",
# icon = shiny::icon("download")
# )
# )
# )
# ),
# bslib::nav_spacer(),
# panels[[1]],
# panels[[2]],
# panels[[3]],
# panels[[4]]
#
# # layout_columns(
# # cards[[1]]
# # ),
# # layout_columns(
# # cards[[2]], cards[[3]]
# # )
# )
# )

View file

@ -0,0 +1,3 @@
# Intro to webResearch/freesearcheR/VOICE
This is just placeholder text.

View file

@ -0,0 +1,68 @@
---
format:
html:
embed-resources: true
title: "webResearch analysis results"
date: today
author: webResearch Tool
toc: true
execute:
echo: false
params:
data.file: NA
---
```{r setup}
web_data <- readr::read_rds(file = params$data.file)
library(gtsummary)
library(gt)
library(easystats)
library(patchwork)
# library(webResearch)
```
## Introduction
Research should be free and open with easy access for all. The webResearch tool attempts to help lower the bar to participate in contributing to science.
## Methods
Analyses were conducted in R version `r paste(version["major"],version["minor"],sep=".")`.
## Results
Below is the baseline characteristics plotted.
```{r}
#| label: tbl-baseline
#| tbl-cap: Baseline characteristics of included data
web_data$table1
```
Here are the regression results.
```{r}
#| label: tbl-regression
#| tbl-cap: Regression analysis results
web_data$table2
```
## Discussion
Good luck on your further work!
## Sensitivity
Here are the results from testing the regression model:
```{r}
#| label: tbl-checks
#| fig-cap: Regression analysis checks
#| fig-height: 8
#| fig-width: 6
#| fig-dpi: 600
plot(web_data$check)
```

View file

@ -1,103 +0,0 @@
m_redcap_readUI <- function(id) {
ns <- NS(id)
tagList(
shiny::textInput(
inputId = "uri",
label = "URI",
value = "https://redcap.your.institution/api/"
),
shiny::textInput(
inputId = "api",
label = "API token",
value = ""
),
shiny::tableOutput(outputId = ns("table")),
shiny::uiOutput(outputId = ns("fields")),
shiny::uiOutput(outputId = ns("instruments")),
shiny::uiOutput(outputId = ns("arms")),
shiny::actionButton(inputId = ns("submit"), "Submit")
)
}
m_redcap_readServer <- function(id) {
ns <- NS(id)
moduleServer(
id,
function(input, output, session) {
ns <- NS(id)
instr <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_instruments(redcap_uri = input$uri, token = input$api)
})
output$instruments <- shiny::renderUI({
shiny::selectizeInput(
inputId = ns("instruments"),
selected = NULL,
label = "Instruments to include",
choices = instr()[["data"]][[1]],
multiple = TRUE
)
})
dd <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_metadata_read(redcap_uri = input$uri, token = input$api)
})
output$fields <- shiny::renderUI({
shiny::selectizeInput(
inputId = ns("fields"),
selected = NULL,
label = "Fields/variables to include",
choices = dd()[["data"]][[1]],
multiple = TRUE
)
})
arms <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_event_read(redcap_uri = input$uri, token = input$api)
})
output$arms <- shiny::renderUI({
shiny::selectizeInput(
inputId = ns("arms"),
selected = NULL,
label = "Arms/events to include",
choices = arms()[["data"]][[3]],
multiple = TRUE
)
})
output$table <- shiny::renderTable({
dd()[["data"]]
})
shiny::eventReactive(input$submit, {
shiny::req(input$api)
data <- REDCapCAST::read_redcap_tables(
uri=input$uri,
token = input$api,
fields = unique(c(dd()[["data"]][[1]][1],input$fields)),
forms = input$instruments,
events = input$arms,
raw_or_label = "both"
)
info <- REDCapR::redcap_project_info_read(redcap_uri = input$uri, token = input$api)
filename <- info$data$project_title
data |>
REDCapCAST::redcap_wider() |>
REDCapCAST::suffix2label() |>
REDCapCAST::as_factor() |>
dplyr::select(-dplyr::ends_with("_complete")) |>
dplyr::select(-dplyr::any_of(dd()[["data"]][[1]][1]))
})
}
)
}

View file

@ -1,91 +0,0 @@
library(REDCapCAST)
library(REDCapR)
library(shiny)
# ns <- shiny::NS(id)
server <- function(input, output, session) {
# ns <- NS(id)
instr <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_instruments(redcap_uri = input$uri, token = input$api)
})
output$instruments <- shiny::renderUI({
shiny::selectizeInput(
inputId = "instruments",
selected = NULL,
label = "Instruments to include",
choices = instr()[["data"]][[1]],
multiple = TRUE
)
})
dd <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_metadata_read(redcap_uri = input$uri, token = input$api)
})
output$fields <- shiny::renderUI({
shiny::selectizeInput(
inputId = "fields",
selected = NULL,
label = "Fields/variables to include",
choices = dd()[["data"]][[1]],
multiple = TRUE
)
})
arms <- shiny::reactive({
shiny::req(input$api)
shiny::req(input$uri)
REDCapR::redcap_event_read(redcap_uri = input$uri, token = input$api)
})
output$arms <- shiny::renderUI({
shiny::selectizeInput(
inputId = "arms",
selected = NULL,
label = "Arms/events to include",
choices = arms()[["data"]][[3]],
multiple = TRUE
)
})
output$table <- shiny::renderTable({
dd()[["data"]]
})
data <- shiny::eventReactive(input$submit, {
browser()
shiny::req(input$api)
data <- REDCapCAST::read_redcap_tables(
uri = input$uri,
token = input$api,
fields = unique(c(dd()[["data"]][[1]][1], input$fields)),
forms = input$instruments,
events = input$arms,
raw_or_label = "both"
)
info <- REDCapR::redcap_project_info_read(redcap_uri = input$uri, token = input$api)
filename <- info$data$project_title
data |>
REDCapCAST::redcap_wider() |>
REDCapCAST::suffix2label() |>
REDCapCAST::as_factor() |>
dplyr::select(-dplyr::ends_with("_complete")) |>
dplyr::select(-dplyr::any_of(dd()[["data"]][[1]][1]))
})
output$export <- DT::renderDT({
data()
})
}

View file

@ -1,23 +0,0 @@
library(REDCapCAST)
library(REDCapR)
library(shiny)
ui <- shiny::fluidPage(
# shiny::helpText("Submit URL and API token to browse download options"),
shiny::textInput(
inputId = "uri",
label = "URI",
value = "https://redcap.your.institution/api/"
),
shiny::textInput(
inputId = "api",
label = "API token",
value = ""
),
shiny::tableOutput("table"),
shiny::uiOutput("fields"),
shiny::uiOutput("instruments"),
shiny::uiOutput("arms"),
shiny::actionButton("submit", "Submit"),
DT::DTOutput("export")
)

View file

@ -31,43 +31,21 @@ if (file.exists(here::here("functions.R"))) {
source(here::here("functions.R"))
}
data_upload <- teal_data_module(
ui <- function(id) {
ns <- NS(id)
shiny::fluidPage(
shiny::radioButtons(
inputId = "import",
label = "Specify categorical variables?",
selected = "no",
inline = TRUE,
choices = list(
"Upload file" = "file",
"Export from REDCap" = "redcap"
)
),
shiny::conditionalPanel(
condition = "input.import=='file'",
m_datafileUI(id)
),
shiny::conditionalPanel(
condition = "input.import=='redcap'",
m_redcap_readUI(id)
)
)
},
server = function(id) {
ns <- NS(id)
moduleServer(id, function(input, output, session) {
shiny::reactive({
if (input$import == "file") {
m_datafileServer(id, output.format = "teal")
} else {
m_redcap_readServer(id, output.format = "teal")
}
})
})
}
)
## This setup works for a single possible source
## The UI will work, even with server dependent selection and REDCap exports,
## but when submitting, it only works for the module mentioned first in the server function
## Also most data formatting is lost when passing to a teal_data_object. Bummer!
##
## FRUSTRATION!!
##
## As I read this, two different apps has to be created as things are now: one for upload, one for REDCap.
## https://insightsengineering.github.io/teal/latest-tag/articles/data-as-shiny-module.html#warning
##
##
##
## Ad option to widen data or keep long (new function, would allow easy(ish) MMRM analyses)
tm_variable_browser_module <- tm_variable_browser(
label = "Variable browser",
@ -76,7 +54,6 @@ tm_variable_browser_module <- tm_variable_browser(
)
)
filters <- teal::teal_slices()
app_source <- "https://github.com/agdamsbo/webresearch"
@ -84,7 +61,7 @@ gh_issues_page <- "https://github.com/agdamsbo/webresearch/issues"
header <- tags$span(
style = "display: flex; align-items: center; justify-content: space-between; margin: 10px 0 10px 0;",
tags$span("webResearch (teal)", style = "font-size: 30px;") # ,
tags$span("REDCap data evaluation", style = "font-size: 30px;") # ,
# tags$span(
# style = "display: flex; align-items: center;",
# tags$img(src = nest_logo, alt = "NEST logo", height = "45px", style = "margin-right:10px;"),
@ -93,19 +70,40 @@ header <- tags$span(
)
footer <- tags$p(
"This teal app was developed by AGDamsbo using the {teal} framework for Shiny apps:",
"This is a simple, app for REDCap-based data browsing and evaluation. Data is only stored temporarily and deleted when the browser is refreshed or closed. The app was developed by AGDamsbo using the {teal} framework for building Shiny apps:",
tags$a(href = app_source, target = "_blank", "Source Code"), ", ",
tags$a(href = gh_issues_page, target = "_blank", "Report Issues")
)
app <- init(
data = data_upload,
# teal_init <- function(data = tdm_redcap_read,
# filter = filters,
# modules = teal::modules(
# teal.modules.general::tm_data_table("Data Table"),
# tm_variable_browser_module
# ),
# title = teal::build_app_title("REDCap browser (teal)"),
# header = header,
# footer = footer, ...) {
# teal::init(data,
# filter,
# modules,
# title,
# header,
# footer,
# ...
# )
# }
#
# redcap_browser_app <- teal_init(data = tdm_data_upload)
app <- teal::init(
data = tdm_redcap_read,
filter = filters,
modules = modules(
tm_data_table("Data Table"),
tm_variable_browser_module
),
title = build_app_title("webResearch (teal)"),
title = build_app_title("REDCap data evaluation"),
header = header,
footer = footer
)

View file

@ -0,0 +1,24 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/modules.R
\name{m_redcap_readUI}
\alias{m_redcap_readUI}
\alias{m_redcap_readServer}
\title{Shiny module to browser and export REDCap data}
\usage{
m_redcap_readUI(id)
m_redcap_readServer(id, output.format = "df")
}
\arguments{
\item{id}{Namespace id}
\item{output.format}{data.frame ("df") or teal data object ("teal")}
}
\value{
shiny ui element
shiny server module
}
\description{
Shiny module to browser and export REDCap data
}