diff --git a/DESCRIPTION b/DESCRIPTION index 7231746..ab96677 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: REDCapCAST Title: REDCap Metadata Casting and Castellated Data Handling -Version: 24.12.1 +Version: 25.1.1 Authors@R: c( person("Andreas Gammelgaard", "Damsbo", email = "agdamsbo@clin.au.dk", role = c("aut", "cre"),comment = c(ORCID = "0000-0002-7559-1154")), @@ -65,9 +65,9 @@ Imports: gtsummary Collate: 'REDCapCAST-package.R' - 'utils.r' - 'process_user_input.r' - 'REDCap_split.r' + 'utils.R' + 'process_user_input.R' + 'REDCap_split.R' 'as_factor.R' 'doc2dd.R' 'ds2dd_detailed.R' diff --git a/NAMESPACE b/NAMESPACE index b09a9ca..078a7c7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -28,6 +28,7 @@ export(clean_redcap_name) export(compact_vec) export(create_html_table) export(create_instrument_meta) +export(cut_string_length) export(d2w) export(doc2dd) export(ds2dd) diff --git a/NEWS.md b/NEWS.md index 25183c5..66700e9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,18 +1,24 @@ -# REDCapCAST 24.12.2 +# REDCapCAST 25.1.1 The newly introduced extension of `forcats::fct_drop()` has been corrected to work as intended as a method. +Conversion of column names to `field_names` are aligning better with REDCap naming. + +Shorten variable names above 100 characters (REDCap criteria; note recommended variable name length is <26) + +Fixed a params conflict in easy_redcap() when specifying raw_or_label. + # REDCapCAST 24.12.1 This release attempts to solve problems hosting the shiny_cast app, while also implementing functions to preserve as much meta data as possible from the REDCap database when exporting data. The hosting on shinyapps.io has given a lot of trouble recently. Modified package structure a little around the `shiny_cast()`, to accommodate an alternative hosting approach with all package functions included in a script instead of requiring the package. -* NEW: A new option to `raw_or_label` in `read_readcap_tables()` has been added: "both". Get raw values with REDCap labels applied as labels. Use `as_factor()` to format factors with original labels and use the `gtsummary` package to easily get beautiful tables with original labels from REDCap. Use `fct_drop()` to drop empty levels. +* NEW: A new option to `raw_or_label` in `read_redcap_tables()` has been added: "both". Get raw values with REDCap labels applied as labels. Use `as_factor()` to format factors with original labels and use the `gtsummary` package to easily get beautiful tables with original labels from REDCap. Use `fct_drop()` to drop empty levels. * NEW: fct_drop() has been added with an extension to `forcats::fct_drop()`, that works across data.frames. Use as `fct_drop()`. -* CHANGE: the default data export method of `easy_redcap()` has been changed to use the new labelled data export with `read_readcap_tables()`. +* CHANGE: the default data export method of `easy_redcap()` has been changed to use the new labelled data export with `read_redcap_tables()`. # REDCapCAST 24.11.3 @@ -165,7 +171,7 @@ The main goal this package is to keep the option to only export a defined subset ### Functions: -* `read_redcap_tables()` **NEW**: this function is mainly an implementation of the combined use of `REDCapR::readcap_read()` and `REDCap_split()` to maintain the focused nature of `REDCapR::readcap_read()`, to only download the specified data. Also implements tests of valid form names and event names. The usual fall-back solution was to get all data. +* `read_redcap_tables()` **NEW**: this function is mainly an implementation of the combined use of `REDCapR::redcap_read()` and `REDCap_split()` to maintain the focused nature of `REDCapR::redcap_read()`, to only download the specified data. Also implements tests of valid form names and event names. The usual fall-back solution was to get all data. * `redcap_wider()` **NEW**: this function pivots the long data frames from `read_redcap_tables()` using `tidyr::pivot_wider()`. diff --git a/R/REDCap_split.r b/R/REDCap_split.r index a9e617a..d89aaf8 100644 --- a/R/REDCap_split.r +++ b/R/REDCap_split.r @@ -80,7 +80,7 @@ #' \item \code{'all'}: a data.frame for each instrument, regardless of #' whether it is a repeating instrument or not. #' } -#' @include process_user_input.r utils.r +#' @include process_user_input.R utils.R #' @export REDCap_split <- function(records, metadata, diff --git a/R/ds2dd_detailed.R b/R/ds2dd_detailed.R index ff137cc..20d4434 100644 --- a/R/ds2dd_detailed.R +++ b/R/ds2dd_detailed.R @@ -1,5 +1,4 @@ utils::globalVariables(c( - "stats::setNames", "field_name", "field_type", "select_choices_or_calculations", @@ -247,7 +246,7 @@ ds2dd <- #' form.name = sample(c("b", "c"), size = 6, replace = TRUE, prob = rep(.5, 2)) #' ) |> #' purrr::pluck("meta") -#' mtcars |> ds2dd_detailed(add.auto.id = TRUE) +#' mtcars |> numchar2fct() |> ds2dd_detailed(add.auto.id = TRUE) #' #' ## Using column name suffix to carry form name #' data <- iris |> @@ -269,6 +268,10 @@ ds2dd_detailed <- function(data, metadata = names(REDCapCAST::redcapcast_meta), convert.logicals = TRUE) { + short_names <- colnames(data) |> lapply(\(.x) cut_string_length(.x,l=90)) |> purrr::reduce(c) + + data <- stats::setNames(data,short_names) + if (convert.logicals) { data <- data |> ## Converts logical to factor, which overwrites attributes @@ -294,7 +297,6 @@ ds2dd_detailed <- function(data, dplyr::tibble() ## form_name and field_name - if (!is.null(form.sep)) { if (form.sep != "") { parts <- strsplit(names(data), split = form.sep) @@ -313,11 +315,14 @@ ds2dd_detailed <- function(data, dd$field_name <- tolower(dd$field_name) } else { dd$form_name <- "data" - dd$field_name <- gsub(" ", "_", tolower(colnames(data))) + + # dd$field_name <- gsub(" ", "_", tolower(colnames(data))) + dd$field_name <- clean_redcap_name(colnames(data)) } } else { ## if no form name prefix, the colnames are used as field_names - dd$field_name <- gsub(" ", "_", tolower(colnames(data))) + # dd$field_name <- gsub(" ", "_", tolower(colnames(data))) + dd$field_name <- clean_redcap_name(colnames(data)) if (is.null(form.name)) { dd$form_name <- "data" @@ -425,7 +430,14 @@ ds2dd_detailed <- function(data, out <- list( data = data |> hms2character() |> - stats::setNames(dd$field_name), + stats::setNames(dd$field_name) |> + lapply(\(.x){ + if (identical("factor",class(.x))){ + as.numeric(.x) + } else { + .x + } + }) |> dplyr::bind_cols(), meta = dd ) diff --git a/R/easy_redcap.R b/R/easy_redcap.R index 2f9ef2a..2f5de90 100644 --- a/R/easy_redcap.R +++ b/R/easy_redcap.R @@ -28,6 +28,9 @@ get_api_key <- function(key.name, ...) { #' \link[keyring]{key_set}, using the default keyring) #' @param widen.data argument to widen the exported data #' @param uri REDCap database API uri +#' @param raw_or_label argument passed on to +#' \link[REDCapCAST]{read_redcap_tables}. Default is "both" to get labelled +#' data. #' @param ... arguments passed on to \link[REDCapCAST]{read_redcap_tables}. #' #' @return data.frame or list depending on widen.data @@ -35,16 +38,22 @@ get_api_key <- function(key.name, ...) { #' #' @examples #' \dontrun{ -#' easy_redcap("My_new_project",fields=c("record_id","age","hypertension")) +#' easy_redcap("My_new_project", fields = c("record_id", "age", "hypertension")) #' } -easy_redcap <- function(project.name, widen.data = TRUE, uri, ...) { - key <- get_api_key(key.name = paste0(project.name, "_REDCAP_API"), - prompt = "Provide REDCap API key:") +easy_redcap <- function(project.name, + widen.data = TRUE, + uri, + raw_or_label = "both", + ...) { + key <- get_api_key( + key.name = paste0(project.name, "_REDCAP_API"), + prompt = "Provide REDCap API key:" + ) out <- read_redcap_tables( uri = uri, token = key, - raw_or_label = "both", + raw_or_label = raw_or_label, ... ) diff --git a/R/read_redcap_tables.R b/R/read_redcap_tables.R index b530333..26ec207 100644 --- a/R/read_redcap_tables.R +++ b/R/read_redcap_tables.R @@ -31,7 +31,7 @@ #' #' @return list of instruments #' @importFrom REDCapR redcap_metadata_read redcap_read redcap_event_instruments -#' @include utils.r +#' @include utils.R #' @export #' #' @examples diff --git a/R/utils.r b/R/utils.r index dfa32ec..06d2351 100644 --- a/R/utils.r +++ b/R/utils.r @@ -97,7 +97,10 @@ focused_metadata <- function(metadata, vars_in_data) { #' @return vector or data frame, same format as input #' @export #' +#' @examples +#' "Research!, ne:ws? and c;l-.ls" |> clean_redcap_name() clean_redcap_name <- function(x) { + gsub("[,.;:?!@]","", gsub( " ", "_", gsub( @@ -108,6 +111,7 @@ clean_redcap_name <- function(x) { ) ) ) + ) } @@ -518,3 +522,22 @@ dummy_fun <- function(...){ gtsummary::add_difference() ) } + + +#' Cut string to desired length +#' +#' @param data data +#' @param l length +#' +#' @returns character string of length l +#' @export +#' +#' @examples +#' "length" |> cut_string_length(l=3) +cut_string_length <- function(data,l=100){ + if (nchar(data)>=l){ + substr(data,1,l) + } else { + data + } +} diff --git a/inst/WORDLIST b/inst/WORDLIST index 9b4b57f..2294268 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -68,6 +68,7 @@ natively ncol og param +params pegeler perl pos diff --git a/inst/shiny-examples/casting/rsconnect/shinyapps.io/agdamsbo/redcapcast.dcf b/inst/shiny-examples/casting/rsconnect/shinyapps.io/agdamsbo/redcapcast.dcf index 88a2738..a263f68 100644 --- a/inst/shiny-examples/casting/rsconnect/shinyapps.io/agdamsbo/redcapcast.dcf +++ b/inst/shiny-examples/casting/rsconnect/shinyapps.io/agdamsbo/redcapcast.dcf @@ -5,6 +5,6 @@ account: agdamsbo server: shinyapps.io hostUrl: https://api.shinyapps.io/v1 appId: 11351429 -bundleId: 9461113 +bundleId: 9642648 url: https://agdamsbo.shinyapps.io/redcapcast/ version: 1 diff --git a/man/REDCap_split.Rd b/man/REDCap_split.Rd index 1389ac8..86df0ad 100644 --- a/man/REDCap_split.Rd +++ b/man/REDCap_split.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/REDCap_split.r +% Please edit documentation in R/REDCap_split.R \name{REDCap_split} \alias{REDCap_split} \title{Split REDCap repeating instruments table into multiple tables} diff --git a/man/clean_redcap_name.Rd b/man/clean_redcap_name.Rd index 18b16a8..6559e56 100644 --- a/man/clean_redcap_name.Rd +++ b/man/clean_redcap_name.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{clean_redcap_name} \alias{clean_redcap_name} \title{clean_redcap_name} @@ -17,3 +17,6 @@ Stepwise removal on non-alphanumeric characters, trailing white space, substitutes spaces for underscores and converts to lower case. Trying to make up for different naming conventions. } +\examples{ +"Research!, ne:ws? and c;l-.ls" |> clean_redcap_name() +} diff --git a/man/cut_string_length.Rd b/man/cut_string_length.Rd new file mode 100644 index 0000000..2c143de --- /dev/null +++ b/man/cut_string_length.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{cut_string_length} +\alias{cut_string_length} +\title{Cut string to desired length} +\usage{ +cut_string_length(data, l = 100) +} +\arguments{ +\item{data}{data} + +\item{l}{length} +} +\value{ +character string of length l +} +\description{ +Cut string to desired length +} +\examples{ +"length" |> cut_string_length(l=3) +} diff --git a/man/d2w.Rd b/man/d2w.Rd index ef41520..ba6b585 100644 --- a/man/d2w.Rd +++ b/man/d2w.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{d2w} \alias{d2w} \title{Convert single digits to words} diff --git a/man/ds2dd_detailed.Rd b/man/ds2dd_detailed.Rd index a4ec67c..113f252 100644 --- a/man/ds2dd_detailed.Rd +++ b/man/ds2dd_detailed.Rd @@ -91,7 +91,7 @@ iris |> form.name = sample(c("b", "c"), size = 6, replace = TRUE, prob = rep(.5, 2)) ) |> purrr::pluck("meta") -mtcars |> ds2dd_detailed(add.auto.id = TRUE) +mtcars |> numchar2fct() |> ds2dd_detailed(add.auto.id = TRUE) ## Using column name suffix to carry form name data <- iris |> diff --git a/man/easy_redcap.Rd b/man/easy_redcap.Rd index 3bd0171..f087d60 100644 --- a/man/easy_redcap.Rd +++ b/man/easy_redcap.Rd @@ -4,7 +4,7 @@ \alias{easy_redcap} \title{Secure API key storage and data acquisition in one} \usage{ -easy_redcap(project.name, widen.data = TRUE, uri, ...) +easy_redcap(project.name, widen.data = TRUE, uri, raw_or_label = "both", ...) } \arguments{ \item{project.name}{The name of the current project (for key storage with @@ -14,6 +14,10 @@ easy_redcap(project.name, widen.data = TRUE, uri, ...) \item{uri}{REDCap database API uri} +\item{raw_or_label}{argument passed on to +\link[REDCapCAST]{read_redcap_tables}. Default is "both" to get labelled +data.} + \item{...}{arguments passed on to \link[REDCapCAST]{read_redcap_tables}.} } \value{ @@ -24,6 +28,6 @@ Secure API key storage and data acquisition in one } \examples{ \dontrun{ -easy_redcap("My_new_project",fields=c("record_id","age","hypertension")) +easy_redcap("My_new_project", fields = c("record_id", "age", "hypertension")) } } diff --git a/man/focused_metadata.Rd b/man/focused_metadata.Rd index 5eea785..f9a0380 100644 --- a/man/focused_metadata.Rd +++ b/man/focused_metadata.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{focused_metadata} \alias{focused_metadata} \title{focused_metadata} diff --git a/man/get_id_name.Rd b/man/get_id_name.Rd index ba12779..7e85f74 100644 --- a/man/get_id_name.Rd +++ b/man/get_id_name.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{get_id_name} \alias{get_id_name} \title{Get the id name} diff --git a/man/is_repeated_longitudinal.Rd b/man/is_repeated_longitudinal.Rd index c12bfd2..838726c 100644 --- a/man/is_repeated_longitudinal.Rd +++ b/man/is_repeated_longitudinal.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{is_repeated_longitudinal} \alias{is_repeated_longitudinal} \title{Test if repeatable or longitudinal} diff --git a/man/match_fields_to_form.Rd b/man/match_fields_to_form.Rd index 6dae0c6..cd89985 100644 --- a/man/match_fields_to_form.Rd +++ b/man/match_fields_to_form.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{match_fields_to_form} \alias{match_fields_to_form} \title{Match fields to forms} diff --git a/man/process_user_input.Rd b/man/process_user_input.Rd index 684e7c0..3defafa 100644 --- a/man/process_user_input.Rd +++ b/man/process_user_input.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/process_user_input.r +% Please edit documentation in R/process_user_input.R \name{process_user_input} \alias{process_user_input} \title{User input processing} diff --git a/man/process_user_input.character.Rd b/man/process_user_input.character.Rd index 4146734..112f835 100644 --- a/man/process_user_input.character.Rd +++ b/man/process_user_input.character.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/process_user_input.r +% Please edit documentation in R/process_user_input.R \name{process_user_input.character} \alias{process_user_input.character} \title{User input processing character} diff --git a/man/process_user_input.data.frame.Rd b/man/process_user_input.data.frame.Rd index 2ad15c1..3c08937 100644 --- a/man/process_user_input.data.frame.Rd +++ b/man/process_user_input.data.frame.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/process_user_input.r +% Please edit documentation in R/process_user_input.R \name{process_user_input.data.frame} \alias{process_user_input.data.frame} \title{User input processing data.frame} diff --git a/man/process_user_input.default.Rd b/man/process_user_input.default.Rd index ad9c83b..0f3dbe8 100644 --- a/man/process_user_input.default.Rd +++ b/man/process_user_input.default.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/process_user_input.r +% Please edit documentation in R/process_user_input.R \name{process_user_input.default} \alias{process_user_input.default} \title{User input processing default} diff --git a/man/process_user_input.response.Rd b/man/process_user_input.response.Rd index b69b5f3..34f5132 100644 --- a/man/process_user_input.response.Rd +++ b/man/process_user_input.response.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/process_user_input.r +% Please edit documentation in R/process_user_input.R \name{process_user_input.response} \alias{process_user_input.response} \title{User input processing response} diff --git a/man/sanitize_split.Rd b/man/sanitize_split.Rd index a150792..c11e60d 100644 --- a/man/sanitize_split.Rd +++ b/man/sanitize_split.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{sanitize_split} \alias{sanitize_split} \title{Sanitize list of data frames} diff --git a/man/split_non_repeating_forms.Rd b/man/split_non_repeating_forms.Rd index b29fd71..a769903 100644 --- a/man/split_non_repeating_forms.Rd +++ b/man/split_non_repeating_forms.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{split_non_repeating_forms} \alias{split_non_repeating_forms} \title{Split a data frame into separate tables for each form} diff --git a/man/strsplitx.Rd b/man/strsplitx.Rd index 464c6a3..c609b22 100644 --- a/man/strsplitx.Rd +++ b/man/strsplitx.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.r +% Please edit documentation in R/utils.R \name{strsplitx} \alias{strsplitx} \title{Extended string splitting} diff --git a/vignettes/REDCapCAST.Rmd b/vignettes/REDCapCAST.Rmd index 4177ed3..0d87e4d 100644 --- a/vignettes/REDCapCAST.Rmd +++ b/vignettes/REDCapCAST.Rmd @@ -37,7 +37,7 @@ shiny_cast() To get you started, the easiest way possible, you can use the `easy_redcap()` function (example below). -You will need an API-key for your REDCap server, the uri/URL/address for the API connection (usually the adress used for accessing your institutions REDCap servar, with an appended "/api/"). +You will need an API-key for your REDCap server, the uri/URL/address for the API connection (usually the address used for accessing your institutions REDCap server, with an appended "/api/"). This function includes a few convenience features to ease your further work.