diff --git a/DESCRIPTION b/DESCRIPTION
index 3e2cc6ab..5a9d85b9 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -141,5 +141,6 @@ Collate:
'update-variables-ext.R'
'utils-labels.R'
'validation.R'
+ 'version_check.R'
'visual_summary.R'
'wide2long.R'
diff --git a/NEWS.md b/NEWS.md
index f08ae0b2..3cfed098 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,8 @@
# FreesearchR 26.3.4
+*NEW* Added app version check against latest release on GitHub. Only runs if internet connection present. No other polling.
+*NEW* Added a "Missing" level to the sankey plot function and adjusted the label font size.
# FreesearchR 26.3.3
diff --git a/R/hosted_version.R b/R/hosted_version.R
index f0c656d1..17135440 100644
--- a/R/hosted_version.R
+++ b/R/hosted_version.R
@@ -1 +1 @@
-hosted_version <- function()'v26.3.4-260312'
+hosted_version <- function()'v26.3.4-260323'
diff --git a/R/launch_FreesearchR.R b/R/launch_FreesearchR.R
index a789f185..92d09a51 100644
--- a/R/launch_FreesearchR.R
+++ b/R/launch_FreesearchR.R
@@ -8,6 +8,8 @@
#' @param data_limit_default default data set observations limit
#' @param data_limit_upper data set observations upper limit
#' @param data_limit_lower data set observations lower limit
+#' @param check_app_version always attempt to check app version against latest
+#' release on GitHub. Default is FALSE
#' @param ... passed on to `shiny::runApp()`
#'
#' @returns shiny app
@@ -22,12 +24,14 @@ launch_FreesearchR <- function(include_globalenv = TRUE,
data_limit_default = 1000,
data_limit_upper = 100000,
data_limit_lower = 1,
+ check_app_version = FALSE,
...) {
Sys.setenv(
INCLUDE_GLOBALENV = include_globalenv,
DATA_LIMIT_DEFAULT = data_limit_default,
DATA_LIMIT_UPPER = data_limit_upper,
- DATA_LIMIT_LOWER = data_limit_lower
+ DATA_LIMIT_LOWER = data_limit_lower,
+ CHECK_APP_VERSION = check_app_version
)
appDir <- system.file("apps", "FreesearchR", package = "FreesearchR")
diff --git a/R/plot_sankey.R b/R/plot_sankey.R
index b3aa1b55..baa864dd 100644
--- a/R/plot_sankey.R
+++ b/R/plot_sankey.R
@@ -33,15 +33,17 @@ sankey_ready <- function(data, pri, sec, numbers = "count", ...) {
dplyr::ungroup()
if (numbers == "count") {
- out <- out |> dplyr::mutate(
- lx = factor(paste0(!!dplyr::sym(pri), "\n(n=", gx.sum, ")")),
- ly = factor(paste0(!!dplyr::sym(sec), "\n(n=", gy.sum, ")"))
- )
+ out <- out |> dplyr::mutate(lx = factor(paste0(
+ !!dplyr::sym(pri), "\n(n=", gx.sum, ")"
+ )), ly = factor(paste0(
+ !!dplyr::sym(sec), "\n(n=", gy.sum, ")"
+ )))
} else if (numbers == "percentage") {
- out <- out |> dplyr::mutate(
- 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), "%)"))
- )
+ out <- out |> dplyr::mutate(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), "%)"
+ )))
}
if (is.factor(data[[pri]])) {
@@ -83,20 +85,38 @@ str_remove_last <- function(data, pattern = "\n") {
#' mtcars |>
#' default_parsing() |>
#' plot_sankey("cyl", "gear", "vs", color.group = "pri")
-plot_sankey <- function(data, pri, sec, ter = NULL, color.group = "pri", colors = NULL,missing.level="Missing") {
+#'
+#' # stRoke::trial |> plot_sankey("mrs_1", "mrs_6")
+plot_sankey <- function(data,
+ pri,
+ sec,
+ ter = NULL,
+ color.group = "pri",
+ colors = NULL,
+ missing.level = "Missing") {
if (!is.null(ter)) {
ds <- split(data, data[ter])
} else {
ds <- list(data)
}
- out <- lapply(ds, \(.ds){
- plot_sankey_single(.ds, pri = pri, sec = sec, color.group = color.group, colors = colors,missing.level=missing.level)
+
+ out <- lapply(ds, \(.ds) {
+ plot_sankey_single(
+ .ds,
+ pri = pri,
+ sec = sec,
+ color.group = color.group,
+ colors = colors,
+ missing.level = missing.level
+ )
})
patchwork::wrap_plots(out)
}
+
+
#' Beautiful sankey plot
#'
#' @param color.group set group to colour by. "x" or "y".
@@ -123,19 +143,31 @@ plot_sankey <- function(data, pri, sec, ter = NULL, color.group = "pri", colors
#' stRoke::trial |>
#' default_parsing() |>
#' plot_sankey_single("diabetes", "hypertension")
-plot_sankey_single <- function(data, pri, sec, color.group = c("pri", "sec"), colors = NULL,missing.level="Missing", ...) {
+plot_sankey_single <- function(data,
+ pri,
+ sec,
+ color.group = c("pri", "sec"),
+ colors = NULL,
+ missing.level = "Missing",
+ ...) {
color.group <- match.arg(color.group)
+
+ # browser()
+ # if (is.na(ds[c(pri,sec)]))
+
# browser()
data_orig <- data
+
data[c(pri, sec)] <- data[c(pri, sec)] |>
dplyr::mutate(
- # dplyr::across(dplyr::where(is.logical), as.factor),
- dplyr::across(dplyr::where(is.factor), forcats::fct_drop)#,
- # dplyr::across(dplyr::where(is.factor), \(.x){forcats::fct_na_value_to_level(.x,missing.level)})
+ dplyr::across(dplyr::where(is.logical), as.factor),
+ dplyr::across(dplyr::where(is.factor), forcats::fct_drop),
+ dplyr::across(dplyr::where(is.factor), \(.x) {
+ forcats::fct_na_value_to_level(.x, missing.level)
+ })
)
-
data <- data |> sankey_ready(pri = pri, sec = sec, ...)
na.color <- "#2986cc"
@@ -148,21 +180,26 @@ plot_sankey_single <- function(data, pri, sec, color.group = c("pri", "sec"), co
main.colors <- main.colors[match(levels(data[[sec]]), levels(data_orig[[sec]]))]
secondary.colors <- rep(na.color, length(levels(data[[pri]])))
- label.colors <- Reduce(c, lapply(list(secondary.colors, rev(main.colors)), contrast_text))
+ label.colors <- Reduce(c, lapply(list(
+ secondary.colors, rev(main.colors)
+ ), contrast_text))
} else {
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]]))]
secondary.colors <- rep(na.color, length(levels(data[[sec]])))
- label.colors <- Reduce(c, lapply(list(rev(main.colors), secondary.colors), contrast_text))
+ label.colors <- Reduce(c, lapply(list(
+ rev(main.colors), secondary.colors
+ ), contrast_text))
}
colors <- c(na.color, main.colors, secondary.colors)
+ colors[is.na(colors)] <- "grey80"
} else {
label.colors <- contrast_text(colors)
}
- group_labels <- c(get_label(data, pri), get_label(data, sec)) |>
+ group_labels <- c(get_label(data_orig, pri), get_label(data_orig, sec)) |>
sapply(line_break) |>
unname()
@@ -181,9 +218,8 @@ plot_sankey_single <- function(data, pri, sec, color.group = c("pri", "sec"), co
knot.pos = 0.4,
curve_type = "sigmoid"
) + ggalluvial::geom_stratum(ggplot2::aes(fill = !!dplyr::sym(sec)),
- size = 2,
- width = 1 / 3.4
- )
+ size = 2,
+ width = 1 / 3.4)
} else {
p <- p +
ggalluvial::geom_alluvium(
@@ -196,9 +232,8 @@ plot_sankey_single <- function(data, pri, sec, color.group = c("pri", "sec"), co
knot.pos = 0.4,
curve_type = "sigmoid"
) + ggalluvial::geom_stratum(ggplot2::aes(fill = !!dplyr::sym(pri)),
- size = 2,
- width = 1 / 3.4
- )
+ size = 2,
+ width = 1 / 3.4)
}
## Will fail to use stat="stratum" if library is not loaded.
@@ -208,13 +243,10 @@ plot_sankey_single <- function(data, pri, sec, color.group = c("pri", "sec"), co
stat = "stratum",
ggplot2::aes(label = after_stat(stratum)),
colour = label.colors,
- size = 8,
+ size = 6,
lineheight = 1
) +
- ggplot2::scale_x_continuous(
- breaks = 1:2,
- labels = group_labels
- ) +
+ ggplot2::scale_x_continuous(breaks = 1:2, labels = group_labels) +
ggplot2::scale_fill_manual(values = colors[-1], na.value = colors[1]) +
# ggplot2::scale_color_manual(values = main.colors) +
ggplot2::theme_void() +
diff --git a/R/sysdata.rda b/R/sysdata.rda
index f8b0df59..efea72cf 100644
Binary files a/R/sysdata.rda and b/R/sysdata.rda differ
diff --git a/README.md b/README.md
index 344b8649..b6444c6d 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,9 @@
-# FreesearchR
+# FreesearchR
-[](https://lifecycle.r-lib.org/articles/stages.html#experimental)
-[](https://doi.org/10.5281/zenodo.14527429)
-[](https://github.com/agdamsbo/FreesearchR/actions/workflows/rhub.yaml)
-[](https://agdamsbo.shinyapps.io/FreesearchR/)
+
+[](https://lifecycle.r-lib.org/articles/stages.html#experimental) [](https://doi.org/10.5281/zenodo.14527429) [](https://github.com/agdamsbo/FreesearchR/actions/workflows/rhub.yaml) [](https://agdamsbo.shinyapps.io/FreesearchR/)
+
The [***FreesearchR***](https://app.freesearchr.org) is a simple, clinical health data exploration and analysis tool to democratise clinical research by assisting any researcher to easily evaluate and analyse data and export publication ready results.
@@ -19,11 +18,11 @@ All feedback is welcome and can be shared as a GitHub issue. Any suggestions on
This app has the following simple goals:
-1. help the health clinician getting an overview of data in quality improvement projects and clinical research
+1. help the health clinician getting an overview of data in quality improvement projects and clinical research
-1. help learners get a good start analysing data and coding in *R*
+2. help learners get a good start analysing data and coding in *R*
-1. ease quick data overview and basic visualisations for any clinical researcher
+3. ease quick data overview and basic visualisations for any clinical researcher
Here’s a polished and restructured version of your README section for clarity, conciseness, and user-friendliness:
@@ -35,32 +34,32 @@ The **FreesearchR** app can be run locally on your machine, ensuring no data is
The app can be configured either by passing a named list to `run_app()` or by setting environment variables in a **Docker Compose** file. The following variables control data access and display behavior. If no values are provided, the app will use the defaults listed below.
-
**Configuration Variables**
-| Variable | Description | Default |
-|-------------------------|-----------------------------------------------------------------------------|-----------|
-| `INCLUDE_GLOBALENV` | Load datasets already present in the global R environment into the app | `FALSE` |
-| `DATA_LIMIT_DEFAULT` | Default number of observations for previewing or working with a dataset | `10,000` |
-| `DATA_LIMIT_UPPER` | Maximum number of observations a user can set for the upper limit. If set to 0, no uppper limit is applied. | `100,000` |
-| `DATA_LIMIT_LOWER` | Minimum number of observations a user can set for the lower limit | `1` |
+| Variable | Description | Default |
+|--------------|--------------------------------------------|--------------|
+| `INCLUDE_GLOBALENV` | Load datasets already present in the global R environment into the app | `FALSE` |
+| `DATA_LIMIT_DEFAULT` | Default number of observations for previewing or working with a dataset | `10,000` |
+| `DATA_LIMIT_UPPER` | Maximum number of observations a user can set for the upper limit. If set to 0, no uppper limit is applied. | `100,000` |
+| `DATA_LIMIT_LOWER` | Minimum number of observations a user can set for the lower limit | `1` |
+| `CHECK_APP_VERSION` | Always print version check results. Checks app version against latest release on GitHub. | `FALSE` |
### Run from R (or RStudio)
If you're working with data in R, **FreesearchR** is a quick and easy tool for exploratory analysis.
-1. **Requirement:** Ensure you have [R](https://www.r-project.org/) installed, and optionally an editor like [RStudio](https://posit.co/download/rstudio-desktop/).
+1. **Requirement:** Ensure you have [R](https://www.r-project.org/) installed, and optionally an editor like [RStudio](https://posit.co/download/rstudio-desktop/).
-2. Open the **R console** and run the following code to install the `{FreesearchR}` package and launch the app:
+2. Open the **R console** and run the following code to install the `{FreesearchR}` package and launch the app:
- ```r
- if (!require("devtools")) install.packages("devtools")
- devtools::install_github("agdamsbo/FreesearchR")
- library(FreesearchR)
- # Load sample data (e.g., mtcars) to make it available in the app
- data(mtcars)
- launch_FreesearchR(INCLUDE_GLOBALENV=TRUE)
- ```
+ ``` r
+ if (!require("devtools")) install.packages("devtools")
+ devtools::install_github("agdamsbo/FreesearchR")
+ library(FreesearchR)
+ # Load sample data (e.g., mtcars) to make it available in the app
+ data(mtcars)
+ launch_FreesearchR(INCLUDE_GLOBALENV=TRUE,CHECK_APP_VERSION=TRUE)
+ ```
All the variables specified above can also be passed to the app on launch from R. Set DATA_LIMIT_UPPER=0 to remove upper data limit. This limit is set to protect the online app version from choking and crashing on large data sets.
@@ -70,7 +69,7 @@ For advanced users, you can deploy **FreesearchR** using Docker. A data folder c
To mount a local data folder, add a `volumes` entry to your `docker-compose.yml` file:
-```yaml
+``` yaml
services:
shiny:
image: ghcr.io/agdamsbo/freesearchr:latest
@@ -86,9 +85,9 @@ services:
restart: on-failure
```
-- The `:ro` flag mounts the folder as **read-only**, preventing the app from modifying your original data files.
+- The `:ro` flag mounts the folder as **read-only**, preventing the app from modifying your original data files.
-- If no volume is mounted, the app will start without any preloaded datasets.
+- If no volume is mounted, the app will start without any preloaded datasets.
## Code of Conduct
diff --git a/SESSION.md b/SESSION.md
index 1bd978b0..44778018 100644
--- a/SESSION.md
+++ b/SESSION.md
@@ -11,11 +11,11 @@
|collate |en_US.UTF-8 |
|ctype |en_US.UTF-8 |
|tz |Europe/Copenhagen |
-|date |2026-03-12 |
+|date |2026-03-23 |
|rstudio |2026.01.1+403 Apple Blossom (desktop) |
|pandoc |3.6.4 @ /opt/homebrew/bin/ (via rmarkdown) |
|quarto |1.7.30 @ /usr/local/bin/quarto |
-|FreesearchR |26.3.4.260312 |
+|FreesearchR |26.3.4.260323 |
--------------------------------------------------------------------------------
@@ -33,6 +33,7 @@
|bit64 |4.6.0-1 |2025-01-16 |CRAN (R 4.5.0) |
|bitops |1.0-9 |2024-10-03 |CRAN (R 4.5.0) |
|boot |1.3-32 |2025-08-29 |CRAN (R 4.5.0) |
+|brio |1.1.5 |2024-04-24 |CRAN (R 4.5.0) |
|broom |1.0.12 |2026-01-27 |CRAN (R 4.5.2) |
|broom.helpers |1.22.0 |2025-09-17 |CRAN (R 4.5.0) |
|bsicons |0.1.2 |2023-11-04 |CRAN (R 4.5.0) |
@@ -43,6 +44,7 @@
|cardx |0.3.2 |2026-02-05 |CRAN (R 4.5.2) |
|caTools |1.18.3 |2024-09-04 |CRAN (R 4.5.0) |
|cellranger |1.1.0 |2016-07-27 |CRAN (R 4.5.0) |
+|cffr |1.2.1 |2026-01-12 |CRAN (R 4.5.2) |
|checkmate |2.3.4 |2026-02-03 |CRAN (R 4.5.2) |
|class |7.3-23 |2025-01-01 |CRAN (R 4.5.0) |
|classInt |0.4-11 |2025-01-08 |CRAN (R 4.5.0) |
@@ -52,6 +54,7 @@
|colorspace |2.1-2 |2025-09-22 |CRAN (R 4.5.0) |
|commonmark |2.0.0 |2025-07-07 |CRAN (R 4.5.0) |
|crayon |1.5.3 |2024-06-20 |CRAN (R 4.5.0) |
+|curl |7.0.0 |2025-08-19 |CRAN (R 4.5.0) |
|data.table |1.18.2.1 |2026-01-27 |CRAN (R 4.5.2) |
|datamods |1.5.3 |2024-10-02 |CRAN (R 4.5.0) |
|datawizard |1.3.0 |2025-10-11 |CRAN (R 4.5.0) |
@@ -66,7 +69,7 @@
|e1071 |1.7-17 |2025-12-18 |CRAN (R 4.5.2) |
|easystats |0.7.5 |2025-07-11 |CRAN (R 4.5.0) |
|ellipsis |0.3.2 |2021-04-29 |CRAN (R 4.5.0) |
-|emmeans |2.0.2 |2026-03-05 |CRAN (R 4.5.2) |
+|emmeans |2.0.1 |2025-12-16 |CRAN (R 4.5.2) |
|esquisse |2.1.0 |2025-02-21 |CRAN (R 4.5.0) |
|estimability |1.5.1 |2024-05-12 |CRAN (R 4.5.0) |
|eulerr |7.0.4 |2025-09-24 |CRAN (R 4.5.0) |
@@ -74,6 +77,7 @@
|farver |2.1.2 |2024-05-13 |CRAN (R 4.5.0) |
|fastmap |1.2.0 |2024-05-15 |CRAN (R 4.5.0) |
|flextable |0.9.11 |2026-02-13 |CRAN (R 4.5.2) |
+|fontawesome |0.5.3 |2024-11-16 |CRAN (R 4.5.0) |
|fontBitstreamVera |0.1.1 |2017-02-01 |CRAN (R 4.5.0) |
|fontLiberation |0.1.0 |2016-10-15 |CRAN (R 4.5.0) |
|fontquiver |0.2.1 |2017-02-01 |CRAN (R 4.5.0) |
@@ -109,9 +113,11 @@
|iterators |1.0.14 |2022-02-05 |CRAN (R 4.5.0) |
|jquerylib |0.1.4 |2021-04-26 |CRAN (R 4.5.0) |
|jsonlite |2.0.0 |2025-03-27 |CRAN (R 4.5.0) |
+|jsonvalidate |1.5.0 |2025-02-07 |CRAN (R 4.5.0) |
|KernSmooth |2.23-26 |2025-01-01 |CRAN (R 4.5.0) |
|keyring |1.4.1 |2025-06-15 |CRAN (R 4.5.0) |
|knitr |1.51 |2025-12-20 |CRAN (R 4.5.2) |
+|labeling |0.4.3 |2023-08-29 |CRAN (R 4.5.0) |
|later |1.4.8 |2026-03-05 |CRAN (R 4.5.2) |
|lattice |0.22-7 |2025-04-02 |CRAN (R 4.5.2) |
|lifecycle |1.0.5 |2026-01-08 |CRAN (R 4.5.2) |
@@ -123,7 +129,7 @@
|memoise |2.0.1 |2021-11-26 |CRAN (R 4.5.0) |
|mime |0.13 |2025-03-17 |CRAN (R 4.5.0) |
|minqa |1.2.8 |2024-08-17 |CRAN (R 4.5.0) |
-|mvtnorm |1.3-5 |2026-03-11 |CRAN (R 4.5.2) |
+|mvtnorm |1.3-2 |2024-11-04 |CRAN (R 4.5.2) |
|NHANES |2.1.0 |2015-07-02 |CRAN (R 4.5.0) |
|nlme |3.1-168 |2025-03-31 |CRAN (R 4.5.0) |
|nloptr |2.2.1 |2025-03-17 |CRAN (R 4.5.0) |
@@ -156,6 +162,7 @@
|R6 |2.6.1 |2025-02-15 |CRAN (R 4.5.0) |
|ragg |1.5.1 |2026-03-06 |CRAN (R 4.5.2) |
|rankinPlot |1.1.0 |2023-01-30 |CRAN (R 4.5.0) |
+|rappdirs |0.3.4 |2026-01-17 |CRAN (R 4.5.2) |
|rbibutils |2.4.1 |2026-01-21 |CRAN (R 4.5.2) |
|RColorBrewer |1.1-3 |2022-04-03 |CRAN (R 4.5.0) |
|Rcpp |1.1.1 |2026-01-10 |CRAN (R 4.5.2) |
@@ -197,6 +204,7 @@
|stringr |1.6.0 |2025-11-04 |CRAN (R 4.5.0) |
|stRoke |25.9.2 |2025-09-30 |CRAN (R 4.5.0) |
|systemfonts |1.3.2 |2026-03-05 |CRAN (R 4.5.2) |
+|testthat |3.3.2 |2026-01-11 |CRAN (R 4.5.2) |
|textshaping |1.0.5 |2026-03-06 |CRAN (R 4.5.2) |
|thematic |0.1.8 |2025-09-29 |CRAN (R 4.5.0) |
|tibble |3.3.1 |2026-01-11 |CRAN (R 4.5.2) |
@@ -208,7 +216,9 @@
|twosamples |2.0.1 |2023-06-23 |CRAN (R 4.5.0) |
|tzdb |0.5.0 |2025-03-15 |CRAN (R 4.5.0) |
|usethis |3.2.1 |2025-09-06 |CRAN (R 4.5.0) |
+|utf8 |1.2.6 |2025-06-08 |CRAN (R 4.5.0) |
|uuid |1.2-2 |2026-01-23 |CRAN (R 4.5.2) |
+|V8 |8.0.1 |2025-10-10 |CRAN (R 4.5.0) |
|vctrs |0.7.1 |2026-01-23 |CRAN (R 4.5.2) |
|viridis |0.6.5 |2024-01-29 |CRAN (R 4.5.0) |
|viridisLite |0.4.3 |2026-02-04 |CRAN (R 4.5.2) |
diff --git a/man/data-plots.Rd b/man/data-plots.Rd
index e5f94f58..cd9efdfd 100644
--- a/man/data-plots.Rd
+++ b/man/data-plots.Rd
@@ -170,6 +170,8 @@ mtcars |>
mtcars |>
default_parsing() |>
plot_sankey("cyl", "gear", "vs", color.group = "pri")
+
+ # stRoke::trial |> plot_sankey("mrs_1", "mrs_6")
mtcars |> plot_scatter(pri = "mpg", sec = "wt")
mtcars |> plot_violin(pri = "mpg", sec = "cyl", ter = "gear")
}