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
commit 00eb49c225
No known key found for this signature in database
16 changed files with 1186 additions and 383 deletions

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)
```