From 3288de7c48959eb891ae5732d010e7806faf2256 Mon Sep 17 00:00:00 2001 From: jcarmezim Date: Wed, 26 Nov 2025 12:27:15 +0100 Subject: [PATCH 1/4] First commit - development --- R/rd_query.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R/rd_query.R b/R/rd_query.R index 121e435..f3baedd 100644 --- a/R/rd_query.R +++ b/R/rd_query.R @@ -93,6 +93,9 @@ rd_query <- function(project = NULL, variables = NA, expression = NA, negate = F data <- as.data.frame(data) dic <- as.data.frame(dic) + # Ensure variables is a character string + variables <- as.character(variables) + # Initialize the query structure queries <- as.data.frame(matrix(ncol = 10, nrow = 0)) colnames(queries) <- c("Identifier", "DAG", "Event", "Instrument", "Field", "Repetition", "Description", "Query", "Code", "Link") From d0c005d9c113ca8d88262e25cef6a2da0d00da4a Mon Sep 17 00:00:00 2001 From: jcarmezim Date: Wed, 26 Nov 2025 12:32:50 +0100 Subject: [PATCH 2/4] Update Readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d770adf..15ae517 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ install.packages("remotes") # Run this line if the 'remotes' package isn't insta remotes::install_github("bruigtp/REDCapDM") ``` - ### Getting Started To learn more about the package’s functionality, visit the [**REDCapDM website**](https://bruigtp.github.io/REDCapDM/articles/REDCapDM.html). The site includes detailed descriptions of the package's functions and access to vignettes that demonstrate how to use REDCapDM effectively in your projects. From c67fa9205b500dde28c81e8de28f998415aa66ce Mon Sep 17 00:00:00 2001 From: jcarmezim Date: Sat, 24 Jan 2026 16:22:16 +0100 Subject: [PATCH 3/4] Issues fixed in redcap_data and rd_checkbox --- R/rd_checkbox.R | 29 ++++++++++++++++++++++++----- R/redcap_data.R | 4 ++-- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/R/rd_checkbox.R b/R/rd_checkbox.R index 3c8a32a..d6be172 100644 --- a/R/rd_checkbox.R +++ b/R/rd_checkbox.R @@ -11,7 +11,7 @@ #' @param event_form Only applicable for longitudinal projects (presence of events). Event-to-form mapping for longitudinal projects. #' @param checkbox_labels Character vector of length 2 for labels of unchecked/checked values. Default: `c("No", "Yes")`. #' @param checkbox_names Logical. If `TRUE` (default), checkbox columns are renamed using choice labels. -#' @param na_logic Controls how missing values are set based on branching logic. Must be one of `"none"` (do nothing), `"missing"` (set to `NA` only when the logic evaluation is `NA`), or `"eval"` (set to `NA` when the logic evaluates to `FALSE`). Defaults to `"none"`. +#' @param na_logic Controls how missing values are set based on branching logic. Must be one of `"none"` (do nothing), `"missing"` (set to `NA` only when the logic evaluation is `NA`), or `"eval"` (set to `NA` when the logic evaluates to `FALSE`). Defaults to `"missing"`. #' #' @return A list with: #' \describe{ @@ -49,7 +49,7 @@ #' @export #' @importFrom stats setNames na.omit -rd_checkbox <- function(project = NULL, data = NULL, dic = NULL, event_form = NULL, checkbox_labels = c("No", "Yes"), checkbox_names = TRUE, na_logic = "none") { +rd_checkbox <- function(project = NULL, data = NULL, dic = NULL, event_form = NULL, checkbox_labels = c("No", "Yes"), checkbox_names = TRUE, na_logic = "missing") { results <- NULL rlogic_eval <- NULL @@ -121,7 +121,7 @@ rd_checkbox <- function(project = NULL, data = NULL, dic = NULL, event_form = NU } } - # Update results with the this transformation + # Update results with the transformation reason <- if (na_logic == "eval") "when the logic isn't satisfied or it's missing" else if (na_logic == "missing") @@ -162,6 +162,12 @@ rd_checkbox <- function(project = NULL, data = NULL, dic = NULL, event_form = NU warning("The project contains repeated instruments, and this function cannot accurately evaluate the branching logic of checkboxes in such cases.", call. = FALSE) } + # Handle the fact that a rd_factor was performed before this function + if (length(var_check_factors) == 0 & all(!grepl("data\\$", dic$branching_logic_show_field_only_if))) { + warning( + "The checkboxes are already in factor form. To properly evaluate checkbox branching logic, please run `rd_dictionary()` first.", call. = FALSE + ) + } caption <- "Checkbox variables advisable to be reviewed" review <- NULL @@ -385,10 +391,23 @@ rd_checkbox <- function(project = NULL, data = NULL, dic = NULL, event_form = NU branching_logic_show_field_only_if = stringr::str_replace_all(.data$branching_logic_show_field_only_if, replace2) ) - # Returning checkboxes to numeric version + # Returning checkboxes to numeric version & applying the same missing values to their factor version if (length(var_check_factors) > 0) { + + correspondence <- correspondence |> + dplyr::mutate(out_factor = paste0(out, ".factor")) + data <- data |> - dplyr::mutate(dplyr::across(correspondence$out, ~ as.numeric(.x))) + dplyr::mutate( + dplyr::across(dplyr::any_of(correspondence$out), as.numeric), + dplyr::across(dplyr::any_of(correspondence$out_factor), ~{ + + var_fac <- data[[dplyr::cur_column()]] + var_num <- data[[sub("\\.factor$", "", dplyr::cur_column())]] + + dplyr::case_when(!is.na(var_num) ~ var_fac) + }) + ) } # Apply the labels to the data diff --git a/R/redcap_data.R b/R/redcap_data.R index 8c3bf29..2636d1a 100644 --- a/R/redcap_data.R +++ b/R/redcap_data.R @@ -59,7 +59,7 @@ #' @export #' @importFrom stats setNames -redcap_data <- function(data_path = NA, dic_path = NA, event_path = NA, uri = NA, token = NA, filter_field = NULL, survey_fields = FALSE) { +redcap_data <- function(data_path = NA, dic_path = NA, event_path = NA, uri = NA, token = NA, sep = NULL, filter_field = NULL, survey_fields = FALSE) { event_form <- NULL @@ -121,7 +121,7 @@ redcap_data <- function(data_path = NA, dic_path = NA, event_path = NA, uri = NA dic <- openxlsx::read.xlsx(dic_path, colNames = FALSE, detectDates = TRUE, sheet = 1) } else if (extension_dic == "csv") { # Read CSV file - dic <- utils::read.csv(dic_path, encoding = "UTF-8", header = FALSE) + dic <- utils::read.csv(dic_path, encoding = "UTF-8", header = FALSE, sep = sep) } else { stop("Unsupported dictionary format. Only CSV and XLSX are supported.", call. = FALSE) } From f318da9413869f838bbe595c24b0b272a91520f8 Mon Sep 17 00:00:00 2001 From: jcarmezim Date: Sat, 24 Jan 2026 17:03:27 +0100 Subject: [PATCH 4/4] Version 1.0.1 --- DESCRIPTION | 5 +++-- NAMESPACE | 1 + NEWS.md | 10 ++++++++++ R/redcap_data.R | 3 ++- R/utils-transform.R | 17 +++++++++-------- man/rd_checkbox.Rd | 4 ++-- man/redcap_data.Rd | 3 +++ 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 76e597e..0219473 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: REDCapDM Type: Package Title: 'REDCap' Data Management -Version: 1.0.0 +Version: 1.0.1 Authors@R: c( person("João", "Carmezim", email = "jcarmezim@igtp.cat", role = c("aut", "cre")), person("Pau", "Satorra", role = c("aut")), @@ -33,7 +33,8 @@ Imports: stringi, cli, forcats, - lifecycle + lifecycle, + magrittr Suggests: knitr, rmarkdown, diff --git a/NAMESPACE b/NAMESPACE index ba00b00..f157bc3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,6 +17,7 @@ export(rd_transform) export(redcap_data) import(cli) importFrom(lifecycle,deprecated) +importFrom(magrittr,`%>%`) importFrom(rlang,":=") importFrom(rlang,.data) importFrom(rlang,eval_tidy) diff --git a/NEWS.md b/NEWS.md index 78af754..be1316d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,13 @@ +# REDCapDM 1.0-1 + +## Bug fixes + +- Fixed a bug in `rd_checkbox` that mis-evaluated the branching logic of certain checkboxes if they had previously been transformed with `rd_factor` + +## New features + +- Added a `sep` argument to the `redcap_data()` function, allowing users to specify the field separator when reading CSV dictionary files. + # REDCapDM 1.0-0 ## Bug fixes diff --git a/R/redcap_data.R b/R/redcap_data.R index 2636d1a..80ee454 100644 --- a/R/redcap_data.R +++ b/R/redcap_data.R @@ -28,6 +28,7 @@ #' @param event_path Path to the event-form mapping file (CSV or XLSX) for longitudinal projects (downloadable via the `Designate Instruments for My Events` tab within the `Project Setup` section of REDCap). #' @param uri REDCap API base URI (use with `token`). #' @param token REDCap API token (use with `uri`). +#' @param sep Character string specifying the field separator for the exported dictionary CSV file. Defaul `","`. #' @param filter_field Optional character vector of field names to request from the API. #' @param survey_fields Logical; include survey-related fields when pulling via API. Default `FALSE`. #' @@ -59,7 +60,7 @@ #' @export #' @importFrom stats setNames -redcap_data <- function(data_path = NA, dic_path = NA, event_path = NA, uri = NA, token = NA, sep = NULL, filter_field = NULL, survey_fields = FALSE) { +redcap_data <- function(data_path = NA, dic_path = NA, event_path = NA, uri = NA, token = NA, sep = ",", filter_field = NULL, survey_fields = FALSE) { event_form <- NULL diff --git a/R/utils-transform.R b/R/utils-transform.R index b5f5ffa..5df6516 100644 --- a/R/utils-transform.R +++ b/R/utils-transform.R @@ -457,6 +457,7 @@ split_event <- function(data,dic,event_form,which=NULL){ #' @param event_form Data frame containing the correspondence of each event with each form. #' @param which Character string specifying a form if only data for that form is desired. #' @param wide Logical indicating if the dataset should be returned in a wide format (`TRUE`) or long format (`FALSE`). +#' @importFrom magrittr `%>%` split_form <- function(data, dic, event_form = NULL, which = NULL, wide=FALSE){ @@ -468,7 +469,7 @@ split_form <- function(data, dic, event_form = NULL, which = NULL, wide=FALSE){ if(longitudinal & is.null(event_form)){ stop("To split the data by form the event_form has to be provided in a longitudinal project", call. = FALSE) } - + #Check if the project has repeated instruments if("redcap_repeat_instrument" %in% names(data)) { repeat_instrument <- dplyr::case_when( @@ -554,29 +555,29 @@ split_form <- function(data, dic, event_form = NULL, which = NULL, wide=FALSE){ dplyr::mutate(df = purrr::map(.data$vars, ~ data |> dplyr::select(tidyselect::all_of(unique(c(basic_redcap_vars, .x)))))) } - + if(repeat_instrument) { form_check <- data %>% - dplyr::distinct(redcap_repeat_instrument, redcap_repeat_instrument.factor) - + dplyr::distinct(.data$redcap_repeat_instrument, .data$redcap_repeat_instrument.factor) + ndata <- ndata %>% dplyr::left_join(form_check, by = dplyr::join_by("form" == "redcap_repeat_instrument")) %>% dplyr::relocate("form_factor" = "redcap_repeat_instrument.factor", .after = form) %>% dplyr::mutate(df = purrr::map2(.data$form_factor, .data$df, ~ { if (is.na(.x)) { .y %>% - dplyr::filter(is.na(redcap_repeat_instrument.factor)) %>% + dplyr::filter(is.na(.data$redcap_repeat_instrument.factor)) %>% dplyr::select(-dplyr::starts_with("redcap_repeat_instrument")) } else { .y %>% - dplyr::filter(redcap_repeat_instrument.factor == .x) %>% + dplyr::filter(.data$redcap_repeat_instrument.factor == .x) %>% dplyr::mutate(redcap_repeat_instrument = redcap_repeat_instrument.factor) %>% dplyr::select(-redcap_repeat_instrument.factor) } })) %>% dplyr::select(-"form_factor") - } - + } + if(repeat_instrument) { form_check <- data |> diff --git a/man/rd_checkbox.Rd b/man/rd_checkbox.Rd index 1a4fa7c..ea4c43d 100644 --- a/man/rd_checkbox.Rd +++ b/man/rd_checkbox.Rd @@ -11,7 +11,7 @@ rd_checkbox( event_form = NULL, checkbox_labels = c("No", "Yes"), checkbox_names = TRUE, - na_logic = "none" + na_logic = "missing" ) } \arguments{ @@ -27,7 +27,7 @@ rd_checkbox( \item{checkbox_names}{Logical. If \code{TRUE} (default), checkbox columns are renamed using choice labels.} -\item{na_logic}{Controls how missing values are set based on branching logic. Must be one of \code{"none"} (do nothing), \code{"missing"} (set to \code{NA} only when the logic evaluation is \code{NA}), or \code{"eval"} (set to \code{NA} when the logic evaluates to \code{FALSE}). Defaults to \code{"none"}.} +\item{na_logic}{Controls how missing values are set based on branching logic. Must be one of \code{"none"} (do nothing), \code{"missing"} (set to \code{NA} only when the logic evaluation is \code{NA}), or \code{"eval"} (set to \code{NA} when the logic evaluates to \code{FALSE}). Defaults to \code{"missing"}.} } \value{ A list with: diff --git a/man/redcap_data.Rd b/man/redcap_data.Rd index 9f65a2d..596a413 100644 --- a/man/redcap_data.Rd +++ b/man/redcap_data.Rd @@ -10,6 +10,7 @@ redcap_data( event_path = NA, uri = NA, token = NA, + sep = ",", filter_field = NULL, survey_fields = FALSE ) @@ -25,6 +26,8 @@ redcap_data( \item{token}{REDCap API token (use with \code{uri}).} +\item{sep}{Character string specifying the field separator for the exported dictionary CSV file. Defaul \code{","}.} + \item{filter_field}{Optional character vector of field names to request from the API.} \item{survey_fields}{Logical; include survey-related fields when pulling via API. Default \code{FALSE}.}