diff --git a/DESCRIPTION b/DESCRIPTION index acb15ebf..e748ceb0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -41,7 +41,9 @@ Imports: purrr (>= 0.3.3), rlang (>= 0.4.4), stringr (>= 1.4.0), - tibble + tibble, + tidyr, + vctrs Suggests: knitr, rmarkdown, diff --git a/NAMESPACE b/NAMESPACE index 8ccc1503..708540e0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,10 +1,16 @@ # Generated by roxygen2: do not edit by hand S3method(print,iso8601) +export(assign_ct) +export(assign_no_ct) export(create_iso8601) +export(ct_map) export(fmt_cmp) +export(hardcode_ct) export(hardcode_no_ct) export(problems) +importFrom(rlang,"%||%") importFrom(rlang,":=") importFrom(rlang,.data) +importFrom(stats,na.omit) importFrom(tibble,tibble) diff --git a/R/assign.R b/R/assign.R new file mode 100644 index 00000000..bdaf46df --- /dev/null +++ b/R/assign.R @@ -0,0 +1,388 @@ +#' @importFrom rlang := +sdtm_assign <- function(raw_dat, + raw_var, + tgt_var, + tgt_dat = raw_dat, + by = NULL, + ct = NULL, + cl = NULL) { + + # TODO: Assertions. + + raw_dat |> + dplyr::right_join(y = tgt_dat, by = by) |> + dplyr::mutate("{tgt_var}" := ct_map(!!rlang::sym(raw_var), ct = ct, cl = cl)) |> + dplyr::select(-rlang::sym(raw_var)) |> + dplyr::relocate(tgt_var, .after = dplyr::last_col()) + +} + +#' Derive an SDTM variable +#' +#' @description +#' - [assign_no_ct()] maps a variable in a source dataset to a target SDTM +#' variable that has no terminology restrictions. +#' +#' - [assign_ct()] maps a variable in a source dataset to a target SDTM variable +#' following controlled terminology recoding. +#' +#' @param raw_dataset The raw dataset. +#' @param raw_variable The raw variable. +#' @param target_sdtm_variable The target SDTM variable. +#' @param target_dataset Target dataset. By default the same as `raw_dataset`. +#' @param merge_to_topic_by If `target_dataset` is different than `raw_dataset`, +#' then this parameter defines keys to use in the join between `raw_dataset` +#' and `target_dataset`. +#' @param study_ct Study controlled terminology specification. +#' @param target_sdtm_variable_codelist_code A codelist code indicating which +#' subset of the controlled terminology to apply in the derivation. +#' +#' @returns The target dataset with the derived variable `target_sdtm_variable`. +#' +#' @examples +#' study_ct <- +#' tibble::tibble( +#' codelist_code = rep("C66729", 8L), +#' term_code = c( +#' "C28161", +#' "C38210", +#' "C38222", +#' "C38223", +#' "C38287", +#' "C38288", +#' "C38305", +#' "C38311" +#' ), +#' CodedData = c( +#' "INTRAMUSCULAR", +#' "EPIDURAL", +#' "INTRA-ARTERIAL", +#' "INTRA-ARTICULAR", +#' "OPHTHALMIC", +#' "ORAL", +#' "TRANSDERMAL", +#' "UNKNOWN" +#' ), +#' term_value = CodedData, +#' collected_value = c( +#' "IM (Intramuscular)", +#' "EP (Epidural)", +#' "IA (Intra-arterial)", +#' "IJ (Intra-articular)", +#' "OP (Ophthalmic)", +#' "PO (Oral)", +#' "DE (Transdermal)", +#' "Unknown" +#' ), +#' term_preferred_term = c( +#' "Intramuscular Route of Administration", +#' "Epidural Route of Administration", +#' "Intraarterial Route of Administration", +#' "Intraarticular Route of Administration", +#' "Ophthalmic Route of Administration", +#' "Oral Route of Administration", +#' "Transdermal Route of Administration", +#' "Unknown Route of Administration" +#' ), +#' term_synonyms = c(rep(NA, 5L), "Intraoral Route of Administration; PO", NA, NA), +#' raw_codelist = rep("ROUTE_CV1", 8L) +#' ) +#' +#' md1 <- +#' tibble::tibble( +#' oak_id = 1:14, +#' raw_source = "MD1", +#' PATIENT_NUM = 101:114, +#' MDRTE = c( +#' "PO (Oral)", +#' "PO (Oral)", +#' NA_character_, +#' "PO", +#' "Intraoral Route of Administration", +#' "PO (Oral)", +#' "IM (Intramuscular)", +#' "IA (Intra-arterial)", +#' "", +#' "Non-standard", +#' "random_value", +#' "IJ (Intra-articular)", +#' "TRANSDERMAL", +#' "OPHTHALMIC" +#' ) +#' ) +#' +#' assign_ct( +#' raw_dataset = md1, +#' raw_variable = "MDRTE", +#' study_ct = study_ct, +#' target_sdtm_variable = "CMROUTE", +#' target_sdtm_variable_codelist_code = "C66729" +#' ) +#' +#' cm_inter <- +#' tibble::tibble( +#' oak_id = 1:14, +#' raw_source = "MD1", +#' PATIENT_NUM = 101:114, +#' CMTRT = c( +#' "BABY ASPIRIN", +#' "CORTISPORIN", +#' "ASPIRIN", +#' "DIPHENHYDRAMINE HCL", +#' "PARCETEMOL", +#' "VOMIKIND", +#' "ZENFLOX OZ", +#' "AMITRYPTYLINE", +#' "BENADRYL", +#' "DIPHENHYDRAMINE HYDROCHLORIDE", +#' "TETRACYCLINE", +#' "BENADRYL", +#' "SOMINEX", +#' "ZQUILL" +#' ), +#' CMINDC = c( +#' NA, +#' "NAUSEA", +#' "ANEMIA", +#' "NAUSEA", +#' "PYREXIA", +#' "VOMITINGS", +#' "DIARHHEA", +#' "COLD", +#' "FEVER", +#' "LEG PAIN", +#' "FEVER", +#' "COLD", +#' "COLD", +#' "PAIN" +#' ), +#' CMROUTE = c( +#' "ORAL", +#' "ORAL", +#' NA, +#' "ORAL", +#' "ORAL", +#' "ORAL", +#' "INTRAMUSCULAR", +#' "INTRA-ARTERIAL", +#' NA, +#' "NON-STANDARD", +#' "RANDOM_VALUE", +#' "INTRA-ARTICULAR", +#' "TRANSDERMAL", +#' "OPHTHALMIC" +#' ) +#' ) +#' +#' assign_ct( +#' raw_dataset = md1, +#' raw_variable = "MDRTE", +#' study_ct = study_ct, +#' target_sdtm_variable = "CMROUTE", +#' target_sdtm_variable_codelist_code = "C66729", +#' target_dataset = cm_inter, +#' merge_to_topic_by = c("oak_id","raw_source","PATIENT_NUM") +#' ) +#' +#' @name assign +NULL + +#' @export +#' @rdname assign +assign_no_ct <- function(raw_dataset, + raw_variable, + target_sdtm_variable, + target_dataset = raw_dataset, + merge_to_topic_by = NULL) { + sdtm_assign( + raw_dat = raw_dataset, + raw_var = raw_variable, + tgt_var = target_sdtm_variable, + tgt_dat = target_dataset, + by = merge_to_topic_by + ) +} + +#' Derive an SDTM variable with controlled terminology +#' +#' [assign_ct()] maps a variable in a source dataset to a target SDTM variable +#' following controlled terminology recoding. +#' +#' @param raw_dataset The raw dataset. +#' @param raw_variable The raw variable. +#' @param target_sdtm_variable The target SDTM variable. +#' @param target_dataset Target dataset. By default the same as `raw_dataset`. +#' @param merge_to_topic_by If `target_dataset` is different than `raw_dataset`, +#' then this parameter defines keys to use in the join between `raw_dataset` +#' and `target_dataset`. +#' @param study_ct Study controlled terminology specification. +#' @param target_sdtm_variable_codelist_code A codelist code indicating which +#' subset of the controlled terminology to apply in the derivation. +#' +#' @returns The target dataset with the derived variable `target_sdtm_variable`. +#' +#' @examples +#' study_ct <- +#' tibble::tibble( +#' codelist_code = rep("C66729", 8L), +#' term_code = c( +#' "C28161", +#' "C38210", +#' "C38222", +#' "C38223", +#' "C38287", +#' "C38288", +#' "C38305", +#' "C38311" +#' ), +#' CodedData = c( +#' "INTRAMUSCULAR", +#' "EPIDURAL", +#' "INTRA-ARTERIAL", +#' "INTRA-ARTICULAR", +#' "OPHTHALMIC", +#' "ORAL", +#' "TRANSDERMAL", +#' "UNKNOWN" +#' ), +#' term_value = CodedData, +#' collected_value = c( +#' "IM (Intramuscular)", +#' "EP (Epidural)", +#' "IA (Intra-arterial)", +#' "IJ (Intra-articular)", +#' "OP (Ophthalmic)", +#' "PO (Oral)", +#' "DE (Transdermal)", +#' "Unknown" +#' ), +#' term_preferred_term = c( +#' "Intramuscular Route of Administration", +#' "Epidural Route of Administration", +#' "Intraarterial Route of Administration", +#' "Intraarticular Route of Administration", +#' "Ophthalmic Route of Administration", +#' "Oral Route of Administration", +#' "Transdermal Route of Administration", +#' "Unknown Route of Administration" +#' ), +#' term_synonyms = c(rep(NA, 5L), "Intraoral Route of Administration; PO", NA, NA), +#' raw_codelist = rep("ROUTE_CV1", 8L) +#' ) +#' +#' md1 <- +#' tibble::tibble( +#' oak_id = 1:14, +#' raw_source = "MD1", +#' PATIENT_NUM = 101:114, +#' MDRTE = c( +#' "PO (Oral)", +#' "PO (Oral)", +#' NA_character_, +#' "PO", +#' "Intraoral Route of Administration", +#' "PO (Oral)", +#' "IM (Intramuscular)", +#' "IA (Intra-arterial)", +#' "", +#' "Non-standard", +#' "random_value", +#' "IJ (Intra-articular)", +#' "TRANSDERMAL", +#' "OPHTHALMIC" +#' ) +#' ) +#' +#' assign_ct( +#' raw_dataset = md1, +#' raw_variable = "MDRTE", +#' study_ct = study_ct, +#' target_sdtm_variable = "CMROUTE", +#' target_sdtm_variable_codelist_code = "C66729" +#' ) +#' +#' cm_inter <- +#' tibble::tibble( +#' oak_id = 1:14, +#' raw_source = "MD1", +#' PATIENT_NUM = 101:114, +#' CMTRT = c( +#' "BABY ASPIRIN", +#' "CORTISPORIN", +#' "ASPIRIN", +#' "DIPHENHYDRAMINE HCL", +#' "PARCETEMOL", +#' "VOMIKIND", +#' "ZENFLOX OZ", +#' "AMITRYPTYLINE", +#' "BENADRYL", +#' "DIPHENHYDRAMINE HYDROCHLORIDE", +#' "TETRACYCLINE", +#' "BENADRYL", +#' "SOMINEX", +#' "ZQUILL" +#' ), +#' CMINDC = c( +#' NA, +#' "NAUSEA", +#' "ANEMIA", +#' "NAUSEA", +#' "PYREXIA", +#' "VOMITINGS", +#' "DIARHHEA", +#' "COLD", +#' "FEVER", +#' "LEG PAIN", +#' "FEVER", +#' "COLD", +#' "COLD", +#' "PAIN" +#' ), +#' CMROUTE = c( +#' "ORAL", +#' "ORAL", +#' NA, +#' "ORAL", +#' "ORAL", +#' "ORAL", +#' "INTRAMUSCULAR", +#' "INTRA-ARTERIAL", +#' NA, +#' "NON-STANDARD", +#' "RANDOM_VALUE", +#' "INTRA-ARTICULAR", +#' "TRANSDERMAL", +#' "OPHTHALMIC" +#' ) +#' ) +#' +#' assign_ct( +#' raw_dataset = md1, +#' raw_variable = "MDRTE", +#' study_ct = study_ct, +#' target_sdtm_variable = "CMROUTE", +#' target_sdtm_variable_codelist_code = "C66729", +#' target_dataset = cm_inter, +#' merge_to_topic_by = c("oak_id","raw_source","PATIENT_NUM") +#' ) +#' +#' +#' @export +#' @rdname assign +assign_ct <- function(raw_dataset, + raw_variable, + target_sdtm_variable, + target_dataset = raw_dataset, + merge_to_topic_by = NULL, + study_ct = NULL, + target_sdtm_variable_codelist_code = NULL) { + sdtm_assign( + raw_dat = raw_dataset, + raw_var = raw_variable, + tgt_var = target_sdtm_variable, + tgt_dat = target_dataset, + by = merge_to_topic_by, + ct = study_ct, + cl = target_sdtm_variable_codelist_code + ) +} diff --git a/R/ct.R b/R/ct.R new file mode 100644 index 00000000..714460b7 --- /dev/null +++ b/R/ct.R @@ -0,0 +1,104 @@ +#' Controlled terminology mappings +#' +#' @description +#' [ct_mappings()] takes a controlled terminology specification and returns the +#' mappings in the form of a [tibble][tibble::tibble-package] in long format, +#' i.e. the recoding of values in the `from` column to the `to` column values, +#' one mapping per row. +#' +#' The resulting mappings are unique, i.e. if `from` values are duplicated in +#' two `from` columns, the first column indicated in `from` takes precedence, +#' and only that mapping is retained in the controlled terminology map. +#' +#' @param ct Controlled terminology specification as a +#' [tibble][tibble::tibble-package]. Each row is for a mapped controlled term. +#' Controlled terms are expected in the column indicated by `to_col`. +#' @param from A character vector of column names indicating the variables +#' containing values to be recoded. +#' @param to A single string indicating the column whose values are to be +#' recoded into. +#' +#' @returns A [tibble][tibble::tibble-package] with two columns, `from` and +#' `to`, indicating the mapping of values, one per row. +#' +#' @examples +#' # example code +#' +#' +#' +#' +#' +#' +#' +#' @importFrom rlang .data +#' @keywords internal +ct_mappings <- function(ct, from = c("collected_value", "term_synonyms"), to = "term_value") { + + # TODO: Assertions and memoisation. + + cols <- c(to, from) + + ct_mappings <- + ct |> + dplyr::mutate(to = !!rlang::sym(to)) |> + tidyr::pivot_longer(cols = dplyr::all_of(cols), + values_to = "from", + names_to = "type") |> + dplyr::select(c("type", "from", "to")) |> + dplyr::mutate(type = factor(.data$type, levels = cols)) |> + dplyr::arrange(.data$type) |> + dplyr::select(-"type") |> + tidyr::drop_na(.data$from) |> + dplyr::mutate(from = str_split(.data$from)) |> + tidyr::unnest(from) |> + dplyr::filter(from != "") |> # In case the split resulted in empty strings. + dplyr::mutate(from = trimws(.data$from), to = trimws(.data$to)) |> + dplyr::distinct(.data$from, .keep_all = TRUE) + + ct_mappings +} + +#' Recode according to controlled terminology +#' +#' [ct_map()] recodes a vector following a controlled terminology. +#' +#' @param x A character vector of terms to be recoded following a controlled +#' terminology. +#' @param ct A [tibble][tibble::tibble-package] providing a controlled +#' terminology specification. +#' @param cl A character vector indicating a set of possible controlled +#' terminology code-lists codes to be used for recoding. By default (`NULL`) +#' all code-lists available in `ct` are used. +#' @param from A character vector of column names indicating the variables +#' containing values to be matched against for terminology recoding. +#' @param to A single string indicating the column whose values are to be +#' recoded into. +#' +#' @returns A character vector of terminology recoded values from `x`. If no +#' match is found in the controlled terminology spec provided in `ct`, then +#' `x` values are returned in uppercase. If `ct` is not provided `x` is +#' returned unchanged. +#' +#' @importFrom rlang %||% .data +#' @export +ct_map <- + function(x, + ct = NULL, + cl = NULL, + from = c("collected_value", "term_synonyms"), + to = "term_value") { + + ct %||% return(x) + + cl <- cl %||% unique(ct$codelist_code) + ct <- dplyr::filter(ct, .data$codelist_code %in% cl) + + mappings <- ct_mappings(ct, from = from, to = to) + recode( + x, + from = mappings$from, + to = mappings$to, + .no_match = toupper(x) + ) + + } diff --git a/R/hardcode_no_ct.R b/R/hardcode.R similarity index 51% rename from R/hardcode_no_ct.R rename to R/hardcode.R index c7249da3..f5720a6e 100644 --- a/R/hardcode_no_ct.R +++ b/R/hardcode.R @@ -1,8 +1,36 @@ +#' @importFrom rlang := +#' @keywords internal +sdtm_hardcode <- function(raw_dat, + raw_var, + tgt_var, + tgt_val, + tgt_dat = raw_dat, + by = NULL, + ct = NULL, + cl = NULL) { + + # TODO: Assertions. + + tgt_val <- ct_map(tgt_val, ct = ct, cl = cl) + + raw_dat |> + dplyr::right_join(y = tgt_dat, by = by) |> + dplyr::mutate("{tgt_var}" := recode(x = !!rlang::sym(raw_var), to = tgt_val)) |> + dplyr::select(-rlang::sym(raw_var)) |> + dplyr::relocate(tgt_var, .after = dplyr::last_col()) + +} + #' Derive an SDTM variable with a hardcoded value #' -#' [hardcode_no_ct()] maps a hardcoded value to a target SDTM variable that has +#' +#' @description +#' - [hardcode_no_ct()] maps a hardcoded value to a target SDTM variable that has #' no terminology restrictions. #' +#' - [hardcode_ct()] maps a hardcoded value to a target SDTM variable with +#' controlled terminology recoding. +#' #' @param raw_dataset The raw dataset. #' @param raw_variable The raw variable. #' @param target_sdtm_variable The target SDTM variable. @@ -11,6 +39,11 @@ #' @param merge_to_topic_by If `target_dataset` is different than `raw_dataset`, #' then this parameter defines keys to use in the join between `raw_dataset` #' and `target_dataset`. +#' @param study_ct Study controlled terminology specification. +#' @param target_sdtm_variable_codelist_code A codelist code indicating which +#' subset of the controlled terminology to apply in the derivation. +#' +#' @returns The target dataset with the derived variable `target_sdtm_variable`. #' #' @examples #' MD1 <- @@ -44,7 +77,6 @@ #' # Derive a new variable `CMCAT` by overwriting `MDRAW` with the #' # hardcoded value "GENERAL CONCOMITANT MEDICATIONS" with a prior join to #' # `target_dataset`. -#' #' hardcode_no_ct( #' raw_dataset = MD1, #' raw_variable = "MDRAW", @@ -54,17 +86,46 @@ #' merge_to_topic_by = c("oak_id", "raw_source", "patient_number") #' ) #' -#' @importFrom rlang := +#' @name harcode +NULL + #' @export +#' @rdname harcode hardcode_no_ct <- function(raw_dataset, raw_variable, target_sdtm_variable, target_hardcoded_value, target_dataset = raw_dataset, merge_to_topic_by = NULL) { - raw_dataset |> - dplyr::mutate("{target_sdtm_variable}" := overwrite(!!rlang::sym(raw_variable), target_hardcoded_value)) |> - dplyr::right_join(y = target_dataset, by = merge_to_topic_by) |> - dplyr::select(-rlang::sym(raw_variable)) |> - dplyr::relocate(target_sdtm_variable, .after = dplyr::last_col()) + sdtm_hardcode( + raw_dat = raw_dataset, + raw_var = raw_variable, + tgt_var = target_sdtm_variable, + tgt_val = target_hardcoded_value, + tgt_dat = target_dataset, + by = merge_to_topic_by + ) +} + +#' @export +#' @rdname harcode +hardcode_ct <- function(raw_dataset, + raw_variable, + target_sdtm_variable, + target_hardcoded_value, + target_dataset = raw_dataset, + merge_to_topic_by = NULL, + study_ct = NULL, + target_sdtm_variable_codelist_code = NULL) { + sdtm_hardcode( + raw_dat = raw_dataset, + raw_var = raw_variable, + tgt_var = target_sdtm_variable, + tgt_val = target_hardcoded_value, + tgt_dat = target_dataset, + by = merge_to_topic_by, + ct = study_ct, + cl = target_sdtm_variable_codelist_code + ) } + diff --git a/R/recode.R b/R/recode.R index 07fe9a09..a645bcb2 100644 --- a/R/recode.R +++ b/R/recode.R @@ -1,64 +1,8 @@ -#' Overwrite values +#' Determine Indices for Recoding #' -#' @description -#' [overwrite()] recodes values in `x` to a new set of values provided in `to`; -#' the values in `to` are recycled to match the length of `x`. By default, -#' missing values remain `NA`. -#' -#' @param x An atomic vector. -#' @param .na New value for missing values in `x`. Defaults to `NA`. -#' -#' @returns A vector of the same length of `x` with new values matching those -#' in `to`. -#' -#' @examples -#' x <- c(letters[1:4], NA, NA) -#' # Recode all values to `"x"` but keep `NA`. -#' sdtm.oak:::overwrite(x, to = "x") -#' -#' # Recode all values to `"x"` but recode `NA` to a new value. -#' sdtm.oak:::overwrite(x, to = "x", .na = "x") -#' sdtm.oak:::overwrite(x, to = "x", .na = "Absent") -#' -#' # If `to` is not a scalar, it is recycled and matched by position for -#' # replacement. -#' sdtm.oak:::overwrite(x, to = c("x", "y")) -#' -#' # `x` can be of other types besides `character`, e.g. replace integers to a -#' # hard-coded new integer value. -#' sdtm.oak:::overwrite(x = 1:5, to = 0) -#' -#' # Example involving `logical` vectors -#' sdtm.oak:::overwrite(x = c(TRUE, FALSE), to = FALSE) -#' -#' # Returned type will be a type compatible with both the types of `to` and -#' # `.na`. -#' sdtm.oak:::overwrite(x = c("sdtm", "adam"), to = 0) -#' sdtm.oak:::overwrite( -#' x = c("sdtm", "adam"), -#' to = 0, -#' .na = NA_character_ -#' ) -#' sdtm.oak:::overwrite( -#' x = c("sdtm", "adam"), -#' to = TRUE, -#' .na = NA_real_ -#' ) -#' -#' @keywords internal -overwrite <- function(x, to, .na = NA) { - # y <- rep_len(to, length(x)) - y <- rlang::rep_along(x, to) - y[is.na(x)] <- .na - - y -} - -#' Determine Indices for Rewriting -#' -#' [index_for_rewrite()] identifies the positions of elements in `x` that match +#' [index_for_recode()] identifies the positions of elements in `x` that match #' any of the values specified in the `from` vector. This function is primarily -#' used to facilitate the rewriting of values by pinpointing which elements in +#' used to facilitate the recoding of values by pinpointing which elements in #' `x` correspond to the `from` values and thus need to be replaced or updated. #' #' @param x A vector of values in which to search for matches. @@ -66,47 +10,47 @@ overwrite <- function(x, to, .na = NA) { #' @return An integer vector of the same length as `x`, containing the indices #' of the matched values from the `from` vector. If an element in `x` does not #' match any value in `from`, the corresponding position in the output will be -#' `NA`. This index information is critical for subsequent rewrite operations. +#' `NA`. This index information is critical for subsequent recoding operations. #' @examples -#' sdtm.oak:::index_for_rewrite(x = 1:5, from = c(2, 4)) +#' sdtm.oak:::index_for_recode(x = 1:5, from = c(2, 4)) #' #' @keywords internal -index_for_rewrite <- function(x, from) { +index_for_recode <- function(x, from) { match(x, from) } -#' Are values to be rewritten? +#' Are values to be recoded? #' -#' `are_to_rewrite` is a helper function designed to determine if any values +#' `are_to_recode` is a helper function designed to determine if any values #' in a vector `x` match the specified `from` values, indicating they are -#' candidates for recoding or rewriting. +#' candidates for recoding. #' #' @param x A vector of values that will be checked against the `from` vector. #' @param from A vector of values that `x` will be checked for matches against. #' @return A logical vector of the same length as `x`, where `TRUE` indicates #' that the corresponding value in `x` matches a value in `from` and -#' should be rewritten, and `FALSE` otherwise. If `x` is empty, returns +#' should be recoded, and `FALSE` otherwise. If `x` is empty, returns #' an empty logical vector. This function is intended for internal use #' and optimization in data transformation processes. #' @keywords internal #' @examples -#' sdtm.oak:::are_to_rewrite(x = 1:5, from = c(2, 4)) +#' sdtm.oak:::are_to_recode(x = 1:5, from = c(2, 4)) #' -#' sdtm.oak:::are_to_rewrite(letters[1:3], from = c("a", "c")) +#' sdtm.oak:::are_to_recode(letters[1:3], from = c("a", "c")) #' #' @keywords internal -are_to_rewrite <- function(x, from) { +are_to_recode <- function(x, from) { # match(x, from, nomatch = 0) != 0 - !is.na(index_for_rewrite(x, from)) + !is.na(index_for_recode(x, from)) } -#' Rewrite values +#' Recode values #' -#' [rewrite()] recodes values in `x` by matching elements in `from` onto values +#' [recode()] recodes values in `x` by matching elements in `from` onto values #' in `to`. #' #' @param x An atomic vector of values are to be recoded. -#' @param from A vector of values to be matched in `x` for rewriting. +#' @param from A vector of values to be matched in `x` for recoded. #' @param to A vector of values to be used as replacement for values in `from`. #' @param .no_match Value to be used as replacement when cases in `from` are not #' matched. @@ -116,17 +60,17 @@ are_to_rewrite <- function(x, from) { #' #' @examples #' x <- c("male", "female", "x", NA) -#' sdtm.oak:::rewrite(x, +#' sdtm.oak:::recode(x, #' from = c("male", "female"), #' to = c("M", "F") #' ) -#' sdtm.oak:::rewrite( +#' sdtm.oak:::recode( #' x, #' from = c("male", "female"), #' to = c("M", "F"), #' .no_match = "?" #' ) -#' sdtm.oak:::rewrite( +#' sdtm.oak:::recode( #' x, #' from = c("male", "female"), #' to = c("M", "F"), @@ -134,13 +78,14 @@ are_to_rewrite <- function(x, from) { #' ) #' #' @keywords internal -rewrite <- function(x, - from, - to, +recode <- function(x, + from = unique(na.omit(x)), + to = from, .no_match = x, .na = NA) { - to <- rlang::rep_along(x, to) - index <- index_for_rewrite(x, from) + # to <- rlang::rep_along(x, to) + to <- vctrs::vec_recycle(to, length(from)) + index <- index_for_recode(x, from) y <- ifelse(!is.na(index), to[index], .no_match) y[is.na(x)] <- .na diff --git a/R/sdtm.oak-package.R b/R/sdtm.oak-package.R index b1a48e6f..0ba23dc1 100644 --- a/R/sdtm.oak-package.R +++ b/R/sdtm.oak-package.R @@ -4,5 +4,6 @@ ## usethis namespace: start #' @importFrom tibble tibble #' @importFrom rlang .data +#' @importFrom stats na.omit ## usethis namespace: end NULL diff --git a/R/str_split.R b/R/str_split.R new file mode 100644 index 00000000..7b5b1125 --- /dev/null +++ b/R/str_split.R @@ -0,0 +1,13 @@ +str_split_ <- function(x, split = ";", quote = '"') { + scan( + text = x, + what = "character", + sep = split, + quote = quote, + quiet = TRUE + ) +} + +str_split <- function(x, split = ";", quote = '"') { + lapply(x, str_split_, split = split, quote = quote) +} diff --git a/inst/WORDLIST b/inst/WORDLIST index 36a406da..0a2a530f 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -11,3 +11,9 @@ iso hardcoded CDISC PMDA +recode +recodes +recoded +recoding +tibble +codelist diff --git a/man/are_to_rewrite.Rd b/man/are_to_recode.Rd similarity index 62% rename from man/are_to_rewrite.Rd rename to man/are_to_recode.Rd index 9fc7e753..bbc5750e 100644 --- a/man/are_to_rewrite.Rd +++ b/man/are_to_recode.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/recode.R -\name{are_to_rewrite} -\alias{are_to_rewrite} -\title{Are values to be rewritten?} +\name{are_to_recode} +\alias{are_to_recode} +\title{Are values to be recoded?} \usage{ -are_to_rewrite(x, from) +are_to_recode(x, from) } \arguments{ \item{x}{A vector of values that will be checked against the \code{from} vector.} @@ -14,19 +14,19 @@ are_to_rewrite(x, from) \value{ A logical vector of the same length as \code{x}, where \code{TRUE} indicates that the corresponding value in \code{x} matches a value in \code{from} and -should be rewritten, and \code{FALSE} otherwise. If \code{x} is empty, returns +should be recoded, and \code{FALSE} otherwise. If \code{x} is empty, returns an empty logical vector. This function is intended for internal use and optimization in data transformation processes. } \description{ -\code{are_to_rewrite} is a helper function designed to determine if any values +\code{are_to_recode} is a helper function designed to determine if any values in a vector \code{x} match the specified \code{from} values, indicating they are -candidates for recoding or rewriting. +candidates for recoding. } \examples{ -sdtm.oak:::are_to_rewrite(x = 1:5, from = c(2, 4)) +sdtm.oak:::are_to_recode(x = 1:5, from = c(2, 4)) -sdtm.oak:::are_to_rewrite(letters[1:3], from = c("a", "c")) +sdtm.oak:::are_to_recode(letters[1:3], from = c("a", "c")) } \keyword{internal} diff --git a/man/assign.Rd b/man/assign.Rd new file mode 100644 index 00000000..0b00a8dd --- /dev/null +++ b/man/assign.Rd @@ -0,0 +1,351 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/assign.R +\name{assign} +\alias{assign} +\alias{assign_no_ct} +\alias{assign_ct} +\title{Derive an SDTM variable} +\usage{ +assign_no_ct( + raw_dataset, + raw_variable, + target_sdtm_variable, + target_dataset = raw_dataset, + merge_to_topic_by = NULL +) + +assign_ct( + raw_dataset, + raw_variable, + target_sdtm_variable, + target_dataset = raw_dataset, + merge_to_topic_by = NULL, + study_ct = NULL, + target_sdtm_variable_codelist_code = NULL +) +} +\arguments{ +\item{raw_dataset}{The raw dataset.} + +\item{raw_variable}{The raw variable.} + +\item{target_sdtm_variable}{The target SDTM variable.} + +\item{target_dataset}{Target dataset. By default the same as \code{raw_dataset}.} + +\item{merge_to_topic_by}{If \code{target_dataset} is different than \code{raw_dataset}, +then this parameter defines keys to use in the join between \code{raw_dataset} +and \code{target_dataset}.} + +\item{study_ct}{Study controlled terminology specification.} + +\item{target_sdtm_variable_codelist_code}{A codelist code indicating which +subset of the controlled terminology to apply in the derivation.} +} +\value{ +The target dataset with the derived variable \code{target_sdtm_variable}. + +The target dataset with the derived variable \code{target_sdtm_variable}. +} +\description{ +\itemize{ +\item \code{\link[=assign_no_ct]{assign_no_ct()}} maps a variable in a source dataset to a target SDTM +variable that has no terminology restrictions. +\item \code{\link[=assign_ct]{assign_ct()}} maps a variable in a source dataset to a target SDTM variable +following controlled terminology recoding. +} + +\code{\link[=assign_ct]{assign_ct()}} maps a variable in a source dataset to a target SDTM variable +following controlled terminology recoding. +} +\examples{ +study_ct <- + tibble::tibble( + codelist_code = rep("C66729", 8L), + term_code = c( + "C28161", + "C38210", + "C38222", + "C38223", + "C38287", + "C38288", + "C38305", + "C38311" + ), + CodedData = c( + "INTRAMUSCULAR", + "EPIDURAL", + "INTRA-ARTERIAL", + "INTRA-ARTICULAR", + "OPHTHALMIC", + "ORAL", + "TRANSDERMAL", + "UNKNOWN" + ), + term_value = CodedData, + collected_value = c( + "IM (Intramuscular)", + "EP (Epidural)", + "IA (Intra-arterial)", + "IJ (Intra-articular)", + "OP (Ophthalmic)", + "PO (Oral)", + "DE (Transdermal)", + "Unknown" + ), + term_preferred_term = c( + "Intramuscular Route of Administration", + "Epidural Route of Administration", + "Intraarterial Route of Administration", + "Intraarticular Route of Administration", + "Ophthalmic Route of Administration", + "Oral Route of Administration", + "Transdermal Route of Administration", + "Unknown Route of Administration" + ), + term_synonyms = c(rep(NA, 5L), "Intraoral Route of Administration; PO", NA, NA), + raw_codelist = rep("ROUTE_CV1", 8L) + ) + +md1 <- + tibble::tibble( + oak_id = 1:14, + raw_source = "MD1", + PATIENT_NUM = 101:114, + MDRTE = c( + "PO (Oral)", + "PO (Oral)", + NA_character_, + "PO", + "Intraoral Route of Administration", + "PO (Oral)", + "IM (Intramuscular)", + "IA (Intra-arterial)", + "", + "Non-standard", + "random_value", + "IJ (Intra-articular)", + "TRANSDERMAL", + "OPHTHALMIC" + ) + ) + +assign_ct( + raw_dataset = md1, + raw_variable = "MDRTE", + study_ct = study_ct, + target_sdtm_variable = "CMROUTE", + target_sdtm_variable_codelist_code = "C66729" + ) + +cm_inter <- + tibble::tibble( + oak_id = 1:14, + raw_source = "MD1", + PATIENT_NUM = 101:114, + CMTRT = c( + "BABY ASPIRIN", + "CORTISPORIN", + "ASPIRIN", + "DIPHENHYDRAMINE HCL", + "PARCETEMOL", + "VOMIKIND", + "ZENFLOX OZ", + "AMITRYPTYLINE", + "BENADRYL", + "DIPHENHYDRAMINE HYDROCHLORIDE", + "TETRACYCLINE", + "BENADRYL", + "SOMINEX", + "ZQUILL" + ), + CMINDC = c( + NA, + "NAUSEA", + "ANEMIA", + "NAUSEA", + "PYREXIA", + "VOMITINGS", + "DIARHHEA", + "COLD", + "FEVER", + "LEG PAIN", + "FEVER", + "COLD", + "COLD", + "PAIN" + ), + CMROUTE = c( + "ORAL", + "ORAL", + NA, + "ORAL", + "ORAL", + "ORAL", + "INTRAMUSCULAR", + "INTRA-ARTERIAL", + NA, + "NON-STANDARD", + "RANDOM_VALUE", + "INTRA-ARTICULAR", + "TRANSDERMAL", + "OPHTHALMIC" + ) + ) + +assign_ct( + raw_dataset = md1, + raw_variable = "MDRTE", + study_ct = study_ct, + target_sdtm_variable = "CMROUTE", + target_sdtm_variable_codelist_code = "C66729", + target_dataset = cm_inter, + merge_to_topic_by = c("oak_id","raw_source","PATIENT_NUM") + ) + +study_ct <- + tibble::tibble( + codelist_code = rep("C66729", 8L), + term_code = c( + "C28161", + "C38210", + "C38222", + "C38223", + "C38287", + "C38288", + "C38305", + "C38311" + ), + CodedData = c( + "INTRAMUSCULAR", + "EPIDURAL", + "INTRA-ARTERIAL", + "INTRA-ARTICULAR", + "OPHTHALMIC", + "ORAL", + "TRANSDERMAL", + "UNKNOWN" + ), + term_value = CodedData, + collected_value = c( + "IM (Intramuscular)", + "EP (Epidural)", + "IA (Intra-arterial)", + "IJ (Intra-articular)", + "OP (Ophthalmic)", + "PO (Oral)", + "DE (Transdermal)", + "Unknown" + ), + term_preferred_term = c( + "Intramuscular Route of Administration", + "Epidural Route of Administration", + "Intraarterial Route of Administration", + "Intraarticular Route of Administration", + "Ophthalmic Route of Administration", + "Oral Route of Administration", + "Transdermal Route of Administration", + "Unknown Route of Administration" + ), + term_synonyms = c(rep(NA, 5L), "Intraoral Route of Administration; PO", NA, NA), + raw_codelist = rep("ROUTE_CV1", 8L) + ) + +md1 <- + tibble::tibble( + oak_id = 1:14, + raw_source = "MD1", + PATIENT_NUM = 101:114, + MDRTE = c( + "PO (Oral)", + "PO (Oral)", + NA_character_, + "PO", + "Intraoral Route of Administration", + "PO (Oral)", + "IM (Intramuscular)", + "IA (Intra-arterial)", + "", + "Non-standard", + "random_value", + "IJ (Intra-articular)", + "TRANSDERMAL", + "OPHTHALMIC" + ) + ) + +assign_ct( + raw_dataset = md1, + raw_variable = "MDRTE", + study_ct = study_ct, + target_sdtm_variable = "CMROUTE", + target_sdtm_variable_codelist_code = "C66729" + ) + +cm_inter <- + tibble::tibble( + oak_id = 1:14, + raw_source = "MD1", + PATIENT_NUM = 101:114, + CMTRT = c( + "BABY ASPIRIN", + "CORTISPORIN", + "ASPIRIN", + "DIPHENHYDRAMINE HCL", + "PARCETEMOL", + "VOMIKIND", + "ZENFLOX OZ", + "AMITRYPTYLINE", + "BENADRYL", + "DIPHENHYDRAMINE HYDROCHLORIDE", + "TETRACYCLINE", + "BENADRYL", + "SOMINEX", + "ZQUILL" + ), + CMINDC = c( + NA, + "NAUSEA", + "ANEMIA", + "NAUSEA", + "PYREXIA", + "VOMITINGS", + "DIARHHEA", + "COLD", + "FEVER", + "LEG PAIN", + "FEVER", + "COLD", + "COLD", + "PAIN" + ), + CMROUTE = c( + "ORAL", + "ORAL", + NA, + "ORAL", + "ORAL", + "ORAL", + "INTRAMUSCULAR", + "INTRA-ARTERIAL", + NA, + "NON-STANDARD", + "RANDOM_VALUE", + "INTRA-ARTICULAR", + "TRANSDERMAL", + "OPHTHALMIC" + ) + ) + +assign_ct( + raw_dataset = md1, + raw_variable = "MDRTE", + study_ct = study_ct, + target_sdtm_variable = "CMROUTE", + target_sdtm_variable_codelist_code = "C66729", + target_dataset = cm_inter, + merge_to_topic_by = c("oak_id","raw_source","PATIENT_NUM") + ) + + +} diff --git a/man/ct_map.Rd b/man/ct_map.Rd new file mode 100644 index 00000000..847d8a77 --- /dev/null +++ b/man/ct_map.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ct.R +\name{ct_map} +\alias{ct_map} +\title{Recode according to controlled terminology} +\usage{ +ct_map( + x, + ct = NULL, + cl = NULL, + from = c("collected_value", "term_synonyms"), + to = "term_value" +) +} +\arguments{ +\item{x}{A character vector of terms to be recoded following a controlled +terminology.} + +\item{ct}{A \link[tibble:tibble-package]{tibble} providing a controlled +terminology specification.} + +\item{cl}{A character vector indicating a set of possible controlled +terminology code-lists codes to be used for recoding. By default (\code{NULL}) +all code-lists available in \code{ct} are used.} + +\item{from}{A character vector of column names indicating the variables +containing values to be matched against for terminology recoding.} + +\item{to}{A single string indicating the column whose values are to be +recoded into.} +} +\value{ +A character vector of terminology recoded values from \code{x}. If no +match is found in the controlled terminology spec provided in \code{ct}, then +\code{x} values are returned in uppercase. If \code{ct} is not provided \code{x} is +returned unchanged. +} +\description{ +\code{\link[=ct_map]{ct_map()}} recodes a vector following a controlled terminology. +} diff --git a/man/ct_mappings.Rd b/man/ct_mappings.Rd new file mode 100644 index 00000000..2d7db0f5 --- /dev/null +++ b/man/ct_mappings.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ct.R +\name{ct_mappings} +\alias{ct_mappings} +\title{Controlled terminology mappings} +\usage{ +ct_mappings( + ct, + from = c("collected_value", "term_synonyms"), + to = "term_value" +) +} +\arguments{ +\item{ct}{Controlled terminology specification as a +\link[tibble:tibble-package]{tibble}. Each row is for a mapped controlled term. +Controlled terms are expected in the column indicated by \code{to_col}.} + +\item{from}{A character vector of column names indicating the variables +containing values to be recoded.} + +\item{to}{A single string indicating the column whose values are to be +recoded into.} +} +\value{ +A \link[tibble:tibble-package]{tibble} with two columns, \code{from} and +\code{to}, indicating the mapping of values, one per row. +} +\description{ +\code{\link[=ct_mappings]{ct_mappings()}} takes a controlled terminology specification and returns the +mappings in the form of a \link[tibble:tibble-package]{tibble} in long format, +i.e. the recoding of values in the \code{from} column to the \code{to} column values, +one mapping per row. + +The resulting mappings are unique, i.e. if \code{from} values are duplicated in +two \code{from} columns, the first column indicated in \code{from} takes precedence, +and only that mapping is retained in the controlled terminology map. +} +\examples{ +# example code + + + + + + + +} +\keyword{internal} diff --git a/man/hardcode_no_ct.Rd b/man/harcode.Rd similarity index 70% rename from man/hardcode_no_ct.Rd rename to man/harcode.Rd index cc37c81e..185263d2 100644 --- a/man/hardcode_no_ct.Rd +++ b/man/harcode.Rd @@ -1,7 +1,9 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/hardcode_no_ct.R -\name{hardcode_no_ct} +% Please edit documentation in R/hardcode.R +\name{harcode} +\alias{harcode} \alias{hardcode_no_ct} +\alias{hardcode_ct} \title{Derive an SDTM variable with a hardcoded value} \usage{ hardcode_no_ct( @@ -12,6 +14,17 @@ hardcode_no_ct( target_dataset = raw_dataset, merge_to_topic_by = NULL ) + +hardcode_ct( + raw_dataset, + raw_variable, + target_sdtm_variable, + target_hardcoded_value, + target_dataset = raw_dataset, + merge_to_topic_by = NULL, + study_ct = NULL, + target_sdtm_variable_codelist_code = NULL +) } \arguments{ \item{raw_dataset}{The raw dataset.} @@ -27,10 +40,22 @@ hardcode_no_ct( \item{merge_to_topic_by}{If \code{target_dataset} is different than \code{raw_dataset}, then this parameter defines keys to use in the join between \code{raw_dataset} and \code{target_dataset}.} + +\item{study_ct}{Study controlled terminology specification.} + +\item{target_sdtm_variable_codelist_code}{A codelist code indicating which +subset of the controlled terminology to apply in the derivation.} +} +\value{ +The target dataset with the derived variable \code{target_sdtm_variable}. } \description{ -\code{\link[=hardcode_no_ct]{hardcode_no_ct()}} maps a hardcoded value to a target SDTM variable that has +\itemize{ +\item \code{\link[=hardcode_no_ct]{hardcode_no_ct()}} maps a hardcoded value to a target SDTM variable that has no terminology restrictions. +\item \code{\link[=hardcode_ct]{hardcode_ct()}} maps a hardcoded value to a target SDTM variable with +controlled terminology recoding. +} } \examples{ MD1 <- @@ -64,7 +89,6 @@ CM_INTER <- # Derive a new variable `CMCAT` by overwriting `MDRAW` with the # hardcoded value "GENERAL CONCOMITANT MEDICATIONS" with a prior join to # `target_dataset`. - hardcode_no_ct( raw_dataset = MD1, raw_variable = "MDRAW", diff --git a/man/index_for_rewrite.Rd b/man/index_for_recode.Rd similarity index 61% rename from man/index_for_rewrite.Rd rename to man/index_for_recode.Rd index 6a729a56..2362517f 100644 --- a/man/index_for_rewrite.Rd +++ b/man/index_for_recode.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/recode.R -\name{index_for_rewrite} -\alias{index_for_rewrite} -\title{Determine Indices for Rewriting} +\name{index_for_recode} +\alias{index_for_recode} +\title{Determine Indices for Recoding} \usage{ -index_for_rewrite(x, from) +index_for_recode(x, from) } \arguments{ \item{x}{A vector of values in which to search for matches.} @@ -15,16 +15,16 @@ index_for_rewrite(x, from) An integer vector of the same length as \code{x}, containing the indices of the matched values from the \code{from} vector. If an element in \code{x} does not match any value in \code{from}, the corresponding position in the output will be -\code{NA}. This index information is critical for subsequent rewrite operations. +\code{NA}. This index information is critical for subsequent recoding operations. } \description{ -\code{\link[=index_for_rewrite]{index_for_rewrite()}} identifies the positions of elements in \code{x} that match +\code{\link[=index_for_recode]{index_for_recode()}} identifies the positions of elements in \code{x} that match any of the values specified in the \code{from} vector. This function is primarily -used to facilitate the rewriting of values by pinpointing which elements in +used to facilitate the recoding of values by pinpointing which elements in \code{x} correspond to the \code{from} values and thus need to be replaced or updated. } \examples{ -sdtm.oak:::index_for_rewrite(x = 1:5, from = c(2, 4)) +sdtm.oak:::index_for_recode(x = 1:5, from = c(2, 4)) } \keyword{internal} diff --git a/man/overwrite.Rd b/man/overwrite.Rd deleted file mode 100644 index 6d608bdf..00000000 --- a/man/overwrite.Rd +++ /dev/null @@ -1,58 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/recode.R -\name{overwrite} -\alias{overwrite} -\title{Overwrite values} -\usage{ -overwrite(x, to, .na = NA) -} -\arguments{ -\item{x}{An atomic vector.} - -\item{.na}{New value for missing values in \code{x}. Defaults to \code{NA}.} -} -\value{ -A vector of the same length of \code{x} with new values matching those -in \code{to}. -} -\description{ -\code{\link[=overwrite]{overwrite()}} recodes values in \code{x} to a new set of values provided in \code{to}; -the values in \code{to} are recycled to match the length of \code{x}. By default, -missing values remain \code{NA}. -} -\examples{ -x <- c(letters[1:4], NA, NA) -# Recode all values to `"x"` but keep `NA`. -sdtm.oak:::overwrite(x, to = "x") - -# Recode all values to `"x"` but recode `NA` to a new value. -sdtm.oak:::overwrite(x, to = "x", .na = "x") -sdtm.oak:::overwrite(x, to = "x", .na = "Absent") - -# If `to` is not a scalar, it is recycled and matched by position for -# replacement. -sdtm.oak:::overwrite(x, to = c("x", "y")) - -# `x` can be of other types besides `character`, e.g. replace integers to a -# hard-coded new integer value. -sdtm.oak:::overwrite(x = 1:5, to = 0) - -# Example involving `logical` vectors -sdtm.oak:::overwrite(x = c(TRUE, FALSE), to = FALSE) - -# Returned type will be a type compatible with both the types of `to` and -# `.na`. -sdtm.oak:::overwrite(x = c("sdtm", "adam"), to = 0) -sdtm.oak:::overwrite( - x = c("sdtm", "adam"), - to = 0, - .na = NA_character_ -) -sdtm.oak:::overwrite( - x = c("sdtm", "adam"), - to = TRUE, - .na = NA_real_ -) - -} -\keyword{internal} diff --git a/man/rewrite.Rd b/man/recode.Rd similarity index 67% rename from man/rewrite.Rd rename to man/recode.Rd index 6f25762f..b7eb79e8 100644 --- a/man/rewrite.Rd +++ b/man/recode.Rd @@ -1,15 +1,15 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/recode.R -\name{rewrite} -\alias{rewrite} -\title{Rewrite values} +\name{recode} +\alias{recode} +\title{Recode values} \usage{ -rewrite(x, from, to, .no_match = x, .na = NA) +recode(x, from = unique(na.omit(x)), to = from, .no_match = x, .na = NA) } \arguments{ \item{x}{An atomic vector of values are to be recoded.} -\item{from}{A vector of values to be matched in \code{x} for rewriting.} +\item{from}{A vector of values to be matched in \code{x} for recoded.} \item{to}{A vector of values to be used as replacement for values in \code{from}.} @@ -22,22 +22,22 @@ matched.} A vector of recoded values. } \description{ -\code{\link[=rewrite]{rewrite()}} recodes values in \code{x} by matching elements in \code{from} onto values +\code{\link[=recode]{recode()}} recodes values in \code{x} by matching elements in \code{from} onto values in \code{to}. } \examples{ x <- c("male", "female", "x", NA) -sdtm.oak:::rewrite(x, +sdtm.oak:::recode(x, from = c("male", "female"), to = c("M", "F") ) -sdtm.oak:::rewrite( +sdtm.oak:::recode( x, from = c("male", "female"), to = c("M", "F"), .no_match = "?" ) -sdtm.oak:::rewrite( +sdtm.oak:::recode( x, from = c("male", "female"), to = c("M", "F"),