cleaning and fixes for a minor release

This commit is contained in:
Andreas Gammelgaard Damsbo 2025-01-29 10:04:38 +01:00
parent 7d82eeebd4
commit 8d20901636
No known key found for this signature in database
29 changed files with 122 additions and 41 deletions

View File

@ -1,6 +1,6 @@
Package: REDCapCAST Package: REDCapCAST
Title: REDCap Metadata Casting and Castellated Data Handling Title: REDCap Metadata Casting and Castellated Data Handling
Version: 24.12.1 Version: 25.1.1
Authors@R: c( Authors@R: c(
person("Andreas Gammelgaard", "Damsbo", email = "agdamsbo@clin.au.dk", person("Andreas Gammelgaard", "Damsbo", email = "agdamsbo@clin.au.dk",
role = c("aut", "cre"),comment = c(ORCID = "0000-0002-7559-1154")), role = c("aut", "cre"),comment = c(ORCID = "0000-0002-7559-1154")),
@ -65,9 +65,9 @@ Imports:
gtsummary gtsummary
Collate: Collate:
'REDCapCAST-package.R' 'REDCapCAST-package.R'
'utils.r' 'utils.R'
'process_user_input.r' 'process_user_input.R'
'REDCap_split.r' 'REDCap_split.R'
'as_factor.R' 'as_factor.R'
'doc2dd.R' 'doc2dd.R'
'ds2dd_detailed.R' 'ds2dd_detailed.R'

View File

@ -28,6 +28,7 @@ export(clean_redcap_name)
export(compact_vec) export(compact_vec)
export(create_html_table) export(create_html_table)
export(create_instrument_meta) export(create_instrument_meta)
export(cut_string_length)
export(d2w) export(d2w)
export(doc2dd) export(doc2dd)
export(ds2dd) export(ds2dd)

14
NEWS.md
View File

@ -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. 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 # 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. 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. 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()`. * 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 # 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: ### 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()`. * `redcap_wider()` **NEW**: this function pivots the long data frames from `read_redcap_tables()` using `tidyr::pivot_wider()`.

View File

@ -80,7 +80,7 @@
#' \item \code{'all'}: a data.frame for each instrument, regardless of #' \item \code{'all'}: a data.frame for each instrument, regardless of
#' whether it is a repeating instrument or not. #' whether it is a repeating instrument or not.
#' } #' }
#' @include process_user_input.r utils.r #' @include process_user_input.R utils.R
#' @export #' @export
REDCap_split <- function(records, REDCap_split <- function(records,
metadata, metadata,

View File

@ -1,5 +1,4 @@
utils::globalVariables(c( utils::globalVariables(c(
"stats::setNames",
"field_name", "field_name",
"field_type", "field_type",
"select_choices_or_calculations", "select_choices_or_calculations",
@ -247,7 +246,7 @@ ds2dd <-
#' form.name = sample(c("b", "c"), size = 6, replace = TRUE, prob = rep(.5, 2)) #' form.name = sample(c("b", "c"), size = 6, replace = TRUE, prob = rep(.5, 2))
#' ) |> #' ) |>
#' purrr::pluck("meta") #' 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 #' ## Using column name suffix to carry form name
#' data <- iris |> #' data <- iris |>
@ -269,6 +268,10 @@ ds2dd_detailed <- function(data,
metadata = names(REDCapCAST::redcapcast_meta), metadata = names(REDCapCAST::redcapcast_meta),
convert.logicals = TRUE) { 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) { if (convert.logicals) {
data <- data |> data <- data |>
## Converts logical to factor, which overwrites attributes ## Converts logical to factor, which overwrites attributes
@ -294,7 +297,6 @@ ds2dd_detailed <- function(data,
dplyr::tibble() dplyr::tibble()
## form_name and field_name ## form_name and field_name
if (!is.null(form.sep)) { if (!is.null(form.sep)) {
if (form.sep != "") { if (form.sep != "") {
parts <- strsplit(names(data), split = form.sep) parts <- strsplit(names(data), split = form.sep)
@ -313,11 +315,14 @@ ds2dd_detailed <- function(data,
dd$field_name <- tolower(dd$field_name) dd$field_name <- tolower(dd$field_name)
} else { } else {
dd$form_name <- "data" 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 { } else {
## if no form name prefix, the colnames are used as field_names ## 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)) { if (is.null(form.name)) {
dd$form_name <- "data" dd$form_name <- "data"
@ -425,7 +430,14 @@ ds2dd_detailed <- function(data,
out <- list( out <- list(
data = data |> data = data |>
hms2character() |> 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 meta = dd
) )

View File

@ -28,6 +28,9 @@ get_api_key <- function(key.name, ...) {
#' \link[keyring]{key_set}, using the default keyring) #' \link[keyring]{key_set}, using the default keyring)
#' @param widen.data argument to widen the exported data #' @param widen.data argument to widen the exported data
#' @param uri REDCap database API uri #' @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}. #' @param ... arguments passed on to \link[REDCapCAST]{read_redcap_tables}.
#' #'
#' @return data.frame or list depending on widen.data #' @return data.frame or list depending on widen.data
@ -37,14 +40,20 @@ get_api_key <- function(key.name, ...) {
#' \dontrun{ #' \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, ...) { easy_redcap <- function(project.name,
key <- get_api_key(key.name = paste0(project.name, "_REDCAP_API"), widen.data = TRUE,
prompt = "Provide REDCap API key:") 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( out <- read_redcap_tables(
uri = uri, uri = uri,
token = key, token = key,
raw_or_label = "both", raw_or_label = raw_or_label,
... ...
) )

View File

@ -31,7 +31,7 @@
#' #'
#' @return list of instruments #' @return list of instruments
#' @importFrom REDCapR redcap_metadata_read redcap_read redcap_event_instruments #' @importFrom REDCapR redcap_metadata_read redcap_read redcap_event_instruments
#' @include utils.r #' @include utils.R
#' @export #' @export
#' #'
#' @examples #' @examples

View File

@ -97,7 +97,10 @@ focused_metadata <- function(metadata, vars_in_data) {
#' @return vector or data frame, same format as input #' @return vector or data frame, same format as input
#' @export #' @export
#' #'
#' @examples
#' "Research!, ne:ws? and c;l-.ls" |> clean_redcap_name()
clean_redcap_name <- function(x) { clean_redcap_name <- function(x) {
gsub("[,.;:?!@]","",
gsub( gsub(
" ", "_", " ", "_",
gsub( gsub(
@ -108,6 +111,7 @@ clean_redcap_name <- function(x) {
) )
) )
) )
)
} }
@ -518,3 +522,22 @@ dummy_fun <- function(...){
gtsummary::add_difference() 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
}
}

View File

@ -68,6 +68,7 @@ natively
ncol ncol
og og
param param
params
pegeler pegeler
perl perl
pos pos

View File

@ -5,6 +5,6 @@ account: agdamsbo
server: shinyapps.io server: shinyapps.io
hostUrl: https://api.shinyapps.io/v1 hostUrl: https://api.shinyapps.io/v1
appId: 11351429 appId: 11351429
bundleId: 9461113 bundleId: 9642648
url: https://agdamsbo.shinyapps.io/redcapcast/ url: https://agdamsbo.shinyapps.io/redcapcast/
version: 1 version: 1

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{REDCap_split}
\alias{REDCap_split} \alias{REDCap_split}
\title{Split REDCap repeating instruments table into multiple tables} \title{Split REDCap repeating instruments table into multiple tables}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{clean_redcap_name}
\alias{clean_redcap_name} \alias{clean_redcap_name}
\title{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. substitutes spaces for underscores and converts to lower case.
Trying to make up for different naming conventions. Trying to make up for different naming conventions.
} }
\examples{
"Research!, ne:ws? and c;l-.ls" |> clean_redcap_name()
}

22
man/cut_string_length.Rd Normal file
View File

@ -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)
}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.r % Please edit documentation in R/utils.R
\name{d2w} \name{d2w}
\alias{d2w} \alias{d2w}
\title{Convert single digits to words} \title{Convert single digits to words}

View File

@ -91,7 +91,7 @@ iris |>
form.name = sample(c("b", "c"), size = 6, replace = TRUE, prob = rep(.5, 2)) form.name = sample(c("b", "c"), size = 6, replace = TRUE, prob = rep(.5, 2))
) |> ) |>
purrr::pluck("meta") 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 ## Using column name suffix to carry form name
data <- iris |> data <- iris |>

View File

@ -4,7 +4,7 @@
\alias{easy_redcap} \alias{easy_redcap}
\title{Secure API key storage and data acquisition in one} \title{Secure API key storage and data acquisition in one}
\usage{ \usage{
easy_redcap(project.name, widen.data = TRUE, uri, ...) easy_redcap(project.name, widen.data = TRUE, uri, raw_or_label = "both", ...)
} }
\arguments{ \arguments{
\item{project.name}{The name of the current project (for key storage with \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{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}.} \item{...}{arguments passed on to \link[REDCapCAST]{read_redcap_tables}.}
} }
\value{ \value{

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{focused_metadata}
\alias{focused_metadata} \alias{focused_metadata}
\title{focused_metadata} \title{focused_metadata}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{get_id_name}
\alias{get_id_name} \alias{get_id_name}
\title{Get the id name} \title{Get the id name}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{is_repeated_longitudinal}
\alias{is_repeated_longitudinal} \alias{is_repeated_longitudinal}
\title{Test if repeatable or longitudinal} \title{Test if repeatable or longitudinal}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{match_fields_to_form}
\alias{match_fields_to_form} \alias{match_fields_to_form}
\title{Match fields to forms} \title{Match fields to forms}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{process_user_input}
\alias{process_user_input} \alias{process_user_input}
\title{User input processing} \title{User input processing}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{process_user_input.character}
\alias{process_user_input.character} \alias{process_user_input.character}
\title{User input processing character} \title{User input processing character}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{process_user_input.data.frame}
\alias{process_user_input.data.frame} \alias{process_user_input.data.frame}
\title{User input processing data.frame} \title{User input processing data.frame}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{process_user_input.default}
\alias{process_user_input.default} \alias{process_user_input.default}
\title{User input processing default} \title{User input processing default}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{process_user_input.response}
\alias{process_user_input.response} \alias{process_user_input.response}
\title{User input processing response} \title{User input processing response}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{sanitize_split}
\alias{sanitize_split} \alias{sanitize_split}
\title{Sanitize list of data frames} \title{Sanitize list of data frames}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % 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} \name{split_non_repeating_forms}
\alias{split_non_repeating_forms} \alias{split_non_repeating_forms}
\title{Split a data frame into separate tables for each form} \title{Split a data frame into separate tables for each form}

View File

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.r % Please edit documentation in R/utils.R
\name{strsplitx} \name{strsplitx}
\alias{strsplitx} \alias{strsplitx}
\title{Extended string splitting} \title{Extended string splitting}

View File

@ -37,7 +37,7 @@ shiny_cast()
To get you started, the easiest way possible, you can use the `easy_redcap()` function (example below). 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. This function includes a few convenience features to ease your further work.