diff --git a/.github/workflows/recheck.yml b/.github/workflows/recheck.yml new file mode 100644 index 00000000..323d2ad3 --- /dev/null +++ b/.github/workflows/recheck.yml @@ -0,0 +1,18 @@ +on: + workflow_dispatch: + inputs: + which: + type: choice + description: Which dependents to check + options: + - strong + - most + +name: Reverse dependency check + +jobs: + revdep_check: + name: Reverse check ${{ inputs.which }} dependents + uses: r-devel/recheck/.github/workflows/recheck.yml@v1 + with: + which: ${{ inputs.which }} diff --git a/DESCRIPTION b/DESCRIPTION index 3172fb75..65931fb1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: REDCapTidieR Type: Package Title: Extract 'REDCap' Databases into Tidy 'Tibble's -Version: 1.1.1 +Version: 1.2.0 Authors@R: c( person("Richard", "Hanna", , "richardshanna91@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0009-0005-6496-8154")), @@ -21,6 +21,7 @@ Imports: checkmate, cli, dplyr, + glue, lobstr, lubridate, purrr, @@ -52,5 +53,5 @@ Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US LazyData: true -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Roxygen: list(markdown = TRUE) diff --git a/NAMESPACE b/NAMESPACE index 60266b24..64b1519b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ S3method(tbl_sum,redcap_supertbl) S3method(vec_ptype_abbr,redcap_supertbl) export(add_skimr_metadata) export(bind_tibbles) +export(combine_checkboxes) export(extract_tibble) export(extract_tibbles) export(fmt_strip_field_embedding) @@ -38,22 +39,31 @@ importFrom(cli,cli_warn) importFrom(cli,qty) importFrom(dplyr,"%>%") importFrom(dplyr,across) +importFrom(dplyr,bind_cols) importFrom(dplyr,bind_rows) importFrom(dplyr,case_when) +importFrom(dplyr,coalesce) +importFrom(dplyr,cur_column) importFrom(dplyr,filter) +importFrom(dplyr,first) importFrom(dplyr,group_by) importFrom(dplyr,if_any) importFrom(dplyr,if_else) importFrom(dplyr,left_join) importFrom(dplyr,mutate) +importFrom(dplyr,n_distinct) +importFrom(dplyr,nth) importFrom(dplyr,pull) importFrom(dplyr,recode) importFrom(dplyr,relocate) importFrom(dplyr,rename) +importFrom(dplyr,right_join) importFrom(dplyr,row_number) +importFrom(dplyr,rowwise) importFrom(dplyr,select) importFrom(dplyr,slice) importFrom(dplyr,summarise) +importFrom(dplyr,ungroup) importFrom(formattable,percent) importFrom(lobstr,obj_size) importFrom(lubridate,is.Date) @@ -66,11 +76,13 @@ importFrom(purrr,discard) importFrom(purrr,flatten_chr) importFrom(purrr,map) importFrom(purrr,map2) +importFrom(purrr,map2_chr) importFrom(purrr,map_int) importFrom(purrr,map_lgl) importFrom(purrr,pluck) importFrom(purrr,pmap) importFrom(purrr,pmap_chr) +importFrom(purrr,reduce) importFrom(purrr,some) importFrom(readr,parse_character) importFrom(readr,parse_date) @@ -80,6 +92,7 @@ importFrom(readr,parse_integer) importFrom(readr,parse_logical) importFrom(readr,parse_time) importFrom(rlang,"!!!") +importFrom(rlang,":=") importFrom(rlang,.data) importFrom(rlang,abort) importFrom(rlang,as_closure) @@ -95,6 +108,7 @@ importFrom(rlang,enexpr) importFrom(rlang,enquo) importFrom(rlang,env_poke) importFrom(rlang,eval_tidy) +importFrom(rlang,expr) importFrom(rlang,get_env) importFrom(rlang,global_env) importFrom(rlang,is_atomic) @@ -103,6 +117,9 @@ importFrom(rlang,is_bare_list) importFrom(rlang,is_installed) importFrom(rlang,new_environment) importFrom(rlang,quo_get_expr) +importFrom(rlang,quo_name) +importFrom(rlang,sym) +importFrom(rlang,syms) importFrom(rlang,try_fetch) importFrom(rlang,zap) importFrom(stats,na.omit) @@ -121,6 +138,7 @@ importFrom(tidyr,complete) importFrom(tidyr,fill) importFrom(tidyr,nest) importFrom(tidyr,pivot_wider) +importFrom(tidyr,separate_wider_delim) importFrom(tidyr,unnest) importFrom(tidyr,unnest_wider) importFrom(tidyselect,all_of) diff --git a/NEWS.md b/NEWS.md index fb1435a0..6211fda9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,5 @@ +# REDCapTidieR 1.2.0 + # REDCapTidieR 1.1.1 (development version) Version 1.1.1 diff --git a/R/REDCapTidieR-package.R b/R/REDCapTidieR-package.R index b4e150a2..bb42a6ce 100644 --- a/R/REDCapTidieR-package.R +++ b/R/REDCapTidieR-package.R @@ -5,22 +5,26 @@ #' expect_factor expect_logical #' @importFrom cli cli_abort cli_fmt cli_text cli_vec cli_warn qty #' @importFrom dplyr %>% across bind_rows case_when filter group_by if_any if_else -#' left_join mutate pull recode relocate rename row_number select slice summarise +#' left_join mutate pull recode relocate rename right_join row_number rowwise +#' select slice summarise ungroup coalesce cur_column bind_cols first nth n_distinct #' @importFrom formattable percent #' @importFrom lobstr obj_size #' @importFrom lubridate is.difftime is.period is.POSIXt is.Date -#' @importFrom purrr compose map map2 map_int map_lgl pluck pmap_chr some pmap discard flatten_chr +#' @importFrom purrr compose map map2 map_int map_lgl pluck pmap_chr some pmap +#' discard flatten_chr map2_chr reduce #' @importFrom REDCapR redcap_arm_export redcap_event_instruments redcap_instruments #' redcap_metadata_read redcap_read_oneshot sanitize_token #' @importFrom rlang .data !!! abort as_closure caller_arg caller_env catch_cnd #' check_installed cnd_muffle current_call current_env enexpr enquo env_poke -#' eval_tidy get_env global_env is_atomic is_bare_formula is_bare_list -#' is_installed new_environment quo_get_expr try_fetch zap as_label +#' eval_tidy get_env global_env is_atomic is_bare_formula is_bare_list quo_name +#' is_installed new_environment quo_get_expr try_fetch zap as_label sym syms expr +#' := #' @importFrom stringi stri_split_fixed #' @importFrom stringr str_detect str_replace str_replace_all str_squish str_trunc #' str_trim str_ends #' @importFrom tibble as_tibble is_tibble tibble -#' @importFrom tidyr complete fill pivot_wider nest unnest unnest_wider +#' @importFrom tidyr complete fill pivot_wider nest separate_wider_delim unnest +#' unnest_wider #' @importFrom tidyselect all_of any_of ends_with eval_select everything #' starts_with where #' @importFrom vctrs vec_ptype_abbr vec_ptype diff --git a/R/checks.R b/R/checks.R index 09c39a7c..bf48ffbe 100644 --- a/R/checks.R +++ b/R/checks.R @@ -659,3 +659,128 @@ check_extra_field_values_message <- function(extra_field_values, call = caller_e values = values ) } + +#' @title +#' Check fields exist for checkbox combination +#' +#' @param fields Vector of character strings to check the length of +#' @param expr An expression +#' @param call The calling environment to use in the error message +#' +#' @keywords internal +check_fields_exist <- function(fields, expr, call = caller_env()) { + expr <- quo_name(expr) + + if (length(fields) == 0) { + msg <- c( + x = "No fields detected using `{expr}`.", + i = "Ensure that the column names specified in {.arg cols} match the columns in your data. Check for typos or use {.pkg tidyselect} helpers like {.code starts_with()}, `contains()`, etc." # nolint: line_length_linter + ) + + cli_abort( + msg, + class = c("missing_checkbox_fields", "REDCapTidieR_cond") + ) + } +} + +#' @title +#' Check metadata fields exist for checkbox combination +#' +#' @description +#' Similar to [check_fields_exist()], but instead of verifying fields that exist +#' in the data tibble this seeks to verify their existence under the metadata +#' tibble `field_name`s. +#' +#' @param metadata_tbl A metadata tibble from the supertibble generated by [read_redcap()]. +#' @param cols Selected columns identified for [`combine_checkboxes()`] to be +#' cross checked against `metadata_tibble$field_name` +#' @param call The calling environment to use in the error message +#' +#' @keywords internal +check_metadata_fields_exist <- function(metadata_tbl, cols, call = caller_env()) { + if (!all(cols %in% metadata_tbl$field_name)) { + msg <- c( + x = "Fields detected not present in metadata.", + `!` = "Column{?s} {.code {cols[!cols %in% metadata_tbl$field_name]}} detected as valid in the data tibble, but not found present in the metadata tibble.", # nolint: line_length_linter + `i` = "This may occur if either the names of the data tibble or the metadata tibble `field_name`s were edited." + ) + + cli_abort( + msg, + class = c("missing_metadata_checkbox_fields", "REDCapTidieR_cond") + ) + } +} + + +#' @title +#' Check fields are of checkbox field type +#' +#' @param metadata_tbl A metadata tibble from a supertibble +#' @param call The calling environment to use in the error message +#' +#' @keywords internal + +check_fields_are_checkboxes <- function(metadata_tbl, call = caller_env()) { + non_checkboxes <- metadata_tbl %>% + filter(.data$field_type != "checkbox") + + if (nrow(non_checkboxes) > 0) { + non_checkboxes <- non_checkboxes %>% + pull(.data$field_name) + + msg <- c( + x = "Non-checkbox fields selected for {.code form_name}", + `!` = "The following fields returned as non-checkbox field types: {.code {non_checkboxes}}" + ) + + cli_abort( + msg, + class = c("non_checkbox_fields", "REDCapTidieR_cond") + ) + } +} + +#' @title Check equal distinct values between two columns +#' +#' @description +#' Takes a dataframe and two columns and checks if [n_distinct()] of the second +#' column is all unique based on grouping of the first column. +#' +#' @param data a dataframe +#' @param col1 a column to group by +#' @param col2 a column to check for uniqueness +#' +#' @keywords internal + +check_equal_col_summaries <- function(data, col1, col2, call = caller_env()) { + summary <- data %>% + summarise( + .by = {{ col1 }}, + n = n_distinct({{ col2 }}) + ) + + total_n <- summary %>% + pull(.data$n) + + if (!all(total_n == 1)) { + col1_n_vals <- summary %>% + filter(.data$n > 1) %>% + pull(col1) + + col2_n_vals <- data %>% # nolint: object_usage_linter + filter(col1 %in% col1_n_vals) %>% + pull(col2) + + msg <- c( + x = "{.code {col1_n_vals}} checkbox field{?s} resulted in multiple output columns: {.code {col2_n_vals}}.", + `!` = "Check that {.code names_glue} defines only 1 output column for each checkbox field." # nolint: line_length_linter + ) + + cli_abort( + msg, + class = c("names_glue_multi_checkbox", "REDCapTidieR_cond") + ) + } +} diff --git a/R/combine_checkboxes.R b/R/combine_checkboxes.R new file mode 100644 index 00000000..a094788b --- /dev/null +++ b/R/combine_checkboxes.R @@ -0,0 +1,291 @@ +#' @title Combine Checkbox Fields into a Single Column +#' +#' @description +#' [combine_checkboxes()] consolidates multiple checkbox fields in a REDCap data +#' tibble into a single column. This transformation simplifies analysis by +#' combining multiple binary columns into a singular and informative labelled +#' factor column. +#' +#' @details +#' [combine_checkboxes()] makes use of the output names of [read_redcap()] +#' data tibbles and metadata tibbles. Changes to checkbox data names or +#' metadata `field_name`s may result in errors. +#' +#' Checkbox fields are expanded to be a variable per checkbox option, separated +#' by underscores. For example, `checkbox_var` with 2 options becomes +#' `checkbox_var___1` and `checkbox_var___2`. [combine_checkboxes()] looks for +#' these and may give a error if it cannot find them. +#' +#' @param supertbl A supertibble generated by [read_redcap()]. Required. +#' @param tbl The `redcap_form_name` of the data tibble to extract. Required. +#' @param cols <[`tidy-select`][tidyr_tidy_select]> Checkbox columns to combine to +#' single column. Required. +#' @param names_prefix String added to the start of every variable name. +#' @param names_sep String to separate new column names from `names_prefix`. +#' @param names_glue Instead of `names_sep` and `names_prefix`, you can supply +#' a glue specification and the unique `.value` to create custom column names. +#' @param names_repair What happens if the output has invalid column names? +#' The default, "check_unique" is to error if the columns are duplicated. +#' Use "minimal" to allow duplicates in the output, or "unique" to de-duplicated +#' by adding numeric suffixes. See [vctrs::vec_as_names()] for more options. +#' @param multi_value_label A string specifying the value to be used when multiple +#' checkbox fields are selected. Default "Multiple". +#' @param values_fill Value to use when no checkboxes are selected. Default `NA`. +#' @param raw_or_label Either 'raw' or 'label' to specify whether to use raw coded +#' values or labels for the options. Default 'label'. +#' @param keep Logical indicating whether to keep the original checkbox fields in +#' the output. Default `TRUE`. +#' +#' @return A modified supertibble. +#' +#' @examples +#' \dontrun{ +#' supertbl <- read_redcap(redcap_uri, token) +#' combine_checkboxes( +#' supertbl = supertbl, +#' tbl = "demographics", +#' cols = starts_with("race"), +#' multi_value_label = "Multiple", +#' values_fill = NA +#' ) +#' } +#' +#' @export + +combine_checkboxes <- function(supertbl, + tbl, + cols, + names_prefix = "", + names_sep = "_", + names_glue = NULL, + names_repair = "check_unique", + multi_value_label = "Multiple", + values_fill = NA, + raw_or_label = "label", + keep = TRUE) { + # Check args --- + check_arg_is_supertbl(supertbl, req_cols = c("redcap_data", "redcap_metadata")) + check_arg_is_character(names_prefix, len = 1) + check_arg_is_character(names_sep, len = 1, any.missing = TRUE) + check_arg_is_character(names_repair, len = 1, any.missing = FALSE) + check_arg_is_character(names_glue, len = 1, any.missing = FALSE, null.ok = TRUE) + check_arg_is_character(tbl, len = 1, any.missing = FALSE) + check_arg_is_character(multi_value_label, len = 1, any.missing = TRUE) + check_arg_is_character(values_fill, len = 1, any.missing = TRUE) + check_arg_choices(raw_or_label, choices = c("label", "raw")) + check_arg_is_logical(keep, len = 1, any.missing = FALSE) + + # Extract tbl from supertbl + data_tbl <- supertbl %>% + extract_tibble(tbl) + + # Save user cols to quo + cols_exp <- enquo(cols) + + # Evaluate the cols expression to get the selected column names + selected_cols <- names(eval_select(cols_exp, data = data_tbl)) + check_fields_exist(fields = selected_cols, expr = cols_exp) # Check supplied fields exist + + # Get metadata reference table, check that chosen fields are checkboxes + metadata_tbl <- supertbl$redcap_metadata[supertbl$redcap_form_name == tbl][[1]] + metadata_spec <- get_metadata_spec(metadata_tbl, selected_cols, names_prefix, names_sep, names_glue) + + # Copy data_tbl to mod, data_tbl to be referenced later + data_tbl_mod <- data_tbl + + data_tbl_mod <- data_tbl_mod %>% + mutate( + across( + selected_cols, + ~ replace_true(.x, + cur_column(), + metadata = metadata_spec, + raw_or_label = raw_or_label + ) + ), + across(selected_cols, as.character) # enforce to character strings + ) + + new_cols <- metadata_spec %>% + nest(.by = .data$.new_value, .key = "metadata") %>% + pmap(convert_checkbox_vals, + data_tbl = data_tbl_mod, + raw_or_label = raw_or_label, multi_value_label = multi_value_label, values_fill = values_fill + ) + + final_tbl <- combine_and_repair_tbls(data_tbl, data_tbl_mod, new_cols, names_repair = names_repair) + + # Keep or remove original multi columns + if (!keep) { + final_tbl <- final_tbl %>% + select(-selected_cols) + } + + # Update the supertbl data tibble + supertbl$redcap_data[supertbl$redcap_form_name == tbl][[1]] <- final_tbl + + supertbl +} + +#' @title Get metadata specification table +#' +#' @inheritParams combine_checkboxes +#' @param metadata_tbl A metadata tibble from the supertibble generated by [read_redcap()]. +#' @param selected_cols Character string vector of field names for checkbox combination +#' +#' @returns a tibble +#' +#' @keywords internal +get_metadata_spec <- function(metadata_tbl, + selected_cols, + names_prefix, + names_sep, + names_glue) { + check_metadata_fields_exist(metadata_tbl, selected_cols) + + # Create a metadata reference table linking field name to raw and label values + out <- metadata_tbl %>% + filter(.data$field_name %in% selected_cols) %>% + mutate( + .value = sub("___.*$", "", .data$field_name) + ) + + if (!is.null(names_glue)) { + # Similar to pivot_*, use of `names_glue` overrides use of names_prefix/sep + glue_env <- out %>% + select(.data$.value) %>% + mutate(.new_value = as.character(glue::glue_data(., names_glue))) %>% # nolint: object_usage_linter + select(.data$.new_value) + + out <- cbind(out, glue_env) + } else { + out <- out %>% + mutate( + .new_value = case_when(names_prefix != "" ~ paste(names_prefix, .value, sep = names_sep), + .default = paste(names_prefix, .data$.value, sep = "") + ) + ) + } + + # Check that for each unique value of .value there is one unique value of .new_value + # May be removed in the future + check_equal_col_summaries(out, .value, .new_value) # nolint: object_usage_linter + + # Make sure selection is checkbox metadata field type + check_fields_are_checkboxes(out) + + # Bind raw/label values per original field grouping + parsed_vals <- tibble() + + for (i in seq_along(unique(out$.value))) { + index <- unique(out$.value)[i] + out_filtered <- out %>% filter(.data$.value == index) + + parsed_vals <- rbind(parsed_vals, parse_labels(first(out_filtered$select_choices_or_calculations))) + } + + bind_cols(out, parsed_vals) %>% + select(.data$field_name, .data$raw, .data$label, .data$.value, .data$.new_value) %>% + relocate(c(.data$.value, .data$.new_value), .after = .data$field_name) +} + +#' @title Replace checkbox TRUEs with raw_or_label values +#' +#' @inheritParams combine_checkboxes +#' @param col A vector +#' @param col_name A string +#' @param metadata A metadata tibble from the original supertibble +#' +#' @description +#' Simple utility function for replacing checkbox field values. +#' +#' @returns A character string +#' +#' @keywords internal +replace_true <- function(col, col_name, metadata, raw_or_label) { + # Replace TRUEs/1s with the appropriate raw or label value from the metadata + replacement <- metadata %>% + filter(.data$field_name == col_name) %>% + pull(raw_or_label) + col <- ifelse(col == TRUE, replacement, NA) # col == TRUE works for raw or label because TRUE == 1 & 1 == TRUE + # Convert non-TRUEs to NA, since values can be either "FALSE" or "0" for unchecked values + return(col) +} + +#' @title Convert a new checkbox column's values +#' +#' @description This function takes a single column of data and converts the values +#' based on the overall data tibble cross referenced with a nested section of the +#' metadata tibble. +#' +#' [case_when()] logic helps determine whether the value is a coalesced singular +#' value or a user-specified one via `multi_value_label` or `values_fill`. +#' +#' @details +#' This function is used in conjunction with [pmap()]. +#' +#' @keywords internal +#' +#' @param metadata A nested portion of the overall metadata tibble +#' @param data_tbl The data tibble from the original supertibble +#' @param .new_value The new column values made by [combine_checkboxes()] +#' @inheritParams combine_checkboxes +convert_checkbox_vals <- function(metadata, .new_value, data_tbl, raw_or_label, multi_value_label, values_fill) { + tibble( + !!.new_value := rowSums(!is.na(data_tbl[names(data_tbl) %in% metadata$field_name])) + ) %>% + mutate( + !!.new_value := case_when(. > 1 ~ multi_value_label, + . == 1 ~ coalesce(!!!data_tbl[, names(data_tbl) %in% metadata$field_name]), + .default = values_fill + ), + !!.new_value := factor(!!sym(.new_value), + levels = c(metadata[[raw_or_label]], multi_value_label, values_fill) + ) + ) +} + +#' @title Combine checkbox fields with respect to repaired outputs +#' +#' @description +#' This function seeks to preserve the original data columns and types from the +#' originally supplied data_tbl and add on the new columns from data_tbl_mod. +#' +#' If `names_repair` presents a repair strategy, the output columns will be +#' captured and updated here while dropping the original columns. +#' +#' @param data_tbl The original data table given to [combine_checkboxes()] +#' @param data_tbl_mod A modified data table from `data_tbl` +#' @param new_cols The new columns created for checkbox combination +#' @inheritParams combine_checkboxes +#' +#' @keywords internal +#' +#' @returns a tibble +combine_and_repair_tbls <- function(data_tbl, data_tbl_mod, new_cols, names_repair) { + # Perform initial column bind with repair strategy + data_tbl_mod <- bind_cols(data_tbl_mod, new_cols, .name_repair = names_repair) + + # Get the column names of each table + cols_data_tbl <- names(data_tbl) + cols_data_tbl_mod <- names(data_tbl_mod) + + # Identify common columns + common_cols <- intersect(cols_data_tbl, cols_data_tbl_mod) + + # Identify unique columns in data_tbl_mod + unique_cols_mod <- setdiff(cols_data_tbl_mod, cols_data_tbl) + + # Select common columns from data_tbl + common_data <- data_tbl %>% + select(all_of(common_cols)) + + # Select unique columns from data_tbl_mod + unique_data_mod <- data_tbl_mod %>% + select(all_of(unique_cols_mod)) + + # Combine the selected columns + combined_data <- bind_cols(common_data, unique_data_mod) + + return(combined_data) +} diff --git a/R/utils.R b/R/utils.R index f6a04763..32dc3a78 100644 --- a/R/utils.R +++ b/R/utils.R @@ -954,3 +954,23 @@ safe_set_variable_labels <- function(data, labs) { labs_to_keep <- intersect(names(labs), colnames(data)) labelled::set_variable_labels(data, !!!labs[labs_to_keep]) } + +#' @title +#' Extract a specific metadata tibble from a supertibble +#' +#' @description +#' Utility function to extract a specific metadata tibble from a supertibble +#' given a `redcap_form_name` +#' +#' @param supertbl A supertibble generated by [read_redcap()]. +#' @param redcap_form_name A character string identifying the `redcap_form_name` +#' the metadata tibble is associated with. +#' +#' @return +#' A tibble +#' +#' @keywords internal + +extract_metadata_tibble <- function(supertbl, redcap_form_name) { + supertbl$redcap_metadata[supertbl$redcap_form_name == redcap_form_name][[1]] +} diff --git a/man/check_equal_col_summaries.Rd b/man/check_equal_col_summaries.Rd new file mode 100644 index 00000000..68af2920 --- /dev/null +++ b/man/check_equal_col_summaries.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/checks.R +\name{check_equal_col_summaries} +\alias{check_equal_col_summaries} +\title{Check equal distinct values between two columns} +\usage{ +check_equal_col_summaries(data, col1, col2, call = caller_env()) +} +\arguments{ +\item{data}{a dataframe} + +\item{col1}{a column to group by} + +\item{col2}{a column to check for uniqueness} +} +\description{ +Takes a dataframe and two columns and checks if \code{\link[=n_distinct]{n_distinct()}} of the second +column is all unique based on grouping of the first column. +} +\keyword{internal} diff --git a/man/check_fields_are_checkboxes.Rd b/man/check_fields_are_checkboxes.Rd new file mode 100644 index 00000000..51caa024 --- /dev/null +++ b/man/check_fields_are_checkboxes.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/checks.R +\name{check_fields_are_checkboxes} +\alias{check_fields_are_checkboxes} +\title{Check fields are of checkbox field type} +\usage{ +check_fields_are_checkboxes(metadata_tbl, call = caller_env()) +} +\arguments{ +\item{metadata_tbl}{A metadata tibble from a supertibble} + +\item{call}{The calling environment to use in the error message} +} +\description{ +Check fields are of checkbox field type +} +\keyword{internal} diff --git a/man/check_fields_exist.Rd b/man/check_fields_exist.Rd new file mode 100644 index 00000000..8c65e2d7 --- /dev/null +++ b/man/check_fields_exist.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/checks.R +\name{check_fields_exist} +\alias{check_fields_exist} +\title{Check fields exist for checkbox combination} +\usage{ +check_fields_exist(fields, expr, call = caller_env()) +} +\arguments{ +\item{fields}{Vector of character strings to check the length of} + +\item{expr}{An expression} + +\item{call}{The calling environment to use in the error message} +} +\description{ +Check fields exist for checkbox combination +} +\keyword{internal} diff --git a/man/check_metadata_fields_exist.Rd b/man/check_metadata_fields_exist.Rd new file mode 100644 index 00000000..cc1c3df7 --- /dev/null +++ b/man/check_metadata_fields_exist.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/checks.R +\name{check_metadata_fields_exist} +\alias{check_metadata_fields_exist} +\title{Check metadata fields exist for checkbox combination} +\usage{ +check_metadata_fields_exist(metadata_tbl, cols, call = caller_env()) +} +\arguments{ +\item{metadata_tbl}{A metadata tibble from the supertibble generated by \code{\link[=read_redcap]{read_redcap()}}.} + +\item{cols}{Selected columns identified for \code{\link[=combine_checkboxes]{combine_checkboxes()}} to be +cross checked against \code{metadata_tibble$field_name}} + +\item{call}{The calling environment to use in the error message} +} +\description{ +Similar to \code{\link[=check_fields_exist]{check_fields_exist()}}, but instead of verifying fields that exist +in the data tibble this seeks to verify their existence under the metadata +tibble \code{field_name}s. +} +\keyword{internal} diff --git a/man/combine_and_repair_tbls.Rd b/man/combine_and_repair_tbls.Rd new file mode 100644 index 00000000..edb4f653 --- /dev/null +++ b/man/combine_and_repair_tbls.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/combine_checkboxes.R +\name{combine_and_repair_tbls} +\alias{combine_and_repair_tbls} +\title{Combine checkbox fields with respect to repaired outputs} +\usage{ +combine_and_repair_tbls(data_tbl, data_tbl_mod, new_cols, names_repair) +} +\arguments{ +\item{data_tbl}{The original data table given to \code{\link[=combine_checkboxes]{combine_checkboxes()}}} + +\item{data_tbl_mod}{A modified data table from \code{data_tbl}} + +\item{new_cols}{The new columns created for checkbox combination} + +\item{names_repair}{What happens if the output has invalid column names? +The default, "check_unique" is to error if the columns are duplicated. +Use "minimal" to allow duplicates in the output, or "unique" to de-duplicated +by adding numeric suffixes. See \code{\link[vctrs:vec_as_names]{vctrs::vec_as_names()}} for more options.} +} +\value{ +a tibble +} +\description{ +This function seeks to preserve the original data columns and types from the +originally supplied data_tbl and add on the new columns from data_tbl_mod. + +If \code{names_repair} presents a repair strategy, the output columns will be +captured and updated here while dropping the original columns. +} +\keyword{internal} diff --git a/man/combine_checkboxes.Rd b/man/combine_checkboxes.Rd new file mode 100644 index 00000000..3d78a441 --- /dev/null +++ b/man/combine_checkboxes.Rd @@ -0,0 +1,83 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/combine_checkboxes.R +\name{combine_checkboxes} +\alias{combine_checkboxes} +\title{Combine Checkbox Fields into a Single Column} +\usage{ +combine_checkboxes( + supertbl, + tbl, + cols, + names_prefix = "", + names_sep = "_", + names_glue = NULL, + names_repair = "check_unique", + multi_value_label = "Multiple", + values_fill = NA, + raw_or_label = "label", + keep = TRUE +) +} +\arguments{ +\item{supertbl}{A supertibble generated by \code{\link[=read_redcap]{read_redcap()}}. Required.} + +\item{tbl}{The \code{redcap_form_name} of the data tibble to extract. Required.} + +\item{cols}{<\code{\link[=tidyr_tidy_select]{tidy-select}}> Checkbox columns to combine to +single column. Required.} + +\item{names_prefix}{String added to the start of every variable name.} + +\item{names_sep}{String to separate new column names from \code{names_prefix}.} + +\item{names_glue}{Instead of \code{names_sep} and \code{names_prefix}, you can supply +a glue specification and the unique \code{.value} to create custom column names.} + +\item{names_repair}{What happens if the output has invalid column names? +The default, "check_unique" is to error if the columns are duplicated. +Use "minimal" to allow duplicates in the output, or "unique" to de-duplicated +by adding numeric suffixes. See \code{\link[vctrs:vec_as_names]{vctrs::vec_as_names()}} for more options.} + +\item{multi_value_label}{A string specifying the value to be used when multiple +checkbox fields are selected. Default "Multiple".} + +\item{values_fill}{Value to use when no checkboxes are selected. Default \code{NA}.} + +\item{raw_or_label}{Either 'raw' or 'label' to specify whether to use raw coded +values or labels for the options. Default 'label'.} + +\item{keep}{Logical indicating whether to keep the original checkbox fields in +the output. Default \code{TRUE}.} +} +\value{ +A modified supertibble. +} +\description{ +\code{\link[=combine_checkboxes]{combine_checkboxes()}} consolidates multiple checkbox fields in a REDCap data +tibble into a single column. This transformation simplifies analysis by +combining multiple binary columns into a singular and informative labelled +factor column. +} +\details{ +\code{\link[=combine_checkboxes]{combine_checkboxes()}} makes use of the output names of \code{\link[=read_redcap]{read_redcap()}} +data tibbles and metadata tibbles. Changes to checkbox data names or +metadata \code{field_name}s may result in errors. + +Checkbox fields are expanded to be a variable per checkbox option, separated +by underscores. For example, \code{checkbox_var} with 2 options becomes +\code{checkbox_var___1} and \code{checkbox_var___2}. \code{\link[=combine_checkboxes]{combine_checkboxes()}} looks for +these and may give a error if it cannot find them. +} +\examples{ +\dontrun{ +supertbl <- read_redcap(redcap_uri, token) +combine_checkboxes( + supertbl = supertbl, + tbl = "demographics", + cols = starts_with("race"), + multi_value_label = "Multiple", + values_fill = NA +) +} + +} diff --git a/man/convert_checkbox_vals.Rd b/man/convert_checkbox_vals.Rd new file mode 100644 index 00000000..9aa48cf4 --- /dev/null +++ b/man/convert_checkbox_vals.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/combine_checkboxes.R +\name{convert_checkbox_vals} +\alias{convert_checkbox_vals} +\title{Convert a new checkbox column's values} +\usage{ +convert_checkbox_vals( + metadata, + .new_value, + data_tbl, + raw_or_label, + multi_value_label, + values_fill +) +} +\arguments{ +\item{metadata}{A nested portion of the overall metadata tibble} + +\item{.new_value}{The new column values made by \code{\link[=combine_checkboxes]{combine_checkboxes()}}} + +\item{data_tbl}{The data tibble from the original supertibble} + +\item{raw_or_label}{Either 'raw' or 'label' to specify whether to use raw coded +values or labels for the options. Default 'label'.} + +\item{multi_value_label}{A string specifying the value to be used when multiple +checkbox fields are selected. Default "Multiple".} + +\item{values_fill}{Value to use when no checkboxes are selected. Default \code{NA}.} +} +\description{ +This function takes a single column of data and converts the values +based on the overall data tibble cross referenced with a nested section of the +metadata tibble. + +\code{\link[=case_when]{case_when()}} logic helps determine whether the value is a coalesced singular +value or a user-specified one via \code{multi_value_label} or \code{values_fill}. +} +\details{ +This function is used in conjunction with \code{\link[=pmap]{pmap()}}. +} +\keyword{internal} diff --git a/man/extract_metadata_tibble.Rd b/man/extract_metadata_tibble.Rd new file mode 100644 index 00000000..0623b75d --- /dev/null +++ b/man/extract_metadata_tibble.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{extract_metadata_tibble} +\alias{extract_metadata_tibble} +\title{Extract a specific metadata tibble from a supertibble} +\usage{ +extract_metadata_tibble(supertbl, redcap_form_name) +} +\arguments{ +\item{supertbl}{A supertibble generated by \code{\link[=read_redcap]{read_redcap()}}.} + +\item{redcap_form_name}{A character string identifying the \code{redcap_form_name} +the metadata tibble is associated with.} +} +\value{ +A tibble +} +\description{ +Utility function to extract a specific metadata tibble from a supertibble +given a \code{redcap_form_name} +} +\keyword{internal} diff --git a/man/get_metadata_spec.Rd b/man/get_metadata_spec.Rd new file mode 100644 index 00000000..efe22d10 --- /dev/null +++ b/man/get_metadata_spec.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/combine_checkboxes.R +\name{get_metadata_spec} +\alias{get_metadata_spec} +\title{Get metadata specification table} +\usage{ +get_metadata_spec( + metadata_tbl, + selected_cols, + names_prefix, + names_sep, + names_glue +) +} +\arguments{ +\item{metadata_tbl}{A metadata tibble from the supertibble generated by \code{\link[=read_redcap]{read_redcap()}}.} + +\item{selected_cols}{Character string vector of field names for checkbox combination} + +\item{names_prefix}{String added to the start of every variable name.} + +\item{names_sep}{String to separate new column names from \code{names_prefix}.} + +\item{names_glue}{Instead of \code{names_sep} and \code{names_prefix}, you can supply +a glue specification and the unique \code{.value} to create custom column names.} +} +\value{ +a tibble +} +\description{ +Get metadata specification table +} +\keyword{internal} diff --git a/man/replace_true.Rd b/man/replace_true.Rd new file mode 100644 index 00000000..b1d8ced5 --- /dev/null +++ b/man/replace_true.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/combine_checkboxes.R +\name{replace_true} +\alias{replace_true} +\title{Replace checkbox TRUEs with raw_or_label values} +\usage{ +replace_true(col, col_name, metadata, raw_or_label) +} +\arguments{ +\item{col}{A vector} + +\item{col_name}{A string} + +\item{metadata}{A metadata tibble from the original supertibble} + +\item{raw_or_label}{Either 'raw' or 'label' to specify whether to use raw coded +values or labels for the options. Default 'label'.} +} +\value{ +A character string +} +\description{ +Simple utility function for replacing checkbox field values. +} +\keyword{internal} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 7801c4fe..ede73fed 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -64,6 +64,11 @@ reference: Export a supertibble to other usable formats. contents: - write_redcap_xlsx +- title: "Supertibble Post-Processing" + desc: > + Helpful functions for supertibble data analytics. + contents: + - combine_checkboxes - title: "Data" contents: - superheroes_supertbl diff --git a/renv.lock b/renv.lock index b29e066a..63c2beee 100644 --- a/renv.lock +++ b/renv.lock @@ -1,6 +1,6 @@ { "R": { - "Version": "4.2.0", + "Version": "4.4.0", "Repositories": [ { "Name": "CRAN", @@ -9,17 +9,6 @@ ] }, "Packages": { - "DBI": { - "Package": "DBI", - "Version": "1.2.2", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "R", - "methods" - ], - "Hash": "164809cd72e1d5160b4cb3aa57f510fe" - }, "R.cache": { "Package": "R.cache", "Version": "0.16.0", @@ -104,25 +93,6 @@ ], "Hash": "e76c401b631961c865b89bb5a4ea3b97" }, - "RSQLite": { - "Package": "RSQLite", - "Version": "2.3.5", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "DBI", - "R", - "bit64", - "blob", - "cpp11", - "memoise", - "methods", - "pkgconfig", - "plogr", - "rlang" - ], - "Hash": "f5a75d57e0a3014a6ef537ac04a80fc6" - }, "Rcpp": { "Package": "Rcpp", "Version": "1.0.12", @@ -144,16 +114,6 @@ ], "Hash": "cad6cf7f1d5f6e906700b9d3e718c796" }, - "assertthat": { - "Package": "assertthat", - "Version": "0.2.1", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "tools" - ], - "Hash": "50c838a310445e954bc13f26f26a6ecf" - }, "backports": { "Package": "backports", "Version": "1.4.1", @@ -198,18 +158,6 @@ ], "Hash": "9fe98599ca456d6552421db0d6772d8f" }, - "blob": { - "Package": "blob", - "Version": "1.2.4", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "methods", - "rlang", - "vctrs" - ], - "Hash": "40415719b5a479b87949f3aa0aee737c" - }, "brew": { "Package": "brew", "Version": "1.0-10", @@ -286,14 +234,14 @@ }, "cli": { "Package": "cli", - "Version": "3.6.2", + "Version": "3.6.3", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "utils" ], - "Hash": "1216ac65ac55ec0058a6f75d7ca0fd52" + "Hash": "b21916dd77a27642b447374a5d30ecf3" }, "clipr": { "Package": "clipr", @@ -359,46 +307,6 @@ ], "Hash": "5a295d7d963cc5035284dcdbaf334f4e" }, - "crancache": { - "Package": "crancache", - "Version": "0.0.0.9001", - "Source": "GitHub", - "RemoteType": "github", - "RemoteHost": "api.github.com", - "RemoteRepo": "crancache", - "RemoteUsername": "r-lib", - "RemoteRef": "HEAD", - "RemoteSha": "7ea4e479bdf780adadd1bd421a5ca23e5f951697", - "Requirements": [ - "callr", - "cranlike", - "curl", - "desc", - "digest", - "parsedate", - "rappdirs", - "rematch2", - "tools", - "utils", - "withr" - ], - "Hash": "795b8389734f11481fdcdf9cdde3002f" - }, - "cranlike": { - "Package": "cranlike", - "Version": "1.0.2", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "DBI", - "RSQLite", - "debugme", - "desc", - "tools", - "utils" - ], - "Hash": "2a531c8d1d45799fe2e880708dfc1097" - }, "crayon": { "Package": "crayon", "Version": "1.5.2", @@ -449,17 +357,6 @@ ], "Hash": "cdc4a473222b0112d4df0bcfbed12d44" }, - "debugme": { - "Package": "debugme", - "Version": "1.1.0", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "crayon", - "grDevices" - ], - "Hash": "2d8a9e4f08f3dd669cb8ddd1eb575959" - }, "desc": { "Package": "desc", "Version": "1.4.3", @@ -671,28 +568,6 @@ ], "Hash": "47b5f30c720c23999b913a1a635cf0bb" }, - "gargle": { - "Package": "gargle", - "Version": "1.5.2", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "R", - "cli", - "fs", - "glue", - "httr", - "jsonlite", - "lifecycle", - "openssl", - "rappdirs", - "rlang", - "stats", - "utils", - "withr" - ], - "Hash": "fc0b272e5847c58cd5da9b20eedbd026" - }, "generics": { "Package": "generics", "Version": "0.1.3", @@ -756,28 +631,6 @@ ], "Hash": "e0b3a53876554bd45879e596cdb10a52" }, - "gmailr": { - "Package": "gmailr", - "Version": "2.0.0", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "R", - "base64enc", - "cli", - "crayon", - "gargle", - "glue", - "httr", - "jsonlite", - "lifecycle", - "mime", - "rappdirs", - "rematch2", - "rlang" - ], - "Hash": "3c643bd9639dbbda700d2593644c053b" - }, "goodpractice": { "Package": "goodpractice", "Version": "1.0.4", @@ -826,14 +679,14 @@ }, "highr": { "Package": "highr", - "Version": "0.10", + "Version": "0.11", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "xfun" ], - "Hash": "06230136b2d2b9ba5805e1963fa6e890" + "Hash": "d65ba49117ca223614f71b60d85b8ab7" }, "hms": { "Package": "hms", @@ -973,7 +826,7 @@ }, "knitr": { "Package": "knitr", - "Version": "1.45", + "Version": "1.48", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -985,7 +838,7 @@ "xfun", "yaml" ], - "Hash": "1ec462871063897135c1bcbe0fc8f07d" + "Hash": "acf380f300c721da9fde7df115a5f86f" }, "labelled": { "Package": "labelled", @@ -1168,13 +1021,6 @@ ], "Hash": "9fa7cdc5fbdb1c8511fdde72a944db63" }, - "parsedate": { - "Package": "parsedate", - "Version": "1.3.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "7f5024cc7af45eeecef657fa62beb568" - }, "pillar": { "Package": "pillar", "Version": "1.9.0", @@ -1268,13 +1114,6 @@ ], "Hash": "876c618df5ae610be84356d5d7a5d124" }, - "plogr": { - "Package": "plogr", - "Version": "0.2.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "09eb987710984fc2905c7129c7d85e65" - }, "praise": { "Package": "praise", "Version": "1.0.0", @@ -1521,53 +1360,6 @@ ], "Hash": "1425f91b4d5d9a8f25352c44a3d914ed" }, - "revdepcheck": { - "Package": "revdepcheck", - "Version": "1.0.0.9001", - "Source": "GitHub", - "RemoteType": "github", - "Remotes": "r-lib/crancache", - "RemoteHost": "api.github.com", - "RemoteRepo": "revdepcheck", - "RemoteUsername": "r-lib", - "RemoteRef": "HEAD", - "RemoteSha": "39b808ef3b2ebcf16cb355f737cba1a78e29368a", - "Remotes": "r-lib/crancache", - "Requirements": [ - "DBI", - "RSQLite", - "assertthat", - "brio", - "callr", - "cli", - "clisymbols", - "crancache", - "crayon", - "curl", - "desc", - "glue", - "gmailr", - "hms", - "httr", - "jsonlite", - "knitr", - "pkgbuild", - "prettyunits", - "processx", - "progress", - "rcmdcheck", - "rematch2", - "remotes", - "rlang", - "sessioninfo", - "tibble", - "utils", - "whoami", - "withr", - "yaml" - ], - "Hash": "f47ecbb03333506fc30a883b9af175f1" - }, "rex": { "Package": "rex", "Version": "1.2.1", @@ -1606,18 +1398,18 @@ }, "rlang": { "Package": "rlang", - "Version": "1.1.3", + "Version": "1.1.4", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "utils" ], - "Hash": "42548638fae05fd9a9b5f3f437fbbbe2" + "Hash": "3eec01f8b1dee337674b2e34ab1f9bc1" }, "rmarkdown": { "Package": "rmarkdown", - "Version": "2.26", + "Version": "2.27", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1636,11 +1428,11 @@ "xfun", "yaml" ], - "Hash": "9b148e7f95d33aac01f31282d49e4f44" + "Hash": "27f9502e1cdbfa195f94e03b0f517484" }, "roxygen2": { "Package": "roxygen2", - "Version": "7.3.1", + "Version": "7.3.2", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1662,7 +1454,7 @@ "withr", "xml2" ], - "Hash": "c25fe7b2d8cba73d1b63c947bf7afdb9" + "Hash": "6ee25f9054a70f44d615300ed531ba8d" }, "rprojroot": { "Package": "rprojroot", @@ -1803,16 +1595,16 @@ }, "stringi": { "Package": "stringi", - "Version": "1.8.3", + "Version": "1.8.4", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "stats", "tools", "utils" ], - "Hash": "058aebddea264f4c99401515182e656a" + "Hash": "39e1144fd75428983dc3f63aa53dfa91" }, "stringr": { "Package": "stringr", @@ -1870,7 +1662,7 @@ }, "testthat": { "Package": "testthat", - "Version": "3.2.1", + "Version": "3.2.1.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -1895,7 +1687,7 @@ "waldo", "withr" ], - "Hash": "4767a686ebe986e6cb01d075b3f09729" + "Hash": "3f6e7e5e2220856ff865e4834766bf2b" }, "textshaping": { "Package": "textshaping", @@ -2145,7 +1937,7 @@ }, "xfun": { "Package": "xfun", - "Version": "0.42", + "Version": "0.45", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -2153,7 +1945,7 @@ "stats", "tools" ], - "Hash": "fd1349170df31f7a10bd98b0189e85af" + "Hash": "ca59c87fe305b16a9141a5874c3a7889" }, "xml2": { "Package": "xml2", diff --git a/renv/settings.json b/renv/settings.json index 0e163611..08d0def7 100644 --- a/renv/settings.json +++ b/renv/settings.json @@ -9,7 +9,7 @@ ], "ppm.enabled": null, "ppm.ignored.urls": [], - "r.version": "4.2.0", + "r.version": "4.4.0", "snapshot.type": "implicit", "use.cache": true, "vcs.ignore.cellar": true, diff --git a/revdep/.gitignore b/revdep/.gitignore deleted file mode 100644 index 111ab324..00000000 --- a/revdep/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -checks -library -checks.noindex -library.noindex -cloud.noindex -data.sqlite -*.html diff --git a/revdep/README.md b/revdep/README.md deleted file mode 100644 index dab9e040..00000000 --- a/revdep/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# Platform - -|field |value | -|:--------|:-----------------------------------| -|version |R version 4.2.3 (2023-03-15) | -|os |macOS Ventura 13.6.5 | -|system |aarch64, darwin20 | -|ui |RStudio | -|language |(EN) | -|collate |en_US.UTF-8 | -|ctype |en_US.UTF-8 | -|tz |America/New_York | -|date |2024-04-11 | -|rstudio |2023.12.1+402 Ocean Storm (desktop) | -|pandoc |NA | - -# Dependencies - -|package |old |new |Δ | -|:------------|:-------|:-------|:--| -|REDCapTidieR |1.1.0 |1.1.1 |* | -|askpass |1.2.0 |1.2.0 | | -|backports |1.4.1 |1.4.1 | | -|base64enc |0.1-3 |0.1-3 | | -|bit |4.0.5 |4.0.5 | | -|bit64 |4.0.5 |4.0.5 | | -|bslib |0.7.0 |0.7.0 | | -|cachem |1.0.8 |1.0.8 | | -|checkmate |2.3.1 |2.3.1 | | -|cli |3.6.2 |3.6.2 | | -|clipr |0.8.0 |0.8.0 | | -|cpp11 |0.4.7 |0.4.7 | | -|crayon |1.5.2 |1.5.2 | | -|curl |5.2.1 |5.2.1 | | -|digest |0.6.35 |0.6.35 | | -|dplyr |1.1.4 |1.1.4 | | -|evaluate |0.23 |0.23 | | -|fansi |1.0.6 |1.0.6 | | -|fastmap |1.1.1 |1.1.1 | | -|fontawesome |0.5.2 |0.5.2 | | -|formattable |0.2.1 |0.2.1 | | -|fs |1.6.3 |1.6.3 | | -|generics |0.1.3 |0.1.3 | | -|glue |1.7.0 |1.7.0 | | -|highr |0.10 |0.10 | | -|hms |1.1.3 |1.1.3 | | -|htmltools |0.5.8.1 |0.5.8.1 | | -|htmlwidgets |1.6.4 |1.6.4 | | -|httr |1.4.7 |1.4.7 | | -|jquerylib |0.1.4 |0.1.4 | | -|jsonlite |1.8.8 |1.8.8 | | -|knitr |1.46 |1.46 | | -|lifecycle |1.0.4 |1.0.4 | | -|lobstr |1.1.2 |1.1.2 | | -|lubridate |1.9.3 |1.9.3 | | -|magrittr |2.0.3 |2.0.3 | | -|memoise |2.0.1 |2.0.1 | | -|mime |0.12 |0.12 | | -|openssl |2.1.1 |2.1.1 | | -|pillar |1.9.0 |1.9.0 | | -|pkgconfig |2.0.3 |2.0.3 | | -|prettyunits |1.2.0 |1.2.0 | | -|progress |1.2.3 |1.2.3 | | -|purrr |1.0.2 |1.0.2 | | -|R6 |2.5.1 |2.5.1 | | -|rappdirs |0.3.3 |0.3.3 | | -|readr |2.1.5 |2.1.5 | | -|REDCapR |1.1.0 |1.1.0 | | -|rlang |1.1.3 |1.1.3 | | -|rmarkdown |2.26 |2.26 | | -|sass |0.4.9 |0.4.9 | | -|stringi |1.8.3 |1.8.3 | | -|stringr |1.5.1 |1.5.1 | | -|sys |3.4.2 |3.4.2 | | -|tibble |3.2.1 |3.2.1 | | -|tidyr |1.3.1 |1.3.1 | | -|tidyselect |1.2.1 |1.2.1 | | -|timechange |0.3.0 |0.3.0 | | -|tinytex |0.50 |0.50 | | -|tzdb |0.4.0 |0.4.0 | | -|utf8 |1.2.4 |1.2.4 | | -|vctrs |0.6.5 |0.6.5 | | -|vroom |1.6.5 |1.6.5 | | -|withr |3.0.0 |3.0.0 | | -|xfun |0.43 |0.43 | | -|yaml |2.3.8 |2.3.8 | | - -# Revdeps - diff --git a/revdep/cran.md b/revdep/cran.md deleted file mode 100644 index 33114b63..00000000 --- a/revdep/cran.md +++ /dev/null @@ -1,7 +0,0 @@ -## revdepcheck results - -We checked 0 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. - - * We saw 0 new problems - * We failed to check 0 packages - diff --git a/revdep/failures.md b/revdep/failures.md deleted file mode 100644 index 9a207363..00000000 --- a/revdep/failures.md +++ /dev/null @@ -1 +0,0 @@ -*Wow, no problems at all. :)* \ No newline at end of file diff --git a/revdep/problems.md b/revdep/problems.md deleted file mode 100644 index 9a207363..00000000 --- a/revdep/problems.md +++ /dev/null @@ -1 +0,0 @@ -*Wow, no problems at all. :)* \ No newline at end of file diff --git a/tests/testthat/test-checks.R b/tests/testthat/test-checks.R index b6713790..54573941 100644 --- a/tests/testthat/test-checks.R +++ b/tests/testthat/test-checks.R @@ -246,3 +246,72 @@ test_that("check_extra_field_values works", { check_extra_field_values(c(1, NA, 2), "1") |> expect_equal("2") }) + +test_that("check_fields_exist works", { + check_fields_exist(fields = character(0), expr = expr(starts_with("temp"))) %>% + expect_error(class = "missing_checkbox_fields") + + check_fields_exist(fields = c(1, 2, 3), expr = expr(starts_with("temp"))) %>% + expect_no_error() +}) + +test_that("check_metadata_fields_exist works", { + metadata_valid <- tibble( + field_name = c("var1", "var2", "var_3"), + ) + valid_cols <- c("var1", "var2", "var_3") + + metadata_invalid <- tibble( + field_name = c("var1", "var2", "var_3_edited"), + ) + invalid_cols <- c("var1", "var2", "var_3_edited") + + check_metadata_fields_exist(metadata_valid, valid_cols) %>% + expect_no_error() + + check_metadata_fields_exist(metadata_invalid, valid_cols) %>% + expect_error(class = "missing_metadata_checkbox_fields") + + check_metadata_fields_exist(metadata_valid, invalid_cols) %>% + expect_error(class = "missing_metadata_checkbox_fields") +}) + +test_that("check_fields_are_checkboxes works", { + metadata <- tibble::tribble( + ~field_name, ~field_type, + "record_id", "text", + "text_field", "text", + "calc_field", "calc", + "checkbox___1", "checkbox", + "checkbox___2", "checkbox", + "checkbox___3", "checkbox" + ) + + metadata_filtered <- metadata %>% + filter("checkbox" %in% field_name) + + expect_error(check_fields_are_checkboxes(metadata), class = "non_checkbox_fields") + expect_no_error(check_fields_are_checkboxes(metadata_filtered)) +}) + +test_that("check_equal_col_summaries works", { + data <- tibble::tribble( + ~"id", ~"col1", ~"col2", + 1, "A", "A1", + 2, "B", "B1", + 3, "C", "C1" + ) + + expect_no_error(check_equal_col_summaries(data, col1, col2)) + + error_data <- tibble::tribble( + ~"id", ~"col1", ~"col2", + 1, "A", "A1", + 2, "A", "A2", + 3, "B", "B1", + 4, "B", "B2" + ) + + check_equal_col_summaries(error_data, col1, col2) %>% + expect_error(class = "names_glue_multi_checkbox") +}) diff --git a/tests/testthat/test-combine_checkboxes.R b/tests/testthat/test-combine_checkboxes.R new file mode 100644 index 00000000..2aa2f1f8 --- /dev/null +++ b/tests/testthat/test-combine_checkboxes.R @@ -0,0 +1,318 @@ +nonrepeat_data <- tibble::tribble( + ~"study_id", ~"multi___1", ~"multi___2", ~"multi___3", ~"single_checkbox___1", ~"extra_data", + 1, TRUE, FALSE, FALSE, TRUE, 1, + 2, TRUE, TRUE, FALSE, TRUE, 2, + 3, FALSE, FALSE, FALSE, FALSE, 3 +) + +nonrepeat_metadata <- tibble::tribble( + ~"field_name", ~"field_type", ~"select_choices_or_calculations", + "study_id", "text", NA, + "multi___1", "checkbox", "1, Red | 2, Yellow | 3, Blue", + "multi___2", "checkbox", "1, Red | 2, Yellow | 3, Blue", + "multi___3", "checkbox", "1, Red | 2, Yellow | 3, Blue", + "single_checkbox___1", "checkbox", "4, Green", + "extra_data", "dropdown", "1, 1 | 2, 2 | 3, 3" +) + +repeat_data <- tibble::tribble( + ~"study_id", ~"redcap_event", ~"redcap_form_instance", ~"repeat___1", ~"repeat___2", ~"repeat___3", + 1, "event_1", 1, TRUE, FALSE, FALSE, + 2, "event_1", 1, TRUE, TRUE, TRUE, + 2, "event_1", 2, FALSE, FALSE, FALSE +) + +repeat_metadata <- tibble::tribble( + ~"field_name", ~"field_type", ~"select_choices_or_calculations", + "study_id", "text", NA, + "repeat___1", "checkbox", "1, A | 2, B | 3, C", + "repeat___2", "checkbox", "1, A | 2, B | 3, C", + "repeat___3", "checkbox", "1, A | 2, B | 3, C" +) + +supertbl <- tibble::tribble( + ~"redcap_form_name", ~"redcap_data", ~"redcap_metadata", + "nonrepeat_instrument", nonrepeat_data, nonrepeat_metadata, + "repeat_instrument", repeat_data, repeat_metadata +) + +class(supertbl) <- c("redcap_supertbl", class(supertbl)) + +test_that("combine_checkboxes returns an expected supertbl", { + out <- combine_checkboxes( + supertbl = supertbl, + tbl = "nonrepeat_instrument", + cols = starts_with("multi") + ) # values_fill declared + + expect_setequal(class(out), c("redcap_supertbl", "tbl_df", "tbl", "data.frame")) + expect_equal(nrow(out), 2) +}) + +test_that("combine_checkboxes works for nonrepeat instrument", { + out <- combine_checkboxes( + supertbl = supertbl, + tbl = "nonrepeat_instrument", + cols = starts_with("multi"), + multi_value_label = "multiple", # multi_value_label declared + values_fill = "none" # values_fill declared + ) %>% + pull(redcap_data) %>% + dplyr::first() + + expected_out <- tibble::tribble( + ~"study_id", ~"multi___1", ~"multi___2", ~"multi___3", ~"single_checkbox___1", ~"extra_data", ~"multi", + 1, TRUE, FALSE, FALSE, TRUE, 1, "Red", + 2, TRUE, TRUE, FALSE, TRUE, 2, "multiple", + 3, FALSE, FALSE, FALSE, FALSE, 3, "none" + ) %>% + mutate( + multi = factor(multi, levels = c("Red", "Yellow", "Blue", "multiple", "none")) + ) + + expect_equal(out, expected_out) +}) + +test_that("combine_checkboxes glue spec works", { + out <- combine_checkboxes( + supertbl = supertbl, + tbl = "nonrepeat_instrument", + cols = starts_with("multi"), + names_glue = "{.value}_suffix", + multi_value_label = "multiple", # multi_value_label declared + values_fill = "none" # values_fill declared + ) %>% + pull(redcap_data) %>% + dplyr::first() + + expected_out <- tibble::tribble( + ~"study_id", ~"multi___1", ~"multi___2", ~"multi___3", ~"single_checkbox___1", ~"extra_data", ~"multi_suffix", + 1, TRUE, FALSE, FALSE, TRUE, 1, "Red", + 2, TRUE, TRUE, FALSE, TRUE, 2, "multiple", + 3, FALSE, FALSE, FALSE, FALSE, 3, "none" + ) %>% + mutate( + multi_suffix = factor(multi_suffix, levels = c("Red", "Yellow", "Blue", "multiple", "none")) + ) + + expect_equal(out, expected_out) + + # glue spec with multiple values + out <- combine_checkboxes( + supertbl = supertbl, + tbl = "nonrepeat_instrument", + cols = c(starts_with("multi"), starts_with("single_checkbox")), + names_glue = "{.value}_suffix", + multi_value_label = "multiple", # multi_value_label declared + values_fill = "none" # values_fill declared + ) %>% + pull(redcap_data) %>% + dplyr::first() + + expected_out <- tibble::tribble( + ~"study_id", ~"multi___1", ~"multi___2", ~"multi___3", ~"single_checkbox___1", + ~"extra_data", ~"multi_suffix", ~"single_checkbox_suffix", + 1, TRUE, FALSE, FALSE, TRUE, 1, "Red", "Green", + 2, TRUE, TRUE, FALSE, TRUE, 2, "multiple", "Green", + 3, FALSE, FALSE, FALSE, FALSE, 3, "none", "none" + ) %>% + mutate( + multi_suffix = factor(multi_suffix, levels = c("Red", "Yellow", "Blue", "multiple", "none")), + single_checkbox_suffix = factor(single_checkbox_suffix, levels = c("Green", "multiple", "none")) + ) + + expect_equal(out, expected_out) +}) + +test_that("combine_checkboxes works for nonrepeat instrument and drop old values", { + out <- combine_checkboxes( + supertbl = supertbl, + tbl = "nonrepeat_instrument", + cols = starts_with("multi"), + keep = FALSE # Test keep = FALSE + ) %>% + pull(redcap_data) %>% + dplyr::first() + + expected_out <- tibble::tribble( + ~"study_id", ~"single_checkbox___1", ~"extra_data", ~"multi", + 1, TRUE, 1, "Red", + 2, TRUE, 2, "Multiple", + 3, FALSE, 3, NA + ) %>% + mutate( + multi = factor(multi, levels = c("Red", "Yellow", "Blue", "Multiple")) + ) + + expect_equal(out, expected_out) +}) + +test_that("combine_checkboxes works for repeat instrument", { + out <- combine_checkboxes( + supertbl = supertbl, + tbl = "repeat_instrument", + cols = starts_with("repeat") + ) %>% + pull(redcap_data) %>% + dplyr::nth(2) + + expected_out <- tibble::tribble( + ~"study_id", ~"redcap_event", ~"redcap_form_instance", ~"repeat___1", ~"repeat___2", ~"repeat___3", ~"repeat", + 1, "event_1", 1, TRUE, FALSE, FALSE, "A", + 2, "event_1", 1, TRUE, TRUE, TRUE, "Multiple", + 2, "event_1", 2, FALSE, FALSE, FALSE, NA + ) %>% + mutate( + `repeat` = factor(`repeat`, levels = c("A", "B", "C", "Multiple")) + ) + + expect_equal(out, expected_out) +}) + +test_that("get_metadata_spec works", { + out <- get_metadata_spec( + metadata_tbl = supertbl$redcap_metadata[[1]], + selected_cols = c("multi___1", "multi___2", "multi___3"), + names_prefix = "", names_sep = "_", names_glue = NULL # Mimic defaults + ) + + expected_out <- tibble::tribble( + ~"field_name", ~".value", ~".new_value", ~"raw", ~"label", + "multi___1", "multi", "multi", "1", "Red", + "multi___2", "multi", "multi", "2", "Yellow", + "multi___3", "multi", "multi", "3", "Blue" + ) + + expect_equal(out, expected_out) +}) + +test_that("replace_true works", { + metadata <- tibble::tribble( + ~"field_name", ~"raw", ~"label", + "multi___1", "1", "Red", + "multi___2", "2", "Yellow", + "multi___3", "3", "Blue" + ) + + out <- replace_true(col = c(TRUE, TRUE, FALSE), col_name = "multi___1", metadata = metadata, raw_or_label = "raw") + expected_out <- c("1", "1", NA) + + expect_equal(out, expected_out) + + out <- replace_true(col = c(TRUE, TRUE, FALSE), col_name = "multi___1", metadata = metadata, raw_or_label = "label") + expected_out <- c("Red", "Red", NA) + + expect_equal(out, expected_out) +}) + +test_that("combine_checkboxes works for multiple checkbox fields", { + out <- combine_checkboxes( + supertbl = supertbl, + tbl = "nonrepeat_instrument", + cols = c(starts_with("multi"), starts_with("single_checkbox")), + keep = FALSE + ) %>% + pull(redcap_data) %>% + dplyr::first() + + expected_out <- tibble::tribble( + ~"study_id", ~"extra_data", ~"multi", ~"single_checkbox", + 1, 1, "Red", "Green", + 2, 2, "Multiple", "Green", + 3, 3, NA, NA + ) %>% + mutate( + multi = factor(multi, levels = c("Red", "Yellow", "Blue", "Multiple")), + single_checkbox = factor(single_checkbox, levels = c("Green", "Multiple")) + ) + + expect_equal(out, expected_out) +}) + +test_that("combine_checkboxes works for multiple checkbox fields with logicals", { + out <- combine_checkboxes( + supertbl = supertbl, + tbl = "nonrepeat_instrument", + cols = c(starts_with("multi") | starts_with("single_checkbox")), + keep = FALSE + ) %>% + pull(redcap_data) %>% + dplyr::first() + + expected_out <- tibble::tribble( + ~"study_id", ~"extra_data", ~"multi", ~"single_checkbox", + 1, 1, "Red", "Green", + 2, 2, "Multiple", "Green", + 3, 3, NA, NA + ) %>% + mutate( + multi = factor(multi, levels = c("Red", "Yellow", "Blue", "Multiple")), + single_checkbox = factor(single_checkbox, levels = c("Green", "Multiple")) + ) + + expect_equal(out, expected_out) +}) + +test_that("convert_checkbox_vals works()", { + metadata <- tibble::tribble( + ~"field_name", ~".value", ~"raw", ~"label", + "multi___1", "multi", "1", "Red", + "multi___2", "multi", "2", "Yellow", + "multi___3", "multi", "3", "Blue" + ) + + # Same as nonrepeat data tbl but with NAs for FALSEs, post processed with metadata spec vals + data_tbl <- tibble::tribble( + ~"study_id", ~"multi___1", ~"multi___2", ~"multi___3", ~"single_checkbox___1", ~"extra_data", + 1, "Red", NA, NA, "Green", 1, + 2, "Red", "Yellow", NA, "Green", 2, + 3, NA, NA, NA, NA, 3 + ) + + out <- convert_checkbox_vals( + metadata = metadata, .new_value = "_multi", data_tbl = data_tbl, + raw_or_label = "label", multi_value_label = "multi", values_fill = NA + ) + + expected_out <- tibble( + `_multi` = factor(c("Red", "multi", NA), levels = c("Red", "Yellow", "Blue", "multi")) + ) + + expect_equal(out, expected_out) +}) + +test_that("combine_and_repair_tbls works", { + data_tbl <- tibble( + id = 1, + x___1 = TRUE, + x___2 = FALSE, + x = "val" + ) + + data_tbl_mod <- tibble( + id = 1, + x___1 = "A", + x___2 = NA, + x = "val" + ) + + new_cols <- list(x = "A") + + expect_error(combine_and_repair_tbls(data_tbl, data_tbl_mod, new_cols, names_repair = "check_unique")) + expect_no_error(combine_and_repair_tbls(data_tbl, data_tbl_mod, new_cols, names_repair = "unique")) %>% + suppressMessages() + + expected_out <- tibble( + id = 1, + x___1 = TRUE, + x___2 = FALSE, + x...4 = "val", + x...5 = "A" + ) + + expect_equal( + expected_out, + combine_and_repair_tbls(data_tbl, data_tbl_mod, new_cols, names_repair = "unique") + ) %>% + suppressMessages() +}) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index e5b7f9a2..2fe31dbd 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -496,3 +496,38 @@ test_that("apply_labs_factor works", { test_that("force_cast converts non chr/numerics to chr", { expect_character(force_cast("2023-01-01", as.Date(NA))) }) + +test_that("get_record_id_field works", { + data_tbl <- tibble::tribble( + ~"test_name", ~"test_value", + 1, 2 + ) + + expect_equal(get_record_id_field(data_tbl), "test_name") +}) + +test_that("extract_metadata_tibble works", { + inst_1_metadata <- tibble::tribble( + ~"field_name", ~"field_type", + "study_id", "text", + "text", "text", + ) + + inst_2_metadata <- tibble::tribble( + ~"field_name", ~"field_type", + "study_id", "text", + "calulated", "calc", + ) + + supertbl <- tibble::tribble( + ~"redcap_form_name", ~"redcap_metadata", + "inst_1", inst_1_metadata, + "inst_2", inst_2_metadata + ) + + class(supertbl) <- c("redcap_supertbl", class(supertbl)) + + out <- extract_metadata_tibble(supertbl = supertbl, redcap_form_name = "inst_1") + + expect_equal(out, inst_1_metadata) +}) diff --git a/utility/refresh.R b/utility/refresh.R index 3d580ec4..82518bda 100644 --- a/utility/refresh.R +++ b/utility/refresh.R @@ -67,7 +67,7 @@ devtools::check( # Equivalent of R-hub rhub::rhub_check(platforms = c("linux", "windows", "ubuntu-next", "ubuntu-release")) devtools::check_win_devel(email = "porterej@chop.edu") # CRAN submission policies encourage the development version # Note: Must be off of VPN -revdepcheck::revdep_check(num_workers = 4) +# revdepcheck::revdep_check(num_workers = 4) # Deprecated # Careful, the last question ultimately uploads it to CRAN, where you can't delete/reverse your decision. # Run as not CRAN to build full vignettes # withr::with_envvar(